Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
59be759
fix(anvil): harden workspace checks
martin-kolinek Aug 26, 2026
0b8698d
docs(anvil): clarify cargo-sort format scope
martin-kolinek Aug 26, 2026
e4a64ed
fix(anvil): retain cargo-sort formatting
martin-kolinek Aug 26, 2026
1429fd6
refactor(anvil): use cargo-each for formatting
martin-kolinek Aug 26, 2026
3fdad6f
chore(anvil): regenerate repository state
martin-kolinek Aug 26, 2026
f6d8a24
fix(anvil): address workspace check review
martin-kolinek Aug 27, 2026
8cf9243
docs(anvil): clarify private semver scope
martin-kolinek Aug 28, 2026
3f88fdf
Merge origin/main into anvil check fixes
martin-kolinek Aug 28, 2026
5800b47
docs(anvil): clarify publishability terminology
martin-kolinek Aug 28, 2026
9d686d2
docs(anvil): restore semver filter rationale
martin-kolinek Aug 28, 2026
b754fa6
fix(anvil): check non-publishable external types
martin-kolinek Aug 28, 2026
569e45f
Merge origin/main into u/makolnek/anvil-generic-check-fixes
martin-kolinek Aug 28, 2026
0594fd4
fix(anvil): bound loom exploration in CI
martin-kolinek Aug 28, 2026
d913346
test(anvil): refresh loom recipe snapshots
martin-kolinek Aug 28, 2026
29d5b35
fix(anvil): use supported bolero profile option
martin-kolinek Aug 29, 2026
d8a7d95
fix(gamma): enable bolero libfuzzer support
martin-kolinek Aug 29, 2026
2a31cec
fix(gamma): keep loom exploration exhaustive
martin-kolinek Aug 31, 2026
5218b7f
Merge origin/main into anvil generic checks
martin-kolinek Aug 31, 2026
c42bf9e
fix(anvil): detect manifest-only drift
martin-kolinek Aug 31, 2026
24ec4fb
docs: clarify bolero feature rationale
martin-kolinek Aug 31, 2026
39f25cc
merge main and normalize lockfile line endings
martin-kolinek Aug 31, 2026
d5c31f8
Merge remote-tracking branch 'origin/main' into u/makolnek/anvil-gene…
martin-kolinek Sep 1, 2026
77d02b1
fix(anvil): align generated check contracts
martin-kolinek Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 50 additions & 10 deletions .anvil.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version = 1
tool = "anvil"
tool_version = "0.5.0"
catalog_checksum = "sha256:b9e76e5dab6d2cd8cd2eaea04bd1b4457ea8839a688795981dc4cc9a415f8092"
catalog_checksum = "sha256:6d8b178f5c0c45122701a7d5e455a50757dd621b4d48a823175124dfddb94194"

[[file]]
path = ".anvil/container/Containerfile"
Expand Down Expand Up @@ -85,7 +85,7 @@ checksum = "sha256:50f04b4ea6c99df8ad7434d320f34dccdb6db6a77b83e3090e35de0ca3a15

[[file]]
path = "justfiles/anvil/checks/bolero.just"
checksum = "sha256:003032f39c781851b880c1d7e63b93573f5252cca9f785b98b04dfa2e858aff3"
checksum = "sha256:754827bb664723169b8d48a6f1e69f9122b5440ba0b94a91622f4d23313e2503"

[[file]]
path = "justfiles/anvil/checks/careful.just"
Expand All @@ -97,7 +97,7 @@ checksum = "sha256:681c8952690496c7d29c7bf83a5ab99c193088b05a5dc7ea3b3b0ed240090

[[file]]
path = "justfiles/anvil/checks/cargo-sort.just"
checksum = "sha256:2652ae52153eebe3fdb1dfd553aa65cab84d11054d5314470d55774a92a742b3"
checksum = "sha256:98c9f90e1b6b3beba5d818bf9a4023068eb61e9f071ffb6f9cd009a7374e9a30"

[[file]]
path = "justfiles/anvil/checks/clippy.just"
Expand Down Expand Up @@ -133,7 +133,7 @@ checksum = "sha256:d003f1d82898706c136f42c3156d5ecc8f288c18f125c964b9dca1d24fa93

