cargo-feature-combinations
Plugin for cargo to run commands against selected (or all) combinations of features.
Installation
# Or install from source
There is also an unofficial Nix package (community-maintained, not maintained by me):
Usage
Just use the command as if it was cargo:
# All cargo arguments are passed along, except
# - `--all-features`
# - `--features`
# - `--no-default-features`
In addition, cargo-fc provides these flags and the matrix subcommand.
To get an idea, consider these examples:
# Run tests and fail on the first failing combination of features
# Show only diagnostics (warnings/errors), suppress build noise
# Same as `--diagnostics-only`, but also deduplicate identical diagnostics across feature combinations
# Silence output and only show the final summary
# Print all combinations of features in JSON (useful for usage in github actions)
-
--diagnostics-only— only warnings/errors, no build noise -
--dedupe— fold identical diagnostics across combinations -
--summary-only— just the per-combination result table -
--show-pruned— redundant combinations implied by other features are pruned -
matrix— machine-readable feature matrix (one row per combination)
For details, please refer to --help:
)
)
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]
For example:
[]
# Exclude groupings of features that are incompatible or do not make sense
= [["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 features from the feature combination matrix
= ["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.
= 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`.
= ["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.
= ["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.
= [
["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.
= [
["hydrate"],
["ssr"],
]
# When enabled, never include the empty feature set (no `--features`), even if
# it would otherwise be generated.
= true
# 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.
= [
["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" },
# ]
= { = "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"
# }, .. ]
[]
= "will show up in the feature matrix"
= false
When using a cargo workspace, you can also exclude packages in your workspace Cargo.toml:
[]
# Exclude packages in the workspace metadata, or the metadata of the *root* package.
= ["package-a", "package-b"]
[]
= []
= []
= ["core"]
[]
= { = "1", = true }
= { = "1", = true }
[]
= ["default"]
= 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.
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
selected packages × effective targets × feature combinations
Declare workspace-wide targets in the workspace Cargo.toml:
[]
= [
"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:
[]
# Run this package only on wasm (overrides the workspace list, does not merge).
= ["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(...)' 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:
- an explicit Cargo
--target <triple>(wins globally for that run), - the package's
targets, - the workspace
targets, CARGO_BUILD_TARGET,- 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].targetprecedence. To run a single target for one invocation, pass an explicit--target <triple>, which overrides all configured lists, or pass--no-targetsto 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.
Unknown aliases and custom subcommands do not receive configured targets unless you declare it:
[]
= true
The same table can override built-in defaults. For example, lint every
configured target but keep cargo fc build on the single effective target:
[]
= 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 targets = false opt-out is quiet.
[!WARNING] The
targetslist is shared by all target-capable commands. It is motivated bycheck/clippy(which only need the target'srustc), but it also applies tobuild(needs a linker), and totest/run(which execute and therefore usually fail for foreign targets). Narrow a single run with an explicit--target <triple>when needed, or pass--no-targetsto ignore the configured lists entirely and use Cargo's default single target.
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:
Or opt in for the workspace:
[]
= 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:
[]
= ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
[]
= { = ["native-cli"] }
[]
= { = ["wasm-app"] }
Workspace target overrides may patch exclude_packages only, and 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:
[]
Example (exclude different features per OS):
[]
= ["default"]
[]
= { = ["metal"] }
[]
= { = ["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"] }
- Override (replace the entire value):
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 must not use add or remove (only override
is allowed).
[]
= ["default"]
= [
["gpu"],
["ui"],
]
= true
[]
= true
# Start from a fresh default config on Linux: `isolated_feature_sets` and
# `skip_optional_dependencies` are not inherited from the base config.
= ["default", "cuda"] # using array shorthand, i.e. override
Usage with github-actions
The github-actions matrix feature can be used together with cargo fc to more efficiently test combinations of features in CI. See 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.