rust-fs-core 0.3.2

Pure-Rust block-device framework — BlockRead/BlockDevice traits + FileDevice + CallbackDevice + LRU cache. Foundation crate for the rust-fs-* drivers and rust-img-* containers.
Documentation
name: release

on:
  push:
    tags:
      - 'v*.*.*'

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  test:
    name: test
    runs-on: ubuntu-latest
    # The same acknowledgement as ci.yml's `test` and `coverage` jobs:
    # tests/device_node_size.rs needs a loop device, this runner is not
    # root, and without the variable the test fails rather than skips.
    # Declared on the job so every suite run below has it.
    env:
      AM_FS_CORE_ALLOW_UNPRIVILEGED_SKIP: "1"
    steps:
      - uses: actions/checkout@v5

      - uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: 1.95.0
          components: clippy
      - uses: Swatinem/rust-cache@v2
      - name: verify packaged output-budget script
        shell: bash
        run: |
          files="$(cargo package --locked --list)"
          grep -Fqx 'scripts/output-budget.sh' <<<"$files" || {
            echo 'package omits scripts/output-budget.sh' >&2
            exit 1
          }

      - name: verify tag matches Cargo.toml version
        run: |
          tag="${GITHUB_REF_NAME#v}"
          crate_ver=$(grep -m1 '^version' Cargo.toml | sed -E 's/version *= *"(.*)"/\1/')
          if [ "$tag" != "$crate_ver" ]; then
            echo "tag $tag does not match Cargo.toml version $crate_ver" >&2
            exit 1
          fi

      - name: test (debug, quiet and budgeted)
        run: |
          bash scripts/tier.sh "test (debug)" debug 750 50000 -- cargo test --locked --all-targets --features cli
          bash scripts/core.sh test-floor debug 330
      # A RELEASE-PROFILE RUN, DECIDED IN #111. The profile published is
      # the release one, and optimisation can change behaviour the debug
      # run never sees: `debug_assert!` bodies vanish, overflow wraps.
      # It runs here, on a tag, not in ci.yml, so pull requests don't pay
      # for a second full suite.
      #
      # Do NOT set EXPECT_OVERFLOW_CHECKS on this run. Overflow checks are
      # meant to be off under --release, so the handshake would arm an
      # assertion that fails every green run. tests/ci_profile.rs refuses
      # that combination.
      - name: test (release, quiet and budgeted)
        run: |
          bash scripts/tier.sh "test (release)" release 750 50000 -- cargo test --locked --release --all-targets --features cli
          bash scripts/core.sh test-floor release 330
      - name: keep the full test transcripts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: tier-logs-release
          path: tmp/logs/
          if-no-files-found: warn
          retention-days: 14
      # Twice: once as a consumer's static library builds this crate (no
      # features), and once with the `cli` feature the tools turn on, so a
      # lint in either shape is caught.
      - run: cargo clippy --locked --all-targets -- -D warnings
      - run: cargo clippy --locked --all-targets --features cli -- -D warnings

  publish:
    name: publish to crates.io
    needs: test
    runs-on: ubuntu-latest
    environment: release
    # The only job with any write grant, because it is the only one that
    # needs one: an OIDC token for crates.io trusted publishing and for
    # signing the attestation, the attestation store, and the release the
    # .crate is attached to.
    permissions:
      id-token: write
      attestations: write
      contents: write
    steps:
      - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable, 2026-09-27
        with:
          toolchain: 1.95.0

      # PROVENANCE. A version on crates.io says nothing about where it was
      # built, so the crate is packaged here, checked after the publish
      # against the checksum crates.io serves, and only then attested and
      # attached to the GitHub release for the tag. Packaged on every run,
      # published or not, so a re-run of a tag whose publish succeeded but
      # whose attestation did not can still finish the job. The same
      # packaging `cargo publish` does, and cargo's archive is
      # reproducible: the file `cargo publish` uploads is this one.
      # THE NOTES BEFORE THE UPLOAD (#209): the release body is this
      # version's CHANGELOG section, and a tag the CHANGELOG does not
      # describe stops here, before crates.io has anything.
      - name: the CHANGELOG describes this version
        shell: bash
        run: |
          set -euo pipefail
          bash scripts/release-notes.sh "$GITHUB_REF_NAME" > "$RUNNER_TEMP/release-notes.md"

      - name: package the crate
        shell: bash
        run: cargo package --no-verify

      # ALREADY PUBLISHED IS NOT A FAILURE. A crate's first
      # version under a new name cannot come from here: trusted publishing
      # needs the crate to exist before its publisher can be registered, so
      # that version is published by hand from this tag's commit. The run
      # then has nothing to upload, and the checksum step below still holds
      # the version crates.io serves to the archive built here, so a hand
      # publish of anything else fails this job.
      - name: is this version already on crates.io
        id: published
        shell: bash
        run: |
          set -euo pipefail
          name="$(grep -m1 '^name = ' Cargo.toml | sed 's/name = //; s/"//g')"
          version="$(grep -m1 '^version = ' Cargo.toml | sed 's/version = //; s/"//g')"
          code="$(curl -s -o /dev/null -w '%{http_code}' \
            -A "${GITHUB_REPOSITORY} release (github.com/${GITHUB_REPOSITORY})" \
            "https://crates.io/api/v1/crates/$name/$version")"
          echo "crates.io answered $code for $name $version"
          if [ "$code" = 200 ]; then echo "already=true" >> "$GITHUB_OUTPUT"; else echo "already=false" >> "$GITHUB_OUTPUT"; fi

      - uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4
        if: steps.published.outputs.already != 'true'
        id: auth

      - name: cargo publish
        if: steps.published.outputs.already != 'true'
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: cargo publish

      # Nothing is attested on trust: the file is compared with the
      # checksum crates.io records for this version, so the attestation
      # speaks for the crates.io download, not merely for a file that was
      # on this runner. The index can trail the upload by a few seconds.
      - name: the packaged crate is the one crates.io serves
        shell: bash
        run: |
          set -euo pipefail
          name="$(grep -m1 '^name = ' Cargo.toml | sed 's/name = //; s/"//g')"
          version="$(grep -m1 '^version = ' Cargo.toml | sed 's/version = //; s/"//g')"
          crate="target/package/$name-$version.crate"
          local_sum="$(sha256sum "$crate" | cut -d' ' -f1)"
          remote_sum=""
          for _ in 1 2 3 4 5 6 7 8 9 10; do
            remote_sum="$(curl -sf "https://crates.io/api/v1/crates/$name/$version" \
              -H 'User-Agent: release-workflow' | jq -r '.version.checksum')" && break
            sleep 6
          done
          if [ "$local_sum" != "$remote_sum" ]; then
            echo "::error::$crate is sha256 $local_sum but crates.io records ${remote_sum:-nothing} for $name $version; the file here is not the published one, so it is not attested"
            exit 1
          fi
          echo "$crate is the crates.io download: sha256 $local_sum"

      - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
        with:
          subject-path: target/package/*.crate

      # So `gh attestation verify` has a file to check that anyone can
      # fetch beside the tag, and so the crates.io download can be
      # compared with it.
      - name: attach the crate to the GitHub release
        shell: bash
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
        run: |
          set -euo pipefail
          if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
            gh release edit "$GITHUB_REF_NAME" --notes-file "$RUNNER_TEMP/release-notes.md"
          else
            gh release create "$GITHUB_REF_NAME" --verify-tag --title "$GITHUB_REF_NAME" \
              --notes-file "$RUNNER_TEMP/release-notes.md"
          fi
          gh release upload "$GITHUB_REF_NAME" target/package/*.crate --clobber