[[file]]
path = "justfiles/anvil/checks/fmt.just"
checksum = "sha256:967b68ef3ed3e0dcac2a1ea22ef92c66a7db802bda718f6e5ce39f529ed8e492"
checksum = "sha256:618907282b9b7d1e5c094d0b29c1c7231465078b617fa233219fe8c4870437cb"

[[file]]
path = "justfiles/anvil/checks/license-headers.just"
Expand All @@ -145,7 +145,7 @@ checksum = "sha256:689f7d5b1eaea32672f8d0b9fff0994cc03bc56fb8ef282a4286f930fc503

[[file]]
path = "justfiles/anvil/checks/loom.just"
checksum = "sha256:4bf509bcca3507aafd9848ce19ea3691ab2cd77de05b344e042f3ba2c300e35b"
checksum = "sha256:a919429e9693c230ba8c9a194c41127708acf2cb1bfd196049a1386e7bb7b4e4"

[[file]]
path = "justfiles/anvil/checks/miri-race-coverage.just"
Expand Down Expand Up @@ -177,11 +177,11 @@ checksum = "sha256:28734aa77526ca5c53d89a32c3e20ae6b42d2032c73ab6a1dbc9831f25cb0

[[file]]
path = "justfiles/anvil/checks/readme-check.just"
checksum = "sha256:d346399f288570066e53fd123baf05f7d4a57f17da8ee687a6d881204c5bae12"
checksum = "sha256:6d4b4c3e4a59e825a3f613e1011272c711d8d6cbdef315e5f6aadf653c6ae206"

[[file]]
path = "justfiles/anvil/checks/semver-check.just"
checksum = "sha256:1b8b67f8c054c8da03d43b746f28c20f5366996e3dea3c21cf25d04a6496a072"
checksum = "sha256:52b31bec3c5d271b25f46d9b1dd6aca4b84e394fd8cf46bfa5386a3df7ed3999"

[[file]]
path = "justfiles/anvil/checks/spellcheck.just"
Expand Down Expand Up @@ -233,7 +233,7 @@ checksum = "sha256:a00153eda6b55d4db33e8019fa74f4a905f9c80b10b0db10955179c0af3e9

[[file]]
path = "justfiles/anvil/helpers.just"
checksum = "sha256:6b71379310b986e9a44000ba9d072c3f8934397a0f7fb1bf27a31a7742ca7002"
checksum = "sha256:72a7b89f85b5bdc55f8c8c4699b78e877a6c78d0bb39e57b57518f5e289858fa"

[[file]]
path = "justfiles/anvil/impact.just"
Expand All @@ -253,11 +253,11 @@ checksum = "sha256:5b2d91569f3fe87cd5f516623a01b5c7ae855ad587870ef9a2ae6d5619dd9

[[file]]
path = "justfiles/anvil/tools.just"
checksum = "sha256:a1e44ca16f172b487afa3997f102512733d3b65a4418cf894cbd749a3abc17dc"
checksum = "sha256:c1f5181d14a734cde8ccc32bbf5acece485791573b7a95cfff61b63d0f1fcb97"

[[file]]
path = "justfiles/anvil/versions.just"
checksum = "sha256:acbea93d5117db747537f4f7b9a5eb90b7d3e0dd3e8684cc0e4dc1dcb15ac93e"
checksum = "sha256:b48c7a29f694acdc4ff02a15bfdae5a286c1d0b6272fffad3acefbd8b1f2dcaa"

[[region]]
host = ".delta.toml"
Expand Down Expand Up @@ -324,6 +324,46 @@ host = "crates/cargo-ensure-no-default-features/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-gamma-attrs-impl/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-gamma-attrs/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-gamma-engine/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-gamma-lib/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-gamma-process/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-gamma-rt/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-gamma-unsafe/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-gamma/Cargo.toml"
id = "anvil-lints"
checksum = "sha256:2dd7c0f21339fd17092b8dedfe924aa86732c3520baab84f914c2d8f4103ac40"

