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
2 changes: 1 addition & 1 deletion .anvil.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version = 1
tool = "anvil"
tool_version = "0.8.0"
catalog_checksum = "sha256:340049d773da5264378aca52285405950b2a317fdf2ca2302e8650b591354e8c"
catalog_checksum = "sha256:c62ab972b9e416813d2f56764e4b7a2fa72850dc9653fbd53feeb6f6970644f4"

[[file]]
path = ".anvil/container/Dockerfile.dockerignore"
Expand Down
19 changes: 11 additions & 8 deletions crates/cargo-anvil/docs/design/ado.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,13 +609,13 @@ threading pre-formatted strings the local run never produces. The chain:
by a 1ESPT `job.yml`).
3. **The group step template** runs `just anvil-<group>` with an impact mode fixed **by
group class at emit time** (never probed from a file). PR groups — which always download the
artifact — export `ANVIL_IMPACT=consume`. In consume mode `anvil-impact` is a pure
artifact — set `ANVIL_IMPACT=consume` in the step environment. In consume mode `anvil-impact` is a pure
no-op — it trusts the downloaded cache verbatim and **neither snapshots nor
recomputes**, so it needs neither cargo-delta nor a fetched base ref. Each scoped
check reads its category's scope from the cache file via `_anvil-impact-include` (into a
local `$include` variable).
4. **Scheduled stages download nothing** and always validate the full workspace, so the
group step exports `ANVIL_IMPACT=off`. Like the PR `consume`, this is fixed by group
group step sets `ANVIL_IMPACT=off` in the step environment. Like the PR `consume`, this is fixed by group
class at emit time and is **not** derived from `target/anvil/impact/impact.state`: the
mode is a property of the group class, not something probed at runtime. (`setup.yml` no
longer caches `target/` at all, so the durable `impact.state` never travels through the
Expand Down Expand Up @@ -681,15 +681,16 @@ steps:
catch { Write-Error "Could not resolve PR title for PR $prId"; exit 1 }
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
- script: just anvil-pr-fast
- pwsh: just anvil-pr-fast
displayName: anvil pr-fast
env:
PR_TITLE: $(PR_TITLE)
# Scope comes from the downloaded target/anvil/impact cache: a PR group
# always downloads the artifact, so it exports ANVIL_IMPACT=consume (trust
# always downloads the artifact, so it sets ANVIL_IMPACT to consume (trust
# the cache; no snapshot/cargo-delta/base ref), whereas a scheduled group
# exports ANVIL_IMPACT=off. The mode is fixed by group class at emit time -- never
# sets ANVIL_IMPACT to off. The mode is fixed by group class at emit time -- never
# probed from the cacheable impact.state marker. See §4.3.
Comment thread
st-dev-gh marked this conversation as resolved.
ANVIL_IMPACT: "consume"
```

The generated per-group step templates take no parameters. Only `pr-fast.yml`
Expand Down Expand Up @@ -734,8 +735,10 @@ takes a single `group` parameter that controls which recipes run:
`pr-fast` matrix leg never installs cargo-mutants.

The template expects the caller to provide the Rust/rustup bootstrap and
`cargo` on PATH (see §6). ADO uses the default `install` backend (source
builds) so adopters do not need to approve a binary-installation service.
`cargo` on PATH (see §6). Command steps use `pwsh`, which is already required
by the template's toolchain fingerprinting step, so Windows agents do not need
Bash. ADO uses the default `install` backend (source builds) so adopters do not
need to approve a binary-installation service.

`impact.yml` invokes `setup.yml` with `group: none`, then installs `cargo-delta`
via `anvil-tool-cargo-delta-install` and runs the shared **`just anvil-impact`**
Expand Down Expand Up @@ -900,7 +903,7 @@ files into upserts/deletions of a sticky PR comment via the Azure DevOps REST AP
path is the supported way).

The wiring lives in the `pr_fast` stage of `pr-stages.yml`, as a pwsh step that runs
on the canonical Linux leg after the `pr-fast` group's `bash: just anvil-pr-fast`
on the canonical Linux leg after the `pr-fast` group's `pwsh: just anvil-pr-fast`
step:

```yaml
Expand Down
23 changes: 17 additions & 6 deletions crates/cargo-anvil/src/anvil/artifacts/ado.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,13 @@ mod tests {

#[test]
fn setup_step_quotes_inline_command_values_containing_colons() {
// An inline `- bash: echo "x: y"` is a YAML *plain scalar*; the inner
// An inline `- pwsh: Write-Host "x: y"` is a YAML *plain scalar*; the inner
// `: ` is parsed as a mapping separator ("Mapping values are not
// allowed in this context"), which ADO rejects at compile time. Such
// values must be wrapped in quotes. Guard every inline command scalar
// in the setup step (and catch the specific group=none echo).
assert!(
SETUP_STEP.contains(r#"- bash: 'echo "anvil-setup: group=none, skipping tool install"'"#),
SETUP_STEP.contains(r#"- pwsh: 'Write-Host "anvil-setup: group=none, skipping tool install"'"#),
"the group=none echo must be single-quoted so its colon stays literal",
);
for line in SETUP_STEP.lines() {
Expand All @@ -289,6 +289,17 @@ mod tests {
}
}

#[test]
fn ado_artifacts_do_not_require_bash() {
for artifact in all() {
let has_bash_step = artifact.body().lines().any(|line| {
let line = line.trim_start();
line.starts_with("- bash:") || line.starts_with("- task: Bash@")
});
assert!(!has_bash_step, "ADO artifact contains a Bash step:\n{}", artifact.body());
}
}
Comment thread
st-dev-gh marked this conversation as resolved.

#[test]
fn group_step_passes_group_to_setup() {
let body = render_group_step("pr-fast");
Expand Down Expand Up @@ -385,9 +396,9 @@ mod tests {
);
// A PR group always downloads the artifact, so it consumes it. The mode
// is fixed by tier -- not probed at runtime from a marker file.
assert!(body.contains("export ANVIL_IMPACT=consume"));
assert!(body.contains(r#"ANVIL_IMPACT: "consume""#));
assert!(
!body.contains("ANVIL_IMPACT=off"),
!body.contains(r#"ANVIL_IMPACT: "off""#),
"a PR group must not fall back to off (it always has the artifact)"
);
assert!(
Expand Down Expand Up @@ -417,9 +428,9 @@ mod tests {
// target/anvil/impact/impact.state, so no leftover state on the agent
// can wrongly enable scoping and skip the full-workspace backstop.
let body = render_group_step("scheduled-test");
assert!(body.contains("export ANVIL_IMPACT=off"));
assert!(body.contains(r#"ANVIL_IMPACT: "off""#));
assert!(
!body.contains("ANVIL_IMPACT=consume"),
!body.contains(r#"ANVIL_IMPACT: "consume""#),
"scheduled group must never consume the impact cache"
);
assert!(
Expand Down
14 changes: 6 additions & 8 deletions crates/cargo-anvil/templates/ado/steps/group.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,22 @@
# target/anvil/impact/ (via job.yml's inputArtifacts) and the scoped checks
# read that cache via their `anvil-impact` dependency -- the same code path as
# a local run. Scheduled group jobs download nothing and run full-workspace
# (see the bash step).
# (see the group step).
steps:
- template: setup.yml
parameters:
group: __GROUP__
__PR_TITLE_STEP__
- bash: |
set -euo pipefail
- pwsh: just anvil-__GROUP__
Comment thread
st-dev-gh marked this conversation as resolved.
displayName: anvil-__GROUP__
env:
__PR_TITLE_ENV__
# The impact mode is fixed by group class at emit time -- never probed from a
# marker file. PR group jobs downloaded the target/anvil/impact artifact
# and trust it verbatim (consume: anvil-impact no-ops -- no snapshot,
# cargo-delta, or base ref); scheduled group jobs force off so every
# tier runs full-workspace.
export ANVIL_IMPACT=__IMPACT_MODE__
just anvil-__GROUP__
displayName: anvil-__GROUP__
env:
__PR_TITLE_ENV__
ANVIL_IMPACT: "__IMPACT_MODE__"
# Disable cargo incremental compilation in CI: the incremental dir is
# not part of the anvil-setup cache (see setup.yml), so generating it is
# pure cost with no cross-run benefit. Mirrors the GitHub impl workflows'
Expand Down
4 changes: 2 additions & 2 deletions crates/cargo-anvil/templates/ado/steps/impact.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ steps:
- template: setup.yml
parameters:
group: none
- bash: just anvil-tool-cargo-delta-install install
- pwsh: just anvil-tool-cargo-delta-install install
displayName: anvil impact (install cargo-delta)
- bash: just anvil-impact
- pwsh: just anvil-impact
Comment thread
st-dev-gh marked this conversation as resolved.
# Run the shared anvil-impact recipe -- the same impact building block
# adopters run locally. It resolves the base ref (_anvil-base-ref),
# snapshots the base ref in a throwaway worktree and the working
Expand Down
6 changes: 3 additions & 3 deletions crates/cargo-anvil/templates/ado/steps/setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ steps:
# group="none" -> skip; caller installs what it needs
# group=<x> -> only that group's prerequisites
- ${{ if eq(parameters.group, 'none') }}:
- bash: 'echo "anvil-setup: group=none, skipping tool install"'
- pwsh: 'Write-Host "anvil-setup: group=none, skipping tool install"'
displayName: anvil setup (group=none -- skip tool install)
- ${{ elseif eq(parameters.group, '') }}:
- bash: just anvil-setup
- pwsh: just anvil-setup
displayName: anvil setup (install full catalog)
- ${{ else }}:
- bash: just anvil-${{ parameters.group }}-setup
- pwsh: just anvil-${{ parameters.group }}-setup
displayName: anvil setup (install ${{ parameters.group }} prerequisites)
Comment thread
st-dev-gh marked this conversation as resolved.
Loading
Loading