name: qa # ── The qa gate: "is this production worth?" ──────────────────────────────── # # dev -> qa -> main. # # `dev` is for committing often. ci.yml answers "is it green" in minutes with # fmt, clippy and the unit suite, so a mistake surfaces while it is still cheap # to fix. `qa` is the release-candidate branch, and THIS workflow is the claim # that a commit is production worth: everything expensive that can run without # physical media. `main` only ever receives a qa that went green here. # # Sibling repos are checked out at `qa`, NOT `dev`. A qa run that resolved its # dependencies from dev tips would be validating a combination that is not the # one being released, which is the exact failure this branch exists to prevent. # # What this gate CANNOT cover: `disc://` and real `iso://` need physical media, # and no hosted runner has an optical drive or the image hoard. Those run on a # self-hosted runner (see the media job at the end) and are the one leg that # stays on hardware. on: push: branches: [qa] workflow_dispatch: jobs: # ── Name the candidate ──────────────────────────────────────────────── # # Every push to `qa` is a release candidate, so every push gets a tag: # v-rc, N incrementing. That is the answer to "which build is on # qa right now, and is it the one I tested?" — a question that otherwise gets # answered from memory. # # This runs FIRST and does not depend on the gates, deliberately. A red # candidate needs a name more than a green one does: "rc3 failed # release-tests on windows" is a sentence you can act on; "qa is red" is not. # Red on qa is a working gate, not an incident — it is the branch saying this # is not production worth yet. Fix on dev, get dev green, push qa again. # # release.yml excludes v*-rc* so a candidate never publishes a release. rc-tag: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Stamp the next rc shell: bash run: | set -euo pipefail v=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) [ -n "$v" ] || { echo "no version in Cargo.toml" >&2; exit 1; } # Numeric sort on the rc ordinal: -rc10 must beat -rc9, and a plain # lexical sort gets that backwards from the tenth candidate on. n=$(git tag -l "v$v-rc*" | sed "s|^v$v-rc||" | sort -n | tail -1) tag="v$v-rc$(( ${n:-0} + 1 ))" git tag "$tag" git push origin "$tag" echo "### Candidate \`$tag\`" >> "$GITHUB_STEP_SUMMARY" # The debug suite runs on every dev push. Release is a DIFFERENT build: # overflow checks are off, debug_assert! is compiled out, and inlining # changes what the optimiser can prove. A test that only passes in debug is # a test that never guarded the binary anyone actually ships. release-tests: strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v5 with: path: libfreemkv - uses: actions/checkout@v5 with: repository: freemkv/freemkv-unlock ref: qa path: freemkv-unlock - uses: dtolnay/rust-toolchain@1.97.0 - uses: Swatinem/rust-cache@v2 with: workspaces: libfreemkv - run: cargo test --release --tests working-directory: libfreemkv # clippy's output is target-dependent: cfg-gated code only gets linted on # the target it compiles for. Linting solely on the dev machine's host # target is how a lint that CI rejects reaches a push. cross-lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: path: libfreemkv - uses: actions/checkout@v5 with: repository: freemkv/freemkv-unlock ref: qa path: freemkv-unlock - uses: dtolnay/rust-toolchain@1.97.0 with: components: clippy - uses: Swatinem/rust-cache@v2 with: workspaces: libfreemkv - run: rustup target add x86_64-unknown-linux-gnu - run: cargo clippy --all-targets --target x86_64-unknown-linux-gnu -- -D warnings working-directory: libfreemkv