Skip to content
Closed
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
330 changes: 250 additions & 80 deletions .anvil.lock

Large diffs are not rendered by default.

112 changes: 112 additions & 0 deletions .anvil/container/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# syntax=docker/dockerfile:1

# >>> anvil-managed: anvil-container-base-image
# Prebuilt binaries installed by `anvil-setup binstall` link against this
# image's glibc, so it tracks the Linux runner the generated workflows use.
# Digest-pinned: a floating tag moves content under a reference that claims to
# name fixed content.
#
# Re-declare BASE_IMAGE in the gap below to build on another base; a later ARG
# wins, and the pins anvil maintains stay current.
ARG BASE_IMAGE=docker.io/library/ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea
# <<< anvil-managed: anvil-container-base-image

# >>> anvil-managed: anvil-container-base
FROM ${BASE_IMAGE}

ARG JUST_VERSION=1.56.0
ARG JUST_SHA256=fa2a8ec1015d9df5330941ade12437488fc40d33f9c9f8cd4eb70a26de11b639
ARG POWERSHELL_VERSION=7.6.3
ARG POWERSHELL_SHA256=856d0765d2332377f9d7a4aea76efdfde4de51446e7738dde2dfda41dba9e2a7
ARG RUSTUP_VERSION=1.29.0
ARG RUSTUP_SHA256=4acc9acc76d5079515b46346a485974457b5a79893cfb01112423c89aeb5aa10
ARG CARGO_BINSTALL_VERSION=1.21.1
ARG CARGO_BINSTALL_SHA256=630c8f8803a686aa6779497f0f0fb51d49822fb5fc3c514d8ced33b34e338e6e

ENV DEBIAN_FRONTEND=noninteractive \
CARGO_HOME=/usr/local/cargo \
RUSTUP_HOME=/usr/local/rustup \
RUSTUP_NO_UPDATE_CHECK=1 \
PATH=/usr/local/cargo/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# <<< anvil-managed: anvil-container-base

