mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
Security - Remove the unsafe Vec::set_len over recycled sector buffers — UB reachable from safe code (GHSA-j8ww-f5fg-9pmh, low). Uses Vec::resize; pooling unchanged. CI / tooling - clippy on stable with the MSRV single-sourced from Cargo.toml; cargo-deny dependency-audit gate with first-party git sources allowed (#15); audience-based comment-guard (#17). Docs - README (keydb ownership, BD-J parser count), drive-access transport/reset notes, mux timeline module-doc links.
366 lines
17 KiB
YAML
366 lines
17 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
# dev -> qa -> main. `dev` is where work lands and is meant to be pushed
|
|
# to often: these are the FAST checks, so a mistake surfaces in minutes.
|
|
# `qa` is the release candidate — it runs these too, plus the expensive
|
|
# suite in qa.yml. `main` only ever moves at release time, to a tagged
|
|
# commit that was already green on qa.
|
|
branches: [main, dev, qa]
|
|
pull_request:
|
|
|
|
jobs:
|
|
# ── Single source of truth for the MSRV ─────────────────────────────────────
|
|
#
|
|
# `rust-version` in Cargo.toml is the ONE place the MSRV is declared. This job
|
|
# reads it; every job that must build/test AT the MSRV consumes
|
|
# `needs.resolve-msrv.outputs.v` instead of a hardcoded pin, so the floor
|
|
# cannot drift out of sync across workflow files. The `test` job building
|
|
# green at this value IS the verification that the declared MSRV is real.
|
|
# clippy and the `consumers` job deliberately do NOT use it — they run on
|
|
# `stable` (lint debt is caught against current Rust; dependents float highest).
|
|
resolve-msrv:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
v: ${{ steps.msrv.outputs.v }}
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- id: msrv
|
|
shell: bash
|
|
run: echo "v=$(grep -m1 '^rust-version' Cargo.toml | sed -E 's/.*\"([^\"]+)\".*/\1/')" >> "$GITHUB_OUTPUT"
|
|
|
|
# CI owns the changelog: main may never carry a released version without a
|
|
# CHANGELOG.md entry for it. The release orchestrator dates an existing
|
|
# `## [X.Y.Z]` heading — it does NOT invent one — so a version bump that
|
|
# forgot its changelog entry must fail here rather than ship a blank release.
|
|
# Hard-fails on main (and PRs targeting main); warns elsewhere so an
|
|
# in-progress dev/qa branch whose entry isn't written yet is not blocked.
|
|
changelog:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Require a CHANGELOG entry for the crate version on main
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
version=$(grep -m1 '^version = ' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/')
|
|
if grep -qE "^## \[${version}\]" CHANGELOG.md; then
|
|
echo "CHANGELOG.md has an entry for ${version}."
|
|
exit 0
|
|
fi
|
|
msg="CHANGELOG.md has no '## [${version}]' entry"
|
|
if [ "${GITHUB_REF_NAME:-}" = "main" ] || [ "${GITHUB_BASE_REF:-}" = "main" ]; then
|
|
echo "::error::${msg} — main requires a changelog entry for the released version."
|
|
exit 1
|
|
fi
|
|
echo "::warning::${msg} (allowed on ${GITHUB_REF_NAME:-unknown}; required before it can reach main)."
|
|
|
|
lint:
|
|
needs: resolve-msrv
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
path: libfreemkv
|
|
# libfreemkv path-deps ../freemkv-unlock on the BRANCH tip (release.sh
|
|
# swaps it to a git tag only inside the tagged commit, then restores the
|
|
# path dep). CI checks out one repo, so the branch tip has never been
|
|
# buildable here — every green run you have ever seen was a tag build,
|
|
# and Windows/Linux were first compiled at release time.
|
|
#
|
|
# Both repos go into subdirectories because actions/checkout refuses a
|
|
# `path:` outside $GITHUB_WORKSPACE, and `../freemkv-unlock` is outside.
|
|
# With this layout the path dep resolves exactly as it does locally.
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
repository: freemkv/freemkv-unlock
|
|
ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}"
|
|
path: freemkv-unlock
|
|
# fmt runs on the pinned MSRV toolchain; clippy runs on stable. Third
|
|
# parties compile our source on their own toolchain and never run our
|
|
# clippy, so the lint toolchain need not match the MSRV — only build/test
|
|
# must (see the test/cross jobs). Linting on stable makes new-lint debt a
|
|
# BLOCKING gate the pinned version would otherwise hide. The MSRV pin is
|
|
# installed second so it stays the default toolchain (fmt uses it
|
|
# implicitly); clippy is invoked explicitly with `+stable`.
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
components: clippy
|
|
- uses: dtolnay/rust-toolchain@master
|
|
with:
|
|
toolchain: ${{ needs.resolve-msrv.outputs.v }}
|
|
components: rustfmt
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: libfreemkv
|
|
- run: cargo fmt --check
|
|
working-directory: libfreemkv
|
|
# Cargo.lock is now committed (issue #15) and the `deny` job audits the
|
|
# resolved graph, but --locked is still omitted: CI's sibling path-patch
|
|
# layout resolves the freemkv crates as path packages, which --locked
|
|
# can't satisfy — the same reason the binary crates omit it in ci.yml.
|
|
# --all-targets so TEST code is linted too. Without it this crate — the
|
|
# reference implementation for the other seven — was the only one whose
|
|
# tests had never been linted at all, and it was hiding 74 findings.
|
|
- run: cargo +stable clippy --all-targets -- -D warnings
|
|
working-directory: libfreemkv
|
|
|
|
test:
|
|
needs: resolve-msrv
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
path: libfreemkv
|
|
# libfreemkv path-deps ../freemkv-unlock on the BRANCH tip (release.sh
|
|
# swaps it to a git tag only inside the tagged commit, then restores the
|
|
# path dep). CI checks out one repo, so the branch tip has never been
|
|
# buildable here — every green run you have ever seen was a tag build,
|
|
# and Windows/Linux were first compiled at release time.
|
|
#
|
|
# Both repos go into subdirectories because actions/checkout refuses a
|
|
# `path:` outside $GITHUB_WORKSPACE, and `../freemkv-unlock` is outside.
|
|
# With this layout the path dep resolves exactly as it does locally.
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
repository: freemkv/freemkv-unlock
|
|
ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}"
|
|
path: freemkv-unlock
|
|
- uses: dtolnay/rust-toolchain@master
|
|
with:
|
|
toolchain: ${{ needs.resolve-msrv.outputs.v }}
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: libfreemkv
|
|
- run: cargo test --tests
|
|
working-directory: libfreemkv
|
|
|
|
deny:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
path: libfreemkv
|
|
# libfreemkv path-deps ../freemkv-unlock on the BRANCH tip (release.sh
|
|
# swaps it to a git tag only inside the tagged commit, then restores the
|
|
# path dep). CI checks out one repo, so the branch tip has never been
|
|
# buildable here — every green run you have ever seen was a tag build,
|
|
# and Windows/Linux were first compiled at release time.
|
|
#
|
|
# Both repos go into subdirectories because actions/checkout refuses a
|
|
# `path:` outside $GITHUB_WORKSPACE, and `../freemkv-unlock` is outside.
|
|
# With this layout the path dep resolves exactly as it does locally.
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
repository: freemkv/freemkv-unlock
|
|
ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}"
|
|
path: freemkv-unlock
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
|
with:
|
|
command: check advisories bans licenses sources
|
|
manifest-path: libfreemkv/Cargo.toml
|
|
|
|
check-macos:
|
|
# dev is the FAST lane: this job still runs, but on the release-candidate
|
|
# branches rather than on every push to dev. Nothing is deleted and no
|
|
# platform stops being checked before a release -- qa.yml independently
|
|
# covers macOS and Windows, and the jobs unique to this file (the Intel
|
|
# macOS build, the Windows release build) run here on qa and main. A push
|
|
# to dev is meant to be cheap and frequent; waiting on three runner pools
|
|
# to agree is what a release candidate is for.
|
|
#
|
|
# `if` SKIPS the job (it does not queue). A queued job would be far worse
|
|
# than a slow one: release.sh's CI gate refuses while any run for the
|
|
# commit is still in progress, so a never-scheduled job blocks releases
|
|
# silently -- see the note on real-media in qa.yml.
|
|
needs: resolve-msrv
|
|
if: github.ref_name == 'qa' || github.ref_name == 'main'
|
|
runs-on: macos-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
path: libfreemkv
|
|
# libfreemkv path-deps ../freemkv-unlock on the BRANCH tip (release.sh
|
|
# swaps it to a git tag only inside the tagged commit, then restores the
|
|
# path dep). CI checks out one repo, so the branch tip has never been
|
|
# buildable here — every green run you have ever seen was a tag build,
|
|
# and Windows/Linux were first compiled at release time.
|
|
#
|
|
# Both repos go into subdirectories because actions/checkout refuses a
|
|
# `path:` outside $GITHUB_WORKSPACE, and `../freemkv-unlock` is outside.
|
|
# With this layout the path dep resolves exactly as it does locally.
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
repository: freemkv/freemkv-unlock
|
|
ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}"
|
|
path: freemkv-unlock
|
|
- uses: dtolnay/rust-toolchain@master
|
|
with:
|
|
toolchain: ${{ needs.resolve-msrv.outputs.v }}
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: libfreemkv
|
|
- run: cargo check
|
|
working-directory: libfreemkv
|
|
|
|
check-windows:
|
|
# dev is the FAST lane: this job still runs, but on the release-candidate
|
|
# branches rather than on every push to dev. Nothing is deleted and no
|
|
# platform stops being checked before a release -- qa.yml independently
|
|
# covers macOS and Windows, and the jobs unique to this file (the Intel
|
|
# macOS build, the Windows release build) run here on qa and main. A push
|
|
# to dev is meant to be cheap and frequent; waiting on three runner pools
|
|
# to agree is what a release candidate is for.
|
|
#
|
|
# `if` SKIPS the job (it does not queue). A queued job would be far worse
|
|
# than a slow one: release.sh's CI gate refuses while any run for the
|
|
# commit is still in progress, so a never-scheduled job blocks releases
|
|
# silently -- see the note on real-media in qa.yml.
|
|
needs: resolve-msrv
|
|
if: github.ref_name == 'qa' || github.ref_name == 'main'
|
|
runs-on: windows-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
path: libfreemkv
|
|
# libfreemkv path-deps ../freemkv-unlock on the BRANCH tip (release.sh
|
|
# swaps it to a git tag only inside the tagged commit, then restores the
|
|
# path dep). CI checks out one repo, so the branch tip has never been
|
|
# buildable here — every green run you have ever seen was a tag build,
|
|
# and Windows/Linux were first compiled at release time.
|
|
#
|
|
# Both repos go into subdirectories because actions/checkout refuses a
|
|
# `path:` outside $GITHUB_WORKSPACE, and `../freemkv-unlock` is outside.
|
|
# With this layout the path dep resolves exactly as it does locally.
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
repository: freemkv/freemkv-unlock
|
|
ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}"
|
|
path: freemkv-unlock
|
|
- uses: dtolnay/rust-toolchain@master
|
|
with:
|
|
toolchain: ${{ needs.resolve-msrv.outputs.v }}
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: libfreemkv
|
|
# Build the tests (not just `cargo check`): catches errors in test
|
|
# code and forces full codegen of the Windows-only SPTI transport
|
|
# (src/scsi/windows.rs), which never compiles on the Linux/macOS dev
|
|
# hosts. We don't `cargo test` here — the suite needs no drive but the
|
|
# extra build is the value; running tests is covered by the Linux job.
|
|
- run: cargo build --tests
|
|
working-directory: libfreemkv
|
|
|
|
# ── Did this change break anything downstream? ──────────────────────────────
|
|
#
|
|
# Every job above proves libfreemkv builds. None proved its DEPENDENTS do,
|
|
# and that gap is real: an engine signature change broke autorip today and
|
|
# went unnoticed because consumer CI only fires on a push to that consumer.
|
|
# libfreemkv sits below all five of them, so a break here is worth strictly
|
|
# more than a break anywhere else in the project.
|
|
#
|
|
# `cargo check --all-targets` only — each dependent owns its own behaviour
|
|
# and has its own suite. The question here is just "does everything built on
|
|
# me still compile against this commit".
|
|
consumers:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with: { path: libfreemkv }
|
|
- uses: actions/checkout@v7
|
|
with: { repository: freemkv/freemkv-unlock, ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}", path: freemkv-unlock }
|
|
- uses: actions/checkout@v7
|
|
with: { repository: freemkv/freemkv-keysources, ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}", path: freemkv-keysources }
|
|
- uses: actions/checkout@v7
|
|
with: { repository: freemkv/freemkv-engine, ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}", path: freemkv-engine }
|
|
- uses: actions/checkout@v7
|
|
with: { repository: freemkv/freemkv-i18n, ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}", path: freemkv-i18n }
|
|
- uses: actions/checkout@v7
|
|
with: { repository: freemkv/freemkv, ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}", path: freemkv }
|
|
- uses: actions/checkout@v7
|
|
with: { repository: freemkv/autorip, ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}", path: autorip }
|
|
- uses: actions/checkout@v7
|
|
with: { repository: freemkv/bdemu, ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}", path: bdemu }
|
|
- name: Point every dependent at THIS libfreemkv commit
|
|
shell: bash
|
|
run: |
|
|
for c in freemkv-keysources freemkv-engine freemkv autorip bdemu; do
|
|
mkdir -p "$c/.cargo"
|
|
cat > "$c/.cargo/config.toml" <<'EOF'
|
|
[patch.crates-io]
|
|
libfreemkv = { path = "../libfreemkv" }
|
|
freemkv-keysources = { path = "../freemkv-keysources" }
|
|
freemkv-engine = { path = "../freemkv-engine" }
|
|
freemkv-i18n = { path = "../freemkv-i18n" }
|
|
EOF
|
|
done
|
|
# The dependents span a higher MSRV than libfreemkv itself — the binaries
|
|
# (freemkv/autorip/bdemu) always track the highest supported Rust. Build
|
|
# them on `stable` so this job satisfies the highest dependent floor; the
|
|
# library's own lower MSRV (verified by the `test` job) cannot compile them.
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: |
|
|
freemkv-keysources
|
|
freemkv-engine
|
|
freemkv
|
|
autorip
|
|
bdemu
|
|
# `cargo check` alone only proves the dependents still COMPILE against
|
|
# this commit. It cannot see a behavioural change — the library keeps its
|
|
# signatures and a dependent's tests start failing. That is the shape of
|
|
# every defect worth catching here, so run their suites too.
|
|
- run: cargo test --tests
|
|
working-directory: freemkv-keysources
|
|
- run: cargo test --tests
|
|
working-directory: freemkv-engine
|
|
- run: cargo test --tests
|
|
working-directory: freemkv
|
|
- run: cargo test --tests
|
|
working-directory: autorip
|
|
- run: cargo test --tests
|
|
working-directory: bdemu
|
|
|
|
# ── Coverage (non-required, additive) ───────────────────────────────────────
|
|
#
|
|
# Reports line coverage to Codecov. Deliberately NOT wired into any release
|
|
# gate: it is informational only (see codecov.yml) and no other job `needs:`
|
|
# it. Mirrors the `test` job's layout exactly — same MSRV toolchain (resolved
|
|
# from Cargo.toml), same dual checkout so `../freemkv-unlock` resolves, cache
|
|
# workspace and working-directory. cargo-llvm-cov comes as a prebuilt binary
|
|
# from taiki-e/install-action, so it is toolchain-independent.
|
|
#
|
|
# No `--locked`: like the fmt/clippy/test jobs above, the sibling path-patch
|
|
# layout resolves the freemkv crates as path packages, which --locked can't satisfy.
|
|
coverage:
|
|
needs: resolve-msrv
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
path: libfreemkv
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
repository: freemkv/freemkv-unlock
|
|
ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}"
|
|
path: freemkv-unlock
|
|
- uses: dtolnay/rust-toolchain@master
|
|
with:
|
|
toolchain: ${{ needs.resolve-msrv.outputs.v }}
|
|
components: llvm-tools-preview
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: libfreemkv
|
|
- uses: taiki-e/install-action@cargo-llvm-cov
|
|
- run: cargo llvm-cov --workspace --ignore-run-fail --lcov --output-path lcov.info
|
|
working-directory: libfreemkv
|
|
- uses: codecov/codecov-action@v7
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
files: libfreemkv/lcov.info
|
|
slug: freemkv/libfreemkv
|
|
fail_ci_if_error: false
|