Configured targets#

Declaring a target list#

Declare workspace-wide targets in the workspace Cargo.toml:

[workspace.metadata.cargo-fc]
targets = [
  "x86_64-unknown-linux-gnu",
  "x86_64-pc-windows-msvc",
  "aarch64-apple-darwin",
]

Now cargo fc check visits every combination on every target.

Per-package target lists#

A package can override the workspace list, or opt out of it:

[package.metadata.cargo-fc]
# Run this package only on wasm (overrides the workspace list, does not merge).
targets = ["wasm32-unknown-unknown"]

The three states:

ValueMeaning
key missinginherit the workspace target list
targets = []opt out of the workspace list; use the single effective target
targets = ["…"]this package’s own list (overrides, does not merge)

A package-level targets key is a capability statement, not just a sweep default: it also filters an explicit --target <triple>. A package whose own list does not admit the requested triple is skipped with a warning — for targets = [], any triple other than the package’s single effective target — so running one configured target at a time keeps the same package coverage as the full sweep. To deliberately override, combine --target with --no-targets.

targets only selects which targets are visited. The target.'cfg(...)' overrides still shape the feature matrix for each concrete target.

Precedence#

For a command that supports targets, each package’s target is resolved as:

  1. an explicit Cargo --target <triple> (replaces the workspace list for the run; a package-level targets still filters membership as described above),
  2. the package’s targets,
  3. the workspace targets,
  4. CARGO_BUILD_TARGET,
  5. the host target.

Configured lists intentionally beat CARGO_BUILD_TARGET. Repository config is the declarative matrix and shouldn’t be silently collapsed by a developer’s ambient environment — this differs from Cargo’s own [build].target precedence. To run a single target for one invocation, pass --target <triple> (replaces the workspace list, honoring package-level targets constraints) or --no-targets (ignore configured lists, fall back to Cargo’s default single target). Combining both ignores package-level constraints too, forcing every selected package onto the explicit target.

The host target runs as a plain build#

When a configured list names the host, cargo fc runs that target the way a bare cargo check would: no --target flag, and plain cargo as the driver.

The flag would change nothing about what is built — Cargo already defaults to the host — but it changes where and how. Output moves from target/debug into target/<triple>/, so an ordinary cargo build in the same workspace can reuse none of it, and Cargo stops applying build.rustflags to build scripts and proc macros, fingerprinting those units differently from every other build in that directory. Listing your host among targets would otherwise cost a second full copy of the host build.

An --aggregate-targets invocation that groups the host with cross targets keeps every --target, the host’s included — dropping one there would narrow what Cargo builds.

To build the host row under target/<triple>/ like every other configured target — for uniform artifact paths in CI, or to keep build.rustflags off build scripts and proc macros — turn the omission off:

[workspace.metadata.cargo-fc]
omit_host_target_flag = false

Or per invocation with --omit-host-target-flag=false. This resolves per package-target like every other flag, and only controls the injected flag; the host row’s driver stays plain cargo unless you set driver yourself.

Which commands expand targets#

Configured targets apply only to commands that accept Cargo’s --target flag. The built-ins check, clippy, build, doc, test, run, and cargo fc matrix get this automatically. Aliases that resolve to a built-in inherit it.

A custom command that doesn’t resolve to a built-in must opt in:

[workspace.metadata.cargo-fc.subcommands.my-custom-cmd]
expand_targets = true

The same table overrides built-in defaults — for example, lint every target but keep build host-only:

[workspace.metadata.cargo-fc.subcommands.build]
expand_targets = false

Well-known plugins (nextest, audit, deny, machete, udeps, leptos, …) have their capability hint suppressed to avoid noise; this does not grant capability. Opt in or out explicitly with the same subcommands.<name> table. If configured targets exist but the command lacks the capability, cargo fc warns once and falls back to the single effective target; an explicit expand_targets = false is quiet.

test and run execute the binary. The targets list is shared by all target-capable commands, but check/clippy only need the target’s rustc, while test/run run the produced binary and can’t execute a foreign target. Keep them host-only — narrow with --target or --no-targets.

Per-target package selection#

Workspace package exclusions can vary by target, using the same cfg(...) selectors and patch semantics:

[workspace.metadata.cargo-fc]
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]

[workspace.metadata.cargo-fc.target.'cfg(target_arch = "wasm32")']
exclude_packages = { add = ["native-cli"] }

[workspace.metadata.cargo-fc.target.'cfg(target_os = "linux")']
exclude_packages = { add = ["wasm-app"] }

Throughput: --aggregate-targets#

--aggregate-targets batches a combination’s compatible configured targets into a single Cargo invocation (one --target per target) instead of one invocation per target. It’s faster on many-core machines and reports results per target group. It falls back to serial execution for run and pruned summaries. Different resolved drivers or child environments split into separate aggregate invocations.

Each row now covers a combination’s whole target group — note targets = [...] (plural), and one invocation per combination instead of one per target:

$ cargo fc --aggregate-targets --summary-only check --workspace
 
     Checking [1/4] app ( targets = [x86_64-unknown-linux-gnu, wasm32-unknown-unknown], features = [] )
     Checking [2/4] app ( targets = [x86_64-unknown-linux-gnu, wasm32-unknown-unknown], features = [simd] )
     Checking [3/4] app ( targets = [x86_64-unknown-linux-gnu, wasm32-unknown-unknown], features = [simd, std] )
     Checking [4/4] app ( targets = [x86_64-unknown-linux-gnu, wasm32-unknown-unknown], features = [std] )
 
    Finished 4 feature combinations for 1 package across 2 targets in 0.00s
 
        PASS app ( targets = [x86_64-unknown-linux-gnu, wasm32-unknown-unknown], 0 errors, 0 warnings, features = [] )
        PASS app ( targets = [x86_64-unknown-linux-gnu, wasm32-unknown-unknown], 0 errors, 0 warnings, features = [simd] )
        PASS app ( targets = [x86_64-unknown-linux-gnu, wasm32-unknown-unknown], 0 errors, 0 warnings, features = [simd, std] )
        PASS app ( targets = [x86_64-unknown-linux-gnu, wasm32-unknown-unknown], 0 errors, 0 warnings, features = [std] )