Skip to content

Latest commit

 

History

History
301 lines (236 loc) · 14.8 KB

File metadata and controls

301 lines (236 loc) · 14.8 KB

Local Recipe Surface

This document describes the justfiles/ox-check/ tree that ox-check writes into a repo, how the recipes are organized, and how local invocations differ from CI invocations (spoiler: they don't — that's the design).

See also:

  • design.md for the overall principles.
  • checks.md for the catalog the recipes implement.
  • updates.md for how these files are tracked / regenerated.

1. File layout

repo/
├── Justfile                                       managed-region: ox-check-imports
│   # >>> ox-check-managed: ox-check-imports
│   # checksum: sha256:…  rendered-by: cargo-ox-check 0.4.1
│   import 'justfiles/ox-check/checks.just'
│   import 'justfiles/ox-check/groups.just'
│   import 'justfiles/ox-check/tiers.just'
│   import 'justfiles/ox-check/tools.just'
│   alias ox-check := ox-check-pr
│   # <<< ox-check-managed: ox-check-imports
│   …user content…
│
└── justfiles/ox-check/                               owned (one checksum per file)
    ├── checks.just          per-check recipes (ox-check-fmt, ox-check-clippy, ox-check-llvm-cov, …)
    ├── groups.just          group recipes (ox-check-pr-fast, ox-check-pr-test, ox-check-nightly-test, …)
    ├── tiers.just           tier aggregators (ox-check-pr, ox-check-nightly, ox-check-full)
    └── tools.just           ox-check-tools-check + ox-check-tools-install + helpers

The Justfile region is the only file ox-check adds to that the user co-owns. The four files under justfiles/ox-check/ are tool-owned (full file checksums). If the user wants to add project-specific recipes, they add them to the top-level Justfile outside the managed region, or to their own additional imported .just files.

2. Recipe layers

justfiles/ox-check/ is structured to make all three levels (check, group, tier) addressable from the command line.

checks.just

One recipe per individual check, each named ox-check-<check>. Recipes are usually a single cargo … line; a handful (license-headers, ensure-no-cyclic-deps, ensure-no-default-features, pr-title, the bench smoke loop) are short [script] blocks. Every check recipe is prefixed with a quick version-gate dependency:

ox-check-clippy: (_ox-check-require "cargo-clippy")
    cargo clippy --workspace --all-targets --all-features --locked -- -D warnings

_ox-check-require is a private recipe in tools.just that calls cargo install --list to verify the tool meets the catalog's declared minimum version; missing or below-minimum tools fail with a one-line cargo install hint. The cost is one cheap cargo install --list invocation per check, well under a second on a warm cache.

groups.just

One recipe per group, named ox-check-<tier>-<group>. The check-recipe and group-recipe namespaces are kept disjoint by naming choice: no check is named <tier>-<group> for any tier × group combination (e.g. the coverage-instrumented test check is named llvm-cov, not test, so that ox-check-pr-test unambiguously refers to the PR-tier test group). The pr-mutants group runs the diff-scoped recipe; nightly-exhaustive runs the full-workspace recipe:

ox-check-pr-fast: ox-check-fmt ox-check-clippy ox-check-cargo-sort ox-check-license-headers \
               ox-check-ensure-no-cyclic-deps ox-check-ensure-no-default-features \
               ox-check-doc-build ox-check-readme-check ox-check-spellcheck ox-check-pr-title \
               ox-check-deny ox-check-audit ox-check-udeps ox-check-semver-check \
               ox-check-external-types ox-check-aprz

ox-check-pr-test: ox-check-llvm-cov ox-check-doc-test ox-check-examples
ox-check-pr-mutants: ox-check-mutants-diff

ox-check-nightly-test: ox-check-llvm-cov ox-check-doc-test ox-check-examples
ox-check-nightly-advisories: ox-check-deny ox-check-audit ox-check-aprz ox-check-clippy ox-check-udeps
ox-check-nightly-runtime: ox-check-miri ox-check-careful
ox-check-nightly-exhaustive: ox-check-mutants-full ox-check-cargo-hack ox-check-bench-only

tiers.just

Three tier aggregators. Each tier is a recipe that depends on the appropriate set of groups in a deterministic order:

ox-check-pr: ox-check-tools-check ox-check-pr-fast ox-check-pr-test ox-check-pr-mutants
ox-check-nightly: ox-check-tools-check ox-check-nightly-test ox-check-nightly-advisories \
               ox-check-nightly-runtime ox-check-nightly-exhaustive
ox-check-full: ox-check-pr ox-check-nightly

