Skip to content

cargo-oxide: fmt must not build the codegen backend - #1188

Merged
nihalpasham merged 1 commit into
NVIDIA:mainfrom
vyncint:fix/fmt-should-not-build-the-backend
Aug 30, 2026
Merged

nihalpasham merged 1 commit into
NVIDIA:mainfrom
vyncint:fix/fmt-should-not-build-the-backend

Conversation

@vyncint

@vyncint vyncint commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Closes #1187.

cargo oxide fmt resolved its context with resolve_context(), which calls backend::find_or_build_backend. On a checkout with no cached librustc_codegen_cuda.so that compiled the whole codegen backend before formatting a single file; in standalone mode it cloned the backend first. Formatting compiles no device code, so none of it was used.

What changed

The Fmt arm resolves passively, and resolve_passive_context's doc names list and fmt alongside the commands it already listed.

 Commands::Fmt { check } => {
-    let ctx = commands::resolve_context();
+    // Formatting compiles no device code. `format_all` reads only
+    // `workspace_root`, `codegen_crate` and `examples_dir`, which the
+    // passive resolver fills in identically, so eager resolution only
+    // added a backend build -- and a clone on a fresh checkout -- ahead
+    // of the first file being formatted.
+    let ctx = commands::resolve_passive_context();
     commands::format_all(&ctx, check);
 }

Root cause across the full path

format_all reads exactly three fields of Context — workspace_root, codegen_crate, examples_dir — and never backend_so or config. Its call graph is closed against Context: run_cargo_fmt, run_cargo_fmt_manifest, run_fmt_command and collect_example_manifests all take &Path / &mut Vec<PathBuf>, so the backend is not reachable from this path even indirectly.

Both resolvers agree on those three fields in every discovery branch:

branch workspace_root codegen_crate examples_dir
workspace find_workspace_root() root/crates/rustc-codegen-cuda codegen_crate/examples
standalone cwd cwd cwd
neither byte-identical error + exit(1) in both

All eleven resolve_context() call sites were classified. run, sanitize, build, fuzz-schedule, test, emit-ltoir, pipeline, inspect, debug and setup compile device code and legitimately build. list, clean, doctor and update already resolve passively. fmt was the only read-only command still resolving eagerly.

Before / after

Workspace checkout with the host-build backend moved aside:

Before

$ cargo oxide fmt --check
Building rustc-codegen-cuda backend...
   Compiling mir-transforms v0.2.1 (.../crates/mir-transforms)
   Compiling mir-lower v0.2.1 (.../crates/mir-lower)
   Compiling cuda-oxide-codegen v0.1.0 (.../crates/cuda-oxide-codegen)
   Compiling mir-importer v0.2.1 (.../crates/mir-importer)
   Compiling rustc_codegen_cuda v0.2.1 (.../crates/rustc-codegen-cuda)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 16.94s
✓ Backend built: .../librustc_codegen_cuda.so
📦 Checking root workspace...

After

$ cargo oxide fmt --check
📦 Checking root workspace...
📦 Checking rustc-codegen-cuda...
📦 Checking cuda-macros device-only fixture...
📦 Checking example: abi_hmm...

The 16.94s is with the rest of the workspace already compiled; from a cold target/ it is minutes, and standalone mode adds a clone. Both runs above were cut off during the examples walk — the point of the comparison is the eight lines before 📦 Checking root workspace..., not total runtime.

Controls

Negative — unformatted code is still caught. Added a badly formatted file to a scaffolded project:

❌ Some files need formatting. Run: cargo oxide fmt      exit=1

then cargo oxide fmt (write mode) → ✅ All crates formatted, and --check again → ✅ All files are properly formatted, exit=0.

Unaffected — device-codegen commands still build. With the backend absent, cargo oxide build array_index still prints Building rustc-codegen-cuda backend... / ✓ Backend built. The fix is not "make everything passive".

Unaffected — standalone mode still formats. A project from cargo oxide new formats and checks correctly through the passive resolver.

One behaviour change, deliberate

The passive resolver uses load_oxide_config_lenient, so an invalid .cargo/cuda-oxide.toml now warns instead of exiting:

before:  Error: could not parse cuda-oxide config .../cuda-oxide.toml: TOML parse error at line 2, column 10     exit=1
after:   Warning: ignoring invalid cuda-oxide config and continuing with defaults
         📦 Checking root workspace... → ✅ All files are properly formatted                                      exit=0

format_all never reads ctx.config, so nothing about what gets formatted changes. This matches the posture doctor, clean, list and update already have, and it seems right that formatting should not be gated on backend config — but it is a real delta, so flagging it rather than burying it. Happy to keep the strict loader for fmt if you would rather the change be strictly the backend build.

On not adding a dispatch guard test