[[region]]
host = "crates/cargo-heather/Cargo.toml"
id = "anvil-lints"
Expand Down
7 changes: 2 additions & 5 deletions .github/workflows/regenerate-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
# 1. Builds cargo-anvil from the PR branch.
# 2. Runs `cargo anvil --dry-run` against the repo root.
# 3. Fails iff the binary would write or propose anything.
# 3. Fails iff the binary would change generated content or `.anvil.lock`.
#
# This is the primary dogfooding mechanism described in
# crates/cargo-anvil/docs/verification.md.
Expand All @@ -17,10 +17,7 @@
name: regenerate-check

on:
pull_request:
paths:
- "crates/cargo-anvil/**"
- ".github/workflows/regenerate-check.yml"
pull_request: {}
merge_group: {}
workflow_dispatch: {}

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ anyhow = { version = "1.0.104", default-features = false }
assert_cmd = { version = "2.2.2", default-features = false }
async-once-cell = { version = "0.5", default-features = false }
blake3 = { version = "1.8.7", default-features = false }
bolero = { version = "0.13.4", default-features = false }
bolero = { version = "0.13.4", default-features = false, features = ["std"] }
# bolero-libfuzzer 0.13.0 calls bolero_engine::any without enabling the feature itself.
bolero-engine = { version = "0.13.4", default-features = false }
Comment thread
martin-kolinek marked this conversation as resolved.
bytes = { version = "1.12.1", default-features = false }
Expand Down
12 changes: 6 additions & 6 deletions crates/cargo-anvil/docs/design/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ that provided the strongest version of the check.

| Check | Invocation | Source |
|--------------------------------|-----------------------------------------------------------|--------|
| `fmt` | `cargo +<pinned-nightly> fmt --all --check` | all |
| `fmt` | `cargo each --workspace --keep-going -- cargo +<pinned-nightly> fmt --manifest-path {manifest} --check`. `cargo-each` resolves workspace membership and invokes rustfmt once per manifest, keeping child commands bounded on every platform while reporting every failing member. Unlike `cargo fmt --all`, local path dependencies outside the workspace are not included. | all |
| `clippy` | `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` | all |
| `cargo-sort` | `cargo sort --workspace --grouped --check --check-format` | oxidizer-github |
| `cargo-sort` | `cargo sort --workspace --grouped --check --check-format`. Since cargo-sort 2.1.2, formatting-only differences are warnings unless `--check-format` is set; Anvil keeps it load-bearing so dependency ordering and Cargo manifest formatting are both enforced. `--grouped` preserves intentional blank-line-separated dependency groups. | oxidizer-github |
| `license-headers` | `cargo heather --workspace` | oxidizer (`heather`), oxidizer-github |
| `ensure-no-cyclic-deps` | `cargo ensure-no-cyclic-deps --workspace` | oxidizer-github (sibling crate in `ox-tools-gh`) |
| `ensure-no-default-features` | `cargo ensure-no-default-features --workspace` | oxidizer-github |
Expand All @@ -170,7 +170,7 @@ that provided the strongest version of the check.
| `deny` | `cargo deny check` | all |
| `audit` | `cargo audit` | oxidizer |
| `udeps` | `cargo +<pinned-nightly> udeps --workspace --all-features` run **twice** — once with default targets (lib + bins) and once with `--all-targets`. cargo-udeps only analyzes the targets it's told to, and each run catches a variant the other masks: the default-targets run surfaces a dep in `[dependencies]` referenced only by tests/benches/examples (it should be a dev-dep; `--all-targets` would see it as "used"), while the `--all-targets` run surfaces unused `[dev-dependencies]` (never compiled by the default-targets run). Together they cover unused deps, unused dev-deps, and deps that should be dev-deps. | oxidizer, oxidizer-github |
| `semver-check` | `cargo semver-checks --baseline-rev <baseline>` per affected library crate. The PR target is the baseline. Exit 100 is a completed check with deny-level findings; exit 101 or another nonzero status means the comparison was inconclusive. Both outcomes write `target/anvil/comments/semver.md` and remain advisory, matching the repository's native `semver` job (`continue-on-error: true`). Proven rename and bin→lib transitions with no comparable baseline, and dependencies proven to be yanked only in the checked-out baseline tree, are skipped without a comment. Anvil preflight failures such as invalid current-workspace metadata or an unavailable baseline ref still fail because the recipe cannot establish what to compare. | oxidizer-github |
| `semver-check` | `cargo semver-checks --baseline-rev <baseline>` per affected publishable library crate. Crates with `publish = false` and bin-only crates have no registry consumer contract and are skipped. The PR target is the baseline. Exit 100 is a completed check with deny-level findings; exit 101 or another nonzero status means the comparison was inconclusive. Both outcomes write `target/anvil/comments/semver.md` and remain advisory, matching the repository's native `semver` job (`continue-on-error: true`). Proven rename and bin→lib transitions with no comparable baseline, and dependencies proven to be yanked only in the checked-out baseline tree, are skipped without a comment. Anvil preflight failures such as invalid current-workspace metadata or an unavailable baseline ref still fail because the recipe cannot establish what to compare. | oxidizer-github |
| `external-types` | `cargo +<catalog-nightly-rustdoc-schema> check-external-types --manifest-path` per library crate (per-manifest because the tool has no `--workspace`/`--package`; bin-only crates have no public API surface and are skipped). Setup installs the catalog version but validation accepts newer installed tools. The selected nightly is tested with the catalog version; an incompatible newer tool fails closed with a tool/nightly compatibility diagnostic rather than silently selecting a different schema. | oxidizer-github |