tools.just

  • ox-check-tools-check — print a status table of every tool's installed version vs. minimum.
  • ox-check-tools-install — install every catalog tool at the minimum version (or skip if already satisfied). Used as a one-shot in CI setup and locally on first use.
  • ox-check-tools-install-missing — install only the tools that are missing or below minimum.
  • _ox-check-require <tool> — internal helper called by each check.

The full tool-version policy these recipes implement is detailed in §3 below.

3. Tool versions and installation

3.1 Policy

The tool never pins exact versions for the user. The catalog records, for each tool, a minimum required version (e.g. cargo-nextest >= 0.9.122). Users are free to install newer versions, use mise/asdf, install via package manager, etc.

3.2 Detecting installed versions

_ox-check-require <tool> (a private just recipe in tools.just) uses cargo install --list to enumerate currently-installed cargo subcommands and their versions, then compares against the catalog minimum. This avoids the problem of tools without a stable --version flag, is fast, and works uniformly for everything the tool cares about (all the cargo-* checks). For the small number of non-cargo dependencies (just itself and pwsh), the recipe falls back to tool --version and a known parser.

3.3 Installing tools

ox-check-tools-install and ox-check-tools-install-missing are plain just recipes that loop over the catalog and run cargo install --locked <tool> --version >=<min>. They are the only mechanism the tool uses to install cargo-managed tools — there is no separate code path for CI. CI setup just calls the recipes. Locally, the user runs the recipes once when ox-check-tools-check complains.

Two prerequisites are not cargo-installable and must be present before the recipes can run:

  • just itself — bootstrap with cargo install just --locked once, or use a system package. Every backend's setup composite/template installs it via cargo as a one-shot.
  • pwsh (PowerShell Core) — used by the pr-title [script] recipe (regex match on $PR_TITLE; no equivalent cargo subcommand and just lacks a boolean-regex primitive). The other historically-scripted checks (license-headers, ensure-no-cyclic-deps, ensure-no-default-features) are now plain cargo subcommands from the ox-tools family and don't need pwsh. pwsh is preinstalled on every relevant CI runner (GH-hosted Linux/Windows/macOS, Microsoft-hosted ADO agents). On a developer machine without pwsh, _ox-check-require pwsh fails with a per-OS install hint pointing at https://github.com/PowerShell/PowerShell. The dependency is kept (rather than dropped to remove the one script) so future additions that don't fit cleanly as cargo subcommands have an established escape hatch.

Trade-off acknowledged: cargo install --locked is slow on a cold cache (several minutes for the full catalog). It is also the most reliable mechanism in restricted networks. Caching (via the GH cache action and the ADO pipeline workspace cache) is configured by the setup action/template to key on Cargo.lock, the toolchain channel, and the binary's catalog hash. See github.md and ado.md.

3.4 Per-check warnings

Every check recipe depends on _ox-check-require <its-tool> so even ad-hoc invocations like just ox-check-miri warn loudly if the installed tool predates the catalog minimum. The full tier invocations additionally print a one-line tools summary at the top.

3.5 The Rust toolchain

rust-toolchain.toml is read but never written, and ox-check never installs a Rust toolchain itself. Per-backend rationale lives in github.md and ado.md; short version: msrustup owns it on ADO/1ESPT, the runner image owns it on GH, the user owns it locally.

_ox-check-require validates the installed rustc against the catalog's minimum at recipe time; missing or below-minimum rustc produces a clean failure message naming the version mismatch. Per-check toolchain requirements (e.g. miri, careful, udeps need nightly) are also enforced by _ox-check-require, which suggests the user-environment-appropriate install command in the failure message (rustup install nightly or "ask your team's pipeline owner to add nightly to msrustup").

4. Impact-scoping pass-through env vars

Every check recipe whose work is per-crate accepts three optional pass-through env vars and forwards them verbatim as --workspace-compatible exclude flags. Empty (the local default) means full workspace; CI populates them from the ox-check-impact building block:

ox-check-clippy: (_ox-check-require "cargo-clippy")
    cargo clippy --workspace ${OX_CHECK_EXCLUDE_NOT_MODIFIED:-} --all-targets --all-features --locked -- -D warnings

ox-check-llvm-cov: (_ox-check-require "cargo-llvm-cov") (_ox-check-require "cargo-nextest")
    cargo llvm-cov nextest --workspace ${OX_CHECK_EXCLUDE_NOT_AFFECTED:-} --all-features --locked --lcov --output-path target/coverage/lcov.info

