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: # WHY THE SIBLING CHECKOUTS — this is NOT a monorepo. # # The committed Cargo.toml resolves libfreemkv from a git TAG (it is off # crates.io). That means CI was building this branch against the LAST RELEASE # of libfreemkv, never against libfreemkv's dev tip — so a coordinated change # across the two repos could not be verified until release.sh cut the tags. # Green here meant "compiles against last month's library". # # The fix mirrors the developer's local layout. Every repo is checked out into # a subdirectory of $GITHUB_WORKSPACE, reproducing # ~/Developer/freemkv/{freemkv-keysources,libfreemkv,freemkv-unlock}, and a # generated .cargo/config.toml redirects the git-tag patch to those paths. # Config-level [patch.crates-io] wins over the manifest one, which is the same # mechanism local development already uses (see the note in Cargo.toml). # # Subdirectories rather than `../libfreemkv` because actions/checkout refuses # any `path:` outside $GITHUB_WORKSPACE, and the workspace IS the repo dir. # Repos stay separate, tags stay separate, the release cascade is untouched. # # libfreemkv path-deps ../freemkv-unlock, which resolves to the workspace root # under this layout — hence the third checkout. # # `--locked` is deliberately absent: Cargo.lock is gitignored here (library # convention), so there is nothing to lock against and it fails rather than # resolves. That is what was breaking this workflow. The lock that has to hold # is the one release.sh regenerates in each consuming binary. jobs: # 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: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: path: freemkv-keysources - uses: actions/checkout@v7 with: repository: freemkv/libfreemkv ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: libfreemkv - uses: actions/checkout@v7 with: repository: freemkv/freemkv-unlock ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: freemkv-unlock - name: Resolve siblings from their dev tips shell: bash working-directory: freemkv-keysources run: | mkdir -p .cargo cat > .cargo/config.toml <<'EOF' [patch.crates-io] libfreemkv = { path = "../libfreemkv" } EOF - uses: dtolnay/rust-toolchain@1.97.0 with: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 with: workspaces: freemkv-keysources - run: cargo fmt --check working-directory: freemkv-keysources # --all-targets so the test code is linted too, not just the library. - run: cargo clippy --all-targets -- -D warnings working-directory: freemkv-keysources test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: path: freemkv-keysources - uses: actions/checkout@v7 with: repository: freemkv/libfreemkv ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: libfreemkv - uses: actions/checkout@v7 with: repository: freemkv/freemkv-unlock ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: freemkv-unlock - name: Resolve siblings from their dev tips shell: bash working-directory: freemkv-keysources run: | mkdir -p .cargo cat > .cargo/config.toml <<'EOF' [patch.crates-io] libfreemkv = { path = "../libfreemkv" } EOF - uses: dtolnay/rust-toolchain@1.97.0 - uses: Swatinem/rust-cache@v2 with: workspaces: freemkv-keysources - run: cargo test --tests working-directory: freemkv-keysources # This crate ships wherever freemkv and autorip ship, and it drags in all of # libfreemkv's platform code (SCSI transports included). Build the tests, not # `cargo check` — check skips codegen and never compiles test-only code. 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. if: github.ref_name == 'qa' || github.ref_name == 'main' runs-on: macos-latest steps: - uses: actions/checkout@v7 with: path: freemkv-keysources - uses: actions/checkout@v7 with: repository: freemkv/libfreemkv ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: libfreemkv - uses: actions/checkout@v7 with: repository: freemkv/freemkv-unlock ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: freemkv-unlock - name: Resolve siblings from their dev tips shell: bash working-directory: freemkv-keysources run: | mkdir -p .cargo cat > .cargo/config.toml <<'EOF' [patch.crates-io] libfreemkv = { path = "../libfreemkv" } EOF - uses: dtolnay/rust-toolchain@1.97.0 - uses: Swatinem/rust-cache@v2 with: workspaces: freemkv-keysources - run: cargo build --tests working-directory: freemkv-keysources # Additive, NON-REQUIRED coverage. Mirrors the `test` job's sibling-checkout # layout and pinned toolchain (nothing in the release cascade depends on it, # and codecov upload is tokenless with fail_ci_if_error:false, so a flaky # upload never reddens the run). `--locked` is deliberately absent here for # the same reason it is in `test`: Cargo.lock is gitignored, so there is no # lockfile to lock against on a fresh runner. coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: path: freemkv-keysources - uses: actions/checkout@v7 with: repository: freemkv/libfreemkv ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: libfreemkv - uses: actions/checkout@v7 with: repository: freemkv/freemkv-unlock ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: freemkv-unlock - name: Resolve siblings from their dev tips shell: bash working-directory: freemkv-keysources run: | mkdir -p .cargo cat > .cargo/config.toml <<'EOF' [patch.crates-io] libfreemkv = { path = "../libfreemkv" } EOF - uses: dtolnay/rust-toolchain@1.97.0 with: components: llvm-tools-preview - uses: Swatinem/rust-cache@v2 with: workspaces: freemkv-keysources - uses: taiki-e/install-action@cargo-llvm-cov - run: cargo llvm-cov --workspace --lcov --output-path lcov.info working-directory: freemkv-keysources - uses: codecov/codecov-action@v5 with: files: freemkv-keysources/lcov.info slug: freemkv/freemkv-keysources fail_ci_if_error: false 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. if: github.ref_name == 'qa' || github.ref_name == 'main' runs-on: windows-latest steps: - uses: actions/checkout@v7 with: path: freemkv-keysources - uses: actions/checkout@v7 with: repository: freemkv/libfreemkv ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: libfreemkv - uses: actions/checkout@v7 with: repository: freemkv/freemkv-unlock ref: "${{ github.ref_name == 'qa' && 'qa' || 'dev' }}" path: freemkv-unlock - name: Resolve siblings from their dev tips shell: bash working-directory: freemkv-keysources run: | mkdir -p .cargo cat > .cargo/config.toml <<'EOF' [patch.crates-io] libfreemkv = { path = "../libfreemkv" } EOF - uses: dtolnay/rust-toolchain@1.97.0 - uses: Swatinem/rust-cache@v2 with: workspaces: freemkv-keysources - run: cargo build --tests working-directory: freemkv-keysources