# >>> anvil-managed: anvil-container-tools
# clang/libclang are required by cargo-spellcheck; the rest is the usual Rust
# link-time set. A bare base has no C runtime development files, so every link
# step fails without build-essential.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential ca-certificates clang libclang-dev curl git libicu-dev \
libssl-dev pkg-config tar \
&& rm -rf /var/lib/apt/lists/*

# pwsh is not optional: every generated anvil recipe is a `script("pwsh",
# "-NoProfile")` recipe.
RUN curl -fsSLo /tmp/powershell.tar.gz \
"https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell-${POWERSHELL_VERSION}-linux-x64.tar.gz" \
&& echo "${POWERSHELL_SHA256} /tmp/powershell.tar.gz" | sha256sum -c - \
&& mkdir -p /opt/microsoft/powershell/7 \
&& tar -xzf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7 \
&& chmod 755 /opt/microsoft/powershell/7/pwsh \
&& ln -s /opt/microsoft/powershell/7/pwsh /usr/local/bin/pwsh \
&& rm /tmp/powershell.tar.gz

RUN curl -fsSLo /tmp/just.tar.gz \
"https://github.com/casey/just/releases/download/${JUST_VERSION}/just-${JUST_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
&& echo "${JUST_SHA256} /tmp/just.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/just.tar.gz -C /usr/local/bin just \
&& chmod 755 /usr/local/bin/just \
&& rm /tmp/just.tar.gz

RUN curl --proto '=https' --tlsv1.2 -fsSLo /tmp/rustup-init \
"https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/x86_64-unknown-linux-gnu/rustup-init" \
&& echo "${RUSTUP_SHA256} /tmp/rustup-init" | sha256sum -c - \
&& chmod 755 /tmp/rustup-init \
&& /tmp/rustup-init -y --profile minimal --default-toolchain none --no-modify-path \
&& rm /tmp/rustup-init

RUN curl -fsSLo /tmp/cargo-binstall.tgz \
"https://github.com/cargo-bins/cargo-binstall/releases/download/v${CARGO_BINSTALL_VERSION}/cargo-binstall-x86_64-unknown-linux-musl.tgz" \
&& echo "${CARGO_BINSTALL_SHA256} /tmp/cargo-binstall.tgz" | sha256sum -c - \
&& mkdir -p "${CARGO_HOME}/bin" \
&& tar -xzf /tmp/cargo-binstall.tgz -C "${CARGO_HOME}/bin" cargo-binstall \
&& chmod 755 "${CARGO_HOME}/bin/cargo-binstall" \
&& rm /tmp/cargo-binstall.tgz
# <<< anvil-managed: anvil-container-tools

# >>> anvil-managed: anvil-container-setup
# The whole recipe tree is copied because `just` parses it to reach the install
# recipes.
#
# The credential files are removed in the same layer that used them: a build
# secret never lands in a layer, but anything the install *writes* with it is
# ordinary content, and the `chmod` below would publish it world-readable. A
# later `RUN` cannot undo that, because the earlier layer keeps them.
#
# `HOME` is not set by this image and `docker build` does not set it either, so
# `${HOME}/.netrc` would expand to `/.netrc` and leave the file root actually
# uses. Both spellings are named.
#
# `registry` and `git` must exist before the `chmod`. The run mounts a named
# volume over each, and an engine seeds a new volume from the image path it
# covers; a path that does not exist seeds as root-owned 0755, which the
# `--user` mapping cannot write, so the first cargo fetch fails with EACCES.
WORKDIR /opt/anvil
COPY justfiles ./justfiles
COPY rust-toolchain.toml ./
RUN printf "import 'justfiles/anvil/mod.just'\n" > Justfile \
&& just anvil-setup binstall \
&& rm -rf "${CARGO_HOME}/registry/cache" "${CARGO_HOME}/registry/src" \
&& rm -f "${CARGO_HOME}/credentials" "${CARGO_HOME}/credentials.toml" "${HOME:-/root}/.netrc" /root/.netrc \
&& mkdir -p "${CARGO_HOME}/registry" "${CARGO_HOME}/git" \
&& chmod -R a+rwX "${CARGO_HOME}" "${RUSTUP_HOME}"
# <<< anvil-managed: anvil-container-setup

# >>> anvil-managed: anvil-container-entry
# Consumed by `anvil-container` itself: a nested invocation from inside the
# image runs the recipe natively instead of launching another container.
ENV ANVIL_IN_CONTAINER=1

WORKDIR /workspace
CMD ["bash"]
# <<< anvil-managed: anvil-container-entry
37 changes: 37 additions & 0 deletions .anvil/container/Dockerfile.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# GENERATED BY cargo-anvil. DO NOT EDIT DIRECTLY.
# Update the corresponding template in the cargo-anvil crate.
#
# BuildKit reads `<dockerfile>.dockerignore` in preference to a root
# `.dockerignore`, so this scopes the exec-image build context without the
# repository having to own a root ignore file or having one silently overridden.
#
# The build context is the repository root but the image only needs two things.
# Excluding everything else keeps a cold build from streaming the whole
# worktree (and every stale `target/`) to the daemon.
#
# The context is narrowed to `justfiles/anvil/` rather than all of `justfiles/`
# so that a cold build does not stream unrelated trees to the daemon. The
# recipes are copied to drive `just anvil-setup`, which needs the whole tree to
# parse, and the whole tree is hashed into the image tag: the tier, group and
# check recipes decide which tools `anvil-setup` reaches, not just the catalog.
#
# `.anvil/container/` is admitted because the Dockerfile is composed: the gaps
# between anvil's regions exist for a repository to add its own instructions,
# and the headline case -- `COPY`ing a corporate root CA in before the first
# download -- needs the file to be in the context. Denying it would leave the
# gap documented but unusable for anything but `RUN`. It is also the directory
# the image tag digests, so what the context admits and what the tag covers stay
# the same set -- including the `.anvil-proposed` siblings both exclude, which
# are anvil's review artifacts rather than build inputs.
*
!justfiles
justfiles/*
!justfiles/anvil
justfiles/anvil/**/*.anvil-proposed
!.anvil
.anvil/*
!.anvil/container
.anvil/container/**/*.anvil-proposed
!rust-toolchain.toml
14 changes: 3 additions & 11 deletions .delta.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# >>> anvil-managed: anvil-delta
# <<< anvil-managed: anvil-delta

# cargo-delta configuration file

