[anneal][v2] Add Cargo workspace resolution and target resolution logic #768
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright 2026 The Fuchsia Authors | |
| # | |
| # Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 | |
| # <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT | |
| # license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. | |
| # This file may not be copied, modified, or distributed except according to | |
| # those terms. | |
| name: Anneal Tests | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| merge_group: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUSTFLAGS: -Dwarnings | |
| RUSTDOCFLAGS: -Dwarnings | |
| CARGO_ZEROCOPY_AUTO_INSTALL_TOOLCHAIN: 1 | |
| jobs: | |
| anneal_tests: | |
| name: Anneal Tests | |
| runs-on: ubuntu-latest | |
| needs: v2_nix_cache | |
| permissions: | |
| actions: read # Required to download the toolchain artifact. | |
| contents: write # Required to push benchmark data to the storage branch | |
| env: | |
| ANNEAL_TOOLCHAIN_DIR: ${{ github.workspace }}/anneal/target/anneal-toolchain | |
| __ZEROCOPY_LOCAL_DEV: 1 | |
| RUSTFLAGS: "" | |
| RUSTDOCFLAGS: "" | |
| RUST_TEST_THREADS: "1" | |
| steps: | |
| - name: Record job start time | |
| id: job_start_time | |
| run: echo "unix=$(date +%s)" >> "$GITHUB_OUTPUT" | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install stable Rust | |
| uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # zizmor: ignore[superfluous-actions] | |
| with: | |
| toolchain: stable | |
| - name: Download Anneal toolchain archive | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: anneal-exocrate.tar.zst | |
| path: anneal/v2/target | |
| # Ensure `llms-full.txt` file is up-to-date. | |
| - name: Check doc generation | |
| run: cargo run -p doc_gen -- --check | |
| working-directory: anneal | |
| - name: Install Anneal toolchain archive | |
| run: cargo run setup --local-archive v2/target/anneal-exocrate.tar.zst | |
| working-directory: anneal | |
| # Run unit tests separately, as they're much less likely to have bugs | |
| # during local development, and this makes the GitHub Actions output | |
| # easier to skim (in particular, it's clear at a glance whether a failure | |
| # is due to unit or integration tests). | |
| - name: Run unit tests | |
| run: cargo test --verbose --bin cargo-anneal | |
| working-directory: anneal | |
| # We duplicate running unit tests since they're very cheap compared to | |
| # integration tests, and this way it's easier to be sure that we run all | |
| # tests instead of specifically trying to carve out unit tests and risk | |
| # missing test categories. | |
| - name: Run all tests | |
| run: | | |
| start=$(date +%s) | |
| cargo test --verbose | |
| end=$(date +%s) | |
| duration=$((end - start)) | |
| echo "Test Time: $duration seconds" | |
| echo "[{\"name\": \"Test Time\", \"unit\": \"seconds\", \"value\": $duration}]" > test_time.json | |
| working-directory: anneal | |
| - name: Combine benchmarks | |
| env: | |
| JOB_START_TIME_UNIX: ${{ steps.job_start_time.outputs.unix }} | |
| run: | | |
| total_duration=$(( $(date +%s) - $JOB_START_TIME_UNIX )) | |
| echo "Total CI Duration (All Steps): $total_duration seconds" | |
| echo "[{\"name\": \"Total CI Duration (All Steps)\", \"unit\": \"seconds\", \"value\": $total_duration}]" > total_time.json | |
| jq -n \ | |
| --slurpfile test anneal/test_time.json \ | |
| --slurpfile total total_time.json \ | |
| '[ | |
| $test[0][0], | |
| $total[0][0] | |
| ]' > output.json | |
| - name: Store CI duration benchmarks | |
| uses: benchmark-action/github-action-benchmark@a60cea5bc7b49e15c1f58f411161f99e0df48372 # v1.22.0 | |
| with: | |
| name: CI Durations | |
| tool: 'customSmallerIsBetter' | |
| output-file-path: output.json | |
| gh-pages-branch: benchmark-data | |
| auto-push: true | |
| save-data-file: ${{ github.ref == 'refs/heads/main' }} | |
| benchmark-data-dir-path: dashboard | |
| fail-on-alert: false | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| verify_examples: | |
| name: Verify example (${{ matrix.example }}) | |
| runs-on: ubuntu-latest | |
| needs: v2_nix_cache | |
| permissions: | |
| actions: read # Required to download the toolchain artifact. | |
| contents: read | |
| env: | |
| ANNEAL_TOOLCHAIN_DIR: ${{ github.workspace }}/anneal/target/anneal-toolchain | |
| __ZEROCOPY_LOCAL_DEV: 1 | |
| RUSTFLAGS: "" | |
| RUSTDOCFLAGS: "" | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| example: | |
| - abs | |
| - anatomy | |
| - checked_add | |
| - const_generics | |
| - design_doc | |
| - linked_list | |
| - namespaces | |
| - never_type | |
| - ptr_concat | |
| - size_of_align_of | |
| - swap | |
| - unchecked_get | |
| - update_max | |
| steps: | |
| - name: Free up disk space | |
| run: | | |
| sudo rm -rf /usr/local/lib/android | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf /usr/local/share/boost | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install stable Rust | |
| uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # zizmor: ignore[superfluous-actions] | |
| with: | |
| toolchain: stable | |
| - name: Download Anneal toolchain archive | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: anneal-exocrate.tar.zst | |
| path: anneal/v2/target | |
| - name: Install Anneal toolchain archive | |
| run: cargo run setup --local-archive v2/target/anneal-exocrate.tar.zst | |
| working-directory: anneal | |
| - name: Verify example | |
| env: | |
| EXAMPLE: ${{ matrix.example }} | |
| run: | | |
| KNOWN_FAILING=("design_doc" "never_type" "ptr_concat") | |
| example="$EXAMPLE" | |
| expect_failure=0 | |
| for kf in "${KNOWN_FAILING[@]}"; do | |
| if [ "$kf" = "$example" ]; then | |
| expect_failure=1 | |
| break | |
| fi | |
| done | |
| echo "Verifying $example (expect failure: $expect_failure)" | |
| if cargo run verify --unsound-allow-is-valid --example "$example"; then | |
| if [ "$expect_failure" -eq 1 ]; then | |
| echo "::error::Example $example succeeded but was expected to fail." | |
| exit 1 | |
| else | |
| echo "Example $example succeeded." | |
| fi | |
| else | |
| if [ "$expect_failure" -eq 1 ]; then | |
| echo "Example $example failed as expected." | |
| else | |
| echo "::error::Example $example failed." | |
| exit 1 | |
| fi | |
| fi | |
| working-directory: anneal | |
| # Build the Nix-produced toolchain archive once and fan out the exact archive | |
| # as a workflow artifact. This avoids forcing every downstream matrix runner | |
| # to realize the same Nix closure through Magic Nix Cache, which can run into | |
| # GitHub Actions cache throttling under parallel fan-out. | |
| v2_nix_cache: | |
| name: Build Anneal Toolchain Archive | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| # Restore a local Nix binary cache populated only by trusted `main` | |
| # pushes. Pull requests and merge-queue runs may read the default-branch | |
| # cache, but they never save their own entries; that keeps sibling PRs | |
| # from publishing toolchain archives for each other. This cache is only | |
| # for cross-run reuse in the builder job. The workflow artifact below | |
| # remains the cross-job fan-out mechanism for the current run. | |
| # | |
| # Keep the key inputs in sync with the local files that influence | |
| # `.#omnibus-archive-ci`. It intentionally excludes most Anneal source | |
| # files so ordinary PRs can reuse the archive built from `main`. | |
| - name: Restore Anneal Nix binary cache | |
| id: restore_anneal_nix_cache | |
| uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: anneal/v2/target/nix-cache | |
| key: anneal-v2-nix-cache-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('anneal/v2/flake.nix', 'anneal/v2/flake.lock', 'anneal/v2/rewrite-lake-vendor.py') }} | |
| - name: Install Nix | |
| uses: DeterminateSystems/determinate-nix-action@441b9e401ac050c38a07d8313748c5c2d17e8aff # v3.6.1 | |
| # On Ubuntu 24.04 (currently `ubuntu-latest`), AppArmor restricts unprivileged user namespaces by default. | |
| # The Nix build sandbox runs `steam-run` (which uses `bubblewrap`/`bwrap`) during the `mathlib-cache-download` | |
| # phase to create an FHS environment. `bwrap` requires creating a user namespace to set up uid mappings, | |
| # which fails with "Permission denied" unless this restriction is temporarily disabled on the host. | |
| # | |
| # We temporarily disable it right before the `nix build` step and re-enable it immediately after | |
| # to maintain the principle of least privilege. | |
| # | |
| # FIXME(#3412): Deduplicate this with what's repeated below? | |
| - name: Enable unprivileged user namespaces (Ubuntu 24.04) | |
| run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 | |
| - name: Build Anneal toolchain archive | |
| env: | |
| ANNEAL_NIX_CACHE_HIT: ${{ steps.restore_anneal_nix_cache.outputs.cache-hit }} | |
| ANNEAL_TRUSTED_MAIN_PUSH: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target | |
| nix_args=() | |
| if [ -f target/nix-cache/nix-cache-info ]; then | |
| nix_args+=(--extra-substituters "file://$PWD/target/nix-cache/?trusted=1") | |
| fi | |
| archive_attr=omnibus-archive | |
| if [ "$ANNEAL_TRUSTED_MAIN_PUSH" = "true" ]; then | |
| # Trusted main-branch pushes pay the compression cost once so | |
| # future PRs can download the smaller archive from the cache. | |
| archive_attr=omnibus-archive-ci | |
| elif [ "$ANNEAL_NIX_CACHE_HIT" = "true" ]; then | |
| # When the level-19 archive is already in the restored Nix cache, | |
| # substituting it is cheaper than rebuilding the level-1 archive. | |
| archive_attr=omnibus-archive-ci | |
| fi | |
| nix build "${nix_args[@]}" ".#$archive_attr" --out-link target/anneal-exocrate.tar.zst | |
| archive="$(readlink -f target/anneal-exocrate.tar.zst)" | |
| rm target/anneal-exocrate.tar.zst | |
| cp "$archive" target/anneal-exocrate.tar.zst | |
| working-directory: anneal/v2 | |
| # Re-enable the AppArmor namespace restriction to restore the runner host's default security posture. | |
| # `if: always()` ensures this cleanup step runs even if the Nix build fails. | |
| - name: Restore AppArmor restriction | |
| if: always() | |
| run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=1 | |
| # Populate the default-branch cache only from trusted `main` pushes. | |
| # GitHub's dependency-cache scoping makes entries saved from `main` | |
| # visible to PRs targeting `main`, while entries from PR merge refs are | |
| # isolated to that PR. Saving only here keeps the cache useful across PRs | |
| # without letting a PR publish an archive for unrelated runs. | |
| - name: Populate Anneal Nix binary cache | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.restore_anneal_nix_cache.outputs.cache-hit != 'true' | |
| run: | | |
| set -euo pipefail | |
| mkdir -p target/nix-cache | |
| nix copy .#omnibus-archive-ci \ | |
| --to "file://$PWD/target/nix-cache/?compression=zstd&compression-level=19&trusted=1" | |
| working-directory: anneal/v2 | |
| - name: Save Anneal Nix binary cache | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.restore_anneal_nix_cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: anneal/v2/target/nix-cache | |
| key: ${{ steps.restore_anneal_nix_cache.outputs.cache-primary-key }} | |
| - name: Upload Anneal toolchain archive | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| path: anneal/v2/target/anneal-exocrate.tar.zst | |
| if-no-files-found: error | |
| retention-days: 1 | |
| archive: false | |
| v2: | |
| name: Run V2 tests | |
| runs-on: ubuntu-latest | |
| # Depending on `v2_nix_cache` avoids duplicate Nix builds; this job only | |
| # downloads the exact archive that the builder job produced. | |
| needs: v2_nix_cache | |
| permissions: | |
| actions: read # Required to download the toolchain artifact. | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Download Anneal toolchain archive | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| name: anneal-exocrate.tar.zst | |
| path: anneal/v2/target | |
| # FIXME: Pin this nightly to the same Rust date encoded in | |
| # anneal/v2/flake.nix, or derive it from the archive metadata, so v2 CI is | |
| # reproducible instead of following whatever nightly happens to be latest. | |
| - name: Install latest nightly Rust | |
| uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # zizmor: ignore[superfluous-actions] | |
| with: | |
| toolchain: nightly | |
| - name: Run V2 tests | |
| run: cargo test --workspace --all-features # include, e.g., tests that assume exocrate prebuilt | |
| working-directory: anneal/v2 | |
| # Used to signal to branch protections that all other jobs have succeeded. | |
| all-jobs-succeed: | |
| # WARNING: This name is load-bearing! It's how GitHub's settings UI | |
| # configures which jobs to block on. DO NOT change this name without | |
| # updating the settings UI to match. | |
| name: All checks succeeded (anneal.yml) | |
| # On failure, we run and unconditionally exit with a failing status code. | |
| # On success, this job is skipped. Jobs skipped using `if:` are considered | |
| # to have succeeded: | |
| # | |
| # https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks | |
| if: failure() | |
| runs-on: ubuntu-latest | |
| needs: [anneal_tests, verify_examples, v2_nix_cache, v2] | |
| steps: | |
| - name: Mark the job as failed | |
| run: exit 1 |