The mapping from check to env var is fixed in the catalog (see checks.md §5). Checks with no per-crate scope (pr-title, aprz, deny, audit, spellcheck, fmt, cargo-sort, license-headers, ensure-no-cyclic-deps, ensure-no-default-features) ignore the vars. Group recipes do not interpolate the vars themselves — each underlying check recipe reads what it needs, so a group recipe is just a dependency list and nothing changes when scoping is disabled.

4.1 OX_CHECK_IMPACT_SKIP early-return hint

A fourth env var, OX_CHECK_IMPACT_SKIP, is set to "true" by the CI wiring when cargo-delta reports that no workspace member is in any impact tier (typically a docs-only PR or a PR touching only files cargo-delta's file_exclude_patterns ignore). It is advisory, not a kill switch:

  • The CI wiring never uses it to skip whole jobs. Every group runs on every PR.
  • Recipes that scope to workspace members may check it and early-return — for example, ox-check-clippy skips the cargo invocation when OX_CHECK_IMPACT_SKIP=true, saving the cargo-delta-computed exclude list from being parsed and the workspace from being touched.
  • Recipes that don't scope to workspace members ignore it. fmt still runs (the source tree may have non-Rust files affected by the PR), deny/audit/aprz still run (their outcome doesn't depend on what was changed), pr-title still runs.

This separation is what makes the wiring layer durably structural. "Which checks can no-op when nothing in the workspace is affected?" is a per-check property and lives in the catalog/recipe, not in the wiring layer. Moving a check between groups never requires touching the stages template / reusable workflow.

A typical skip-aware recipe looks like:

ox-check-clippy: (_ox-check-require "cargo-clippy")
    @[ "${OX_CHECK_IMPACT_SKIP:-false}" = "true" ] && echo 'no affected crates; skipping clippy' && exit 0; \
        cargo clippy --workspace ${OX_CHECK_EXCLUDE_NOT_MODIFIED:-} --all-targets --all-features --locked -- -D warnings

(On Windows, with set shell := ["pwsh", "-NoProfile", "-Command"], the equivalent short-circuit uses if ($env:OX_CHECK_IMPACT_SKIP -eq 'true') { exit 0 }.)

4.2 Local impact-scoped runs

Not the default. To preview what CI would skip, run cargo-delta manually and export the env vars:

git stash; git checkout origin/main
cargo delta -c .delta.toml snapshot > /tmp/base.json
git stash pop
cargo delta -c .delta.toml snapshot > /tmp/head.json
export OX_CHECK_EXCLUDE_NOT_AFFECTED="$(cargo delta -c .delta.toml impact \
    --baseline /tmp/base.json --current /tmp/head.json -f cargo-excludes --affected)"
just ox-check-pr-test

A wrapper recipe (ox-check-impact-set base=origin/main) is left to v2: it has subtle git-state interactions and the manual flow is good enough for the rare case a developer actually wants to reproduce CI scoping locally.

5. Daily driver

$ just ox-check
[just] running ox-check-tools-check
[just] running ox-check-pr-fast
[just] running ox-check-pr-test
[just] running ox-check-pr-mutants
ox-check OK

ox-check is an alias for ox-check-pr (set in the managed Justfile region). All three tiers (ox-check-pr, ox-check-nightly, ox-check-full) are first-class — locally reproducible with exactly the same arguments CI uses, because CI invokes the same just recipes.

6. No-tooling fallback

A user with only cargo (no just, no cargo-ox-check) can still run the basics:

cargo test   --workspace --all-targets --all-features --locked
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
cargo fmt --check

The same commands appear as the body of the corresponding just recipes in justfiles/ox-check/checks.just, so they are discoverable by reading that file. The fallback covers core hygiene only — coverage, miri, mutants, etc. still require their respective tools.

7. Customization at the recipe level

Per the four customization tiers in design.md §7:

  • Add your own recipes to the top-level Justfile outside the managed region. The Justfile's managed region only contains import lines and an alias — your recipes never collide with it.
  • Add your own .just files and import them after the managed region's closing sentinel.
  • Override a single ox-check recipe: the just import-and-override rules make this awkward (just doesn't have a "the most specific definition wins" rule). The recommended way is to copy the recipe you want to change into your top-level Justfile with a different name (e.g. my-clippy) and reference that from your own group/tier recipes. Don't fight the ox-check-* names; just compose around them.
  • Disable a recipe wholesale: opt out of the managed Justfile region per updates.md §opt-out. This stops the imports from happening at all, so all ox-check-* recipes vanish. Use this only when ox-check is no longer the right tool for your repo.

Customizing the contents of justfiles/ox-check/*.just is supported — they're owned files, so editing them flips them to "dirty" and the next update writes a .ox-check-proposed sibling instead of overwriting. See updates.md for the lifecycle.