cargo-feature-combinations 0.3.0

run cargo commands for all feature combinations
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
## cargo-feature-combinations

[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/romnn/cargo-feature-combinations/build.yaml?label=build">](https://github.com/romnn/cargo-feature-combinations/actions/workflows/build.yaml)
[<img alt="test status" src="https://img.shields.io/github/actions/workflow/status/romnn/cargo-feature-combinations/test.yaml?label=test">](https://github.com/romnn/cargo-feature-combinations/actions/workflows/test.yaml)
[![dependency status](https://deps.rs/repo/github/romnn/cargo-feature-combinations/status.svg)](https://deps.rs/repo/github/romnn/cargo-feature-combinations)
[<img alt="docs.rs" src="https://img.shields.io/docsrs/cargo-feature-combinations/latest?label=docs.rs">](https://docs.rs/cargo-feature-combinations)
[<img alt="crates.io" src="https://img.shields.io/crates/v/cargo-feature-combinations">](https://crates.io/crates/cargo-feature-combinations)

Plugin for `cargo` to run commands against selected (or all) combinations of features.

The CLI is the supported interface. The Rust API exists for the binaries and
integration tests and has no stability guarantees.

<p align="center">
  <img src="docs/check.png" alt="cargo fc check running across every feature combination of a workspace" width="500">
</p>

### Installation

```bash
brew install --cask romnn/tap/cargo-fc

# Or install from source
cargo install --locked cargo-feature-combinations
```

There is also an unofficial Nix package (community-maintained, not maintained by me):

```bash
nix-shell --packages cargo-feature-combinations
```

### Usage

Just use the command as if it was `cargo`:

```bash
cargo fc check
cargo fc test
cargo fc build

# All cargo arguments are passed along, except 
#   - `--all-features`
#   - `--features` 
#   - `--no-default-features` 
cargo fc check -p <my-crate> --all-targets
```

In addition, cargo-fc provides these flags and the `matrix` subcommand.
To get an idea, consider these examples:

```bash
# Run tests and fail on the first failing combination of features
cargo fc --fail-fast test

# Show only diagnostics (warnings/errors), suppress build noise
cargo fc --diagnostics-only clippy

# Same as `--diagnostics-only`, but also deduplicate identical diagnostics across feature combinations
cargo fc --dedupe clippy

# Silence output and only show the final summary
cargo fc --summary-only build

# Print all combinations of features in JSON (useful for usage in github actions)
cargo fc matrix --pretty
```

<details>
<summary>More screenshots</summary>

1. **`--diagnostics-only`** — only warnings/errors, no build noise

   <img src="docs/diagnostics.png" alt="cargo fc --diagnostics-only clippy" width="500">

2. **`--dedupe`** — fold identical diagnostics across combinations

   <img src="docs/dedupe.png" alt="cargo fc --dedupe clippy" width="500">

3. **`--summary-only`** — just the per-combination result table

   <img src="docs/summary.png" alt="cargo fc --summary-only check" width="500">

4. **`--show-pruned`** — redundant combinations implied by other features are pruned

   <img src="docs/pruned.png" alt="cargo fc --show-pruned check" width="500">

5. **`matrix`** — machine-readable feature matrix (one row per combination)

   <img src="docs/matrix.png" alt="cargo fc matrix piped to jq" width="500">

</details>

For details, please refer to `--help`:

```bash
$ cargo fc --help

USAGE:
    cargo fc [+toolchain] [SUBCOMMAND] [SUBCOMMAND_OPTIONS]
    cargo fc [+toolchain] [OPTIONS] [CARGO_OPTIONS] [CARGO_SUBCOMMAND]

SUBCOMMAND:
    matrix                  Print JSON feature combination matrix to stdout
        --pretty            Print pretty JSON
    version                 Print version information

OPTIONS:
    -h, --help              Print help information
    -V, --version           Print version information
    --manifest-path <path>  Path to Cargo.toml to inspect
    -p, --package <name>    Include only this workspace package (repeatable)
    --exclude-package <name>
    --exclude <name>        Exclude a workspace package from feature
                            combinations (repeatable). `--exclude` is accepted
                            with `--workspace` for Cargo-compatible workspace
                            package selection.
    --diagnostics-only      Show only diagnostics (warnings/errors) per
                            feature combination. Subcommand must accept
                            --message-format=... and emit rustc JSON
                            diagnostics (e.g. build, check, clippy, doc,
                            or any alias/wrapper that does the same)
    --dedupe, --dedup       Like --diagnostics-only, but also deduplicate
                            identical diagnostics across feature combinations
    --summary-only
    --summary
    --silent                Hide cargo output and only show the final summary
    --fail-fast             Fail fast on the first bad feature combination
    --errors-only           Allow all warnings, show errors only (-Awarnings).
                            This appends to RUSTFLAGS or CARGO_ENCODED_RUSTFLAGS;
                            like any RUSTFLAGS env override, it shadows
                            config-file target rustflags.
    --packages-only         In matrix mode, emit one row per package-target
                            instead of one row per feature combination
    --only-packages-with-lib-target
                            Only consider packages with a library target
    --pedantic              Treat warnings like errors in summary and
                            when using --fail-fast
    --no-prune-implied      Disable automatic pruning of redundant feature
                            combinations implied by other features
    --show-pruned           Show pruned feature combinations in the summary
    --aggregate-targets     Batch each combination's configured targets into a
                            single Cargo invocation (one `--target` per target)
                            instead of one invocation per target. Faster on
                            many cores; reports results per target group. Falls
                            back to serial for `run` and pruned summaries.
    --no-targets            Ignore configured target lists for this invocation
                            and use Cargo's default single target (--target,
                            then CARGO_BUILD_TARGET, then host). An alternative
                            to passing an explicit --target <triple>.
    --install-missing-targets
                            Install missing Rust target components with rustup
                            before running Cargo. Explicit opt-in because this
                            may mutate the toolchain and use the network.
    --driver <bin>          Program invoked in place of `cargo` for each build
                            (e.g. `cargo-zigbuild`, `cross`). Defaults to plain
                            `cargo` for host-only runs and to `cargo-zigbuild`
                            when any non-host target is planned, so native-C
                            dependencies cross-compile. Also settable via
                            [workspace.metadata.cargo-fc].driver; pass `cargo` to
                            force plain cargo.

ENVIRONMENT:
    CARGO                   Program used for plain Cargo invocations
    CARGO_DRIVER            Set in child processes to the resolved driver
    CARGO_FC_VERBOSE        Boolean default for verbose cargo-fc headers
    VERBOSE                 Deprecated fallback for CARGO_FC_VERBOSE
```

### Configuration

In your `Cargo.toml`, you can configure the feature combination matrix.
The following metadata key aliases are all supported:

```
[package.metadata.cargo-fc]              (recommended)
[package.metadata.fc]
[package.metadata.cargo-feature-combinations]
[package.metadata.feature-combinations]
```

#### Override model

Every setting resolves along **one precedence chain**, broadest to narrowest —
workspace → package, and within each: base → `subcommands.<cmd>` →
`target.'cfg(...)'` → `target.'cfg(...)'.subcommands.<cmd>`. A narrower scope
overrides a broader one (so a `target.'cfg(...)'` override beats a bare
`subcommands.<cmd>` one).

Wherever a setting is valid, it accepts the **same forms**, consistently:

- `key = value`**override**: replace the inherited value exactly. For
  set-like values this is the array shorthand `key = [...]`, which is exactly
  equivalent to the patch op `key = { override = [...] }`.
- `key = { override = [...], add = [...], remove = [...] }` — explicit **patch**
  ops on a set-like value: `override` replaces the whole value, `add` unions
  into it, `remove` subtracts from it. (Scalars — bools and `driver` — only have
  `override`, i.e. `key = value`.)
- `replace = true` on a section — **reset**: ignore everything broader in the
  chain and start this section from defaults.

The matrix shows where each setting may be overridden (`✓`), and the only
scopes where it does not apply (`—`):

| setting | ws | ws·target | ws·sub | ws·tgt·sub | pkg | pkg·target | pkg·sub | pkg·tgt·sub |
|---|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| cargo-fc flags |||||||||
| feature matrix¹ |||||||||
| `exclude_packages`² ||||| —* ||||
| `targets` (list)³ |||||||||
| `expand_targets` |||||||||
| `driver` |||||||||
| `replace`|||||||||

The places a setting is *not* overridable, and why:

1. **feature matrix** (`exclude_features`, `only_features`, `*_feature_sets`,
   `skip_optional_dependencies`, `no_empty_feature_set`, `max_combinations`,
   `matrix`) — a workspace
   isn't a crate, so it has no features to shape.
2. **`exclude_packages`** — a package can't exclude its *sibling* packages; run
   membership is a workspace-level decision. The bare `pkg` scope accepts
   `exclude_packages` only as a deprecated root-package spelling kept for
   backwards compatibility; it is folded into the workspace base set with a
   deprecation warning and is rejected in `pkg·target`, `pkg·sub`, and
   `pkg·tgt·sub`.
3. **`targets` (the list)** inside a `target.'cfg(...)'` section — circular: the
   section was selected *because* a target matched, so redefining the list there
   is self-referential. (Per-subcommand lists, e.g. "test only on host", are
   fine.)
4. **`replace`** at a *workspace base* — nothing broader exists for it to reset.
   (`replace` at a *package* base is fine: it discards the inherited workspace
   config for that package.)
5. **`expand_targets`** outside a `subcommands.<cmd>` table — it is a
   per-subcommand capability, not a base or target-wide setting.

Notes: **`driver`** resolves per (package × target × command); when
`aggregate_targets = true` runs several targets in one invocation, a per-target
`driver` forces serial execution. **`expand_targets`** (the capability formerly
spelled `targets = true|false` on a subcommand) gates whether cargo-fc drives a
command across the target matrix at all.

For example:

```toml
[package.metadata.cargo-fc]

# Exclude groupings of features that are incompatible or do not make sense
exclude_feature_sets = [["foo", "bar"]]

# To exclude only the empty feature set from the matrix, you can either enable
# `no_empty_feature_set = true` or explicitly list an empty set here:
exclude_feature_sets = [[]]

# Exclude features from the feature combination matrix
exclude_features = ["default", "full"]

# Skip implicit features that correspond to optional dependencies from the
# matrix.
#
# When enabled, the implicit features that Cargo generates for optional
# dependencies (of the form `foo = ["dep:foo"]` in the feature graph) are
# removed from the combinatorial matrix. This mirrors the behaviour of the
# `skip_optional_dependencies` flag in the `cargo-all-features` crate.
skip_optional_dependencies = true

# Include features in the feature combination matrix
#
# These features will be added to every generated feature combination.
# This does not restrict which features are varied for the combinatorial
# matrix. To restrict the matrix to a specific allowlist of features, use
# `only_features`.
include_features = ["feature-that-must-always-be-set"]

# Only consider these features when generating the combinatorial matrix.
#
# When set, features not listed here are ignored for the combinatorial matrix.
# When empty, all package features are considered.
only_features = ["default", "full"]

# In the end, always add these exact combinations to the overall feature matrix, 
# unless one is already present there.
#
# Non-existent features are ignored. Other configuration options are ignored.
include_feature_sets = [
    ["foo-a", "bar-a", "other-a"],
]

# Allow only the listed feature sets.
#
# When this list is non-empty, the feature matrix will consist exactly of the
# configured sets (after dropping non-existent features). No powerset is
# generated.
allow_feature_sets = [
    ["hydrate"],
    ["ssr"],
]

# When enabled, never include the empty feature set (no `--features`), even if
# it would otherwise be generated.
no_empty_feature_set = true

# Override the default safety limit of 100000 generated feature combinations.
max_combinations = 250000

# When at least one isolated feature set is configured, stop taking all project 
# features as a whole, and instead take them in these isolated sets. Build a 
# sub-matrix for each isolated set, then merge sub-matrices into the overall 
# feature matrix. If any two isolated sets produce an identical feature 
# combination, such combination will be included in the overall matrix only once.
#
# This feature is intended for projects with large number of features, sub-sets 
# of which are completely independent, and thus don’t need cross-play.
#
# Non-existent features are ignored. Other configuration options are still 
# respected.
isolated_feature_sets = [
    ["foo-a", "foo-b", "foo-c"],
    ["bar-a", "bar-b"],
    ["other-a", "other-b", "other-c"],
]

# Optional: Custom metadata for `cargo fc matrix` output.
# It appears under the row's `metadata` key.
# $ cargo fc matrix --pretty
#   [
#     { "features": "", "metadata": { "kind": "ci" }, "name": "my-crate", "target": "x86_64-unknown-linux-gnu" },
#     { "features": "a", "metadata": { "kind": "ci" }, "name": "my-crate", "target": "x86_64-unknown-linux-gnu" },
#     { "features": "b", "metadata": { "kind": "ci" }, "name": "my-crate", "target": "x86_64-unknown-linux-gnu" },
#     { "features": "a,b", "metadata": { "kind": "ci" }, "name": "my-crate", "target": "x86_64-unknown-linux-gnu" },
#   ]
matrix = { kind = "ci" }

# Optional: Matrix metadata can also be configured in its own section.
# $ cargo fc matrix --pretty
#   [{
#       "features": "",
#       "metadata": {
#         "requires-gpu": false,
#         "value-for-this-crate": "will show up in the feature matrix"
#       },
#       "name": "my-crate",
#       "target": "x86_64-unknown-linux-gnu"
#    }, .. ]
[package.metadata.cargo-fc.matrix]
value-for-this-crate = "will show up in the feature matrix"
requires-gpu = false
```

When using a cargo workspace, you can also exclude packages in your workspace `Cargo.toml`:

```toml
[workspace.metadata.cargo-fc]
# Exclude packages in the workspace metadata, or the metadata of the *root* package.
exclude_packages = ["package-a", "package-b"]
```

<details>
<summary>Example: skipping optional dependency features</summary>

```toml
[features]
default = []
core = []
cli = ["core"]

[dependencies]
tokio = { version = "1", optional = true }
serde = { version = "1", optional = true }

[package.metadata.cargo-fc]
exclude_features = ["default"]
skip_optional_dependencies = true
```

With this configuration, the feature matrix will only vary the `core` and
`cli` features. The implicit `tokio` and `serde` features that correspond to
optional dependencies are excluded from the matrix, avoiding a combinatorial
explosion over integration features. If you still want to test specific
combinations that include `tokio` or `serde`, you can list them explicitly in
`include_feature_sets`.

</details>

---

### Configured targets

By default `cargo fc` runs for a single effective target (the same one Cargo
would pick: `--target`, then `CARGO_BUILD_TARGET`, then the host). You can
instead declare a list of target triples to check by default, turning the run
into a full matrix of

```text
selected packages × effective targets × feature combinations
```

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

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

Individual packages can override the workspace list, or opt out of it:

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

# Or opt out of configured targets entirely and use the single effective target:
# targets = []
```

- **missing key** — inherit the workspace target list,
- **`targets = []`** — opt out of the workspace list and use the single
  effective target (`CARGO_BUILD_TARGET`, then host),
- **`targets = ["…"]`** — this package's own target list (overrides, not
  merges with, the workspace list).

`targets` only selects which targets are visited. The
[`target.'cfg(...)'`](#target-specific-configuration) overrides below still
shape the feature matrix for each concrete target.

#### Precedence

When the selected command supports targets, each package's targets are resolved as:

1. an explicit Cargo `--target <triple>` (wins globally for that run),
2. the package's `targets`,
3. the workspace `targets`,
4. `CARGO_BUILD_TARGET`,
5. the host target.

> [!IMPORTANT]
> Configured target lists intentionally take precedence over
> `CARGO_BUILD_TARGET` — repository config is the declarative matrix and should
> not 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 an explicit `--target <triple>`, which overrides all
> configured lists, or pass `--no-targets` to ignore the configured lists and
> fall back to Cargo's default single target.

#### Which commands receive configured targets

Configured targets are applied only to commands that accept Cargo's `--target`
flag. Built-in subcommands cargo-fc recognizes — `check`, `clippy`, `build`,
`doc`, `test`, `run` (and `cargo fc matrix`) — get this capability
automatically by default.

cargo-fc resolves cargo command aliases from your `.cargo/config.toml` before
running, so an alias that expands to a built-in inherits the built-in's
capability automatically. With `lint = "clippy --all-targets --no-deps"`,
`cargo fc lint` behaves exactly like `cargo fc clippy` — no opt-in required.

A custom subcommand that does **not** resolve to a built-in still needs an
explicit declaration:

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

For well-known cargo plugins such as `nextest`, `audit`, `deny`, `machete`,
`udeps`, and `leptos`, cargo-fc suppresses the capability hint by default to
avoid noisy output. This does not grant target capability; opt in or out
explicitly with the same `subcommands.<name>` table when you want a local
policy.

The same table can override built-in defaults. For example, lint every
configured target but keep `cargo fc build` on the single effective target:

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

For built-in short aliases, the long command's policy also applies unless the
short alias has its own entry. If configured targets exist but the selected
command lacks this capability by default, cargo-fc warns once and falls back to
the single effective target. An explicit `expand_targets = false` opt-out is quiet.

#### Configurable cargo-fc flags

Cargo-fc boolean flags can be configured in `Cargo.toml` with the same name as
the CLI flag, using `_` instead of `-`. CLI flags still win for one invocation.

```toml
[workspace.metadata.cargo-fc]
dedupe = true
fail_fast = true

[package.metadata.cargo-fc]
pedantic = false

[package.metadata.cargo-fc.target.'cfg(target_os = "windows")']
errors_only = true

[package.metadata.cargo-fc.target.'cfg(target_os = "windows")'.subcommands.clippy]
dedupe = false

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

The configurable flag keys are:

```toml
summary_only = true
diagnostics_only = true
dedupe = true
verbose = true
pedantic = true
errors_only = true
packages_only = true
fail_fast = true
no_prune_implied = true
prune_implied = true
show_pruned = true
aggregate_targets = true
no_targets = true
install_missing_targets = true
only_packages_with_lib_target = true
```

`dedupe = true` implies diagnostics-only output. `prune_implied` is the positive
config spelling for `no_prune_implied`; configure only one spelling in a given
scope.

Flag precedence is broad-to-narrow:

1. workspace config,
2. matching workspace target config,
3. package config,
4. matching package target config,
5. explicit CLI flags.

At each config level, a matching `subcommands.<name>` table is applied after
that level's plain flags, so command-specific defaults override broader
defaults. Alias config for the raw command token wins; otherwise cargo-fc uses
the resolved alias target when one is known.

Broad config-driven diagnostics apply only to commands where diagnostics-only
mode is safe by default. Built-in `build`, `check`, `clippy`, and `doc` get
broad `diagnostics_only = true` and `dedupe = true` settings. Built-in `test`,
`run`, and unresolved custom commands do not, because they are not reliable
JSON-diagnostics-only commands. Aliases that resolve to a safe built-in inherit
that behavior.

For a custom command, unresolved alias, or a built-in such as `test`, opt in by
setting the behavior in that command's own table:

```toml
[workspace.metadata.cargo-fc.subcommands.my-custom-cmd]
dedupe = true
```

Known cargo plugins get the same quiet treatment as target capability hints:
cargo-fc does not assume they are diagnostics-safe, but it also does not warn
when broad diagnostics defaults are ignored for them.

Subcommand-local diagnostics flags are explicit and are applied even for
commands that are not safe by default. `dedupe = true` implies
`diagnostics_only = true`; setting `dedupe = true` together with
`diagnostics_only = false` is rejected as contradictory. Use
`diagnostics_only = false` or `dedupe = false` in a narrower scope to override a
broader default. Explicit CLI flags are always forwarded.

> [!WARNING]
> The `targets` list is shared by all target-capable commands. `check`/`clippy`
> only need the target's `rustc`, but `test`/`run` **execute** the binary and so
> cannot run a foreign target — keep them host-only (narrow with `--target` or
> `--no-targets`). `build` links and, like `clippy`, cross-compiles native-C
> dependencies; see the build driver below.

#### Build driver (cross-compiling native dependencies)

Cross-compiling a crate with native-C build dependencies (e.g. `aws-lc-sys` via
`rustls`) needs a cross C toolchain — the host `cc` can't target another OS. To
make that transparent, **when any non-host target is planned cargo-fc invokes
[`cargo-zigbuild`](https://github.com/rust-cross/cargo-zigbuild) instead of plain
`cargo`**, so zig supplies the cross C compiler and linker for every target. You
must have `cargo-zigbuild` (and `zig`) installed; host-only runs use plain
`cargo`.

Override the driver with `--driver <bin>` or in config:

```toml
[workspace.metadata.cargo-fc]
driver = "cargo-zigbuild"   # the cross-compile default; set "cargo" to opt out

# `driver` is a normal scalar setting, so it follows the same precedence chain
# as everything else (see "Override model"): a package, a `target.'cfg(...)'`, or
# a `subcommands.<cmd>` may override it. cargo-fc launches each package × target
# × command separately, so each can resolve its own driver:
[package.metadata.cargo-fc.target.'cfg(target_arch = "wasm32")']
driver = "cargo"            # this one crate builds wasm with plain cargo
```

`--driver` wins over all config; within config a narrower scope wins over a
broader one, and both win over the automatic choice. Point it at any cargo
wrapper (`cross`, `cargo-careful`, …), or set `cargo` to force plain cargo even
when cross-compiling. If the selected driver is missing, cargo-fc warns with the
install/override options before returning the spawn error.

> `--aggregate-targets` batches a package's targets into one Cargo invocation,
> which can only use one driver — so if a package resolves different drivers per
> target, cargo-fc runs those targets serially instead.

#### Installing missing Rust targets

By default cargo-fc does not mutate the Rust toolchain. To install missing
configured target components via rustup before running a command, opt in per
invocation:

```bash
cargo fc check --install-missing-targets
```

Or opt in for the workspace:

```toml
[workspace.metadata.cargo-fc]
install_missing_targets = true
```

#### Per-target workspace package selection

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

```toml
[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"] }
fail_fast = false
```

Workspace target overrides may patch `exclude_packages` and set cargo-fc flag
defaults for matching targets. They apply to every concrete effective target —
including single-target runs selected by `--target`, `CARGO_BUILD_TARGET`, or
the host.

### Target-specific configuration

You can override configuration for specific targets using Cargo-style `cfg(...)` expressions.
Overrides are configured under:

```toml
[package.metadata.cargo-fc.target.'cfg(...)']
```

Example (exclude different features per OS):

```toml
[package.metadata.cargo-fc]
exclude_features = ["default"]

[package.metadata.cargo-fc.target.'cfg(target_os = "linux")']
exclude_features = { add = ["metal"] }

[package.metadata.cargo-fc.target.'cfg(target_os = "macos")']
exclude_features = { add = ["cuda"] }
```

Patch semantics for collection-like keys such as `exclude_features`, `include_features`,
`only_features`, `*_feature_sets`:

- **Array syntax is always an override**
  - `exclude_features = ["cuda"]` replaces the entire value.
  - This is equivalent to `exclude_features = { override = ["cuda"] }`.
- **Patch object syntax is explicit**
  - Override (replace the entire value):
    - `exclude_features = { override = ["cuda"] }`
  - Add (union with the base value):
    - `exclude_features = { add = ["cuda"] }`
  - Remove (subtract from the base value):
    - `exclude_features = { remove = ["cuda"] }`

Patches are applied in order: override (or base), then remove, then add.
If a value appears in both `add` and `remove`, add wins.

When multiple target override sections match (e.g. `cfg(unix)` and `cfg(target_os = "linux")`),
their `add` and `remove` sets are unioned. Conflicting `override` values result in an error.

Matrix metadata tables merge recursively. Other matrix metadata values,
including arrays, replace the base value.

##### `replace = true`

If a matching target override sets `replace = true`, resolution starts from a fresh default
configuration (instead of inheriting from the base config). To avoid confusion, when
`replace = true` is set, patchable fields in that same section must not use `add` or
`remove` (only override is allowed).

<details>
<summary>Example: Start from fresh config with `replace=true`</summary>

```toml
[package.metadata.cargo-fc]
exclude_features = ["default"]
isolated_feature_sets = [
  ["gpu"],
  ["ui"],
]
skip_optional_dependencies = true

[package.metadata.cargo-fc.target.'cfg(target_os = "linux")']
replace = true

# Start from a fresh default config on Linux: `isolated_feature_sets` and
# `skip_optional_dependencies` are not inherited from the base config.
exclude_features = ["default", "cuda"] # using array shorthand, i.e. override
```
</details>

### Command-specific configuration

Just as you can override configuration per target triple, you can override it
per cargo subcommand under:

```toml
[package.metadata.cargo-fc.subcommands.<command>]
```

A subcommand override accepts the **same feature-matrix keys as a target
override** — `exclude_features`, `include_features`, `only_features`,
`*_feature_sets`, `skip_optional_dependencies`, `no_empty_feature_set`, and
`matrix` — with identical patch semantics (`key = [...]` or `{ override = [...] }`
to replace; `{ add = [...] }` / `{ remove = [...] }` for incremental edits). This lets the feature combinations
built for one command differ from another. For example, enable a heavy `gpu`
feature when building, but skip it when testing:

```toml
[package.metadata.cargo-fc]
# `gpu` is part of the matrix by default (e.g. for `cargo fc build`).

[package.metadata.cargo-fc.subcommands.test]
# ...but never test the `gpu` combinations.
exclude_features = { add = ["gpu"] }
```

Or restrict `cargo fc test` to a single focused feature set:

```toml
[package.metadata.cargo-fc.subcommands.test]
only_features = ["core"]
```

The override applies to that command only. Built-in short aliases (`t` → `test`,
`b` → `build`, …) and your own `.cargo/config.toml` aliases that resolve to a
built-in inherit the override automatically, matching how cargo-fc resolves the
`targets` capability and flag defaults.

Target and subcommand overrides compose. A
`target.'cfg(...)'.subcommands.<command>` section applies only when **both** the
target matches and the command is selected:

```toml
[package.metadata.cargo-fc.target.'cfg(target_os = "linux")'.subcommands.test]
exclude_features = { add = ["cuda"] }
```

Feature-matrix layers resolve broad-to-narrow, mirroring flag precedence — later
layers override earlier ones:

1. package config,
2. matching package `subcommands.<command>`,
3. matching package `target.'cfg(...)'`,
4. matching package `target.'cfg(...)'.subcommands.<command>`.

> [!NOTE]
> Feature sets are per package, so these feature-matrix keys are only accepted
> in **package**-scope subcommand tables. Workspace-scope subcommand tables
> (`[workspace.metadata.cargo-fc.subcommands.<command>]`) continue to accept only
> the `targets` capability and cargo-fc flags.

---

### Usage with github-actions

The github-actions [matrix](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs) feature can be used together with `cargo fc` to more efficiently test combinations of features in CI. See [GITHUB_ACTIONS.md](./docs/GITHUB_ACTIONS.md) for more information.

### Local development

For local development and testing, you can point `cargo fc` to another project using
the `--manifest-path` flag.

```bash
cargo run -- cargo check --manifest-path ../path/to/Cargo.toml
cargo run -- cargo matrix --manifest-path ../path/to/Cargo.toml --pretty
```