diff --git a/.config/nextest.toml b/.config/nextest.toml
index 08f7cd2c..054cb657 100644
--- a/.config/nextest.toml
+++ b/.config/nextest.toml
@@ -1,20 +1,20 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
-# Every case in cargo-anvil's pr_title suite runs the anvil-pr-title recipe,
-# which starts a PowerShell process. PowerShell intermittently aborts while
-# parsing an assembly name during startup (PowerShell/PowerShell#26940), and
-# concurrent interpreter startups on constrained runners hit that abort often
-# enough to fail the suite. The suite carries `#[serial]` for the plain libtest
-# runners (`cargo test`, `cargo careful test`), but nextest runs each case in
-# its own process, where an in-process lock cannot serialize anything. This
-# group is what serializes them under nextest.
+# Every case in cargo-anvil's pr_title and impact suites starts PowerShell.
+# PowerShell intermittently aborts while parsing an assembly name during
+# startup (PowerShell/PowerShell#26940), and concurrent interpreter startups
+# on constrained runners hit that abort often enough to fail the suite. The
+# suites carry `#[serial]` for plain libtest runners (`cargo test`, `cargo
+# careful test`), but nextest runs each case in its own process, where an
+# in-process lock cannot serialize anything. This group serializes them under
+# nextest.
#
# `binary(...)` rather than `test(...)`: the latter filters test names, none of
-# which contain `pr_title`.
+# which contain the integration-test binary names.
[test-groups]
-pr-title = { max-threads = 1 }
+powershell = { max-threads = 1 }
[[profile.default.overrides]]
-filter = 'binary(pr_title)'
-test-group = 'pr-title'
+filter = 'binary(pr_title) | binary(impact)'
+test-group = 'powershell'
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3465cdcd..d61280d1 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -65,7 +65,7 @@ jobs:
enable-sccache: true
rust-toolchain: RUST_LATEST
rust-components: clippy, rustfmt
- cargo-tools: CARGO_DOC2README_VERSION, CARGO_WORKSPACES_VERSION, CARGO_ENSURE_NO_DEFAULT_FEATURES_VERSION, CARGO_ENSURE_NO_CYCLIC_DEPS_VERSION, CARGO_DENY_VERSION, CARGO_SORT_VERSION, JUST_VERSION
+ cargo-tools: CARGO_DOC2README_VERSION, CARGO_ENSURE_NO_DEFAULT_FEATURES_VERSION, CARGO_ENSURE_NO_CYCLIC_DEPS_VERSION, CARGO_DENY_VERSION, CARGO_SORT_VERSION, JUST_VERSION
# execute
- name: Clippy
diff --git a/Cargo.lock b/Cargo.lock
index 3d5d250a..2ff420ac 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -68,7 +68,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -79,14 +79,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
name = "antlr4rust"
-version = "0.3.0-rc2"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d240d49ee89063f90fa0cb18aead41a5893cd544a1785983dc3bf5c3d5faa58b"
+checksum = "093d520274bfff7278d776f7ea12981a0a0a6f96db90964658e0f38fc6e9a6a6"
dependencies = [
"better_any",
"bit-set",
@@ -263,6 +263,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
[[package]]
name = "bolero"
version = "0.13.4"
@@ -310,7 +319,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98a5782f2650f80d533f58ec339c6dce4cc5428f9c2755894f98156f52af81f2"
dependencies = [
"bolero-generator-derive",
+ "either",
+ "getrandom 0.3.4",
"rand_core 0.9.5",
+ "rand_xoshiro",
]
[[package]]
@@ -438,7 +450,7 @@ dependencies = [
"bytes",
"camino",
"cargo_metadata",
- "cel-interpreter",
+ "cel",
"chrono",
"clap",
"compact_str",
@@ -576,6 +588,7 @@ version = "0.1.0"
dependencies = [
"blake3",
"bolero",
+ "bolero-engine",
"camino",
"compact_str",
"proc-macro2",
@@ -593,6 +606,7 @@ version = "0.1.0"
dependencies = [
"blake3",
"bolero",
+ "bolero-engine",
"camino",
"cargo-gamma-attrs-impl",
"cargo-gamma-engine",
@@ -716,27 +730,18 @@ dependencies = [
]
[[package]]
-name = "cel-interpreter"
-version = "0.10.0"
+name = "cel"
+version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a76c07820046cc8239526fceec6df147a979ae48644dbad274fc3ce38ab0973b"
+checksum = "9f93082a93da8fd78394852602ced1e2e7754ed8c29dc813fa80b01a1baf9032"
dependencies = [
- "cel-parser",
+ "antlr4rust",
"chrono",
+ "lazy_static",
"nom",
- "paste",
+ "pastey",
"serde",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "cel-parser"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "546fb134998490c5c47fc7a29c7535e725d2e403f172040e8f263d0b318bff5f"
-dependencies = [
- "antlr4rust",
- "lazy_static",
+ "thiserror 2.0.20",
]
[[package]]
@@ -999,6 +1004,15 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
[[package]]
name = "csv"
version = "1.4.0"
@@ -1099,8 +1113,18 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "block-buffer",
- "crypto-common",
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.7",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "crypto-common 0.2.2",
]
[[package]]
@@ -1121,7 +1145,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1225,7 +1249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1278,9 +1302,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flate2"
-version = "1.1.9"
+version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
dependencies = [
"crc32fast",
"libz-sys",
@@ -2436,6 +2460,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "hybrid-array"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
+dependencies = [
+ "typenum",
+]
+
[[package]]
name = "hyper"
version = "1.11.0"
@@ -3030,9 +3063,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.8.9"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
dependencies = [
"adler2",
"simd-adler32",
@@ -3055,7 +3088,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08"
dependencies = [
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -3086,9 +3119,9 @@ dependencies = [
[[package]]
name = "mutants"
-version = "0.0.3"
+version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126"
+checksum = "add0ac067452ff1aca8c5002111bd6b1c895baee6e45fcbc44e0193aea17be56"
[[package]]
name = "nix"
@@ -3232,14 +3265,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "owo-colors"
-version = "4.3.0"
+version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
+checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8"
[[package]]
name = "parking_lot"
@@ -3264,12 +3297,6 @@ dependencies = [
"windows-link",
]
-[[package]]
-name = "paste"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
-
[[package]]
name = "pastey"
version = "0.2.3"
@@ -3468,7 +3495,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -3801,9 +3828,9 @@ dependencies = [
[[package]]
name = "rust_xlsxwriter"
-version = "0.98.2"
+version = "0.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "093d730a6f64f9620e047b2de2876e61a691fbcaca3aaf436edade9b897c7a27"
+checksum = "c073c0bd3868defc46035bf95b2e70389cefc59c528b8674fe90594c9c2b3f6f"
dependencies = [
"zip",
]
@@ -3940,7 +3967,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -3997,7 +4024,7 @@ dependencies = [
"security-framework",
"security-framework-sys",
"webpki-root-certs",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -4210,7 +4237,7 @@ checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -4219,19 +4246,19 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423"
dependencies = [
- "digest",
+ "digest 0.10.7",
"sha1",
]
[[package]]
name = "sha2"
-version = "0.10.9"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
- "cpufeatures 0.2.17",
- "digest",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
]
[[package]]
@@ -4338,7 +4365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [
"libc",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -4495,7 +4522,7 @@ dependencies = [
"getrandom 0.4.3",
"once_cell",
"rustix",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -4505,7 +4532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
dependencies = [
"rustix",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -5205,7 +5232,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index da544043..2d11ae6e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,12 +23,14 @@ homepage = "https://github.com/microsoft/ox-tools"
[workspace.dependencies]
# external dependencies
-anyhow = { version = "1.0.100", default-features = false }
-assert_cmd = { version = "2.2.0", default-features = false }
+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.5", default-features = false }
+blake3 = { version = "1.8.7", default-features = false }
bolero = { version = "0.13.4", default-features = false }
-bytes = { version = "1.11.1", default-features = false }
+# bolero-libfuzzer 0.13.0 calls bolero_engine::any without enabling the feature itself.
+bolero-engine = { version = "0.13.4", default-features = false }
+bytes = { version = "1.12.1", default-features = false }
camino = { version = "1.2.5", default-features = false }
# local dependencies
cargo-aprz-lib = { path = "crates/cargo-aprz-lib", default-features = false, version = "1.1.0" }
@@ -40,46 +42,47 @@ cargo-gamma-rt = { path = "crates/cargo-gamma-rt", default-features = false, ver
cargo-gamma-unsafe = { path = "crates/cargo-gamma-unsafe", default-features = false, version = "0.1.0" }
cargo-heather = { path = "crates/cargo-heather", default-features = false, version = "0.2.1" }
cargo_metadata = { version = "0.23.1", default-features = false }
-cel-interpreter = { version = "0.10.0", default-features = false }
-chrono = { version = "0.4.40", default-features = false }
-clap = { version = "4.5.60", default-features = false }
-clap_complete = { version = "4.6.8", default-features = false }
+cel = { version = "0.14.4", default-features = false }
+chrono = { version = "0.4.45", default-features = false }
+clap = { version = "4.6.6", default-features = false }
+clap_complete = { version = "4.6.9", default-features = false }
compact_str = { version = "0.10.0", default-features = false }
csv = { version = "1.4.0", default-features = false }
directories = { version = "6.0.0", default-features = false }
duct = { version = "1.1.1", default-features = false }
env_logger = { version = "0.11.11", default-features = false }
-flate2 = { version = "1.1.9", default-features = false }
-futures = { version = "0.3.31", default-features = false }
-futures-util = { version = "0.3.31", default-features = false }
+flate2 = { version = "1.1.10", default-features = false }
+futures = { version = "0.3.34", default-features = false }
+futures-util = { version = "0.3.34", default-features = false }
humantime-serde = { version = "1.1.1", default-features = false }
ignore = { version = "0.4.33", default-features = false }
indicatif = { version = "0.18.6", default-features = false }
-insta = { version = "1.44.1", default-features = false }
+insta = { version = "1.48.0", default-features = false }
layered = { version = "0.3.6", default-features = false }
lcov = { version = "0.8.2", default-features = false }
-libc = { version = "0.2.178", default-features = false }
-log = { version = "0.4.33", default-features = false }
+libc = { version = "0.2.189", default-features = false }
+log = { version = "0.4.34", default-features = false }
loom = { version = "0.7.2", default-features = false }
-many_cpus = { version = "2.1.0", default-features = false }
+many_cpus = { version = "2.4.16", default-features = false }
mimalloc = { version = "0.1.52", default-features = false }
mmap-rs = { version = "0.7.0", default-features = false }
-mutants = { version = "0.0.3", default-features = false }
+mutants = { version = "0.0.4", default-features = false }
ohno = { version = "0.5.0", default-features = false }
-owo-colors = { version = "4.3.0", default-features = false }
+owo-colors = { version = "4.4.0", default-features = false }
pastey = { version = "0.2.3", default-features = false }
percent-encoding = { version = "2.3.2", default-features = false }
petgraph = { version = "0.8.3", default-features = false }
predicates = { version = "3.1.4", default-features = false }
-proc-macro2 = { version = "1.0.103", default-features = false }
+proc-macro2 = { version = "1.0.107", default-features = false }
+# 0.0.349 includes a parser fixture that crashes CodeQL 2.26.4; retry the bump with a newer CodeQL release.
ra_ap_syntax = { version = "0.0.331", default-features = false }
-rapidhash = { version = "4.1.1", default-features = false }
+rapidhash = { version = "4.5.1", default-features = false }
reflink-copy = { version = "0.1.30", default-features = false }
-regex = { version = "1.12.2", default-features = false }
+regex = { version = "1.13.1", default-features = false }
reqwest = { version = "0.13.4", default-features = false }
rmp-serde = { version = "1.3.1", default-features = false }
-rust_xlsxwriter = { version = "0.98.2", default-features = false }
-rustc-hash = { version = "2.1.0", default-features = false }
+rust_xlsxwriter = { version = "0.99.0", default-features = false }
+rustc-hash = { version = "2.1.3", default-features = false }
rustc_lexer = { version = "0.1.0", default-features = false }
rustdoc-types-v50 = { package = "rustdoc-types", version = "0.50.0", default-features = false }
rustdoc-types-v51 = { package = "rustdoc-types", version = "0.51.0", default-features = false }
@@ -92,10 +95,10 @@ rustdoc-types-v57 = { package = "rustdoc-types", version = "0.57.4", default-fea
rustsec = { version = "0.33.0", default-features = false }
seatbelt = { version = "0.8.0", default-features = false }
semver = { version = "1.0.28", default-features = false }
-serde = { version = "1.0.228", default-features = false }
-serde_json = { version = "1.0.145", default-features = false }
+serde = { version = "1.0.229", default-features = false }
+serde_json = { version = "1.0.151", default-features = false }
serial_test = { version = "4.0.1", default-features = false }
-sha2 = { version = "0.10.9", default-features = false }
+sha2 = { version = "0.11.0", default-features = false }
strsim = { version = "0.11.1", default-features = false }
strum = { version = "0.28.0", default-features = false }
syn = { version = "3.0.4", default-features = false }
@@ -103,11 +106,11 @@ tar = { version = "0.4.46", default-features = false }
tempfile = { version = "3.27.0", default-features = false }
terminal_size = { version = "0.4.4", default-features = false }
tick = { version = "0.6.0", default-features = false }
-tokio = { version = "1.48.0", default-features = false }
-toml = { version = "1.1.2", default-features = false }
-toml_edit = { version = "0.25.12", default-features = false, features = ["parse", "display"] }
-tracing = { version = "0.1.41", default-features = false }
-tracing-subscriber = { version = "0.3.20", default-features = false }
+tokio = { version = "1.53.1", default-features = false }
+toml = { version = "1.1.4", default-features = false }
+toml_edit = { version = "0.25.13", default-features = false, features = ["parse", "display"] }
+tracing = { version = "0.1.44", default-features = false }
+tracing-subscriber = { version = "0.3.23", default-features = false }
url = { version = "2.5.8", default-features = false }
vlen = { version = "0.4.6", default-features = false }
walkdir = { version = "2.5.0", default-features = false }
diff --git a/constants.env b/constants.env
index 318ae2c3..f6bdda2f 100644
--- a/constants.env
+++ b/constants.env
@@ -17,20 +17,19 @@ RUST_NIGHTLY_EXTERNAL_TYPES=nightly-2026-03-20
# Cargo tools
CARGO_CAREFUL_VERSION=0.4.10
CARGO_CHECK_EXTERNAL_TYPES_VERSION=0.5.0
-CARGO_DENY_VERSION=0.19.8
-CARGO_DOC2README_VERSION=0.7.2
+CARGO_DENY_VERSION=0.20.2
+CARGO_DOC2README_VERSION=0.7.3
CARGO_ENSURE_NO_CYCLIC_DEPS_VERSION=0.2.0
CARGO_ENSURE_NO_DEFAULT_FEATURES_VERSION=1.1.0
CARGO_HACK_VERSION=0.6.45
-CARGO_LLVM_COV_VERSION=0.8.7
-CARGO_MUTANTS_VERSION=27.0.0
-CARGO_NEXTEST_VERSION=0.9.137
-CARGO_SEMVER_CHECKS_VERSION=0.49.0
+CARGO_LLVM_COV_VERSION=0.9.0
+CARGO_MUTANTS_VERSION=27.1.0
+CARGO_NEXTEST_VERSION=0.9.143
+CARGO_SEMVER_CHECKS_VERSION=0.50.0
CARGO_SORT_VERSION=2.1.4
CARGO_SPELLCHECK_VERSION=0.15.7
CARGO_UDEPS_VERSION=0.1.61
-CARGO_WORKSPACES_VERSION=0.4.2
# Other tools
-JUST_VERSION=1.46.0
-SCCACHE_VERSION=v0.13.0
+JUST_VERSION=1.58.0
+SCCACHE_VERSION=v0.17.0
diff --git a/crates/cargo-anvil/Cargo.toml b/crates/cargo-anvil/Cargo.toml
index 0cbfd34d..6214d4b8 100644
--- a/crates/cargo-anvil/Cargo.toml
+++ b/crates/cargo-anvil/Cargo.toml
@@ -32,7 +32,7 @@ path = "src/main.rs"
clap = { workspace = true, features = ["derive", "std", "help", "usage", "error-context", "string"] }
mutants = { workspace = true }
ohno = { workspace = true, features = ["app-err"] }
-sha2 = { workspace = true, features = ["std"] }
+sha2.workspace = true
toml_edit = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["fmt"] }
diff --git a/crates/cargo-anvil/README.md b/crates/cargo-anvil/README.md
index e8843c60..a656e95e 100644
--- a/crates/cargo-anvil/README.md
+++ b/crates/cargo-anvil/README.md
@@ -445,7 +445,7 @@ And `docs/verification.md` for the continuous-validation strategy.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
- [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQblcBzF-_WZVYbCN9Rt1pYQLsblkUTM0oENsMbNe4wSAldeq9hZIGDa2NhcmdvLWFudmlsZTAuNS4wa2NhcmdvX2Fudmls
+ [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQblcBzF-_WZVYbCN9Rt1pYQLsblkUTM0oENsMbNe4wSAldeq9hZIGDa2NhcmdvLWFudmlsZTAuNS4wa2NhcmdvX2Fudmls
[__link0]: https://crates.io/crates/cargo-delta
[__link1]: https://crates.io/crates/cargo-spellcheck
[__link2]: https://crates.io/crates/cargo-coverage-gate
diff --git a/crates/cargo-anvil/docs/design/checks.md b/crates/cargo-anvil/docs/design/checks.md
index 9e5f916e..3496e885 100644
--- a/crates/cargo-anvil/docs/design/checks.md
+++ b/crates/cargo-anvil/docs/design/checks.md
@@ -164,7 +164,7 @@ that provided the strongest version of the check.
| `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 |
| `doc-build` | `RUSTDOCFLAGS='-D warnings' cargo doc --workspace --all-features --no-deps` | oxidizer-github |
-| `readme-check` | `cargo doc2readme --check` for each publishable crate that does not opt out through `[package.metadata.ox-gen-readme]`; library rustdoc is preferred, with binary rustdoc used for bin-only crates | oxidizer-github |
+| `readme-check` | `cargo doc2readme --check` for each publishable crate that does not opt out through `[package.metadata.ox-gen-readme]`; generation and checking share one crate-selection path, library or proc-macro rustdoc is preferred, and binary rustdoc is used for bin-only crates | oxidizer-github |
| `spellcheck` | `cargo spellcheck check --code 1` | oxidizer-github |
| `pr-title` | Repository policy regex applied to the title in the `PR_TITLE` env var. The accepted types (`feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `build`, `ci`, `perf`, `revert`) are a deliberate subset of Conventional Commits, not the complete grammar. The title must occupy a single line, so trailing content after the description is rejected. A rejected title reports the accepted title formats and the case-insensitive type names, so the author can correct the title from the check output alone. Skipped only outside a pull request context, where `PR_TITLE` is unset or empty (local runs and cloud builds that are not pull request builds); an invalid title or failure to retrieve a known PR's title fails loudly. GitHub supplies the event title directly. ADO resolves it through the REST API because `System.PullRequest.Title` does not exist. | oxidizer-github |
| `deny` | `cargo deny check` | all |
diff --git a/crates/cargo-anvil/src/anvil/artifacts/justfile.rs b/crates/cargo-anvil/src/anvil/artifacts/justfile.rs
index b870538a..8df8e822 100644
--- a/crates/cargo-anvil/src/anvil/artifacts/justfile.rs
+++ b/crates/cargo-anvil/src/anvil/artifacts/justfile.rs
@@ -364,7 +364,7 @@ mod tests {
let checks = all_check_bodies();
for needle in [
"anvil-bolero: target discovery failed",
- "anvil-readme-check: cargo metadata failed",
+ "${prefix}: cargo metadata failed",
"running tests without coverage for opted-out packages",
"all affected packages opted out of coverage",
"could not resolve the cargo-careful executable",
@@ -379,6 +379,10 @@ mod tests {
checks.contains("bolero list --profile release --package $packageName"),
"bolero discovery must use the execution profile once per affected package"
);
+ assert!(
+ checks.contains("bolero test --profile release --engine libfuzzer"),
+ "bolero execution must use the profile option accepted by the pinned cargo-bolero"
+ );
assert!(
!checks.contains("bolero list @bareArgs"),
"bolero discovery must not pass repeated --package arguments"
diff --git a/crates/cargo-anvil/templates/justfiles/anvil/checks/bolero.just b/crates/cargo-anvil/templates/justfiles/anvil/checks/bolero.just
index 37f7d21b..d23fb3a9 100644
--- a/crates/cargo-anvil/templates/justfiles/anvil/checks/bolero.just
+++ b/crates/cargo-anvil/templates/justfiles/anvil/checks/bolero.just
@@ -103,7 +103,7 @@ anvil-bolero: anvil-bolero-validate-prereqs anvil-impact
exit 1
}
Write-Host "anvil-bolero: ${packageName}::${testName}"
- & cargo '+{{ rust_nightly }}' bolero test --release --engine libfuzzer -T 60s --package $packageName $testName
+ & cargo '+{{ rust_nightly }}' bolero test --profile release --engine libfuzzer -T 60s --package $packageName $testName
if ($LASTEXITCODE -ne 0) { $failed = $true }
}
if ($failed) { exit 1 }
diff --git a/crates/cargo-anvil/templates/justfiles/anvil/checks/readme-check.just b/crates/cargo-anvil/templates/justfiles/anvil/checks/readme-check.just
index 1d258e25..5ec58e43 100644
--- a/crates/cargo-anvil/templates/justfiles/anvil/checks/readme-check.just
+++ b/crates/cargo-anvil/templates/justfiles/anvil/checks/readme-check.just
@@ -22,20 +22,28 @@
# `[package.metadata.ox-gen-readme]\ndisable = true` to their
# Cargo.toml. anvil skips those crates.
#
-# Bin-only crates have no library rustdoc to base a README on, so they
-# are skipped as well (cargo doc2readme requires a library target).
+# Library or proc-macro rustdoc is preferred when a crate has one; bin-only
+# crates use their binary rustdoc so every publishable crate follows the same
+# generation policy.
# TODO(anvil-runner): even after a `cargo ox-run` helper absorbs the
# skip/splat preamble, this recipe stays multi-step: per-crate
-# iteration over library targets, cargo-metadata-driven opt-outs
+# iteration over eligible targets, cargo-metadata-driven opt-outs
# (publish=false, [package.metadata.ox-gen-readme] disable), per-crate
# Push-Location into the crate dir (cargo-doc2readme is CWD-sensitive
# rather than --manifest-path-driven), and per-crate template-path
-# resolution. The recipe iterates the full library-crate set every run.
+# resolution. The recipe iterates the full eligible-crate set every run.
-# Check that crate README files are up to date.
+# Generate or check every eligible crate README with the same selection policy.
[script("pwsh", "-NoProfile")]
-anvil-readme-check: anvil-readme-check-validate-prereqs
+_anvil-readme mode: anvil-readme-check-validate-prereqs
$ErrorActionPreference = 'Stop'
+ $action = '{{ mode }}'
+ if ($action -notin @('generate', 'check')) {
+ [Console]::Error.WriteLine("anvil-readme: invalid mode '$action'; expected 'generate' or 'check'")
+ exit 2
+ }
+ $check = $action -eq 'check'
+ $prefix = "anvil-readme-${action}"
# Detect a workspace-level README template. Two conventional
# locations: crates/README.j2 (cargo-workspaces idiom) or
# README.j2 at repo root.
@@ -43,26 +51,28 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
foreach ($candidate in 'crates/README.j2', 'README.j2') {
if (Test-Path $candidate) { $template = (Resolve-Path $candidate).Path; break }
}
- # Iterate every library crate, then drop bin-only crates and opt-outs.
+ # Iterate every crate with rustdoc, then drop private crates and opt-outs.
# Unscoped: a change to the workspace-level README template is a repo-level
# input cargo-delta cannot map to a package, so this check always runs the
- # full library set rather than an impact subset.
+ # full eligible-crate set rather than an impact subset.
$metadataJson = & cargo metadata --no-deps --format-version 1
if ($LASTEXITCODE -ne 0) {
- Write-Error 'anvil-readme-check: cargo metadata failed'
+ Write-Error "${prefix}: cargo metadata failed"
exit $LASTEXITCODE
}
$meta = $metadataJson | ConvertFrom-Json
$hadFailure = $false
foreach ($p in $meta.packages) {
$name = $p.name
- if (-not ($p.targets | Where-Object { $_.kind -contains 'lib' })) { continue }
+ $hasLib = [bool]($p.targets | Where-Object { ($_.kind -contains 'lib') -or ($_.kind -contains 'proc-macro') })
+ $hasBin = [bool]($p.targets | Where-Object { $_.kind -contains 'bin' })
+ if (-not $hasLib -and -not $hasBin) { continue }
# Skip private crates (publish = false). They aren't released
# and rarely have a polished README. Mirrors the
# `cargo workspaces exec --ignore-private` idiom adopters
# commonly use.
if ($p.publish -is [array] -and $p.publish.Count -eq 0) {
- Write-Host "anvil-readme-check: $name (skipped: publish = false)"
+ Write-Host "${prefix}: $name (skipped: publish = false)"
continue
}
$disabled = $false
@@ -70,13 +80,13 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
$disabled = $true
}
if ($disabled) {
- Write-Host "anvil-readme-check: $name (opted out via [package.metadata.ox-gen-readme])"
+ Write-Host "${prefix}: $name (opted out via [package.metadata.ox-gen-readme])"
continue
}
- Write-Host "anvil-readme-check: $name"
+ Write-Host "${prefix}: $name"
# cargo doc2readme writes / compares relative to its CWD (not
- # --manifest-path), so chdir into the crate before invoking
- # --check. We also compute a per-crate relative path to the
+ # --manifest-path), so chdir into the crate before invoking it.
+ # We also compute a per-crate relative path to the
# workspace-level template so the same template file works for
# every crate (parallels the cargo-workspaces idiom).
$crateDir = Split-Path -Parent $p.manifest_path
@@ -87,7 +97,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
} else {
$null
}
- $args = @('doc2readme', '--check')
+ $target = if ($hasLib) { '--lib' } else { '--bin' }
+ $args = @('doc2readme', $target)
+ if ($check) { $args += '--check' }
if ($relTemplate) { $args += @('--template', $relTemplate) }
& cargo @args
if ($LASTEXITCODE -ne 0) { $hadFailure = $true }
@@ -97,6 +109,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
}
if ($hadFailure) { exit 1 }
+# Check that crate README files are up to date.
+anvil-readme-check: (_anvil-readme "check")
+
# Install prerequisites for the `anvil-readme-check` recipe.
[group("anvil-setup")]
anvil-readme-check-setup installer="install": (anvil-tool-cargo-doc2readme-install installer)
diff --git a/crates/cargo-anvil/templates/justfiles/anvil/versions.just b/crates/cargo-anvil/templates/justfiles/anvil/versions.just
index 564a3b90..0a4aeaa1 100644
--- a/crates/cargo-anvil/templates/justfiles/anvil/versions.just
+++ b/crates/cargo-anvil/templates/justfiles/anvil/versions.just
@@ -56,16 +56,16 @@ cargo_careful_version := "0.4.10"
cargo_check_external_types_version := "0.5.0"
cargo_coverage_gate_version := "0.2.0"
cargo_delta_version := "0.3.1"
-cargo_deny_version := "0.19.8"
-cargo_doc2readme_version := "0.7.2"
+cargo_deny_version := "0.20.2"
+cargo_doc2readme_version := "0.7.3"
cargo_ensure_no_cyclic_deps_version := "0.2.0"
cargo_ensure_no_default_features_version := "1.1.0"
cargo_hack_version := "0.6.45"
cargo_heather_version := "0.2.1"
-cargo_llvm_cov_version := "0.8.7"
-cargo_mutants_version := "27.0.0"
-cargo_nextest_version := "0.9.137"
-cargo_semver_checks_version := "0.49.0"
+cargo_llvm_cov_version := "0.9.0"
+cargo_mutants_version := "27.1.0"
+cargo_nextest_version := "0.9.143"
+cargo_semver_checks_version := "0.50.0"
cargo_sort_version := "2.1.4"
cargo_spellcheck_version := "0.15.7"
cargo_udeps_version := "0.1.61"
diff --git a/crates/cargo-anvil/tests/impact.rs b/crates/cargo-anvil/tests/impact.rs
index 42363af8..ad0bd838 100644
--- a/crates/cargo-anvil/tests/impact.rs
+++ b/crates/cargo-anvil/tests/impact.rs
@@ -36,6 +36,7 @@ use std::{env, fs};
use cargo_anvil::Catalog;
use cargo_anvil::test_support::{Cli, run_update};
+use serial_test::serial;
use tempfile::TempDir;
fn write(path: &Path, contents: &str) {
@@ -265,6 +266,7 @@ fn run_impact(root: &Path) -> String {
}
#[test]
+#[serial]
fn impact_cache_regenerates_per_key_and_reuses_when_unchanged() {
if !tools_available() {
return;
@@ -356,6 +358,7 @@ fn impact_cache_regenerates_per_key_and_reuses_when_unchanged() {
}
#[test]
+#[serial]
fn baseline_regenerates_when_delta_config_changes_without_moving_the_base() {
if !tools_available() {
return;
@@ -414,6 +417,7 @@ fn baseline_regenerates_when_delta_config_changes_without_moving_the_base() {
}
#[test]
+#[serial]
fn current_snapshot_ignores_tracked_target_changes_without_moving_head() {
if !tools_available() {
return;
@@ -473,6 +477,7 @@ fn current_snapshot_ignores_tracked_target_changes_without_moving_head() {
}
#[test]
+#[serial]
fn impact_empty_output_when_head_equals_base() {
if !tools_available() {
return;
@@ -516,6 +521,7 @@ fn impact_empty_output_when_head_equals_base() {
}
#[test]
+#[serial]
fn impact_off_short_circuits_without_computing() {
if !core_tools_available() {
return;
@@ -548,6 +554,7 @@ fn impact_off_short_circuits_without_computing() {
}
#[test]
+#[serial]
fn impact_widens_to_full_workspace_when_working_tree_is_dirty() {
if !tools_available() {
return;
@@ -616,6 +623,7 @@ fn impact_widens_to_full_workspace_when_working_tree_is_dirty() {
}
#[test]
+#[serial]
fn impact_include_reads_zero_byte_modified_file_without_throwing() {
if !tools_available() {
return;
@@ -651,6 +659,7 @@ fn impact_include_reads_zero_byte_modified_file_without_throwing() {
}
#[test]
+#[serial]
fn impact_dirty_tree_widens_without_needing_a_resolvable_base() {
if !core_tools_available() {
return;
@@ -707,6 +716,7 @@ fn impact_dirty_tree_widens_without_needing_a_resolvable_base() {
}
#[test]
+#[serial]
fn impact_consume_mode_trusts_cache_without_recompute() {
if !tools_available() {
return;
@@ -752,6 +762,7 @@ fn impact_consume_mode_trusts_cache_without_recompute() {
}
#[test]
+#[serial]
fn consumer_reuses_cache_with_unresolvable_base_only_under_consume() {
if !tools_available() {
return;
@@ -809,6 +820,7 @@ fn consumer_reuses_cache_with_unresolvable_base_only_under_consume() {
}
#[test]
+#[serial]
fn impact_falls_back_to_full_workspace_when_base_has_no_workspace() {
if !tools_available() {
return;
@@ -904,6 +916,7 @@ fn path_with_prefix(dir: &Path) -> OsString {
}
#[test]
+#[serial]
fn scoped_check_consumes_cached_package_list_and_skips_on_sentinel() {
if !tools_available() {
return;
@@ -1000,6 +1013,7 @@ fn fake_cargo_with_metadata(dir: &Path, log: &Path, metadata_json: &Path) {
}
#[test]
+#[serial]
fn loom_runs_declared_targets_in_full_workspace_mode() {
if !tools_available() {
return;
@@ -1047,6 +1061,7 @@ fn loom_runs_declared_targets_in_full_workspace_mode() {
}
#[test]
+#[serial]
fn invalid_anvil_impact_value_fails_loudly_without_computing() {
if !core_tools_available() {
return;
@@ -1089,6 +1104,7 @@ fn invalid_anvil_impact_value_fails_loudly_without_computing() {
}
#[test]
+#[serial]
fn missing_base_ref_on_clean_tree_fails_with_fetch_guidance() {
if !tools_available() {
return;
@@ -1121,6 +1137,7 @@ fn missing_base_ref_on_clean_tree_fails_with_fetch_guidance() {
}
#[test]
+#[serial]
fn shallow_clone_fails_with_unshallow_guidance() {
if !tools_available() {
return;
@@ -1146,6 +1163,7 @@ fn shallow_clone_fails_with_unshallow_guidance() {
}
#[test]
+#[serial]
fn consume_without_downloaded_cache_fails_loudly() {
if !core_tools_available() {
return;
@@ -1248,6 +1266,7 @@ fn run_format(root: &Path, tier: &str, impact_json_rel: &str) -> (String, String
}
#[test]
+#[serial]
fn impact_format_fails_hard_on_unmappable_name() {
if !tools_available() {
return;
@@ -1272,6 +1291,7 @@ fn impact_format_fails_hard_on_unmappable_name() {
}
#[test]
+#[serial]
fn impact_format_maps_proc_macro_target_name_to_its_package() {
if !tools_available() {
return;
@@ -1368,6 +1388,7 @@ fn snapshot_toolchain_probe(caller_toolchain: Option<&str>) -> Vec {
}
#[test]
+#[serial]
fn impact_snapshot_runs_baseline_under_active_toolchain_then_restores_caller_value() {
if !core_tools_available() {
return;
diff --git a/crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap b/crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap
index c0ab6168..80284f95 100644
--- a/crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap
+++ b/crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap
@@ -2887,7 +2887,7 @@ anvil-bolero: anvil-bolero-validate-prereqs anvil-impact
exit 1
}
Write-Host "anvil-bolero: ${packageName}::${testName}"
- & cargo '+{{ rust_nightly }}' bolero test --release --engine libfuzzer -T 60s --package $packageName $testName
+ & cargo '+{{ rust_nightly }}' bolero test --profile release --engine libfuzzer -T 60s --package $packageName $testName
if ($LASTEXITCODE -ne 0) { $failed = $true }
}
if ($failed) { exit 1 }
@@ -4130,20 +4130,28 @@ anvil-pr-title-validate-prereqs: anvil-tool-pwsh-validate-prereqs
# `[package.metadata.ox-gen-readme]\ndisable = true` to their
# Cargo.toml. anvil skips those crates.
#
-# Bin-only crates have no library rustdoc to base a README on, so they
-# are skipped as well (cargo doc2readme requires a library target).
+# Library or proc-macro rustdoc is preferred when a crate has one; bin-only
+# crates use their binary rustdoc so every publishable crate follows the same
+# generation policy.
# TODO(anvil-runner): even after a `cargo ox-run` helper absorbs the
# skip/splat preamble, this recipe stays multi-step: per-crate
-# iteration over library targets, cargo-metadata-driven opt-outs
+# iteration over eligible targets, cargo-metadata-driven opt-outs
# (publish=false, [package.metadata.ox-gen-readme] disable), per-crate
# Push-Location into the crate dir (cargo-doc2readme is CWD-sensitive
# rather than --manifest-path-driven), and per-crate template-path
-# resolution. The recipe iterates the full library-crate set every run.
+# resolution. The recipe iterates the full eligible-crate set every run.
-# Check that crate README files are up to date.
+# Generate or check every eligible crate README with the same selection policy.
[script("pwsh", "-NoProfile")]
-anvil-readme-check: anvil-readme-check-validate-prereqs
+_anvil-readme mode: anvil-readme-check-validate-prereqs
$ErrorActionPreference = 'Stop'
+ $action = '{{ mode }}'
+ if ($action -notin @('generate', 'check')) {
+ [Console]::Error.WriteLine("anvil-readme: invalid mode '$action'; expected 'generate' or 'check'")
+ exit 2
+ }
+ $check = $action -eq 'check'
+ $prefix = "anvil-readme-${action}"
# Detect a workspace-level README template. Two conventional
# locations: crates/README.j2 (cargo-workspaces idiom) or
# README.j2 at repo root.
@@ -4151,26 +4159,28 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
foreach ($candidate in 'crates/README.j2', 'README.j2') {
if (Test-Path $candidate) { $template = (Resolve-Path $candidate).Path; break }
}
- # Iterate every library crate, then drop bin-only crates and opt-outs.
+ # Iterate every crate with rustdoc, then drop private crates and opt-outs.
# Unscoped: a change to the workspace-level README template is a repo-level
# input cargo-delta cannot map to a package, so this check always runs the
- # full library set rather than an impact subset.
+ # full eligible-crate set rather than an impact subset.
$metadataJson = & cargo metadata --no-deps --format-version 1
if ($LASTEXITCODE -ne 0) {
- Write-Error 'anvil-readme-check: cargo metadata failed'
+ Write-Error "${prefix}: cargo metadata failed"
exit $LASTEXITCODE
}
$meta = $metadataJson | ConvertFrom-Json
$hadFailure = $false
foreach ($p in $meta.packages) {
$name = $p.name
- if (-not ($p.targets | Where-Object { $_.kind -contains 'lib' })) { continue }
+ $hasLib = [bool]($p.targets | Where-Object { ($_.kind -contains 'lib') -or ($_.kind -contains 'proc-macro') })
+ $hasBin = [bool]($p.targets | Where-Object { $_.kind -contains 'bin' })
+ if (-not $hasLib -and -not $hasBin) { continue }
# Skip private crates (publish = false). They aren't released
# and rarely have a polished README. Mirrors the
# `cargo workspaces exec --ignore-private` idiom adopters
# commonly use.
if ($p.publish -is [array] -and $p.publish.Count -eq 0) {
- Write-Host "anvil-readme-check: $name (skipped: publish = false)"
+ Write-Host "${prefix}: $name (skipped: publish = false)"
continue
}
$disabled = $false
@@ -4178,13 +4188,13 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
$disabled = $true
}
if ($disabled) {
- Write-Host "anvil-readme-check: $name (opted out via [package.metadata.ox-gen-readme])"
+ Write-Host "${prefix}: $name (opted out via [package.metadata.ox-gen-readme])"
continue
}
- Write-Host "anvil-readme-check: $name"
+ Write-Host "${prefix}: $name"
# cargo doc2readme writes / compares relative to its CWD (not
- # --manifest-path), so chdir into the crate before invoking
- # --check. We also compute a per-crate relative path to the
+ # --manifest-path), so chdir into the crate before invoking it.
+ # We also compute a per-crate relative path to the
# workspace-level template so the same template file works for
# every crate (parallels the cargo-workspaces idiom).
$crateDir = Split-Path -Parent $p.manifest_path
@@ -4195,7 +4205,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
} else {
$null
}
- $args = @('doc2readme', '--check')
+ $target = if ($hasLib) { '--lib' } else { '--bin' }
+ $args = @('doc2readme', $target)
+ if ($check) { $args += '--check' }
if ($relTemplate) { $args += @('--template', $relTemplate) }
& cargo @args
if ($LASTEXITCODE -ne 0) { $hadFailure = $true }
@@ -4205,6 +4217,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
}
if ($hadFailure) { exit 1 }
+# Check that crate README files are up to date.
+anvil-readme-check: (_anvil-readme "check")
+
# Install prerequisites for the `anvil-readme-check` recipe.
[group("anvil-setup")]
anvil-readme-check-setup installer="install": (anvil-tool-cargo-doc2readme-install installer)
@@ -6812,16 +6827,16 @@ cargo_careful_version := "0.4.10"
cargo_check_external_types_version := "0.5.0"
cargo_coverage_gate_version := "0.2.0"
cargo_delta_version := "0.3.1"
-cargo_deny_version := "0.19.8"
-cargo_doc2readme_version := "0.7.2"
+cargo_deny_version := "0.20.2"
+cargo_doc2readme_version := "0.7.3"
cargo_ensure_no_cyclic_deps_version := "0.2.0"
cargo_ensure_no_default_features_version := "1.1.0"
cargo_hack_version := "0.6.45"
cargo_heather_version := "0.2.1"
-cargo_llvm_cov_version := "0.8.7"
-cargo_mutants_version := "27.0.0"
-cargo_nextest_version := "0.9.137"
-cargo_semver_checks_version := "0.49.0"
+cargo_llvm_cov_version := "0.9.0"
+cargo_mutants_version := "27.1.0"
+cargo_nextest_version := "0.9.143"
+cargo_semver_checks_version := "0.50.0"
cargo_sort_version := "2.1.4"
cargo_spellcheck_version := "0.15.7"
cargo_udeps_version := "0.1.61"
diff --git a/crates/cargo-anvil/tests/snapshots/snapshots__github_backend.snap b/crates/cargo-anvil/tests/snapshots/snapshots__github_backend.snap
index b05e44c0..2bc8163f 100644
--- a/crates/cargo-anvil/tests/snapshots/snapshots__github_backend.snap
+++ b/crates/cargo-anvil/tests/snapshots/snapshots__github_backend.snap
@@ -2766,7 +2766,7 @@ anvil-bolero: anvil-bolero-validate-prereqs anvil-impact
exit 1
}
Write-Host "anvil-bolero: ${packageName}::${testName}"
- & cargo '+{{ rust_nightly }}' bolero test --release --engine libfuzzer -T 60s --package $packageName $testName
+ & cargo '+{{ rust_nightly }}' bolero test --profile release --engine libfuzzer -T 60s --package $packageName $testName
if ($LASTEXITCODE -ne 0) { $failed = $true }
}
if ($failed) { exit 1 }
@@ -4009,20 +4009,28 @@ anvil-pr-title-validate-prereqs: anvil-tool-pwsh-validate-prereqs
# `[package.metadata.ox-gen-readme]\ndisable = true` to their
# Cargo.toml. anvil skips those crates.
#
-# Bin-only crates have no library rustdoc to base a README on, so they
-# are skipped as well (cargo doc2readme requires a library target).
+# Library or proc-macro rustdoc is preferred when a crate has one; bin-only
+# crates use their binary rustdoc so every publishable crate follows the same
+# generation policy.
# TODO(anvil-runner): even after a `cargo ox-run` helper absorbs the
# skip/splat preamble, this recipe stays multi-step: per-crate
-# iteration over library targets, cargo-metadata-driven opt-outs
+# iteration over eligible targets, cargo-metadata-driven opt-outs
# (publish=false, [package.metadata.ox-gen-readme] disable), per-crate
# Push-Location into the crate dir (cargo-doc2readme is CWD-sensitive
# rather than --manifest-path-driven), and per-crate template-path
-# resolution. The recipe iterates the full library-crate set every run.
+# resolution. The recipe iterates the full eligible-crate set every run.
-# Check that crate README files are up to date.
+# Generate or check every eligible crate README with the same selection policy.
[script("pwsh", "-NoProfile")]
-anvil-readme-check: anvil-readme-check-validate-prereqs
+_anvil-readme mode: anvil-readme-check-validate-prereqs
$ErrorActionPreference = 'Stop'
+ $action = '{{ mode }}'
+ if ($action -notin @('generate', 'check')) {
+ [Console]::Error.WriteLine("anvil-readme: invalid mode '$action'; expected 'generate' or 'check'")
+ exit 2
+ }
+ $check = $action -eq 'check'
+ $prefix = "anvil-readme-${action}"
# Detect a workspace-level README template. Two conventional
# locations: crates/README.j2 (cargo-workspaces idiom) or
# README.j2 at repo root.
@@ -4030,26 +4038,28 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
foreach ($candidate in 'crates/README.j2', 'README.j2') {
if (Test-Path $candidate) { $template = (Resolve-Path $candidate).Path; break }
}
- # Iterate every library crate, then drop bin-only crates and opt-outs.
+ # Iterate every crate with rustdoc, then drop private crates and opt-outs.
# Unscoped: a change to the workspace-level README template is a repo-level
# input cargo-delta cannot map to a package, so this check always runs the
- # full library set rather than an impact subset.
+ # full eligible-crate set rather than an impact subset.
$metadataJson = & cargo metadata --no-deps --format-version 1
if ($LASTEXITCODE -ne 0) {
- Write-Error 'anvil-readme-check: cargo metadata failed'
+ Write-Error "${prefix}: cargo metadata failed"
exit $LASTEXITCODE
}
$meta = $metadataJson | ConvertFrom-Json
$hadFailure = $false
foreach ($p in $meta.packages) {
$name = $p.name
- if (-not ($p.targets | Where-Object { $_.kind -contains 'lib' })) { continue }
+ $hasLib = [bool]($p.targets | Where-Object { ($_.kind -contains 'lib') -or ($_.kind -contains 'proc-macro') })
+ $hasBin = [bool]($p.targets | Where-Object { $_.kind -contains 'bin' })
+ if (-not $hasLib -and -not $hasBin) { continue }
# Skip private crates (publish = false). They aren't released
# and rarely have a polished README. Mirrors the
# `cargo workspaces exec --ignore-private` idiom adopters
# commonly use.
if ($p.publish -is [array] -and $p.publish.Count -eq 0) {
- Write-Host "anvil-readme-check: $name (skipped: publish = false)"
+ Write-Host "${prefix}: $name (skipped: publish = false)"
continue
}
$disabled = $false
@@ -4057,13 +4067,13 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
$disabled = $true
}
if ($disabled) {
- Write-Host "anvil-readme-check: $name (opted out via [package.metadata.ox-gen-readme])"
+ Write-Host "${prefix}: $name (opted out via [package.metadata.ox-gen-readme])"
continue
}
- Write-Host "anvil-readme-check: $name"
+ Write-Host "${prefix}: $name"
# cargo doc2readme writes / compares relative to its CWD (not
- # --manifest-path), so chdir into the crate before invoking
- # --check. We also compute a per-crate relative path to the
+ # --manifest-path), so chdir into the crate before invoking it.
+ # We also compute a per-crate relative path to the
# workspace-level template so the same template file works for
# every crate (parallels the cargo-workspaces idiom).
$crateDir = Split-Path -Parent $p.manifest_path
@@ -4074,7 +4084,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
} else {
$null
}
- $args = @('doc2readme', '--check')
+ $target = if ($hasLib) { '--lib' } else { '--bin' }
+ $args = @('doc2readme', $target)
+ if ($check) { $args += '--check' }
if ($relTemplate) { $args += @('--template', $relTemplate) }
& cargo @args
if ($LASTEXITCODE -ne 0) { $hadFailure = $true }
@@ -4084,6 +4096,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
}
if ($hadFailure) { exit 1 }
+# Check that crate README files are up to date.
+anvil-readme-check: (_anvil-readme "check")
+
# Install prerequisites for the `anvil-readme-check` recipe.
[group("anvil-setup")]
anvil-readme-check-setup installer="install": (anvil-tool-cargo-doc2readme-install installer)
@@ -6691,16 +6706,16 @@ cargo_careful_version := "0.4.10"
cargo_check_external_types_version := "0.5.0"
cargo_coverage_gate_version := "0.2.0"
cargo_delta_version := "0.3.1"
-cargo_deny_version := "0.19.8"
-cargo_doc2readme_version := "0.7.2"
+cargo_deny_version := "0.20.2"
+cargo_doc2readme_version := "0.7.3"
cargo_ensure_no_cyclic_deps_version := "0.2.0"
cargo_ensure_no_default_features_version := "1.1.0"
cargo_hack_version := "0.6.45"
cargo_heather_version := "0.2.1"
-cargo_llvm_cov_version := "0.8.7"
-cargo_mutants_version := "27.0.0"
-cargo_nextest_version := "0.9.137"
-cargo_semver_checks_version := "0.49.0"
+cargo_llvm_cov_version := "0.9.0"
+cargo_mutants_version := "27.1.0"
+cargo_nextest_version := "0.9.143"
+cargo_semver_checks_version := "0.50.0"
cargo_sort_version := "2.1.4"
cargo_spellcheck_version := "0.15.7"
cargo_udeps_version := "0.1.61"
diff --git a/crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap b/crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap
index dc0bc298..1f003367 100644
--- a/crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap
+++ b/crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap
@@ -1639,7 +1639,7 @@ anvil-bolero: anvil-bolero-validate-prereqs anvil-impact
exit 1
}
Write-Host "anvil-bolero: ${packageName}::${testName}"
- & cargo '+{{ rust_nightly }}' bolero test --release --engine libfuzzer -T 60s --package $packageName $testName
+ & cargo '+{{ rust_nightly }}' bolero test --profile release --engine libfuzzer -T 60s --package $packageName $testName
if ($LASTEXITCODE -ne 0) { $failed = $true }
}
if ($failed) { exit 1 }
@@ -2882,20 +2882,28 @@ anvil-pr-title-validate-prereqs: anvil-tool-pwsh-validate-prereqs
# `[package.metadata.ox-gen-readme]\ndisable = true` to their
# Cargo.toml. anvil skips those crates.
#
-# Bin-only crates have no library rustdoc to base a README on, so they
-# are skipped as well (cargo doc2readme requires a library target).
+# Library or proc-macro rustdoc is preferred when a crate has one; bin-only
+# crates use their binary rustdoc so every publishable crate follows the same
+# generation policy.
# TODO(anvil-runner): even after a `cargo ox-run` helper absorbs the
# skip/splat preamble, this recipe stays multi-step: per-crate
-# iteration over library targets, cargo-metadata-driven opt-outs
+# iteration over eligible targets, cargo-metadata-driven opt-outs
# (publish=false, [package.metadata.ox-gen-readme] disable), per-crate
# Push-Location into the crate dir (cargo-doc2readme is CWD-sensitive
# rather than --manifest-path-driven), and per-crate template-path
-# resolution. The recipe iterates the full library-crate set every run.
+# resolution. The recipe iterates the full eligible-crate set every run.
-# Check that crate README files are up to date.
+# Generate or check every eligible crate README with the same selection policy.
[script("pwsh", "-NoProfile")]
-anvil-readme-check: anvil-readme-check-validate-prereqs
+_anvil-readme mode: anvil-readme-check-validate-prereqs
$ErrorActionPreference = 'Stop'
+ $action = '{{ mode }}'
+ if ($action -notin @('generate', 'check')) {
+ [Console]::Error.WriteLine("anvil-readme: invalid mode '$action'; expected 'generate' or 'check'")
+ exit 2
+ }
+ $check = $action -eq 'check'
+ $prefix = "anvil-readme-${action}"
# Detect a workspace-level README template. Two conventional
# locations: crates/README.j2 (cargo-workspaces idiom) or
# README.j2 at repo root.
@@ -2903,26 +2911,28 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
foreach ($candidate in 'crates/README.j2', 'README.j2') {
if (Test-Path $candidate) { $template = (Resolve-Path $candidate).Path; break }
}
- # Iterate every library crate, then drop bin-only crates and opt-outs.
+ # Iterate every crate with rustdoc, then drop private crates and opt-outs.
# Unscoped: a change to the workspace-level README template is a repo-level
# input cargo-delta cannot map to a package, so this check always runs the
- # full library set rather than an impact subset.
+ # full eligible-crate set rather than an impact subset.
$metadataJson = & cargo metadata --no-deps --format-version 1
if ($LASTEXITCODE -ne 0) {
- Write-Error 'anvil-readme-check: cargo metadata failed'
+ Write-Error "${prefix}: cargo metadata failed"
exit $LASTEXITCODE
}
$meta = $metadataJson | ConvertFrom-Json
$hadFailure = $false
foreach ($p in $meta.packages) {
$name = $p.name
- if (-not ($p.targets | Where-Object { $_.kind -contains 'lib' })) { continue }
+ $hasLib = [bool]($p.targets | Where-Object { ($_.kind -contains 'lib') -or ($_.kind -contains 'proc-macro') })
+ $hasBin = [bool]($p.targets | Where-Object { $_.kind -contains 'bin' })
+ if (-not $hasLib -and -not $hasBin) { continue }
# Skip private crates (publish = false). They aren't released
# and rarely have a polished README. Mirrors the
# `cargo workspaces exec --ignore-private` idiom adopters
# commonly use.
if ($p.publish -is [array] -and $p.publish.Count -eq 0) {
- Write-Host "anvil-readme-check: $name (skipped: publish = false)"
+ Write-Host "${prefix}: $name (skipped: publish = false)"
continue
}
$disabled = $false
@@ -2930,13 +2940,13 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
$disabled = $true
}
if ($disabled) {
- Write-Host "anvil-readme-check: $name (opted out via [package.metadata.ox-gen-readme])"
+ Write-Host "${prefix}: $name (opted out via [package.metadata.ox-gen-readme])"
continue
}
- Write-Host "anvil-readme-check: $name"
+ Write-Host "${prefix}: $name"
# cargo doc2readme writes / compares relative to its CWD (not
- # --manifest-path), so chdir into the crate before invoking
- # --check. We also compute a per-crate relative path to the
+ # --manifest-path), so chdir into the crate before invoking it.
+ # We also compute a per-crate relative path to the
# workspace-level template so the same template file works for
# every crate (parallels the cargo-workspaces idiom).
$crateDir = Split-Path -Parent $p.manifest_path
@@ -2947,7 +2957,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
} else {
$null
}
- $args = @('doc2readme', '--check')
+ $target = if ($hasLib) { '--lib' } else { '--bin' }
+ $args = @('doc2readme', $target)
+ if ($check) { $args += '--check' }
if ($relTemplate) { $args += @('--template', $relTemplate) }
& cargo @args
if ($LASTEXITCODE -ne 0) { $hadFailure = $true }
@@ -2957,6 +2969,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
}
if ($hadFailure) { exit 1 }
+# Check that crate README files are up to date.
+anvil-readme-check: (_anvil-readme "check")
+
# Install prerequisites for the `anvil-readme-check` recipe.
[group("anvil-setup")]
anvil-readme-check-setup installer="install": (anvil-tool-cargo-doc2readme-install installer)
@@ -5564,16 +5579,16 @@ cargo_careful_version := "0.4.10"
cargo_check_external_types_version := "0.5.0"
cargo_coverage_gate_version := "0.2.0"
cargo_delta_version := "0.3.1"
-cargo_deny_version := "0.19.8"
-cargo_doc2readme_version := "0.7.2"
+cargo_deny_version := "0.20.2"
+cargo_doc2readme_version := "0.7.3"
cargo_ensure_no_cyclic_deps_version := "0.2.0"
cargo_ensure_no_default_features_version := "1.1.0"
cargo_hack_version := "0.6.45"
cargo_heather_version := "0.2.1"
-cargo_llvm_cov_version := "0.8.7"
-cargo_mutants_version := "27.0.0"
-cargo_nextest_version := "0.9.137"
-cargo_semver_checks_version := "0.49.0"
+cargo_llvm_cov_version := "0.9.0"
+cargo_mutants_version := "27.1.0"
+cargo_nextest_version := "0.9.143"
+cargo_semver_checks_version := "0.50.0"
cargo_sort_version := "2.1.4"
cargo_spellcheck_version := "0.15.7"
cargo_udeps_version := "0.1.61"
diff --git a/crates/cargo-aprz-lib/Cargo.toml b/crates/cargo-aprz-lib/Cargo.toml
index e009c06e..0180aeb9 100644
--- a/crates/cargo-aprz-lib/Cargo.toml
+++ b/crates/cargo-aprz-lib/Cargo.toml
@@ -60,7 +60,7 @@ internals = []
bytes = { workspace = true }
camino = { workspace = true }
cargo_metadata = { workspace = true }
-cel-interpreter = { workspace = true, features = ["chrono"] }
+cel = { workspace = true, features = ["chrono"] }
chrono = { workspace = true, features = ["serde", "clock", "std", "now"] }
clap = { workspace = true, features = [
"std",
diff --git a/crates/cargo-aprz-lib/src/expr/evaluator.rs b/crates/cargo-aprz-lib/src/expr/evaluator.rs
index 2bc968fd..5fc590f1 100644
--- a/crates/cargo-aprz-lib/src/expr/evaluator.rs
+++ b/crates/cargo-aprz-lib/src/expr/evaluator.rs
@@ -8,8 +8,8 @@
use std::sync::{Arc, LazyLock};
-use cel_interpreter::objects::Map;
-use cel_interpreter::{Context, Program, Value};
+use cel::objects::Map;
+use cel::{Context, Program, Value};
use chrono::{DateTime, Local};
use super::{Appraisal, Expression, ExpressionDisposition, ExpressionOutcome, Risk};
@@ -144,7 +144,7 @@ fn evaluate_expression(program: &Program, name: &str, context: &Context) -> Resu
/// `&'static str`, so they are identical for every crate. Building them once and handing out
/// `Arc` clones avoids allocating a fresh `String` per metric per crate, which would otherwise
/// scale with the size of the dependency graph.
-#[expect(clippy::rc_buffer, reason = "cel-interpreter's map keys are Arc")]
+#[expect(clippy::rc_buffer, reason = "cel's map keys are Arc")]
static METRIC_KEYS: LazyLock>> = LazyLock::new(|| {
let mut keys = crate::hash_map_with_capacity(METRIC_DEFINITIONS.len());
for def in METRIC_DEFINITIONS {
@@ -157,7 +157,7 @@ static METRIC_KEYS: LazyLock>> = LazyLo
/// Returns the interned CEL key for a metric's suffix, falling back to a fresh allocation for
/// names that are not part of the built-in metric set (as used by some tests).
-#[expect(clippy::rc_buffer, reason = "cel-interpreter's map keys are Arc")]
+#[expect(clippy::rc_buffer, reason = "cel's map keys are Arc")]
fn metric_key(name: &str, suffix: &str) -> Arc {
METRIC_KEYS.get(name).map_or_else(|| Arc::new(suffix.to_string()), Arc::clone)
}
diff --git a/crates/cargo-aprz-lib/src/expr/expression.rs b/crates/cargo-aprz-lib/src/expr/expression.rs
index c05c229c..91219d3b 100644
--- a/crates/cargo-aprz-lib/src/expr/expression.rs
+++ b/crates/cargo-aprz-lib/src/expr/expression.rs
@@ -5,7 +5,7 @@
use std::sync::Arc;
-use cel_interpreter::Program;
+use cel::Program;
use serde::de::Error as DeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
diff --git a/crates/cargo-aprz-lib/src/facts/crates/tables/mod.rs b/crates/cargo-aprz-lib/src/facts/crates/tables/mod.rs
index df2fecfe..8bef21ce 100644
--- a/crates/cargo-aprz-lib/src/facts/crates/tables/mod.rs
+++ b/crates/cargo-aprz-lib/src/facts/crates/tables/mod.rs
@@ -28,22 +28,22 @@
//! Each table is stored as a binary file with the following structure:
//!
//! ```text
-//! ┌──────────────────────────────────────────────────────┐
-//! │ FORMAT_MAGIC: u64 (8 bytes) │
-//! │ - Magic number identifying format version │
-//! ├──────────────────────────────────────────────────────┤
-//! │ ROW_COUNT: u64 (8 bytes) │
-//! │ - Total number of rows in table │
-//! ├──────────────────────────────────────────────────────┤
-//! │ TIMESTAMP: u64 (8 bytes) │
-//! │ - Unix epoch seconds when table was created │
-//! │ - Used for TTL validation │
-//! ├──────────────────────────────────────────────────────┤
-//! │ Row Data (variable length) │
-//! │ - Variable-length encoded integers (vlen crate) │
-//! │ - Strings stored as length prefix + UTF-8 bytes │
-//! │ - Optional fields use discriminant byte │
-//! └──────────────────────────────────────────────────────┘
+//! +------------------------------------------------------+
+//! | FORMAT_MAGIC: u64 (8 bytes) |
+//! | - Magic number identifying format version |
+//! +------------------------------------------------------+
+//! | ROW_COUNT: u64 (8 bytes) |
+//! | - Total number of rows in table |
+//! +------------------------------------------------------+
+//! | TIMESTAMP: u64 (8 bytes) |
+//! | - Unix epoch seconds when table was created |
+//! | - Used for TTL validation |
+//! +------------------------------------------------------+
+//! | Row Data (variable length) |
+//! | - Variable-length encoded integers (vlen crate) |
+//! | - Strings stored as length prefix + UTF-8 bytes |
+//! | - Optional fields use discriminant byte |
+//! +------------------------------------------------------+
//! ```
mod categories_table;
diff --git a/crates/cargo-coverage-gate/README.md b/crates/cargo-coverage-gate/README.md
index 0554abcb..293cba5e 100644
--- a/crates/cargo-coverage-gate/README.md
+++ b/crates/cargo-coverage-gate/README.md
@@ -105,7 +105,7 @@ plus the appropriate exit code.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
- [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQbDzRwf0qddWQbQiTzhu0-bE0bX-rutkvfDuYbITgXvtMXiRVhZIGDc2NhcmdvLWNvdmVyYWdlLWdhdGVlMC4zLjBzY2FyZ29fY292ZXJhZ2VfZ2F0ZQ
+ [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQbDzRwf0qddWQbQiTzhu0-bE0bX-rutkvfDuYbITgXvtMXiRVhZIGDc2NhcmdvLWNvdmVyYWdlLWdhdGVlMC4zLjBzY2FyZ29fY292ZXJhZ2VfZ2F0ZQ
[__link0]: https://github.com/taiki-e/cargo-llvm-cov
[__link1]: https://docs.rs/cargo-coverage-gate/0.3.0/cargo_coverage_gate/fn.evaluate.html
[__link2]: https://docs.rs/cargo-coverage-gate/0.3.0/cargo_coverage_gate/struct.EvaluatedReport.html
diff --git a/crates/cargo-gamma-attrs-impl/Cargo.toml b/crates/cargo-gamma-attrs-impl/Cargo.toml
index 24a038a2..74fca384 100644
--- a/crates/cargo-gamma-attrs-impl/Cargo.toml
+++ b/crates/cargo-gamma-attrs-impl/Cargo.toml
@@ -19,6 +19,10 @@ repository = "https://github.com/microsoft/ox-tools/tree/main/crates/cargo-gamma
[package.metadata.docs.rs]
all-features = true
+[package.metadata.ox-gen-readme]
+# This is an implementation detail of `cargo-gamma-attrs`; its README is an explicit stability warning.
+disable = true
+
[package.metadata.cargo_check_external_types]
# This implementation crate exists to provide proc-macro entry points, so its
# public API necessarily accepts and returns proc-macro token streams.
diff --git a/crates/cargo-gamma-attrs-impl/README.md b/crates/cargo-gamma-attrs-impl/README.md
index 04f216a6..dd1d696d 100644
--- a/crates/cargo-gamma-attrs-impl/README.md
+++ b/crates/cargo-gamma-attrs-impl/README.md
@@ -13,49 +13,11 @@
-The implementation behind [`cargo-gamma-attrs`][__link0],
-which is where the inert `#[gamma::skip]`, `#[gamma::expect_survived]` and
-`#[gamma::expect_killed]` attributes are actually exposed.
-
-You almost certainly want that crate instead. This one is a normal library rather than a
-proc-macro crate so its logic can be called by ordinary tests, covered, and mutation tested.
-What remains in the proc-macro crate is a shim thin enough to read at a glance.
-
-## Why this crate exists
-
-`cargo-gamma-attrs` is a proc-macro crate, and a proc macro’s code runs only inside `rustc`,
-while some *other* crate is being compiled. That puts it beyond the reach of both measurements
-this project cares about:
-
-* A coverage harness collects counters from test binaries. A proc macro increments its counters
- inside the compiler, which writes no profile the harness sees.
-* A mutation run selects one mutant per test process at run time. A proc macro has already
- finished by then, so none of its mutants can be active while a test is watching.
-
-Splitting the logic into an ordinary library makes it reachable by coverage and mutation tests.
-The proc-macro crate remains a thin shim.
-
-## What the macros accept
-
-See the [`cargo-gamma-attrs`][__link1] documentation for the
-user-facing description. In brief: a comma-separated selector list, optionally followed by
-`reason = "..."` and `tag = "..."`, both of which must be string literals.
-
-`#[gamma::value()]` instead takes an expression. It is checked by [`value`][__link2], because its
-argument is spliced into the user’s crate as a mutant and must be exactly one expression.
-
-## Stability
-
-This crate is an implementation detail of `cargo-gamma-attrs` and carries no stability
-guarantee of its own. Depend on `cargo-gamma-attrs`.
+This is an implementation detail of the cargo-gamma-attrs crate. Do not take a dependency on this
+crate as it may change in incompatible ways without warning.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
-
- [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQbH5RUmmY8e-sbYyqmHPyeK9obgdLJAJ7T65AbUAUW0Y4uz2thZIGDdmNhcmdvLWdhbW1hLWF0dHJzLWltcGxlMC4xLjB2Y2FyZ29fZ2FtbWFfYXR0cnNfaW1wbA
- [__link0]: https://crates.io/crates/cargo-gamma-attrs
- [__link1]: https://docs.rs/cargo-gamma-attrs
- [__link2]: https://docs.rs/cargo-gamma-attrs-impl/0.1.0/cargo_gamma_attrs_impl/?search=value
diff --git a/crates/cargo-gamma-attrs-impl/docs/DESIGN.md b/crates/cargo-gamma-attrs-impl/docs/DESIGN.md
index 61270db4..02120d97 100644
--- a/crates/cargo-gamma-attrs-impl/docs/DESIGN.md
+++ b/crates/cargo-gamma-attrs-impl/docs/DESIGN.md
@@ -19,5 +19,6 @@ attributes exported by `cargo-gamma-attrs`.
## Stability
The crate is published only to support `cargo-gamma-attrs`. Its Rust API is an
-implementation detail; the diagnostics and accepted attribute syntax are the
-user-visible contract.
+implementation detail, so its rustdoc is hidden and its hand-written README
+warns downstream users not to depend on it. The diagnostics and accepted
+attribute syntax are the user-visible contract.
diff --git a/crates/cargo-gamma-attrs-impl/src/lib.rs b/crates/cargo-gamma-attrs-impl/src/lib.rs
index 61b274e7..dd9765a6 100644
--- a/crates/cargo-gamma-attrs-impl/src/lib.rs
+++ b/crates/cargo-gamma-attrs-impl/src/lib.rs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
#![cfg(not(all(test, miri)))]
+#![doc(hidden)]
#![forbid(
unsafe_code,
reason = "every raw platform call in this workspace lives in `cargo-gamma-unsafe`, behind a safe interface"
diff --git a/crates/cargo-gamma-engine/Cargo.toml b/crates/cargo-gamma-engine/Cargo.toml
index 1c2ac055..0d312fe2 100644
--- a/crates/cargo-gamma-engine/Cargo.toml
+++ b/crates/cargo-gamma-engine/Cargo.toml
@@ -19,6 +19,10 @@ repository = "https://github.com/microsoft/ox-tools/tree/main/crates/cargo-gamma
[package.metadata.docs.rs]
all-features = true
+[package.metadata.ox-gen-readme]
+# This is an implementation detail of `cargo-gamma`; its README is an explicit stability warning.
+disable = true
+
# Establish the cross-platform CI baseline for this new crate. Raise it as coverage improves.
[package.metadata.coverage-gate]
min-lines-percent = 99.2
@@ -36,6 +40,7 @@ syn = { workspace = true, features = ["full", "parsing", "printing", "visit", "e
[dev-dependencies]
bolero = { workspace = true }
+bolero-engine = { workspace = true, features = ["any"] }
walkdir = { workspace = true }
# >>> anvil-managed: anvil-lints
diff --git a/crates/cargo-gamma-engine/README.md b/crates/cargo-gamma-engine/README.md
index 48f0a32d..9f7100dc 100644
--- a/crates/cargo-gamma-engine/README.md
+++ b/crates/cargo-gamma-engine/README.md
@@ -13,17 +13,11 @@
-This crate is an internal implementation detail of
-[`cargo-gamma`][__link0]. It contains the Rust source parsing,
-mutation collection, stable identity, and schema instrumentation pipeline.
-
-Do not depend on it directly. Its API may change incompatibly without notice; it is published
-only so that `cargo-gamma` can be installed through crates.io.
+This is an implementation detail of the cargo-gamma tool. Do not take a dependency on this crate
+as it may change in incompatible ways without warning.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
-
- [__link0]: https://crates.io/crates/cargo-gamma
diff --git a/crates/cargo-gamma-engine/docs/DESIGN.md b/crates/cargo-gamma-engine/docs/DESIGN.md
index 0914e076..a82f854e 100644
--- a/crates/cargo-gamma-engine/docs/DESIGN.md
+++ b/crates/cargo-gamma-engine/docs/DESIGN.md
@@ -25,4 +25,6 @@ identity, mutator selection, and mutant-schema instrumentation.
## Stability
The crate is published so `cargo-gamma` can be installed from crates.io. Its
-Rust API is internal and carries no independent compatibility guarantee.
+Rust API is internal and carries no independent compatibility guarantee. Its
+rustdoc is hidden, and its hand-written README warns downstream users not to
+depend on it.
diff --git a/crates/cargo-gamma-engine/src/ops/collect/collector.rs b/crates/cargo-gamma-engine/src/ops/collect/collector.rs
index b4f5c989..9d76700a 100644
--- a/crates/cargo-gamma-engine/src/ops/collect/collector.rs
+++ b/crates/cargo-gamma-engine/src/ops/collect/collector.rs
@@ -673,19 +673,19 @@ impl<'a> Collector<'a> {
let wildcard = node
.arms
.iter()
- .position(|arm| is_catch_all(&arm.pat) && self.cfg.holds_for(&arm.attrs));
+ .position(|arm| !matches!(&arm.pat, Pat::Guard(_)) && is_catch_all(&arm.pat) && self.cfg.holds_for(&arm.attrs));
for (index, arm) in node.arms.iter().enumerate() {
if self.skipped(&arm.attrs) {
continue;
}
- if let Pat::Guard(guard) = &arm.pat {
+ if let Pat::Guard(guarded) = &arm.pat {
self.condition(
"match_guard.negate",
"match_guard.always_true",
"match_guard.always_false",
- &guard.guard,
+ &guarded.guard,
);
// An arm that already has a guard is disabled by forcing that guard false, which
diff --git a/crates/cargo-gamma-engine/src/ops/collect/defaults.rs b/crates/cargo-gamma-engine/src/ops/collect/defaults.rs
index 020f7112..1a39f71d 100644
--- a/crates/cargo-gamma-engine/src/ops/collect/defaults.rs
+++ b/crates/cargo-gamma-engine/src/ops/collect/defaults.rs
@@ -500,7 +500,7 @@ impl<'ast> Visit<'ast> for Defaults {
// because the alias's target is what names the error either way.
if name_of(&node.ty).as_deref() == Some("Result") {
let defaulted = node.generics.params.iter().find_map(|param| match param {
- GenericParam::Type(ty) => ty.default.as_ref().and_then(|(_, default)| name_of(default)),
+ GenericParam::Type(ty) => ty.default.as_ref().and_then(|(_equals, ty)| name_of(ty)),
_ => None,
});
diff --git a/crates/cargo-gamma-lib/Cargo.toml b/crates/cargo-gamma-lib/Cargo.toml
index 08b539dc..afb297f8 100644
--- a/crates/cargo-gamma-lib/Cargo.toml
+++ b/crates/cargo-gamma-lib/Cargo.toml
@@ -19,6 +19,10 @@ repository = "https://github.com/microsoft/ox-tools/tree/main/crates/cargo-gamma
[package.metadata.docs.rs]
all-features = true
+[package.metadata.ox-gen-readme]
+# This is an implementation detail of `cargo-gamma`; its README is an explicit stability warning.
+disable = true
+
# Establish the cross-platform CI baseline for this new crate. Raise it as coverage improves.
[package.metadata.coverage-gate]
min-lines-percent = 96.8
@@ -68,6 +72,7 @@ libc = { workspace = true, features = ["std"] }
[dev-dependencies]
bolero = { workspace = true }
+bolero-engine = { workspace = true, features = ["any"] }
# Only the integration test that pins the proc-macro's hand-copied limit constants against this
# library's own needs it; it is never used outside `#[cfg(test)]`/`tests/`.
cargo-gamma-attrs-impl = { workspace = true }
diff --git a/crates/cargo-gamma-lib/README.md b/crates/cargo-gamma-lib/README.md
index ba093fbe..ee3d80f1 100644
--- a/crates/cargo-gamma-lib/README.md
+++ b/crates/cargo-gamma-lib/README.md
@@ -13,15 +13,11 @@
-Internal implementation library for [`cargo-gamma`][__link0].
-
-This crate is an implementation detail. Do not depend on it: it may change in incompatible
-ways without warning, and it carries no semver commitment to anything it exposes.
+This is an implementation detail of the cargo-gamma tool. Do not take a dependency on this crate
+as it may change in incompatible ways without warning.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
-
- [__link0]: https://crates.io/crates/cargo-gamma
diff --git a/crates/cargo-gamma-lib/docs/DESIGN.md b/crates/cargo-gamma-lib/docs/DESIGN.md
index 2d76f02d..b0346d3c 100644
--- a/crates/cargo-gamma-lib/docs/DESIGN.md
+++ b/crates/cargo-gamma-lib/docs/DESIGN.md
@@ -24,4 +24,5 @@ verdicts, incremental reuse, reporting, and command dispatch.
The primary public contract is the `cargo gamma` command surface and its
configuration, reports, diagnostics, and exit codes. The Rust API is an
-implementation detail used by the thin executable crate.
+implementation detail used by the thin executable crate. Its rustdoc is hidden,
+and its hand-written README warns downstream users not to depend on it.
diff --git a/crates/cargo-gamma-lib/src/commands/run.rs b/crates/cargo-gamma-lib/src/commands/run.rs
index abc930f8..b6308b02 100644
--- a/crates/cargo-gamma-lib/src/commands/run.rs
+++ b/crates/cargo-gamma-lib/src/commands/run.rs
@@ -750,7 +750,6 @@ fn measured(host: &mut H, args: &RunArgs, progress_when: When, styler:
|| (crate::HashMap::default(), 0, Vec::new()),
|prepared| adopt(args, &mut survey, &prepared.base, Some(&prepared.context), &prepared.inputs),
);
- drop(cache_locks);
// A dry run reports on the whole population and builds nothing, so there is no package-by-
// package sequence to interleave the scan with; it is simply scanned.
@@ -790,7 +789,7 @@ fn measured(host: &mut H, args: &RunArgs, progress_when: When, styler:
verdict_log: VerdictLog::default(),
};
- let outcome = exec::run(&survey, &selection, &config, &mut events);
+ let outcome = exec::run_with_locks(&survey, &selection, &config, &mut events, cache_locks);
// A phase that failed never got to say what it found, so the line it opened is still waiting
// for an ending. Close it before the error is printed, or the error arrives as the rest of
diff --git a/crates/cargo-gamma-lib/src/exec/build/tests.rs b/crates/cargo-gamma-lib/src/exec/build/tests.rs
index 895b1ab9..87f5a132 100644
--- a/crates/cargo-gamma-lib/src/exec/build/tests.rs
+++ b/crates/cargo-gamma-lib/src/exec/build/tests.rs
@@ -1066,7 +1066,8 @@ fn the_rollback_limit_error_describes_only_the_build_that_hit_it() {
);
assert!(error.contains("1 of the 1 rollback rounds"), "{error}");
- assert!(!error.contains("41"), "{error}");
+ assert!(!error.contains("41 blamed during this build"), "{error}");
+ assert!(!error.contains("last rounds of this build: 41"), "{error}");
assert!(!error.contains("10 of"), "{error}");
}
diff --git a/crates/cargo-gamma-lib/src/exec/measure.rs b/crates/cargo-gamma-lib/src/exec/measure.rs
index b5827781..a7df1303 100644
--- a/crates/cargo-gamma-lib/src/exec/measure.rs
+++ b/crates/cargo-gamma-lib/src/exec/measure.rs
@@ -43,12 +43,22 @@ const GROUP_LIMIT: usize = 5;
/// baseline does not pass — a failing baseline means every comparison in the run has nothing to
/// compare against.
pub fn run(survey: &Survey, selection: &Selection, config: &Config, events: &mut impl Events) -> Result {
+ run_with_locks(survey, selection, config, events, None)
+}
+
+pub(crate) fn run_with_locks(
+ survey: &Survey,
+ selection: &Selection,
+ config: &Config,
+ events: &mut impl Events,
+ locks: Option,
+) -> Result {
let Measured {
mut plan,
built,
stuck,
dropped,
- } = measure(survey, selection, config, events)?;
+ } = measure_with_locks(survey, selection, config, events, locks)?;
// Nothing was live, so nothing was copied, built or measured. The plan still describes every
// mutant that was found and why each one is not being run, which is what the caller reports.
@@ -456,6 +466,16 @@ struct Cleared {
/// made to succeed, or the baseline does not pass — a failing baseline means every comparison in
/// the run has nothing to compare against.
pub fn measure(survey: &Survey, selection: &Selection, config: &Config, events: &mut impl Events) -> Result {
+ measure_with_locks(survey, selection, config, events, None)
+}
+
+fn measure_with_locks(
+ survey: &Survey,
+ selection: &Selection,
+ config: &Config,
+ events: &mut impl Events,
+ locks: Option,
+) -> Result {
let started = Instant::now();
let (memory, unbounded) = admit_memory_control(config)?;
@@ -481,7 +501,7 @@ pub fn measure(survey: &Survey, selection: &Selection, config: &Config, events:
// workspace can spend as much duplicating itself as compiling, and the aggregate cannot say
// which.
let copy_started = Instant::now();
- let mut work = Workspace::prepare(&plan.root, config, events)?;
+ let mut work = Workspace::prepare_with_locks(&plan.root, config, events, locks)?;
let copy = copy_started.elapsed();
// Settled once, before anything is spawned, so the baseline and the sweep cannot disagree about
diff --git a/crates/cargo-gamma-lib/src/exec/mod.rs b/crates/cargo-gamma-lib/src/exec/mod.rs
index c7fcde73..402ce1cd 100644
--- a/crates/cargo-gamma-lib/src/exec/mod.rs
+++ b/crates/cargo-gamma-lib/src/exec/mod.rs
@@ -53,6 +53,7 @@ pub use events::Events;
pub use incremental_mode::IncrementalMode;
pub use loader::UNDER_GAMMA_VAR;
pub(crate) use manifest::RUNTIME_CRATE;
+pub(crate) use measure::run_with_locks;
pub use measure::{Built, Measured, Oracle, measure, run};
pub use memory::{DEFAULT_HEADROOM, DEFAULT_MULTIPLIER, Demand, MemoryControl, MemoryPolicy};
// Named for its subject at this level, where `support` alone would say nothing about what is
diff --git a/crates/cargo-gamma-lib/src/exec/verdict.rs b/crates/cargo-gamma-lib/src/exec/verdict.rs
index d638a57f..93d45043 100644
--- a/crates/cargo-gamma-lib/src/exec/verdict.rs
+++ b/crates/cargo-gamma-lib/src/exec/verdict.rs
@@ -2175,7 +2175,7 @@ mod tests {
active: Some(1),
timeout: Some(Duration::from_secs(5)),
stall: Stall {
- budget: Some(measured.saturating_mul(2)),
+ budget: Some(measured.saturating_mul(4)),
},
request: MemoryRequest::default(),
only: Only::All,
diff --git a/crates/cargo-gamma-lib/src/exec/verdict/hubs.rs b/crates/cargo-gamma-lib/src/exec/verdict/hubs.rs
index b5df5ee8..894a6aa1 100644
--- a/crates/cargo-gamma-lib/src/exec/verdict/hubs.rs
+++ b/crates/cargo-gamma-lib/src/exec/verdict/hubs.rs
@@ -271,22 +271,27 @@ mod loom_models {
/// The live count returns to exactly zero after all four production reader roles finish.
///
- /// The coordinator first establishes the four-reader high-water mark, then all four independent
- /// reader threads finish concurrently. The contended `live` observation also models diagnostics
- /// racing abandoned readers as they finally close. Every increment must still have exactly one
- /// decrement.
+ /// The coordinator first establishes the four-reader high-water mark, then the two jobs finish
+ /// their two readers concurrently. Modeling each job as one thread preserves cross-job
+ /// contention and all four decrements without exploring equivalent permutations among four
+ /// symmetric finisher threads. The contended `live` observation also models diagnostics racing
+ /// abandoned readers as they finally close.
pub(super) fn a_readers_gauge_returns_to_exactly_zero_under_any_interleaving() {
loom::model(|| {
let readers = Arc::new(Readers::new());
- let mut finishers = Vec::with_capacity(CONCURRENT_READER_STREAMS);
+ let mut finishers = Vec::with_capacity(CONCURRENT_JOBS);
for _stream in 0..CONCURRENT_READER_STREAMS {
readers.started();
}
- for _stream in 0..CONCURRENT_READER_STREAMS {
+ for _job in 0..CONCURRENT_JOBS {
let readers = Arc::clone(&readers);
- finishers.push(loom::thread::spawn(move || readers.finished()));
+ finishers.push(loom::thread::spawn(move || {
+ for _stream in 0..STREAMS_PER_JOB {
+ readers.finished();
+ }
+ }));
}
assert!(
diff --git a/crates/cargo-gamma-lib/src/exec/workspace.rs b/crates/cargo-gamma-lib/src/exec/workspace.rs
index f634baab..9181acb4 100644
--- a/crates/cargo-gamma-lib/src/exec/workspace.rs
+++ b/crates/cargo-gamma-lib/src/exec/workspace.rs
@@ -29,6 +29,8 @@ use crate::Result;
use crate::discover::TargetFile;
use crate::error::error;
+pub(crate) type CacheLocks = (File, Option);
+
/// The guard runtime's sources, embedded so that the vendored copy cannot drift from the real one.
const RUNTIME_SOURCES: [(&str, &str); 3] = [
("lib.rs", include_str!("../../../cargo-gamma-rt/src/lib.rs")),
@@ -192,7 +194,18 @@ impl Workspace {
}
/// Copies and instruments the tree.
+ #[cfg(test)]
pub(super) fn prepare(source: &Utf8Path, config: &Config, events: &mut impl Events) -> Result {
+ Self::prepare_with_locks(source, config, events, None)
+ }
+
+ /// Copies and instruments the tree while retaining locks taken before cache adoption.
+ pub(super) fn prepare_with_locks(
+ source: &Utf8Path,
+ config: &Config,
+ events: &mut impl Events,
+ locks: Option,
+ ) -> Result {
config.cargo.validate()?;
// Absolute from here on, so that the copy's own exclusion — which compares the scratch base
@@ -214,7 +227,12 @@ impl Workspace {
fs::create_dir_all(base.as_std_path())
.map_err(|cause| error!("could not create the scratch directory at `{base}`").caused_by(cause))?;
- let (workspace_lock, cache_lock) = claim_cache(source, config.cache_dir.as_deref())?;
+ let (workspace_lock, cache_lock) = match locks {
+ Some(locks) => locks,
+ None => claim_cache(source, config.cache_dir.as_deref())?,
+ };
+ #[cfg(feature = "internals")]
+ crate::testing::pause_during_workspace_preparation(source);
migrate_legacy_directory(&base, "tree", "workspace")?;
migrate_legacy_directory(&base, "build", "target")?;
@@ -1147,7 +1165,7 @@ pub(crate) fn claim_workspace(root: &Utf8Path) -> Result {
}
/// Takes every lock needed to use one workspace's reusable state.
-pub(crate) fn claim_cache(root: &Utf8Path, cache: Option<&Utf8Path>) -> Result<(File, Option)> {
+pub(crate) fn claim_cache(root: &Utf8Path, cache: Option<&Utf8Path>) -> Result {
let workspace = claim_workspace(root)?;
let base = gamma_base(root, cache);
let default = gamma_base(root, None);
diff --git a/crates/cargo-gamma-lib/src/testing.rs b/crates/cargo-gamma-lib/src/testing.rs
index 3801fd54..31f484e7 100644
--- a/crates/cargo-gamma-lib/src/testing.rs
+++ b/crates/cargo-gamma-lib/src/testing.rs
@@ -29,14 +29,93 @@ use core::time::Duration;
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
-use std::sync::{OnceLock, mpsc};
+use std::sync::{Mutex, OnceLock, mpsc};
use std::{fs, panic, thread};
-use camino::Utf8PathBuf;
+use camino::{Utf8Path, Utf8PathBuf};
use crate::commands::Host;
use crate::exec::Workspace;
+type PauseChannels = (mpsc::SyncSender<()>, mpsc::Receiver<()>);
+
+static WORKSPACE_PREPARATION_PAUSES: OnceLock>> = OnceLock::new();
+
+/// A deterministic pause after workspace preparation receives a command's cache locks.
+#[derive(Debug)]
+pub struct WorkspacePreparationPause {
+ reached: mpsc::Receiver<()>,
+ release: Option>,
+ waiting: bool,
+}
+
+impl WorkspacePreparationPause {
+ /// Waits until workspace preparation receives the cache locks.
+ pub fn wait(&mut self) {
+ self.reached
+ .recv_timeout(Duration::from_mins(2))
+ .expect("the registered command should reach workspace preparation before the test budget expires");
+ self.waiting = true;
+ }
+
+ /// Allows the paused command to continue.
+ pub fn release(mut self) {
+ self.release_waiter();
+ }
+
+ fn release_waiter(&mut self) {
+ if self.waiting {
+ let sender = self
+ .release
+ .take()
+ .expect("a pause that reached the boundary retains exactly one release sender");
+ let _released = sender.send(());
+ self.waiting = false;
+ }
+ }
+}
+
+impl Drop for WorkspacePreparationPause {
+ fn drop(&mut self) {
+ self.release_waiter();
+ }
+}
+
+/// Registers a workspace-preparation pause for one workspace root.
+pub fn hold_during_workspace_preparation(root: Utf8PathBuf) -> WorkspacePreparationPause {
+ let (reached_sender, reached) = mpsc::sync_channel(0);
+ let (release, release_receiver) = mpsc::sync_channel(0);
+ let pauses = WORKSPACE_PREPARATION_PAUSES.get_or_init(|| Mutex::new(crate::HashMap::default()));
+ let prior = pauses
+ .lock()
+ .expect("a prior workspace-preparation test panicked while registering its pause")
+ .insert(root, (reached_sender, release_receiver));
+
+ assert!(prior.is_none(), "only one workspace-preparation pause may target a workspace");
+
+ WorkspacePreparationPause {
+ reached,
+ release: Some(release),
+ waiting: false,
+ }
+}
+
+pub(crate) fn pause_during_workspace_preparation(root: &Utf8Path) {
+ let Some(pauses) = WORKSPACE_PREPARATION_PAUSES.get() else {
+ return;
+ };
+ let pause = pauses
+ .lock()
+ .expect("a prior workspace-preparation test panicked while taking its pause")
+ .remove(root);
+
+ if let Some((reached, release)) = pause
+ && reached.send(()).is_ok()
+ {
+ let _released = release.recv();
+ }
+}
+
/// A [`Host`] that captures both streams in memory.
///
/// The defaults describe a plain redirected pipe: not a terminal, no width, and an empty
diff --git a/crates/cargo-gamma-lib/tests/session.rs b/crates/cargo-gamma-lib/tests/session.rs
index 54c13e4d..3dff59af 100644
--- a/crates/cargo-gamma-lib/tests/session.rs
+++ b/crates/cargo-gamma-lib/tests/session.rs
@@ -387,6 +387,53 @@ fn a_second_run_reestablishes_the_first_runs_kills() {
assert!(!output.contains("Iterating"), "a kill must never be carried: {output}");
}
+#[test]
+fn an_incremental_command_holds_its_cache_lock_from_adoption_through_preparation() {
+ step_aside_if_nested!();
+ let dir = std::sync::Arc::new(workspace(SUBJECT));
+ let cache = TempDir::new().expect("could not create the redirected cache");
+ let cache = cache.path().to_str().expect("cache path is not UTF-8").to_owned();
+ let args = ["--mutators", "relational", "--incremental", "build", "--cache-dir", cache.as_str()];
+ let (warmed, output) = session(&dir, &args);
+
+ assert_eq!(warmed, EXIT_OK, "{output}");
+
+ let root = Utf8PathBuf::from_path_buf(dir.path().to_path_buf()).expect("workspace path is not UTF-8");
+ let mut pause = cargo_gamma_lib::testing::hold_during_workspace_preparation(root);
+ let first_dir = std::sync::Arc::clone(&dir);
+ let first_cache = cache.clone();
+ let first = std::thread::spawn(move || {
+ session(
+ &first_dir,
+ &[
+ "--mutators",
+ "relational",
+ "--incremental",
+ "build",
+ "--cache-dir",
+ first_cache.as_str(),
+ ],
+ )
+ });
+
+ pause.wait();
+
+ let (blocked, output) = session(&dir, &args);
+
+ assert_ne!(blocked, EXIT_OK, "{output}");
+ assert!(output.contains("already using"), "{output}");
+
+ pause.release();
+
+ let (first_code, output) = first.join().expect("the paused incremental command should finish after release");
+
+ assert_eq!(first_code, EXIT_OK, "{output}");
+
+ let (reacquired, output) = session(&dir, &args);
+
+ assert_eq!(reacquired, EXIT_OK, "{output}");
+}
+
#[test]
fn an_unasserted_side_effect_leaves_a_survivor() {
step_aside_if_nested!();
diff --git a/crates/cargo-gamma-process/Cargo.toml b/crates/cargo-gamma-process/Cargo.toml
index 6b3b582d..98f88b42 100644
--- a/crates/cargo-gamma-process/Cargo.toml
+++ b/crates/cargo-gamma-process/Cargo.toml
@@ -19,6 +19,10 @@ repository = "https://github.com/microsoft/ox-tools/tree/main/crates/cargo-gamma
[package.metadata.docs.rs]
all-features = true
+[package.metadata.ox-gen-readme]
+# This is an implementation detail of `cargo-gamma`; its README is an explicit stability warning.
+disable = true
+
# Establish the cross-platform CI baseline for this new crate. Raise it as coverage improves.
[package.metadata.coverage-gate]
min-lines-percent = 91.6
diff --git a/crates/cargo-gamma-process/README.md b/crates/cargo-gamma-process/README.md
index ca15ae7b..a539a1a8 100644
--- a/crates/cargo-gamma-process/README.md
+++ b/crates/cargo-gamma-process/README.md
@@ -13,54 +13,11 @@
-This crate is an internal implementation detail of
-[`cargo-gamma`][__link0]. It contains cargo-gamma’s bounded
-process-tree containment, accounting, observation, and termination lifecycle.
-
-Do not depend on it directly. Its API may change incompatibly without notice; it is published
-only so that `cargo-gamma` can be installed through crates.io.
-
-## Process-tree lifecycle
-
-Killing the process a run started is not enough. A test that shells out to a server, a database
-or another build leaves those behind when the harness above them is cut off, and they take two
-things with them: file locks inside the scratch tree, which turn the next run into a failure
-that has nothing to do with any mutant, and inherited pipe handles, which keep whoever is
-reading this tool’s output from ever seeing end of file. A run that ends with a hung consumer is
-worse than one that ends with a wrong verdict, because nobody can even see the verdict.
-
-Both platforms have a way to say “this process and everything descended from it” — a process
-group on Unix and a job object on Windows — but neither is reachable from `std`. The raw calls
-live in `cargo-gamma-unsafe`, which exposes safe interfaces; this crate composes them into the
-lifecycle used by the rest of the tool.
-
-The same boundary accounts for memory because it is the only place that knows the whole process
-tree rather than only its leader. A [`MemoryRequest`][__link1] passed to [`prepare`][__link2] asks for measurement,
-a ceiling, or neither; [`ProcessTree::usage`][__link3] answers once the process tree is gone. On Windows
-requested accounting requires a dedicated job carrying the limit and accounting; on Linux a
-cgroup leaf supplied by `cargo-gamma-unsafe` serves both purposes.
-
-The boundary is in force from the child’s first instruction. On Linux the child moves itself
-into the cgroup between fork and exec; on Windows it normally starts suspended, enters the
-dedicated job, and only then runs. A child that cannot enter a job already created for it is
-rejected because an inherited job cannot be opened later to terminate its descendants. Failure
-to create a job is tolerated only when no accounting was requested, degrading termination to
-the leader alone. A peak that reached the limit is therefore enforced by the kernel rather than
-inferred after the fact.
-
-A terminal delivers `Ctrl-C` to the whole foreground process group, so a child sharing this
-process’s group dies with it automatically while a child leading its own group does not. Windows
-normally preserves that guarantee through a dedicated job that dies with its last handle. Unix
-installs explicit interruption handling through `cargo-gamma-unsafe`.
+This is an implementation detail of the cargo-gamma tool. Do not take a dependency on this crate
+as it may change in incompatible ways without warning.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
-
- [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQbyT97-uOZjGMbJSTUNtzD3T0bGSLqnro0bgkbPvyEineYDERhZIGDc2NhcmdvLWdhbW1hLXByb2Nlc3NlMC4xLjBzY2FyZ29fZ2FtbWFfcHJvY2Vzcw
- [__link0]: https://crates.io/crates/cargo-gamma
- [__link1]: https://docs.rs/cargo-gamma-process/0.1.0/cargo_gamma_process/?search=MemoryRequest
- [__link2]: https://docs.rs/cargo-gamma-process/0.1.0/cargo_gamma_process/?search=prepare
- [__link3]: https://docs.rs/cargo-gamma-process/0.1.0/cargo_gamma_process/?search=ProcessTree::usage
diff --git a/crates/cargo-gamma-process/docs/DESIGN.md b/crates/cargo-gamma-process/docs/DESIGN.md
index a0ead882..53e3496b 100644
--- a/crates/cargo-gamma-process/docs/DESIGN.md
+++ b/crates/cargo-gamma-process/docs/DESIGN.md
@@ -8,6 +8,13 @@
This crate provides cargo-gamma's bounded process-tree lifecycle: launch,
observation, memory accounting, interruption, and termination.
+Killing only the process a run started is insufficient. A test can spawn a
+server, database, or nested build that outlives its harness, retaining scratch
+tree file locks that break the next run. Descendants can also inherit output
+pipe handles, preventing consumers from observing end of file and leaving an
+otherwise completed run hung before its verdict can be read. Containment
+therefore covers the complete descendant tree.
+
## Boundaries
- Policy such as timeout and memory-limit calculation belongs in
@@ -24,7 +31,16 @@ observation, memory accounting, interruption, and termination.
- The `fault-injection` feature is test-only infrastructure.
- The crate forbids unsafe code.
+Containment is active from the child's first instruction so descendants cannot
+escape during a launch-time gap. Linux moves the child into its cgroup between
+fork and exec. Windows normally creates the child suspended, assigns it to its
+dedicated job, and resumes it only after assignment succeeds. This boundary
+also owns memory accounting because it observes the complete tree rather than
+only its leader; kernel-enforced cgroup or job limits cannot be reconstructed
+reliably from a post-exit peak.
+
## Stability
The crate is published only as an implementation dependency of cargo-gamma and
-does not promise a stable downstream API.
+does not promise a stable downstream API. Its rustdoc is hidden, and its
+hand-written README warns downstream users not to depend on it.
diff --git a/crates/cargo-gamma-rt/Cargo.toml b/crates/cargo-gamma-rt/Cargo.toml
index c67bfa41..3bcf8bba 100644
--- a/crates/cargo-gamma-rt/Cargo.toml
+++ b/crates/cargo-gamma-rt/Cargo.toml
@@ -19,6 +19,10 @@ repository = "https://github.com/microsoft/ox-tools/tree/main/crates/cargo-gamma
[package.metadata.docs.rs]
all-features = true
+[package.metadata.ox-gen-readme]
+# This is an implementation detail of `cargo-gamma`; its README is an explicit stability warning.
+disable = true
+
# Establish the lowest Windows baseline for this new crate. Raise it as coverage improves.
[package.metadata.coverage-gate]
min-lines-percent = 90.8
diff --git a/crates/cargo-gamma-rt/README.md b/crates/cargo-gamma-rt/README.md
index 0501dc26..31c6aa56 100644
--- a/crates/cargo-gamma-rt/README.md
+++ b/crates/cargo-gamma-rt/README.md
@@ -13,197 +13,11 @@
-Runtime support injected into crates under mutation test by `cargo-gamma`.
-
-This crate is injected into the dependency graph of the crate under test while a mutation run
-is in progress. You should never need to depend on it directly.
-
-`cargo-gamma` rewrites the crate under test so that every mutation site carries a *guard*: a
-cheap runtime check that activates exactly one mutant. That lets a whole population of mutants
-live in a single compiled artifact — the *mutant schema*, after Untch, Offutt and Harrold, who
-introduced the construction in 1993 — instead of requiring one build per mutant. Since a build
-is by far the most expensive step in the loop, testing a mutant drops from minutes to the cost
-of launching a process.
-
-## What a guard looks like
-
-[`a`][__link0] is the only function the instrumented source calls, and it appears in one of three shapes,
-chosen by what Rust will accept in that position:
-
-```text
-// an expression, whose value the mutant replaces
-(if ::gamma_rt::a(7u32) { (a) <= (b) } else { a < b })
-
-// a block, whose body the mutant replaces
-{ if ::gamma_rt::a(12u32) { Default::default() } else { ..the real body.. } }
-
-// a statement, which the mutant deletes
-if !::gamma_rt::a(19u32) { self.entries.push(value); }
-```
-
-Sites nest — in `a + b < c` the `<` site contains the `+` site — and only the `else` arm carries
-instrumented children. Exactly one mutant is live in a process, so if the `<` mutant is active
-then no `+` mutant can be, and the taken arm can hold plain original text. That is what keeps
-the encoding linear in the size of the source rather than exponential in nesting depth.
-
-## You do not depend on this crate
-
-`cargo-gamma` copies the workspace to a scratch tree, writes this crate into it, and adds the
-dependency there. Nothing is added to your manifest, nothing is fetched from the network, and
-your own build is never instrumented. The package is `cargo-gamma-rt` but its library is named
-`gamma_rt`, which is why instrumented source can say `::gamma_rt::a` without a rename.
-
-The copy embedded in the tool is this exact source, so the vendored runtime cannot drift from
-the one the guards were generated against.
-
-## Why this crate has no dependencies
-
-It has zero dependencies, no features, no build script and no `std`, by design. Anything else
-would perturb feature unification in *the user’s* tree, which could change what their code
-compiles to and therefore what their tests prove, or stop a `no_std` tree from building once
-the shim is injected into it. Zero dependencies is a correctness requirement, not a
-preference.
-
-For the same reason [`a`][__link1] must stay trivial. It is called at every mutation site of every
-execution of the suite, so its cost is multiplied by the whole population: a cached atomic load
-and a comparison, behind a branch the predictor learns immediately.
-
-## A worked example
-
-Given this function, and a mutant that turns `<` into `<=`:
-
-```rust
-fn below(a: u32, b: u32) -> bool {
- a < b
-}
-```
-
-`cargo-gamma` rewrites it in the scratch tree as:
-
-```rust
-fn below(a: u32, b: u32) -> bool {
- if ::gamma_rt::a(7u32) {
- (a) <= (b)
- } else {
- a < b
- }
-}
-```
-
-The whole population lives in one binary, and the run launches it once per mutant:
-
-```text
-GAMMA_ACTIVE=7 ./target/debug/deps/my_crate-abc123 # mutant 7 is live
-GAMMA_ACTIVE=8 ./target/debug/deps/my_crate-abc123 # mutant 8 is live
-./target/debug/deps/my_crate-abc123 # nothing is live: the baseline
-```
-
-## Selection protocol
-
-The active mutant is named by the [`ACTIVE_VAR`][__link2] environment variable, captured exactly once
-during process startup before user code can start threads. The value is a decimal mutant
-ordinal. [`NONE`][__link3] means no mutant is active, which is how the baseline run and every ordinary
-build behave — including builds of proc macros, where an active mutant could otherwise hang the
-compiler.
-
-An unset, empty, or unparsable value all mean [`NONE`][__link4]. A build that links this crate but is not
-being driven by a mutation run must behave exactly as it did before, and the ordinals are
-1-based precisely so that “absent” and “explicitly unmutated” are the same answer. Failure to
-acquire the startup environment is different: the runtime emits [`ENVIRONMENT_ERROR_MARKER`][__link5]
-and exits, so the parent cannot mistake a mutant that never activated for a survivor.
-
-```rust
-use gamma_rt::{ACTIVE_VAR, CENSUS_VAR, NONE, a, active, any};
-
-// Stated for an ordinary process. A census is its own mode: it activates no mutant, so
-// `active` reports `NONE`, and every guard answers `false` while recording the site it stands
-// at — including the sites this example would walk past.
-if std::env::var_os(CENSUS_VAR).is_none_or(|path| path.is_empty()) {
- // In an ordinary process nothing is selected, so every guard takes its original arm.
- if active() == NONE {
- assert!(!any());
- assert!(!a(NONE));
- assert!(!a(1));
- assert!(!a(9_999));
- }
-
- // Only a positive ordinal can select a mutant.
- if active() != NONE {
- assert!(a(active()));
- }
-}
-
-assert_eq!(ACTIVE_VAR, "GAMMA_ACTIVE");
-```
-
-Reading it once, rather than per call, is what makes the guard cheap; it also means a test that
-sets the variable on itself changes nothing, which is the honest behavior. The run drives
-selection by launching a fresh process per mutant.
-
-## The three entry points
-
-[`a`][__link6] is what the guards call, and the only one of the three that instrumented source contains.
-[`active`][__link7] and [`any`][__link8] are there for the tool’s own diagnostics and for anyone inspecting a
-scratch tree by hand:
-
-```rust
-use gamma_rt::{active, any};
-
-// Useful in a scratch tree when you are trying to work out which mutant a failing
-// reproduction actually ran.
-if any() {
- println!("mutant {} is live", active());
-} else {
- println!("baseline");
-}
-```
-
-## Making two iterators one type
-
-[`Either`][__link9] is the one other thing instrumented source mentions, and it exists because the guard
-is an `if`. A function returning `impl Iterator- ` returns a single concrete type that
-its body picks, so `if a(n) { core::iter::empty() } else { ..the real body.. }` has arms of two
-different types and will not compile. Wrapping each arm in a variant makes them one type:
-
-```text
-{ if ::gamma_rt::a(4u32) { ::gamma_rt::Either::L(core::iter::empty()) }
- else { ::gamma_rt::Either::R({ ..the real body.. }) } }
-```
-
-See [`Either`][__link10] for why this is not a `Box`.
-
-## `no_std`, and what it does not buy
-
-This crate is `#![no_std]`. It has to be: it is injected into the dependency graph of every
-crate the tool instruments, so a shim that linked `std` could not be instrumented into a tree
-whose target has no `std` in its sysroot — and that failure is not attributable to any mutant,
-so the rollback loop cannot withdraw anything to rescue it. The whole tree simply stops
-building. The tests link `std`, which costs nothing because they are never compiled into a
-user’s build.
-
-What `no_std` does not buy is a mutation run on a target with no environment. A mutant is
-selected by reading `GAMMA_ACTIVE`, which needs POSIX `getenv` or the Win32 equivalent; a
-target with neither gets the arm that reports no mutant at all, so the instrumented code
-compiles and runs the original everywhere. That is deliberately the safe direction — every
-mutant is reported as surviving rather than a mutated program being reported as correct — but
-it means the useful case is a `no_std` *crate* whose tests are run on a hosted target, which is
-how nearly every `no_std` library is tested anyway.
+This is an implementation detail of the cargo-gamma tool. Do not take a dependency on this crate
+as it may change in incompatible ways without warning.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
-
- [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQbw7tiVz7VIbMbp0tv-g1QPj8b-zYmwf0qFaIbBB-y9UIz0QRhZIGDbmNhcmdvLWdhbW1hLXJ0ZTAuMS4wbmNhcmdvX2dhbW1hX3J0
- [__link0]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=a
- [__link1]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=a
- [__link10]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=Either
- [__link2]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=ACTIVE_VAR
- [__link3]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=NONE
- [__link4]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=NONE
- [__link5]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=ENVIRONMENT_ERROR_MARKER
- [__link6]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=a
- [__link7]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=active
- [__link8]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=any
- [__link9]: https://docs.rs/cargo-gamma-rt/0.1.0/cargo_gamma_rt/?search=Either
diff --git a/crates/cargo-gamma-rt/docs/DESIGN.md b/crates/cargo-gamma-rt/docs/DESIGN.md
index f611e5a3..7f2183f2 100644
--- a/crates/cargo-gamma-rt/docs/DESIGN.md
+++ b/crates/cargo-gamma-rt/docs/DESIGN.md
@@ -15,6 +15,8 @@ equivalent to the original program.
- No features and no build script.
- `no_std` compatibility.
- The library target remains named `gamma_rt`.
+- Rustdoc is hidden, and the hand-written README warns downstream users not to
+ depend on this implementation crate.
These constraints prevent injection from perturbing dependency resolution,
feature unification, offline builds, or the target crate's standard-library
diff --git a/crates/cargo-gamma-rt/src/lib.rs b/crates/cargo-gamma-rt/src/lib.rs
index e0093e8d..174dd390 100644
--- a/crates/cargo-gamma-rt/src/lib.rs
+++ b/crates/cargo-gamma-rt/src/lib.rs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
#![no_std]
+#![doc(hidden)]
#![cfg_attr(
all(any(unix, windows), miri),
expect(
diff --git a/crates/cargo-gamma-unsafe/Cargo.toml b/crates/cargo-gamma-unsafe/Cargo.toml
index 8ebf1453..4444d579 100644
--- a/crates/cargo-gamma-unsafe/Cargo.toml
+++ b/crates/cargo-gamma-unsafe/Cargo.toml
@@ -19,6 +19,10 @@ repository = "https://github.com/microsoft/ox-tools/tree/main/crates/cargo-gamma
[package.metadata.docs.rs]
all-features = true
+[package.metadata.ox-gen-readme]
+# This is an implementation detail of `cargo-gamma`; its README is an explicit stability warning.
+disable = true
+
# Establish the lowest cross-platform baseline for this new crate. Raise it as coverage improves.
[package.metadata.coverage-gate]
min-lines-percent = 86.5
diff --git a/crates/cargo-gamma-unsafe/README.md b/crates/cargo-gamma-unsafe/README.md
index cf8a1181..e523893e 100644
--- a/crates/cargo-gamma-unsafe/README.md
+++ b/crates/cargo-gamma-unsafe/README.md
@@ -13,30 +13,11 @@
-The platform calls [`cargo-gamma`][__link0] cannot make safely,
-behind an interface that is safe to call. This crate is an implementation detail of the tool;
-you should never need to depend on it directly.
-
-Two things the tool does have no safe expression in `std`: killing a whole process subtree (a
-process group on Unix, a job object on Windows) and bounding what that subtree allocates (a
-cgroup leaf on Linux, the same job object on Windows). Neither is a case of reaching for
-`unsafe` to go faster — there is no safe version to prefer.
-
-Concentrating those calls here is what lets every other crate in the workspace carry
-`#![forbid(unsafe_code)]`, which turns “we reviewed the unsafe code” into a property the
-compiler checks on every build. `cargo-gamma-rt` is the one exception, and only because it is
-injected into the dependency graph of the crate under test and so can depend on nothing at
-all.
-
-Policy does not live here. What a memory ceiling *should* be is arithmetic on a baseline
-measurement, and it stays in `cargo-gamma-lib` where it can be tested without a kernel. This
-crate answers “what can the platform do, and do it”; its caller answers “what should we ask
-for”.
+This is an implementation detail of the cargo-gamma tool. Do not take a dependency on this crate
+as it may change in incompatible ways without warning.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
-
- [__link0]: https://crates.io/crates/cargo-gamma
diff --git a/crates/cargo-gamma-unsafe/docs/DESIGN.md b/crates/cargo-gamma-unsafe/docs/DESIGN.md
index 7cb3fd61..3d5c8eb8 100644
--- a/crates/cargo-gamma-unsafe/docs/DESIGN.md
+++ b/crates/cargo-gamma-unsafe/docs/DESIGN.md
@@ -8,6 +8,14 @@
This crate contains the operating-system calls cargo-gamma cannot express
safely through the standard library, exposed through safe interfaces.
+Process-subtree termination and memory containment have no safe standard
+library equivalents; the unsafe code here is required for platform capability,
+not performance. Concentrating those calls in this crate lets the rest of the
+workspace use `#![forbid(unsafe_code)]`, turning the boundary from a review
+convention into a property checked by the compiler on every build.
+`cargo-gamma-rt` is the sole exception because it is injected into the subject
+crate's dependency graph and must remain dependency-free.
+
## Boundaries
- Unix process groups and Windows job objects provide process-tree control.
@@ -21,7 +29,9 @@ safely through the standard library, exposed through safe interfaces.
- Linux controller delegation moves only cargo-gamma itself and refuses a
cgroup shared with any other process.
- Policy and limit calculation remain in `cargo-gamma-lib`; this crate only
- reports and applies platform capabilities.
+ reports and applies platform capabilities. For example, choosing a memory
+ ceiling is testable arithmetic in `cargo-gamma-lib`; this crate answers only
+ what the host can enforce and applies the requested value.
- Unsafe blocks are concentrated here, documented, and hidden behind
invariants that callers can satisfy safely.
@@ -30,3 +40,9 @@ safely through the standard library, exposed through safe interfaces.
Unix and Windows implementations are selected with target-specific
dependencies. Unsupported hosts report the missing capability rather than
silently substituting weaker semantics.
+
+## Stability
+
+The crate is published only as an implementation dependency of cargo-gamma. Its
+rustdoc is hidden, and its hand-written README warns downstream users not to
+depend on its unstable Rust API.
diff --git a/crates/cargo-heather/README.md b/crates/cargo-heather/README.md
index 118795db..9b764276 100644
--- a/crates/cargo-heather/README.md
+++ b/crates/cargo-heather/README.md
@@ -193,7 +193,7 @@ is supplied.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.
- [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQbtpJMWoUHDG0bJGvg_sbiCH8b66-weBRIetcbfF7fD5ccBythZIGDbWNhcmdvLWhlYXRoZXJlMC4zLjBtY2FyZ29faGVhdGhlcg
+ [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQbtpJMWoUHDG0bJGvg_sbiCH8b66-weBRIetcbfF7fD5ccBythZIGDbWNhcmdvLWhlYXRoZXJlMC4zLjBtY2FyZ29faGVhdGhlcg
[__link0]: https://doc.rust-lang.org/stable/std/?search=io::Read
[__link1]: https://doc.rust-lang.org/stable/std/?search=io::Write
[__link10]: https://docs.rs/cargo-heather/0.3.0/cargo_heather/?search=FileKind::detect
diff --git a/deny.toml b/deny.toml
index 523d092b..25c9016a 100644
--- a/deny.toml
+++ b/deny.toml
@@ -9,13 +9,7 @@ yanked = "deny"
unmaintained = "all"
# Advisories we have consciously accepted. Each entry needs a reason and,
# where the fix is out of our hands, the upstream we are waiting on.
-ignore = [
- # `paste` is unmaintained but has no safe upgrade. It reaches us only
- # transitively through `cel-interpreter`, which `cargo-aprz` uses to
- # evaluate appraisal expressions. Revisit when cel-interpreter moves to
- # `pastey` (which this workspace already depends on directly).
- "RUSTSEC-2024-0436",
-]
+ignore = []
# <<< anvil-managed: anvil-deny-advisories
# >>> anvil-managed: anvil-deny-bans
diff --git a/gamma.toml b/gamma.toml
index 9a77b728..52e87d90 100644
--- a/gamma.toml
+++ b/gamma.toml
@@ -1,4 +1,11 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
-exclude-files = ["crates/cargo-gamma-lib/src/testing.rs"]
+exclude-files = [
+ # Repository automation invokes external tools rather than implementing product behavior.
+ "crates/automation/**",
+ # Integration-test fixtures are deliberately compiled through the `internals` feature.
+ "crates/cargo-gamma-lib/src/testing.rs",
+ # Process fault injection exists only to force test-only operating-system failure paths.
+ "crates/cargo-gamma-process/src/faults.rs",
+]
diff --git a/justfiles/anvil/checks/bolero.just b/justfiles/anvil/checks/bolero.just
index 37f7d21b..d23fb3a9 100644
--- a/justfiles/anvil/checks/bolero.just
+++ b/justfiles/anvil/checks/bolero.just
@@ -103,7 +103,7 @@ anvil-bolero: anvil-bolero-validate-prereqs anvil-impact
exit 1
}
Write-Host "anvil-bolero: ${packageName}::${testName}"
- & cargo '+{{ rust_nightly }}' bolero test --release --engine libfuzzer -T 60s --package $packageName $testName
+ & cargo '+{{ rust_nightly }}' bolero test --profile release --engine libfuzzer -T 60s --package $packageName $testName
if ($LASTEXITCODE -ne 0) { $failed = $true }
}
if ($failed) { exit 1 }
diff --git a/justfiles/anvil/checks/readme-check.just b/justfiles/anvil/checks/readme-check.just
index 1d258e25..5ec58e43 100644
--- a/justfiles/anvil/checks/readme-check.just
+++ b/justfiles/anvil/checks/readme-check.just
@@ -22,20 +22,28 @@
# `[package.metadata.ox-gen-readme]\ndisable = true` to their
# Cargo.toml. anvil skips those crates.
#
-# Bin-only crates have no library rustdoc to base a README on, so they
-# are skipped as well (cargo doc2readme requires a library target).
+# Library or proc-macro rustdoc is preferred when a crate has one; bin-only
+# crates use their binary rustdoc so every publishable crate follows the same
+# generation policy.
# TODO(anvil-runner): even after a `cargo ox-run` helper absorbs the
# skip/splat preamble, this recipe stays multi-step: per-crate
-# iteration over library targets, cargo-metadata-driven opt-outs
+# iteration over eligible targets, cargo-metadata-driven opt-outs
# (publish=false, [package.metadata.ox-gen-readme] disable), per-crate
# Push-Location into the crate dir (cargo-doc2readme is CWD-sensitive
# rather than --manifest-path-driven), and per-crate template-path
-# resolution. The recipe iterates the full library-crate set every run.
+# resolution. The recipe iterates the full eligible-crate set every run.
-# Check that crate README files are up to date.
+# Generate or check every eligible crate README with the same selection policy.
[script("pwsh", "-NoProfile")]
-anvil-readme-check: anvil-readme-check-validate-prereqs
+_anvil-readme mode: anvil-readme-check-validate-prereqs
$ErrorActionPreference = 'Stop'
+ $action = '{{ mode }}'
+ if ($action -notin @('generate', 'check')) {
+ [Console]::Error.WriteLine("anvil-readme: invalid mode '$action'; expected 'generate' or 'check'")
+ exit 2
+ }
+ $check = $action -eq 'check'
+ $prefix = "anvil-readme-${action}"
# Detect a workspace-level README template. Two conventional
# locations: crates/README.j2 (cargo-workspaces idiom) or
# README.j2 at repo root.
@@ -43,26 +51,28 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
foreach ($candidate in 'crates/README.j2', 'README.j2') {
if (Test-Path $candidate) { $template = (Resolve-Path $candidate).Path; break }
}
- # Iterate every library crate, then drop bin-only crates and opt-outs.
+ # Iterate every crate with rustdoc, then drop private crates and opt-outs.
# Unscoped: a change to the workspace-level README template is a repo-level
# input cargo-delta cannot map to a package, so this check always runs the
- # full library set rather than an impact subset.
+ # full eligible-crate set rather than an impact subset.
$metadataJson = & cargo metadata --no-deps --format-version 1
if ($LASTEXITCODE -ne 0) {
- Write-Error 'anvil-readme-check: cargo metadata failed'
+ Write-Error "${prefix}: cargo metadata failed"
exit $LASTEXITCODE
}
$meta = $metadataJson | ConvertFrom-Json
$hadFailure = $false
foreach ($p in $meta.packages) {
$name = $p.name
- if (-not ($p.targets | Where-Object { $_.kind -contains 'lib' })) { continue }
+ $hasLib = [bool]($p.targets | Where-Object { ($_.kind -contains 'lib') -or ($_.kind -contains 'proc-macro') })
+ $hasBin = [bool]($p.targets | Where-Object { $_.kind -contains 'bin' })
+ if (-not $hasLib -and -not $hasBin) { continue }
# Skip private crates (publish = false). They aren't released
# and rarely have a polished README. Mirrors the
# `cargo workspaces exec --ignore-private` idiom adopters
# commonly use.
if ($p.publish -is [array] -and $p.publish.Count -eq 0) {
- Write-Host "anvil-readme-check: $name (skipped: publish = false)"
+ Write-Host "${prefix}: $name (skipped: publish = false)"
continue
}
$disabled = $false
@@ -70,13 +80,13 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
$disabled = $true
}
if ($disabled) {
- Write-Host "anvil-readme-check: $name (opted out via [package.metadata.ox-gen-readme])"
+ Write-Host "${prefix}: $name (opted out via [package.metadata.ox-gen-readme])"
continue
}
- Write-Host "anvil-readme-check: $name"
+ Write-Host "${prefix}: $name"
# cargo doc2readme writes / compares relative to its CWD (not
- # --manifest-path), so chdir into the crate before invoking
- # --check. We also compute a per-crate relative path to the
+ # --manifest-path), so chdir into the crate before invoking it.
+ # We also compute a per-crate relative path to the
# workspace-level template so the same template file works for
# every crate (parallels the cargo-workspaces idiom).
$crateDir = Split-Path -Parent $p.manifest_path
@@ -87,7 +97,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
} else {
$null
}
- $args = @('doc2readme', '--check')
+ $target = if ($hasLib) { '--lib' } else { '--bin' }
+ $args = @('doc2readme', $target)
+ if ($check) { $args += '--check' }
if ($relTemplate) { $args += @('--template', $relTemplate) }
& cargo @args
if ($LASTEXITCODE -ne 0) { $hadFailure = $true }
@@ -97,6 +109,9 @@ anvil-readme-check: anvil-readme-check-validate-prereqs
}
if ($hadFailure) { exit 1 }
+# Check that crate README files are up to date.
+anvil-readme-check: (_anvil-readme "check")
+
# Install prerequisites for the `anvil-readme-check` recipe.
[group("anvil-setup")]
anvil-readme-check-setup installer="install": (anvil-tool-cargo-doc2readme-install installer)
diff --git a/justfiles/anvil/versions.just b/justfiles/anvil/versions.just
index 564a3b90..0a4aeaa1 100644
--- a/justfiles/anvil/versions.just
+++ b/justfiles/anvil/versions.just
@@ -56,16 +56,16 @@ cargo_careful_version := "0.4.10"
cargo_check_external_types_version := "0.5.0"
cargo_coverage_gate_version := "0.2.0"
cargo_delta_version := "0.3.1"
-cargo_deny_version := "0.19.8"
-cargo_doc2readme_version := "0.7.2"
+cargo_deny_version := "0.20.2"
+cargo_doc2readme_version := "0.7.3"
cargo_ensure_no_cyclic_deps_version := "0.2.0"
cargo_ensure_no_default_features_version := "1.1.0"
cargo_hack_version := "0.6.45"
cargo_heather_version := "0.2.1"
-cargo_llvm_cov_version := "0.8.7"
-cargo_mutants_version := "27.0.0"
-cargo_nextest_version := "0.9.137"
-cargo_semver_checks_version := "0.49.0"
+cargo_llvm_cov_version := "0.9.0"
+cargo_mutants_version := "27.1.0"
+cargo_nextest_version := "0.9.143"
+cargo_semver_checks_version := "0.50.0"
cargo_sort_version := "2.1.4"
cargo_spellcheck_version := "0.15.7"
cargo_udeps_version := "0.1.61"
diff --git a/justfiles/basic.just b/justfiles/basic.just
index 424fa08a..f65219c6 100644
--- a/justfiles/basic.just
+++ b/justfiles/basic.just
@@ -98,12 +98,10 @@ miri-example EXAMPLE_NAME:
}
# Update README.md files for all packages.
-readme:
- cargo workspaces exec --ignore-private cargo doc2readme --lib --template ../README.j2
+readme: (_anvil-readme "generate")
# Check that README.md files for all packages are up to date.
-readme-check:
- cargo workspaces exec --ignore-private cargo doc2readme --check --lib --template ../README.j2
+readme-check: (_anvil-readme "check")
# Runs unit and integration tests.
[script]
diff --git a/justfiles/setup.just b/justfiles/setup.just
index 11b424a2..df084b79 100644
--- a/justfiles/setup.just
+++ b/justfiles/setup.just
@@ -32,7 +32,6 @@ install-tools:
cargo-semver-checks@$env:CARGO_SEMVER_CHECKS_VERSION `
cargo-sort@$env:CARGO_SORT_VERSION `
cargo-udeps@$env:CARGO_UDEPS_VERSION `
- cargo-workspaces@$env:CARGO_WORKSPACES_VERSION `
--locked
# This tool is not well maintained and fails to actually build if using locked dependencies. Okay then.