# Patterns for files and folders to exclude from analysis.
Expand Down Expand Up @@ -82,14 +85,3 @@ assume_patterns = [
# The remote branch to compare against for determining changed files
# If not specified, uses the default branch detection
remote_branch = "origin/main"

# >>> anvil-managed: anvil-delta
[delta]
# Include the workspace root files that should invalidate every member's
# impact analysis when changed (lockfile, root manifest, toolchain).
root-files = [
"Cargo.lock",
"Cargo.toml",
"rust-toolchain.toml",
]
# <<< anvil-managed: anvil-delta
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@
# Force LF line endings for Rust sources regardless of the checkout
# platform, so rustfmt and other tooling see consistent newlines.
*.rs text eol=lf
*.sh text eol=lf
# <<< anvil-managed: anvil-gitattributes
140 changes: 42 additions & 98 deletions .github/actions/anvil-impact/action.yml
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Managed by cargo-anvil. Update the corresponding template in the cargo-anvil
# crate unless the change is repository-specific.
# GENERATED BY cargo-anvil. DO NOT EDIT DIRECTLY.
# Update cargo-anvil and regenerate; repository-specific edits stop automatic updates.
# Update behaviour: https://github.com/microsoft/ox-tools/blob/main/crates/cargo-anvil/docs/design/updates.md
name: anvil-impact
description: |
Compute the cargo-delta impact set for this PR and emit per-tier
include lists.
Compute the cargo-delta impact set for this PR and publish it as the
`anvil-impact-<runner.os>` workflow artifact.

Outputs:
include_modified - "--package X@ver --package Y@ver" string for crates
whose source files changed in the diff, or "--skip"
if the modified set is empty. Each package is a
version-qualified cargo spec (name@version) so it
resolves unambiguously even when a like-named crate
is also a different-versioned transitive dependency.
include_affected - same shape, for crates in the affected set
(modified βˆͺ rev-deps).
include_required - same shape, for crates in the required set
(affected βˆͺ workspace-internal transitive deps).
This runs the same `anvil-impact` recipe adopters run locally: it snapshots
the base ref and the working tree, runs `cargo delta impact`, and
writes the durable cache under `target/anvil/impact/` (the per-tier
`include_<tier>.txt` lists, `impact.json`, and the `snapshots/`). The whole
directory is uploaded so each downstream group job can download it and read
the cache exactly as a local run does -- rather than threading the include
lists through job outputs / environment variables. This keeps CI and local
execution identical by construction.

Recipes in checks.just interpret each variable per their tier:
modified-tier recipes (fmt, license-headers, spellcheck, ...) short-
circuit on "--skip"; affected-tier recipes (clippy, tests, ...) and
required-tier recipes (doc, cargo-hack, udeps) splice their include
list into the cargo invocation, defaulting to --workspace when unset
(local runs without impact wiring).

Unscoped recipes (deny, audit, aprz, pr-title) ignore all three
variables and always run unconditionally.
outputs:
include_modified:
description: Pre-formatted --package args for the modified tier.
value: ${{ steps.compute.outputs.include_modified }}
include_affected:
description: Pre-formatted --package args for the affected tier.
value: ${{ steps.compute.outputs.include_affected }}
include_required:
description: Pre-formatted --package args for the required tier.
value: ${{ steps.compute.outputs.include_required }}
Computed once per OS family (see anvil-pr-impl.yml) because an
OS-conditional dependency changes the reverse-dep set only in that host's
cargo-metadata graph.
runs:
using: composite
steps:
Expand All @@ -53,68 +34,31 @@ runs:
- name: Install cargo-delta
shell: bash
run: just anvil-tool-cargo-delta-install binstall
- id: compute
name: Compute impact
- name: Compute impact
shell: bash
run: |
set -euo pipefail
# Resolve the baseline ref via the shared anvil resolver (single
# source of truth; see the _anvil-base-ref recipe). On GitHub PRs it
# resolves GITHUB_BASE_REF to origin/<branch>; BASE_REF overrides.
base="$(just _anvil-base-ref)"
# The baseline worktree is checked out at the merge target, whose
# rust-toolchain.toml may pin a toolchain that is NOT installed on the
# runner (any PR that bumps rust-toolchain.toml hits this). The
# snapshot is metadata-only (file presence + content hashes), so run
# the baseline under the runner's *active* toolchain via
# RUSTUP_TOOLCHAIN, which overrides the worktree's rust-toolchain.toml.
# Capture it here, in the PR checkout, where it is resolved + installed.
active_toolchain="$(rustup show active-toolchain | head -n1 | cut -d' ' -f1)"
# cargo delta has no --base flag; the flow is two snapshots
# (baseline at the merge target + current at HEAD) compared by
# `cargo delta impact`. We use a temporary worktree to snapshot
# the baseline without disturbing the checked-out tree.
cargo delta snapshot > "$RUNNER_TEMP/anvil-current.json"
git worktree add --detach "$RUNNER_TEMP/anvil-baseline" "$base"
# When anvil is first introduced (this PR adds the cargo workspace),
# the baseline branch has no root Cargo.toml, so there is no
# workspace for cargo-delta to snapshot. Detect that explicitly and
# fall back to full-workspace validation (empty include_* -> recipes
# default to --workspace) rather than running cargo-delta. This is a
# targeted check: any *other* cargo-delta failure below is a real
# error and is allowed to fail the impact job (and the gated pr-* jobs).
if [ ! -f "$RUNNER_TEMP/anvil-baseline/Cargo.toml" ] ; then
echo "anvil impact: baseline '$base' has no root Cargo.toml (not a cargo workspace yet -- first-time anvil adoption?). Falling back to full-workspace validation." >&2
git worktree remove --force "$RUNNER_TEMP/anvil-baseline"
{
echo "include_modified="
echo "include_affected="
echo "include_required="
} >> "$GITHUB_OUTPUT"
exit 0
fi
( cd "$RUNNER_TEMP/anvil-baseline" \
&& RUSTUP_TOOLCHAIN="$active_toolchain" cargo delta snapshot ) \
> "$RUNNER_TEMP/anvil-baseline.json"
git worktree remove --force "$RUNNER_TEMP/anvil-baseline"
cargo delta impact \
--baseline "$RUNNER_TEMP/anvil-baseline.json" \
--current "$RUNNER_TEMP/anvil-current.json" \
--format json \
> "$RUNNER_TEMP/anvil-impact.json"
# Format each tier into the `--package name@version ...` shape the
# checks.just recipes consume (or the "--skip" sentinel when a
# tier is empty), via the shared _anvil-impact-format recipe --
# single source of truth across the GitHub and ADO impact steps.
# See that recipe in checks.just for the version-qualification and
# lib-name translation rationale. Capture each formatted value into
# a variable first, then write plain `name=value` lines, so the
# quoted JSON-path argument never sits inside the output `echo`.
modified="$(just _anvil-impact-format modified "$RUNNER_TEMP/anvil-impact.json")"
affected="$(just _anvil-impact-format affected "$RUNNER_TEMP/anvil-impact.json")"
required="$(just _anvil-impact-format required "$RUNNER_TEMP/anvil-impact.json")"
{
echo "include_modified=$modified"
echo "include_affected=$affected"
echo "include_required=$required"
} >> "$GITHUB_OUTPUT"
# 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
# tree, runs `cargo delta impact`, and writes the cache under
# target/anvil/impact/. This is the only job that runs cargo-delta to
# compute the impact set; group jobs install it as a setup prereq (for
# local recompute-capable runs) but consume the downloaded artifact
# instead of recomputing.
run: just anvil-impact
- name: Upload impact artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
# Per-OS name so a downstream leg downloads the impact set computed on
# its own host (Linux / Windows). The two arm legs reuse their OS
# family's artifact.
name: anvil-impact-${{ runner.os }}
path: target/anvil/impact
# Match GitHub's default 30-day workflow-rerun window. Consumer group
# jobs (pr-fast, pr-slow, ...) unconditionally download this artifact
# and run under ANVIL_IMPACT=consume, so a rerun triggered within that
# window must still find the impact set. A shorter lifetime (e.g. 1 day)
# would silently cap the effective rerun window at that lifetime -- the
# download step would fail once the artifact expired even though GitHub
# still offers the rerun. The set is small (a few cache files), so the
# storage cost of the full window is negligible.
retention-days: 30
55 changes: 0 additions & 55 deletions .github/actions/anvil-pr-fast/action.yml

This file was deleted.

Loading
Loading