The obvious guard — a test that reads main.rs and asserts the Fmt arm calls the passive resolver — is the shape you closed #1050 and #1133 for, and correctly: it passes on the text while the property can be false, since an arm could reach the backend through a helper or call find_or_build_backend directly. A behavioural test is not available either, because format_all shells out to cargo fmt and calls process::exit(1) on failure, so it would pass or fail on whether the host's default toolchain has rustfmt.

So this follows the tree's existing convention for the same decision: doctor, clean, list and update each carry a comment on the arm and no guard, and the one-line choice is verified by reading it. If you want the invariant enforced, I think it needs one executable source for "which commands may build the backend" rather than a scan, and I would rather agree that design with you first than ship a check that can be green while wrong.

Scope

Test plan

  • cargo test -p cargo-oxide — 185 passed, 0 failed.
  • cargo clippy -p cargo-oxide --all-targets -- -D warnings — clean.
  • cargo fmt --check — clean.
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps -p cargo-oxide — clean.
  • Guard scripts: check-cli-doc-coverage, check-crate-inventory, check-spdx-headers, check-toolchain-parity, check-test-matrix-coverage, check-error-example-status, check-example-license-policy, check-reserved-prefixes, check-book-api-names, check-book-catalog-stamp — all pass.

`cargo oxide fmt` resolved its context with `resolve_context`, which calls
`backend::find_or_build_backend`: on a checkout with no cached
`librustc_codegen_cuda.so` the command compiled the entire codegen backend --
and in standalone mode cloned it first -- before formatting a single file.

Formatting compiles no device code. `format_all` reads only `workspace_root`,
`codegen_crate` and `examples_dir`; neither it nor `run_cargo_fmt`,
`run_cargo_fmt_manifest`, `run_fmt_command` nor `collect_example_manifests`
takes a `Context` at all, so `backend_so` is unreachable from this path.
`resolve_passive_context` fills those three fields identically in every
discovery branch, so the eager resolver bought nothing.

`doctor`, `clean`, `list` and `update` already resolve passively for the same
reason; `fmt` was the one read-only command left resolving eagerly. Its doc
comment now names it alongside them.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint force-pushed the fix/fmt-should-not-build-the-backend branch from 6009af3 to 88d3190 Compare August 28, 2026 23:44
@vyncint

vyncint commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main after #1190 split commands.rs into per-concern modules. The change is unchanged in substance, now in its new homes: the dispatch arm stays in main.rs, and the resolve_passive_context doc moved to commands/context.rs.

Re-verified the claims against the refactored tree rather than assuming they survived: format_all (now commands/fmt.rs) still reads only workspace_root, codegen_crate and examples_dir, and run_cargo_fmt, run_cargo_fmt_manifest, run_fmt_command and collect_example_manifests all still take &Path/&mut Vec<PathBuf>, so backend_so remains unreachable from this path. The only backend token in fmt.rs is a word in a doc comment.

Gates green on the rebase: 185 tests, clippy clean, cargo fmt --check clean.

@nihalpasham

Copy link
Copy Markdown
Collaborator

Reviewed and validated end to end; ready as-is. The one-line switch to resolve_passive_context() is the repo's own mechanism for build-free commands (doctor/clean/list/update already use it), and we confirmed the load-bearing claim independently: format_all reads only workspace_root/codegen_crate/examples_dir, which both resolvers fill identically, so the backend is provably unreachable from the fmt path.

Reproduced on a fresh checkout (no cached backend .so):

$ cargo oxide fmt --check
before:  Building rustc-codegen-cuda backend...     <- minutes of compiling,
            Compiling mir-lower v0.2.1 ...             all thrown away
after:   Checking root workspace...                 <- starts immediately
         ...
         All files are properly formatted  (exit 0)

control: cargo oxide build vecadd still prints the backend-build line

On the open question you flagged (invalid config now warns instead of aborting): we're accepting that posture.

  • fmt never reads ctx.config, so the formatted set is identical either way
  • doctor owns config validation, and the other four passive commands already behave this way; fmt doubling as a config gate was an accident of the eager resolver
  • confirmed the delta prints a loud two-line warning and continues, exit 0 on clean trees

Also agree with the no-scan-test call; we closed #1050 and #1133 for exactly the green-while-wrong problem. Gates on the merged tree: 189 tests, clippy -D warnings, fmt, all clean. CI approval + landing next; #1048 stays open since the parity-gate question is untouched.

@nihalpasham nihalpasham added bug Something isn't working cargo-oxide The `cargo oxide` subcommand and build automation build-related Build scripts, toolchain discovery, or compilation setup QoL-improvement Quality-of-life improvement; not a correctness fix labels Aug 30, 2026
@nihalpasham
nihalpasham merged commit 2bbc5e5 into NVIDIA:main Aug 30, 2026
52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working build-related Build scripts, toolchain discovery, or compilation setup cargo-oxide The `cargo oxide` subcommand and build automation QoL-improvement Quality-of-life improvement; not a correctness fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cargo oxide fmt builds the whole codegen backend before formatting anything fmt gate and cargo oxide fmt have no automated parity check

2 participants