### `pr-slow`
Expand Down Expand Up @@ -198,8 +198,8 @@ This is the same set of checks that used to live in the standalone `pr-test` gro
|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|
| `miri` | `cargo +<pinned-nightly> miri test --all-features --tests` over the impact-affected packages. Uses libtest (one process per test binary), **not** `cargo miri nextest run`: under miri, nextest's process-per-test model pays miri's expensive std-initialization re-interpretation for *every* test and roughly doubles wall-time on a large suite (the dominant cost on the PR critical path). `--tests` runs lib/bin unit tests and integration tests (the same target set nextest ran) while excluding doctests, which miri can't run; it is used in preference to `--lib --tests` because `--lib` errors with "no library targets found" on a bin-only affected package under impact scoping. Slow tests opt out per-test with `#[cfg_attr(miri, ignore)]` -- anvil doesn't pass exotic `MIRIFLAGS`; the per-test opt-out is the canonical mechanism. libtest exits 0 when a binary's tests are all skipped, so no `--no-tests=pass` workaround is needed. The recipe reads its scope from the `target/anvil/impact/` cache via `_anvil-impact-include`; because it depends on `anvil-impact`, a clean direct or PR invocation is impact-scoped (unaffected packages are skipped). It runs the full workspace only when scoping is off — the scheduled/full tiers set `ANVIL_IMPACT=off`, and a dirty local tree widens for safety. | oxidizer, oxidizer-github |
| `careful` | `cargo +<catalog-nightly> careful test --all-features --locked` over the impact-affected packages. cargo-careful uses a debug-instrumented std in a stable cache path. Because Cargo fingerprints the sysroot path rather than its contents, the recipe records the actual `rustc -vV` and SHA-256 of the resolved `cargo-careful` executable in `target/anvil/careful-sysroot.id`; either changing triggers `cargo clean`. The executable hash is used because cargo-careful rejects version-only invocations. This remains correct when validation accepts a newer installed cargo-careful. | oxidizer-github |
| `loom` | For each `[[test]]` target that declares `required-features = ["loom"]`, `cargo test -p <pkg> --release --all-features --locked --test <target> -- --test-threads=1` with `RUSTFLAGS="--cfg loom"`. [`loom`](https://crates.io/crates/loom) is a permutation-based concurrency model checker that explores thread interleavings. Targets are detected **structurally** from `cargo metadata` (a test target whose `kind` contains `test` and whose `required-features` contains `loom`) -- not via a filename/cfg/comment heuristic -- and only those targets run, so loom never touches a crate's ordinary tests. The `loom` feature selects the target (`required-features`); `--cfg loom` activates loom (source swaps std↔loom atomics on `#[cfg(loom)]`, and `[target.'cfg(loom)'.dependencies] loom` links only under the cfg) -- both are required. Scoped per-package with `-p` (never `--workspace`) so the global cfg never leaks into deps reachable only through other members. **Fail-loud**: a crate that declares loom support (a `loom` feature or a `cfg(loom)` dependency) but exposes no such test target errors out rather than silently no-opping. When no crate ships a loom target the recipe skips (exit 0). | oxidizer-github |
| `bolero` | Uses the catalog nightly and release profile consistently to discover targets one package at a time, then runs each affected libfuzzer target for 60 seconds on Linux. Explicitly selecting `release` avoids cargo-bolero's implicit, adopter-defined `fuzz` profile and matches target execution. Per-package discovery is required because `cargo-bolero list` accepts only one `--package`; local whole-workspace runs enumerate workspace members before discovery. A successful empty discovery is a no-op; metadata, discovery, or parsing failure fails the check. Non-Linux hosts skip because cargo-bolero's native dependencies are unsupported there, while harnesses still run as ordinary tests. | oxidizer-github |
| `loom` | For each `[[test]]` target that declares `required-features = ["loom"]`, `cargo test -p <pkg> --release --all-features --locked --test <target> -- --test-threads=1` with `RUSTFLAGS="--cfg loom"`. [`loom`](https://crates.io/crates/loom) is a permutation-based concurrency model checker that explores thread interleavings. Anvil does not impose a global exploration bound: each model owns its topology and must remain tractable under exhaustive exploration, avoiding redundant symmetric participants that multiply equivalent schedules. Targets are detected **structurally** from `cargo metadata` (a test target whose `kind` contains `test` and whose `required-features` contains `loom`) -- not via a filename/cfg/comment heuristic -- and only those targets run, so loom never touches a crate's ordinary tests. The `loom` feature selects the target (`required-features`); `--cfg loom` activates loom (source swaps std↔loom atomics on `#[cfg(loom)]`, and `[target.'cfg(loom)'.dependencies] loom` links only under the cfg) -- both are required. Scoped per-package with `-p` (never `--workspace`) so the global cfg never leaks into deps reachable only through other members. **Fail-loud**: a crate that declares loom support (a `loom` feature or a `cfg(loom)` dependency) but exposes no such test target errors out rather than silently no-opping. When no crate ships a loom target the recipe skips (exit 0). | oxidizer-github |
| `bolero` | Uses the catalog nightly and release profile consistently to discover targets one package at a time, then runs each affected libfuzzer target for 60 seconds on Linux. Explicitly selecting `release` avoids cargo-bolero's implicit, adopter-defined `fuzz` profile and matches target execution. Adopters that disable `bolero`'s default features must enable its `std` feature; that feature supplies the compile-time `bolero-engine/any` support required by the libfuzzer adapter. Per-package discovery is required because `cargo-bolero list` accepts only one `--package`; local whole-workspace runs enumerate workspace members before discovery. A successful empty discovery is a no-op; metadata, discovery, or parsing failure fails the check. Non-Linux hosts skip because cargo-bolero's native dependencies are unsupported there, while harnesses still run as ordinary tests. | oxidizer-github |
Comment thread
martin-kolinek marked this conversation as resolved.
Outdated

#### `pr-mutants` (mutation testing)

Expand Down Expand Up @@ -345,7 +345,7 @@ Each catalog check is tagged with one of four buckets:

| Bucket | `$include` tier | Behavior when a tier value is present | Behavior when unscoped (`ANVIL_IMPACT=off` / no cache) |
|-----------|-------------------------------|-----------------------------------------------------------------------------|--------------------------------------|
| modified | `_anvil-impact-include modified` | If `--skip`: exit 0. Otherwise run unconditionally (tool is workspace-wide). | Run unconditionally. |
| modified | `_anvil-impact-include modified` | If `--skip`: exit 0. Otherwise run the check's complete selected scope without package splicing. | Run the complete selected scope. |
| affected | `_anvil-impact-include affected` | If `--skip`: exit 0. Otherwise splice the value into the cargo invocation. | Default to `--workspace`. |
| required | `_anvil-impact-include required` | If `--skip`: exit 0. Otherwise splice the value into the cargo invocation. | Default to `--workspace`. |
| unscoped | *(none)* | Always run. | Always run. |
Expand Down
Loading
Loading