Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .anvil.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version = 1
tool = "anvil"
tool_version = "0.7.0"
catalog_checksum = "sha256:7ab5d33f1eb49c1f7982d6623079488505a838d163955afc0f7fff782b0a4cf5"
catalog_checksum = "sha256:545245661e6d0b3edb1411ec48d0c797f5ec9eda4cf248f11828b0cb51a40f19"

[[file]]
path = ".anvil/container/Dockerfile.dockerignore"
Expand Down Expand Up @@ -109,7 +109,7 @@ checksum = "sha256:7e67d90a9d8bd8cd5c8adb68baf41fdd4625bff4d425b5bf18b75baee6289

[[file]]
path = "justfiles/anvil/checks/fmt.just"
checksum = "sha256:0434e1a8720453a4bc635ea56af1d19875ac79e6fdd76349052cd57e70392eb8"
checksum = "sha256:4f258afb70f8186c270f28958b2376cb13199dd5b2897588312d841fbffe88be"

[[file]]
path = "justfiles/anvil/checks/license-headers.just"
Expand Down
19 changes: 17 additions & 2 deletions crates/cargo-anvil/src/anvil/artifacts/justfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,21 @@ mod tests {
fn checks_do_not_invoke_an_implicit_default_cargo() {
for (path, body) in CHECK_FILES {
let caches_stable_args = body.contains("$stableArgs = {{_anvil_stable_toolchain_args}}");
for line in body.lines().map(str::trim) {
// A recipe may pin RUSTUP_TOOLCHAIN for its own process instead of
// writing `+toolchain` at each site. That is the stronger selection:
// the rustup shim consults it for every descendant, which is what a
// dispatcher like `cargo fmt` needs.
let mut pins_toolchain_environment = false;
for raw in body.lines() {
let line = raw.trim();
// An unindented line starts a new recipe, so one recipe's pin
// cannot license an unpinned cargo in the next.
if !raw.starts_with([' ', '\t']) && !line.is_empty() {
pins_toolchain_environment = false;
}
if line.starts_with("$env:RUSTUP_TOOLCHAIN = '{{") {
pins_toolchain_environment = true;
}
let invokes_cargo = line.starts_with("cargo ")
|| line.starts_with("& cargo ")
|| line.contains("= cargo ")
Expand All @@ -799,7 +813,8 @@ mod tests {
.map(|(_, arguments)| arguments.trim_start())
.expect("invokes_cargo patterns always include 'cargo '");
assert!(
toolchain.starts_with("'+")
pins_toolchain_environment
|| toolchain.starts_with("'+")
|| toolchain.starts_with("\"+")
|| toolchain.starts_with("{{_anvil_stable_toolchain_args}}")
|| (caches_stable_args && toolchain.starts_with("@stableArgs")),
Expand Down
12 changes: 11 additions & 1 deletion crates/cargo-anvil/templates/justfiles/anvil/checks/fmt.just
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,24 @@
# Iterate workspace members rather than every local path dependency that
# `cargo fmt --all` discovers. `--keep-going` reports formatting failures from
# every member while retaining a bounded rustfmt command line per invocation.
#
# Select the nightly through RUSTUP_TOOLCHAIN, not `+toolchain`. `cargo fmt`
# only dispatches: cargo-fmt runs `rustfmt` as a child process through the
# rustup shim, which reads RUSTUP_TOOLCHAIN and inherits it from any outer
# `+`-selected cargo. So an inner `cargo +nightly fmt` still ran STABLE
# rustfmt, which downgrades every unstable option above to a warning and exits
# 0 -- a green check enforcing nothing. The `cargo each` wrapper is left
# unpinned for the same reason: that pin is what leaks, and the wrapper only
# enumerates members, it compiles nothing.

# Check Rust source formatting.
[script("pwsh", "-NoProfile")]
anvil-fmt: anvil-fmt-validate-prereqs anvil-impact
$ErrorActionPreference = 'Stop'
$include = (& "{{ just_executable() }}" _anvil-impact-include modified)
if ($include -eq '--skip') { Write-Host 'anvil-fmt: no modified packages; skipping'; exit 0 }
cargo {{_anvil_stable_toolchain_args}} each --workspace --keep-going '--' cargo '+{{ rust_nightly }}' fmt --manifest-path '{manifest}' --check
$env:RUSTUP_TOOLCHAIN = '{{ rust_nightly }}'
cargo each --workspace --keep-going '--' cargo fmt --manifest-path '{manifest}' --check
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

# Per-check setup + validate-prereqs
Expand Down
56 changes: 48 additions & 8 deletions crates/cargo-anvil/tests/recipe_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,8 +908,17 @@ fn public_api_checks_fail_when_metadata_discovery_fails() {
}
}

/// `cargo fmt` only dispatches: cargo-fmt runs `rustfmt` as a child process through
/// the rustup shim, which reads `RUSTUP_TOOLCHAIN` and inherits it from any outer
/// `+`-selected cargo. So the recipe pins that variable rather than writing
/// `+toolchain`, and has to hold against a hostile ambient selection.
#[test]
fn fmt_delegates_workspace_iteration_to_cargo_each() {
fn fmt_runs_the_pinned_nightly_over_workspace_members() {
assert!(
!FMT.contains("_anvil_stable_toolchain_args"),
"a stable-pinned outer cargo exports its selection into cargo-each's children, \
which is exactly what reaches rustfmt"
);
if !tools_available() {
return;
}
Expand All @@ -923,20 +932,51 @@ fn fmt_delegates_workspace_iteration_to_cargo_each() {
"anvil-impact",
],
);
let log = tmp.path().join("cargo.log");
let output = run_just(tmp.path(), &["anvil-fmt"], &[("FAKE_CARGO_LOG", log.as_os_str())]);
let args_log = tmp.path().join("cargo-args.log");
let toolchain_log = tmp.path().join("cargo-toolchain.log");
let output = run_just(
tmp.path(),
&["anvil-fmt"],
&[
("FAKE_CARGO_LOG", args_log.as_os_str()),
("FAKE_CARGO_TOOLCHAIN_LOG", toolchain_log.as_os_str()),
("RUSTUP_TOOLCHAIN", OsStr::new("test-stable")),
],
);
assert!(
output.status.success(),
"per-package formatting failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let commands = fs::read_to_string(&log).unwrap();
let invocations = fs::read_to_string(&args_log).unwrap();
assert!(
commands.contains("each --workspace --keep-going -- cargo +nightly-test fmt --manifest-path {manifest} --check"),
"unexpected cargo invocation: {commands}"
);
assert!(!commands.contains("fmt --all"));
invocations.contains("each --workspace --keep-going -- cargo fmt --manifest-path {manifest} --check"),
"unexpected cargo invocation: {invocations}"
);
assert!(!invocations.contains("fmt --all"));
// The fake cargo appends one line to each log per invocation, so the logs
// are positionally aligned and the formatting command can be identified by
// its arguments.
let selections = fs::read_to_string(&toolchain_log).unwrap();
let formatting: Vec<_> = invocations
.lines()
.zip(selections.lines())
.filter(|(arguments, _)| arguments.contains("fmt"))
.collect();
assert!(
!formatting.is_empty(),
"the fmt recipe must reach cargo so its toolchain selection is observable:\n{invocations}"
);
for (arguments, selection) in formatting {
assert_eq!(
selection.trim(),
"nightly-test",
"`cargo {arguments}` inherited '{selection}' instead of the pinned nightly, so stable \
rustfmt would downgrade every unstable rustfmt.toml option to a warning and the check \
would pass without enforcing any of them"
);
}
}

#[test]
Expand Down
12 changes: 11 additions & 1 deletion crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2263,14 +2263,24 @@ anvil-external-types-validate-prereqs: anvil-toolchain-nightly-external-types-va
# Iterate workspace members rather than every local path dependency that
# `cargo fmt --all` discovers. `--keep-going` reports formatting failures from
# every member while retaining a bounded rustfmt command line per invocation.
#
# Select the nightly through RUSTUP_TOOLCHAIN, not `+toolchain`. `cargo fmt`
# only dispatches: cargo-fmt runs `rustfmt` as a child process through the
# rustup shim, which reads RUSTUP_TOOLCHAIN and inherits it from any outer
# `+`-selected cargo. So an inner `cargo +nightly fmt` still ran STABLE
# rustfmt, which downgrades every unstable option above to a warning and exits
# 0 -- a green check enforcing nothing. The `cargo each` wrapper is left
# unpinned for the same reason: that pin is what leaks, and the wrapper only
# enumerates members, it compiles nothing.

# Check Rust source formatting.
[script("pwsh", "-NoProfile")]
anvil-fmt: anvil-fmt-validate-prereqs anvil-impact
$ErrorActionPreference = 'Stop'
$include = (& "{{ just_executable() }}" _anvil-impact-include modified)
if ($include -eq '--skip') { Write-Host 'anvil-fmt: no modified packages; skipping'; exit 0 }
cargo {{_anvil_stable_toolchain_args}} each --workspace --keep-going '--' cargo '+{{ rust_nightly }}' fmt --manifest-path '{manifest}' --check
$env:RUSTUP_TOOLCHAIN = '{{ rust_nightly }}'
cargo each --workspace --keep-going '--' cargo fmt --manifest-path '{manifest}' --check
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

# Per-check setup + validate-prereqs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2351,14 +2351,24 @@ anvil-external-types-validate-prereqs: anvil-toolchain-nightly-external-types-va
# Iterate workspace members rather than every local path dependency that
# `cargo fmt --all` discovers. `--keep-going` reports formatting failures from
# every member while retaining a bounded rustfmt command line per invocation.
#
# Select the nightly through RUSTUP_TOOLCHAIN, not `+toolchain`. `cargo fmt`
# only dispatches: cargo-fmt runs `rustfmt` as a child process through the
# rustup shim, which reads RUSTUP_TOOLCHAIN and inherits it from any outer
# `+`-selected cargo. So an inner `cargo +nightly fmt` still ran STABLE
# rustfmt, which downgrades every unstable option above to a warning and exits
# 0 -- a green check enforcing nothing. The `cargo each` wrapper is left
# unpinned for the same reason: that pin is what leaks, and the wrapper only
# enumerates members, it compiles nothing.

# Check Rust source formatting.
[script("pwsh", "-NoProfile")]
anvil-fmt: anvil-fmt-validate-prereqs anvil-impact
$ErrorActionPreference = 'Stop'
$include = (& "{{ just_executable() }}" _anvil-impact-include modified)
if ($include -eq '--skip') { Write-Host 'anvil-fmt: no modified packages; skipping'; exit 0 }
cargo {{_anvil_stable_toolchain_args}} each --workspace --keep-going '--' cargo '+{{ rust_nightly }}' fmt --manifest-path '{manifest}' --check
$env:RUSTUP_TOOLCHAIN = '{{ rust_nightly }}'
cargo each --workspace --keep-going '--' cargo fmt --manifest-path '{manifest}' --check
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

# Per-check setup + validate-prereqs
Expand Down
12 changes: 11 additions & 1 deletion crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1084,14 +1084,24 @@ anvil-external-types-validate-prereqs: anvil-toolchain-nightly-external-types-va
# Iterate workspace members rather than every local path dependency that
# `cargo fmt --all` discovers. `--keep-going` reports formatting failures from
# every member while retaining a bounded rustfmt command line per invocation.
#
# Select the nightly through RUSTUP_TOOLCHAIN, not `+toolchain`. `cargo fmt`
# only dispatches: cargo-fmt runs `rustfmt` as a child process through the
# rustup shim, which reads RUSTUP_TOOLCHAIN and inherits it from any outer
# `+`-selected cargo. So an inner `cargo +nightly fmt` still ran STABLE
# rustfmt, which downgrades every unstable option above to a warning and exits
# 0 -- a green check enforcing nothing. The `cargo each` wrapper is left
# unpinned for the same reason: that pin is what leaks, and the wrapper only
# enumerates members, it compiles nothing.

# Check Rust source formatting.
[script("pwsh", "-NoProfile")]
anvil-fmt: anvil-fmt-validate-prereqs anvil-impact
$ErrorActionPreference = 'Stop'
$include = (& "{{ just_executable() }}" _anvil-impact-include modified)
if ($include -eq '--skip') { Write-Host 'anvil-fmt: no modified packages; skipping'; exit 0 }
cargo {{_anvil_stable_toolchain_args}} each --workspace --keep-going '--' cargo '+{{ rust_nightly }}' fmt --manifest-path '{manifest}' --check
$env:RUSTUP_TOOLCHAIN = '{{ rust_nightly }}'
cargo each --workspace --keep-going '--' cargo fmt --manifest-path '{manifest}' --check
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

# Per-check setup + validate-prereqs
Expand Down
12 changes: 11 additions & 1 deletion justfiles/anvil/checks/fmt.just
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,24 @@
# Iterate workspace members rather than every local path dependency that
# `cargo fmt --all` discovers. `--keep-going` reports formatting failures from
# every member while retaining a bounded rustfmt command line per invocation.
#
# Select the nightly through RUSTUP_TOOLCHAIN, not `+toolchain`. `cargo fmt`
# only dispatches: cargo-fmt runs `rustfmt` as a child process through the
# rustup shim, which reads RUSTUP_TOOLCHAIN and inherits it from any outer
# `+`-selected cargo. So an inner `cargo +nightly fmt` still ran STABLE
# rustfmt, which downgrades every unstable option above to a warning and exits
# 0 -- a green check enforcing nothing. The `cargo each` wrapper is left
# unpinned for the same reason: that pin is what leaks, and the wrapper only
# enumerates members, it compiles nothing.

# Check Rust source formatting.
[script("pwsh", "-NoProfile")]
anvil-fmt: anvil-fmt-validate-prereqs anvil-impact
$ErrorActionPreference = 'Stop'
$include = (& "{{ just_executable() }}" _anvil-impact-include modified)
if ($include -eq '--skip') { Write-Host 'anvil-fmt: no modified packages; skipping'; exit 0 }
cargo {{_anvil_stable_toolchain_args}} each --workspace --keep-going '--' cargo '+{{ rust_nightly }}' fmt --manifest-path '{manifest}' --check
$env:RUSTUP_TOOLCHAIN = '{{ rust_nightly }}'
cargo each --workspace --keep-going '--' cargo fmt --manifest-path '{manifest}' --check
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

# Per-check setup + validate-prereqs
Expand Down
Loading