From eaf2f67c5d742958df77d76b57f01e0c4c6a5b09 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Mon, 13 Jul 2026 08:48:39 +0000 Subject: [PATCH] Overhaul and optimize CI (#694) - Update action versions. - Stop running CI on Mac OS entirely. - Use the Rust version that comes with CI instead of installing stable. (vendor in the GitHub annotations matcher from `setup-rust-toolchain`) - Only run build and tests at the CI runner's (stable) Rust version. - Only use the MSRV version for the "check minimal versions" job. - Remove the "determine MSRV" job. - For Clippy, use the same nightly version as was used for the cache. - Only update the caches every week. --- .github/workflows/ci.yml | 461 ++++++++++++------------ .github/workflows/rust-annotations.json | 61 ++++ 2 files changed, 291 insertions(+), 231 deletions(-) create mode 100644 .github/workflows/rust-annotations.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1241ce3a..7d6f82ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,53 @@ # Continuous Integration: making sure the codebase works # ====================================================== # -# This workflow tests modifications to 'domain', ensuring that 'domain' can be -# used by others successfully. It verifies certain aspects of the codebase, +# This workflow tests modifications to 'domain', ensuring that 'domain' can +# be used by others successfully. It verifies certain aspects of the codebase, # such as the formatting and feature flag combinations, and runs the full test -# suite. It runs on Ubuntu, Mac OS, and Windows. +# suite. It runs on Ubuntu and Windows. +# +# NOTE: We don't test on MacOS right now because the CI runner is expensive +# and at this moment (2026-07-09) 'domain' does not have MacOS-specific code. +# It can be re-enabled if necessary. +# +# --- Rust versions +# +# The data cached in `target/` is specific to the Rust compiler version that +# generated it, so we need to be careful to use the same compiler version when +# we re-use cached `target/`s. +# +# Where possible, we use the compiler version that comes pre-installed on the +# CI runners. We assume this is fairly close to the latest Rust stable, and +# that it does not change often. +# +# NOTE: We use `actions-rust-lang/setup-rust-toolchain` to help set up +# Rust, but it does not support using the pre-installed CI version. We +# would like to use it in these cases because it also configures GitHub to +# provide annotations for Rust tool output; we copy their code and do that +# configuration manually (see `rust-annotations.json`). +# See . +# +# --- Caches +# +# `$os-$arch-system-$run_id` caches the `target/` generated by `cargo test` +# with all features enabled, using the Rust version pre-installed on the CI +# runner. `os` is in `{Linux, Windows}`, `arch` is `X64`. +# +# `$os-$arch-nightly-clippy-$run_id` caches the `target/` generated by `cargo +# clippy` with all features enabled, using a nightly Rust version. The Rust +# version (in `rustup toolchain` format) is saved to `target/rust-version`. +# `os` is `Linux`, `arch` is `X64`. +# +# NOTE: We assume the Rust version installed on the CI runner does not change +# often. In theory, we could write down the Rust version in the cache and +# explicitly install that version when restoring from the cache; but this +# probably adds unnecessary overhead. +# +# NOTE: GitHub treats cache entries as immutable (although it _does_ allow +# explicitly removing and re-adding them). We include the run ID so the cache +# key is unique every time; when restoring from the cache, we don't specify +# the run ID, and GitHub will automatically find the most recent cache entry +# using a prefix of the full key. name: CI @@ -23,8 +66,8 @@ on: - '.github/workflows/ci.yml' # If a pull request is merged, at least one commit is added to the target - # branch. If the target is another pull request, it will be caught by the - # above event. We miss PRs that merge to a non-PR branch, except for the + # branch. If the target is another pull request, it will be caught by the + # above event. We miss PRs that merge to a non-PR branch, except for the # 'main' branch. # Execute when a commit is pushed to 'main' (including merged PRs) or to a @@ -39,8 +82,8 @@ on: - 'Cargo.lock' - '.github/workflows/ci.yml' - # Rebuild 'main' every week. This will account for changes to dependencies - # and to Rust, either of which can trigger new failures. Rust releases are + # Rebuild 'main' every week. This will account for changes to dependencies + # and to Rust, either of which can trigger new failures. Rust releases are # every 6 weeks, on a Thursday; this event runs every Friday. schedule: - cron: '0 10 * * FRI' @@ -56,9 +99,9 @@ jobs: # Check Formatting # ---------------- # - # NOTE: This job is run even if no '.rs' files have changed. Inserting such + # NOTE: This job is run even if no '.rs' files have changed. Inserting such # a check would require using a separate workflow file or using third-party - # actions. Most commits do change '.rs' files, and 'cargo-fmt' is pretty + # actions. Most commits do change '.rs' files, and 'cargo-fmt' is pretty # fast, so optimizing this is not necessary. check-fmt: name: Check formatting @@ -67,31 +110,40 @@ jobs: # Load the repository. - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - # Set up the Rust toolchain. - # - # Disable the cache since it's not relevant for formatting. - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - components: rustfmt - cache: false + # Don't set up a Rust toolchain; use whatever is on the runner already. + # It will be close enough to the latest Rust stable. + + # Enable a problem matcher to get inline annotations. + - name: Enable annotations + run: echo "::add-matcher::.github/workflows/rust-annotations.json" + + # Don't restore from the cache; it's not needed here. # Do the actual formatting check. - name: Check formatting run: cargo fmt --all -- --check - # Build Documentation - # ---------------- + # Check + # ----- # - # Validate that the documentation builds without any issues. - build-doc: - name: Check docs + # Rust does not provide any way to check that all possible feature flag + # combinations will succeed, so we need to try them manually here. We assume + # this choice is not influenced by the OS or Rust version. For each feature + # flag combination, we check that everything compiles and that documentation + # succeeds. + check: + name: Check strategy: - matrix: # "" -> use default features - features: ["", "--all-features"] + matrix: + features: + # We always enable `domain::new`. + # TODO: The following currently break: + # - "--no-default-features -F unstable-new" + # - "--no-default-features -F unstable-new,alloc" + - "-F unstable-new" # + default features + - "--all-features" runs-on: ubuntu-latest env: RUSTDOCFLAGS: "-D warnings" @@ -99,92 +151,78 @@ jobs: # Load the repository. - name: Checkout repository - uses: actions/checkout@v4 - - # Set up the Rust toolchain. - - name: Set up Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - cache: false - - - name: Build documentation for validation - run: cargo doc --no-deps ${{ matrix.features }} - - # Determine MSRV - # -------------- - # - # The MSRV needs to be determined as we will test 'domain' against the Rust - # compiler at that version. - determine-msrv: - name: Determine MSRV - runs-on: ubuntu-latest - outputs: - msrv: ${{ steps.determine-msrv.outputs.msrv }} - steps: - - # Load the repository. - - name: Checkout repository - uses: actions/checkout@v4 - - # Determine the MSRV. - - name: Determine MSRV - id: determine-msrv - run: | - msrv=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name=="domain")|.rust_version'` - echo "msrv=$msrv" >> "$GITHUB_OUTPUT" - - # Check Feature Flags - # ------------------- - # - # Rust does not provide any way to check that all possible feature flag - # combinations will succeed, so we need to try them manually here. We will - # assume this choice is not influenced by the OS or Rust version. - check-feature-flags: - name: Check feature flags - runs-on: ubuntu-latest - steps: - - # Load the repository. - - name: Checkout repository - uses: actions/checkout@v4 - - # Set up the Rust toolchain. - - name: Set up Rust - id: setup-rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - cache: false + uses: actions/checkout@v7 # Restore a cache of dependencies and 'target'. - name: Restore a dependency cache id: cache-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v6 with: path: | - Cargo.cached.lock ~/.cargo target/ - # Cache by OS and Rust version. - key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}- + # Cache by OS and Rust version. Assume the "system" version (i.e. + # whatever is on the CI runner) doesn't change that often. We _could_ + # figure out what the CI runner has and key by that, but that would + # probably add unnecessary overhead. + key: ${{ runner.os }}-${{ runner.arch }}-system- - # Do the actual feature flag checks. - # - # NOTE: This does not benefit from the 'target' folder cached by a 'cargo - # check --all-features --all-targets' execution. Due to the minimal - # dependency set, it still runs fairly quickly. - - name: Check empty feature set - run: cargo check --all-targets --no-default-features --features unstable-new + # Don't set up a Rust toolchain; use whatever is on the runner already. + # It will be close enough to the latest Rust stable. - - name: Check `alloc` without `std` - run: cargo check --all-targets --no-default-features --features alloc,unstable-new + # Enable a problem matcher to get inline annotations. + - name: Enable annotations + run: echo "::add-matcher::.github/workflows/rust-annotations.json" + + - name: Check + run: cargo check ${{ matrix.features }} + + # `cargo doc` should benefit from the existing work done by `check`, so + # run it immediately afterwards. This eliminates the need for a separate + # `cargo doc` job. + - name: Doc + run: cargo doc --no-deps ${{ matrix.features }} + + # Check Examples + # -------------- + # + # Make sure examples compile, with the specific feature flags `Cargo.toml` + # indicates they need. + check-examples: + name: Check examples + runs-on: ubuntu-latest + steps: + + # Load the repository. + - name: Checkout repository + uses: actions/checkout@v7 + + # Restore a cache of dependencies and 'target'. + - name: Restore a dependency cache + id: cache-restore + uses: actions/cache/restore@v6 + with: + path: | + ~/.cargo + target/ + # Cache by OS and Rust version. Assume the "system" version (which + # corresponds to whatever is on the CI runner) doesn't change that + # often. We _could_ figure out what the CI runner has and key by that, + # but that would probably add unnecessary overhead. + key: ${{ runner.os }}-${{ runner.arch }}-system- + + # Don't set up a Rust toolchain; use whatever is on the runner already. + # It will be close enough to the latest Rust stable. + + # Enable a problem matcher to get inline annotations. + - name: Enable annotations + run: echo "::add-matcher::.github/workflows/rust-annotations.json" # Check the required feature flags for every example. - name: Check required features of examples run: | # Scrape crate metadata and construct the right 'check' commands. - # Cargo deosn't have an option to select the right features for us. + # Cargo doesn't have an option to select the right features for us. # See: https://github.com/rust-lang/cargo/issues/4663 cargo metadata --no-deps --format-version 1 \ | jq -r '.packages[].targets[]|select(.kind|any(.=="example"))|{name,features:(.["required-features"]+[]|join(","))}|"\(.name) \(.features)"' \ @@ -196,7 +234,9 @@ jobs: # ---------------------- # # Ensure that 'domain' compiles with the oldest compatible versions of all - # packages, even those 'domain' depends upon indirectly. + # packages, even those 'domain' depends upon indirectly. This is compiled + # with the declared MSRV to make sure Clippy's minimal versions lint didn't + # miss anything. check-minimal-versions: name: Check minimal versions runs-on: ubuntu-latest @@ -206,27 +246,35 @@ jobs: # Load the repository. - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 + + # Determine the MSRV. + - name: Determine MSRV + id: determine-msrv + run: | + msrv=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name=="domain")|.rust_version'` + echo "msrv=$msrv" >> "$GITHUB_OUTPUT" + + # TODO: Caching? # Set up the Rust toolchain. - - name: Set up Rust nightly + - name: Set up Rust (at MSRV) id: setup-rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: nightly, stable + toolchain: nightly, ${{ steps.determine-msrv.outputs.msrv }} cache: false + matcher: false - # TODO: Cache minimal-version dependencies? + # Enable a problem matcher to get inline annotations. + - name: Enable annotations + run: echo "::add-matcher::.github/workflows/rust-annotations.json" # Lock all dependencies to their minimal versions. - name: Lock dependencies to minimal versions run: cargo +nightly update -Z minimal-versions # Check that 'domain' compiles. - # - # NOTE: This does not benefit from the 'target' folder cached by a 'cargo - # check --all-features --all-targets' execution. It may be worthwhile to - # cache this 'target' folder separately (TODO). - name: Check run: cargo check --all-targets --all-features --locked @@ -248,45 +296,75 @@ jobs: # Load the repository. - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 + + # Restore a cached `target/`. + - name: Restore from cache + if: github.event_name != 'schedule' + id: cache-restore + uses: actions/cache/restore@v6 + with: + path: | + ~/.cargo + target/ + # Cache by OS. Use a recently cached version of Rust nightly. + key: ${{ runner.os }}-${{ runner.arch }}-nightly-clippy- + + # Determine the Rust version provided by the cache. + # + # If we didn't restore from cache, use the latest nightly. + - name: Determine the cached Rust version + id: determine-rust-version + run: | + if [[ -f target/rust-version ]]; then + echo "rust-version=$(cat target/rust-version)" >> "$GITHUB_OUTPUT" + else + echo "rust-version=nightly" >> "$GITHUB_OUTPUT" + fi # Set up the Rust toolchain. - name: Set up Rust nightly id: setup-rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: nightly + toolchain: ${{ steps.determine-rust-version.outputs.rust-version }} components: clippy cache: false + matcher: false - # Restore a cache of dependencies and 'target'. - - name: Restore from cache - id: cache-restore - uses: actions/cache/restore@v4 - with: - path: | - Cargo.cached.lock - ~/.cargo - target/ - # Cache by OS and Rust version. - key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}- + # Enable a problem matcher to get inline annotations. + - name: Enable annotations + run: echo "::add-matcher::.github/workflows/rust-annotations.json" # Do the actually Clippy run. - name: Check Clippy - run: cargo +nightly clippy --all-targets --all-features + run: cargo clippy --all-targets --all-features + + # Note the Rust version in the cache. + - name: Note Rust version for the cache + if: github.event_name == 'schedule' + run: | + rustup toolchain list | grep nightly- | tee target/rust-version + + # Save to the cache every week. + - name: Save to the cache + uses: actions/cache/save@v6 + if: github.event_name == 'schedule' + with: + path: | + ~/.cargo + target/ + key: ${{ runner.os }}-${{ runner.arch }}-nightly-clippy-${{ github.run_id }} # Test # ---- # - # Ensure that 'domain' compiles and its test suite passes, on a large number - # of operating systems and Rust versions. + # Ensure that 'domain' compiles and its test suite passes. test: name: Test - needs: determine-msrv strategy: matrix: - os: [ubuntu-latest, macOS-latest, windows-latest] - rust: ["${{ needs.determine-msrv.outputs.msrv }}", stable, nightly] + os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} env: RUSTFLAGS: "-D warnings" @@ -295,138 +373,59 @@ jobs: # Load the repository. - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 + + # Restore a cached `target/`. + - name: Restore from cache + id: cache-restore + uses: actions/cache/restore@v6 + with: + path: | + ~/.cargo + target/ + key: ${{ runner.os }}-${{ runner.arch }}-system- + + # Don't set up a Rust toolchain; use whatever is on the runner already. + # It will be close enough to the latest Rust stable. + + # Enable a problem matcher to get inline annotations. + - name: Enable annotations + run: echo "::add-matcher::.github/workflows/rust-annotations.json" # Prepare the environment on Windows - name: Prepare Windows environment if: matrix.os == 'windows-latest' shell: bash run: | + # Filter out `openssl` because it's a pain to compile on Windows. # Cargo doesn't support enabling all but one feature, so determine the - # complete feature list and filter out 'openssl'. + # complete feature list and filter it out manually. features=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name == "domain")|.features|keys|map(select(.!="openssl"))|join(",")'` # Overwrite the 'DOMAIN_FEATURES' environment variable. echo "DOMAIN_FEATURES=--features=$features" >> "$GITHUB_ENV" # See echo "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=rust-lld" >> "$GITHUB_ENV" - # Set up the Rust toolchain. - - name: Set up Rust ${{ matrix.rust }} - id: setup-rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - cache: false - - # Restore a cache of dependencies and 'target'. - - name: Restore from cache - id: cache-restore - uses: actions/cache/restore@v4 - with: - path: | - Cargo.cached.lock - ~/.cargo - target/ - # Cache by OS and Rust version. - key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}- - - # For MSRV builds, use the cached 'Cargo.lock'. - - name: Use the cached Cargo lockfile - if: matrix.rust == needs.determine-msrv.outputs.msrv - # Remove 'Cargo.lock' even if a cached lockfile is unavailable. - run: mv Cargo.cached.lock Cargo.lock || rm Cargo.lock - # Build and run the test suite. - name: Test run: cargo test --all-targets $DOMAIN_FEATURES # Test docs. + # + # Passing `--all-targets` to `cargo test` (as in the previous step) + # prevents doc tests from running. + # See . - name: Test docs run: cargo test --doc $DOMAIN_FEATURES - # Build Cache - # ----------- - # - # Prepare a cache for checking and building 'domain', on 'main'. - cache: - name: Cache - needs: determine-msrv - strategy: - matrix: - os: [ubuntu-latest, macOS-latest, windows-latest] - rust: ["${{ needs.determine-msrv.outputs.msrv }}", stable, nightly] - runs-on: ${{ matrix.os }} - if: github.ref == 'refs/heads/main' - env: - RUSTFLAGS: "-D warnings" - DOMAIN_FEATURES: "--all-features" - # See - CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: "rust-lld" - steps: - - # Load the repository. - - name: Checkout repository - uses: actions/checkout@v4 - - # Prepare the environment on Windows - - name: Prepare Windows environment - if: matrix.os == 'windows-latest' - shell: bash - run: | - # Cargo doesn't support enabling all but one feature, so determine the - # complete feature list and filter out 'openssl'. - features=`cargo metadata --no-deps --format-version 1 | jq -r '.packages[]|select(.name == "domain")|.features|keys|map(select(.!="openssl"))|join(",")'` - # Overwrite the 'DOMAIN_FEATURES' environment variable. - echo "DOMAIN_FEATURES=--features=$features" >> "$GITHUB_ENV" - - # Set up the Rust toolchain. - - name: Set up Rust ${{ matrix.rust }} - id: setup-rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - cache: false - - # Restore a cache of dependencies and 'target'. - - name: Restore from cache - uses: actions/cache/restore@v4 - with: - path: | - Cargo.cached.lock - ~/.cargo - target/ - # Cache by OS and Rust version. - key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}- - - # For MSRV builds, use the cached 'Cargo.lock'. - - name: Use the cached Cargo lockfile - if: matrix.rust == needs.determine-msrv.outputs.msrv - # Remove 'Cargo.lock' even if a cached lockfile is unavailable. - run: mv Cargo.cached.lock Cargo.lock || rm Cargo.lock - - # Build all of 'domain'. - - name: Build - run: cargo build --all-targets $DOMAIN_FEATURES - - # Copy to the Cargo lockfile for optional caching. - - name: Copy the Cargo lockfile for optional caching - run: cp Cargo.lock Cargo.cached.lock - # Save to the cache. - name: Save to the cache - uses: actions/cache/save@v4 + if: github.event_name == 'schedule' + uses: actions/cache/save@v6 with: path: | - Cargo.cached.lock ~/.cargo target/ - # Cache by OS and Rust version. - # - # GitHub treats cache entries as immutable (although it _does_ allow - # explicitly removing and re-adding them). We include the run ID so - # the cache key is unique every time; when restoring from the cache, - # we don't specify the run ID, and GitHub will automatically find the - # most recent cache entry using the key as a prefix. - key: ${{ runner.os }}-${{ steps.setup-rust.outputs.cachekey }}-${{ github.run_id }} + key: ${{ runner.os }}-${{ runner.arch }}-system-${{ github.run_id }} # TODO: Use 'cargo-semver-checks' on releases. diff --git a/.github/workflows/rust-annotations.json b/.github/workflows/rust-annotations.json new file mode 100644 index 00000000..e2416ca7 --- /dev/null +++ b/.github/workflows/rust-annotations.json @@ -0,0 +1,61 @@ +// Tell GitHub how to extract inline annotations from the output of various +// Rust tools. Copied from `actions-rust-lang/setup-rust-toolchain`. +// +// Permalink to copied version: +// https://github.com/actions-rust-lang/setup-rust-toolchain/blob/166cdcfd11aee3cb47222f9ddb555ce30ddb9659/rust.json +// +// # License (MIT) +// +// Copyright (c) 2022 actions-rust-lang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +{ + "problemMatcher": [ + { + "owner": "rustfmt", + "severity": "warning", + "pattern": [ + { + "regexp": "^(Diff in (.+))(?: at line |:)(\\d+):$", + "message": 1, + "file": 2, + "line": 3 + } + ] + }, + { + "owner": "clippy", + "pattern": [ + { + "regexp": "^(?:\\x1b\\[[\\d;]+m)*(warning|warn|error)(?:\\x1b\\[[\\d;]+m)*(\\[(.*)\\])?(?:\\x1b\\[[\\d;]+m)*:(?:\\x1b\\[[\\d;]+m)* ([^\\x1b]*)(?:\\x1b\\[[\\d;]+m)*$", + "severity": 1, + "message": 4, + "code": 3 + }, + { + "regexp": "^(?:\\x1b\\[[\\d;]+m)*\\s*(?:\\x1b\\[[\\d;]+m)*\\s*--> (?:\\x1b\\[[\\d;]+m)*(.*):(\\d*):(\\d*)(?:\\x1b\\[[\\d;]+m)*$", + "file": 1, + "line": 2, + "column": 3 + } + ] + } + ] +}