Skip to content

Commit 3e9e9bd

Browse files
committed
feat: stabilize -Zconfig-include
# Stabilization report ## Summary The `include` key in Cargo configuration files allows loading additional config files, enabling better organization, sharing, and management of Cargo configurations across projects and environments. This feature has been available under the `-Zconfig-include` flag since 2019 (Cargo 1.42) and has seen real-world usage. The stabilization includes support for multiple syntax forms and the `optional` field, which were added in October 2025 based on user feedback. Tracking issue: #7723 ### What is stabilized The `include` configuration key allows loading additional config files. **Supported syntax:** - String: `include = "path.toml"` - Array: `include = ["a.toml", "b.toml"]` - Inline tables: `include = [{ path = "optional.toml", optional = true }]` - Array of tables: `[[include]]` with `path` and `optional` fields **Key behaviors:** - Paths are relative to the including config file and must end with `.toml` - Glob syntax and templated paths (with `{}` braces} are disallowed in paths. - Merge follows precedence order: included files (left-to-right) → parent config - `optional = true` silently skips missing files (default: `false`) - Cyclic includes are detected and reported as errors See the config documentation for complete details and examples. ### Future extensions Several potential extensions are not implemented at this time: * Glob patterns: like `include = "config.d/*.toml"` — #9306 * Conditional include: conditions like gitconfig's `includeIf` — #7723 (comment) * Variable substitution and template: placeholders like `{CONFIG_DIR}` or `{CARGO_HOME}` — #15769 * Implicit-include: like `.cargo/config.user.toml` or `.cargo/config.d` for config fragments — [#t-cargo > Built-in &#96;.cargo/config.local.toml&#96;for non-committed config](https://rust-lang.zulipchat.com/#narrow/channel/246057-t-cargo/topic/Built-in.20.60.2Ecargo.2Fconfig.2Elocal.2Etoml.60for.20non-committed.20config/with/558705263) * Environment variable include support: like `CARGO_INCLUDE=path/to/config.toml` — #6728 See "Doors closed" for more. ## Design ### Key evolution All significant changes occurred during the unstable period (2019-2024) and were approved by the Cargo team. **1. File naming restrictions** (#12298, 2023-06-21) (#16285, 2025-11-21) The syntax has a couple restrictions: * File path should end with `.toml` extension * File path should not contain glob syntax or template braces The team considered the restriction was reasonable. The restriction applies to config file discovery but not to `--config` CLI arguments which has already been stabilized. **2. Loading precedence for arrays** Config values in array elements are loaded left to right, with later values taking precedence. The parent config file's values always take precedence over included configs. This provides intuitive layering behavior. **3. Syntax complexity** (#16174, 2025-10-30) The feature started with simple string/array syntax. The team debated and decided to add table syntax before stabilization to allow future extensions. **4. Optional includes by default vs. explicit** (#16180, 2025-10-31) Some users wanted missing files to be silently ignored by default for local customization workflows. Others wanted errors to catch typos. The team chose to error by default but added an explicit `optional = true` field, requiring users to be intentional about optional behavior. ### Nightly extensions No nightly-only extensions remain. The feature is fully stabilized as implemented. ### Doors closed **This stabilization commits to**: 1. Supporting the `include` key in Cargo configuration 2. Relative path resolution from the including config file 3. Left-to-right merge order for arrays 4. Parent config taking precedence over includes 6. The `path` and `optional` fields in table syntax **This does NOT prevent**: - Adding glob/wildcard support - Adding conditional includes - Adding variable substitution and template The `[[include]]` table syntax could optionally have a field to enable the future extensions above. For example, ```toml [[include]] path = "path/config/*.toml" glob = true [[include]] path = "path/*/config.toml" if = "<some-condition>" [[include]] path = "path/*/config.toml" templatized = true ``` **This MAY prevent**: * Adding new implicit-include for user local config or config fragments directory As we are going to allow all file paths. Adding any implicit includes after stabilization might break the merge precedence if people already include those paths. However, implicit-include for config fragments (e.g., `include = [".cargo/config.toml/"]`) can be supported in the future without breaking changes, if `config.toml/` is a directory. ## Feedback ### Call for testing No formal "call for testing" was issued, but the feature has been available under `-Zconfig-include` since Cargo 1.42 (2019) and has seen real-world adoption. ### Use cases Users reported use cases: - **Sharing flags and environment conditionally**: [Tock OS](https://github.com/tock/tock), [esp-hal](https://github.com/esp-rs/esp-hal), rtos, and some FFI libraries use it for preset management across multiple board configurations for different hardware platforms, architectures, and downstream crates. A board's config (used by entering its directory) is defined by pulling from role-based config slices. - **Beyond hierarchical discovery**: Some use cases require explicit includes because configs need to be loaded from locations outside the hierarchical path, or need to be conditionally included based on per-package or per-machine requirements that can't rely on the directory structure alone. This usually happens in a meta build system that generates configs, especially when setting `CARGO_HOME` to a different location off the hierarchical path. - **User and project configuration**: Projects with checked-in configs (e.g., `[profile.test] debug = false` for CI) can allow developers to override settings locally without modifying the checked-in file. Developers can include an optional `.cargo/local-config.toml` without using git workarounds like `update-index --assume-unchanged`. ### Coverage Test coverage is comprehensive in `tests/testsuite/config_include.rs`: - Merge behavior: left-to-right order, hierarchy interaction - Path handling: relative paths, different directory structures - Cycle detection: Direct and indirect cycles - Error cases: missing files, invalid paths, wrong extensions, missing required fields - Syntax variations: string, array, inline table, array of tables - Optional includes: missing optional files, mixed optional/required - CLI integration: includes from `--config` arguments - Forward compatibility: unknown table fields ### Known limitations Issue #15769 tracks inconsistent relative path behavior between `include` paths (relative to config file) and other config paths like `build.target-dir` (relative to cargo root). This is considered a known limitation and confusion that can be addressed separately and doesn't block stabilization. No other known limitations blocking stabilization. ## History - 2019-02-25: Original proposal (#6699) - 2019-12-19: initial implementation (#7649) - 2023-06-21: file extension restriction added (#12298) - 2025-10-30: table syntax support added (#16174) - 2025-10-31: optional field support added (#16180) - 2025-11-21: glob and template syntax restriction added (#16285) ## Acknowledgments Contributors to this feature: - `@ehuss` for initial implementation and design - `@weihanglo` for extra syntax support and enhancement - `@rust-lang/cargo` team for the support, review and feedback - All users providing feedback in #7723
1 parent 9fa462f commit 3e9e9bd

File tree

7 files changed

+142
-353
lines changed

7 files changed

+142
-353
lines changed

src/cargo/core/features.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,6 @@ unstable_cli_options!(
856856
cargo_lints: bool = ("Enable the `[lints.cargo]` table"),
857857
checksum_freshness: bool = ("Use a checksum to determine if output is fresh rather than filesystem mtime"),
858858
codegen_backend: bool = ("Enable the `codegen-backend` option in profiles in .cargo/config.toml file"),
859-
config_include: bool = ("Enable the `include` key in config files"),
860859
direct_minimal_versions: bool = ("Resolve minimal dependency versions instead of maximum (direct dependencies only)"),
861860
dual_proc_macros: bool = ("Build proc-macros for both the host and the target"),
862861
feature_unification: bool = ("Enable new feature unification modes in workspaces"),
@@ -978,6 +977,8 @@ const STABILIZED_PACKAGE_WORKSPACE: &str =
978977

979978
const STABILIZED_BUILD_DIR: &str = "build.build-dir is now always enabled.";
980979

980+
const STABILIZED_CONFIG_INCLUDE: &str = "The `include` config key is now always available";
981+
981982
fn deserialize_comma_separated_list<'de, D>(
982983
deserializer: D,
983984
) -> Result<Option<Vec<String>>, D::Error>
@@ -1362,6 +1363,7 @@ impl CliUnstable {
13621363
"doctest-xcompile" => stabilized_warn(k, "1.89", STABILIZED_DOCTEST_XCOMPILE),
13631364
"package-workspace" => stabilized_warn(k, "1.89", STABILIZED_PACKAGE_WORKSPACE),
13641365
"build-dir" => stabilized_warn(k, "1.91", STABILIZED_BUILD_DIR),
1366+
"config-include" => stabilized_warn(k, "1.93", STABILIZED_CONFIG_INCLUDE),
13651367

13661368
// Unstable features
13671369
// Sorted alphabetically:
@@ -1376,7 +1378,6 @@ impl CliUnstable {
13761378
"build-std-features" => self.build_std_features = Some(parse_list(v)),
13771379
"cargo-lints" => self.cargo_lints = parse_empty(k, v)?,
13781380
"codegen-backend" => self.codegen_backend = parse_empty(k, v)?,
1379-
"config-include" => self.config_include = parse_empty(k, v)?,
13801381
"direct-minimal-versions" => self.direct_minimal_versions = parse_empty(k, v)?,
13811382
"dual-proc-macros" => self.dual_proc_macros = parse_empty(k, v)?,
13821383
"feature-unification" => self.feature_unification = parse_empty(k, v)?,

src/cargo/util/context/mod.rs

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,8 @@ pub const TOP_LEVEL_CONFIG_KEYS: &[&str] = &[
187187
enum WhyLoad {
188188
/// Loaded due to a request from the global cli arg `--config`
189189
///
190-
/// Indirect configs loaded via [`config-include`] are also seen as from cli args,
190+
/// Indirect configs loaded via [`ConfigInclude`] are also seen as from cli args,
191191
/// if the initial config is being loaded from cli.
192-
///
193-
/// [`config-include`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#config-include
194192
Cli,
195193
/// Loaded due to config file discovery.
196194
FileDiscovery,
@@ -1139,19 +1137,7 @@ impl GlobalContext {
11391137
self.merge_cli_args()?;
11401138
}
11411139

1142-
// Load the unstable flags from config file here first, as the config
1143-
// file itself may enable inclusion of other configs. In that case, we
1144-
// want to re-load configs with includes enabled:
11451140
self.load_unstable_flags_from_config()?;
1146-
if self.unstable_flags.config_include {
1147-
// If the config was already loaded (like when fetching the
1148-
// `[alias]` table), it was loaded with includes disabled because
1149-
// the `unstable_flags` hadn't been set up, yet. Any values
1150-
// fetched before this step will not process includes, but that
1151-
// should be fine (`[alias]` is one of the only things loaded
1152-
// before configure). This can be removed when stabilized.
1153-
self.reload_rooted_at(self.cwd.clone())?;
1154-
}
11551141

11561142
// Ignore errors in the configuration files. We don't want basic
11571143
// commands like `cargo version` to error out due to config file
@@ -1278,9 +1264,7 @@ impl GlobalContext {
12781264
let home = self.home_path.clone().into_path_unlocked();
12791265
self.walk_tree(&self.cwd, &home, |path| {
12801266
let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1281-
if self.cli_unstable().config_include {
1282-
self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1283-
}
1267+
self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
12841268
result.push(cv);
12851269
Ok(())
12861270
})
@@ -1351,11 +1335,9 @@ impl GlobalContext {
13511335
///
13521336
/// This is actual implementation of loading a config value from a path.
13531337
///
1354-
/// * `includes` determines whether to load configs from [`config-include`].
1338+
/// * `includes` determines whether to load configs from [`ConfigInclude`].
13551339
/// * `seen` is used to check for cyclic includes.
13561340
/// * `why_load` tells why a config is being loaded.
1357-
///
1358-
/// [`config-include`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#config-include
13591341
fn _load_file(
13601342
&self,
13611343
path: &Path,
@@ -1407,10 +1389,7 @@ impl GlobalContext {
14071389
) -> CargoResult<CV> {
14081390
// Get the list of files to load.
14091391
let includes = self.include_paths(&mut value, true)?;
1410-
// Check unstable.
1411-
if !self.cli_unstable().config_include {
1412-
return Ok(value);
1413-
}
1392+
14141393
// Accumulate all values here.
14151394
let mut root = CV::Table(HashMap::new(), value.definition().clone());
14161395
for include in includes {

src/doc/src/reference/config.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,57 @@ without pipelining.
828828
Specifies a custom user-agent header to use. The default if not specified is a
829829
string that includes Cargo's version.
830830

831+
### `include`
832+
833+
* Type: string, array of strings, or array of tables
834+
* Default: none
835+
* Environment: not supported
836+
837+
Loads additional config files. Paths are relative to the config file that
838+
includes them. Only paths ending with `.toml` are accepted.
839+
840+
Supports the following formats:
841+
842+
```toml
843+
# single path
844+
include = "path/to/mordor.toml"
845+
846+
# array of paths
847+
include = ["frodo.toml", "samwise.toml"]
848+
849+
# inline tables
850+
include = [
851+
"simple.toml",
852+
{ path = "optional.toml", optional = true }
853+
]
854+
855+
# array of tables
856+
[[include]]
857+
path = "required.toml"
858+
859+
[[include]]
860+
path = "optional.toml"
861+
optional = true
862+
```
863+
864+
When using table syntax (inline tables or array of tables), the following
865+
fields are supported:
866+
867+
* `path` (string, required): Path to the config file to include.
868+
* `optional` (boolean, default: false): If `true`, missing files are silently
869+
skipped instead of causing an error.
870+
871+
The merge behavior of `include` follows a precedence order
872+
where later values take precedence over earlier ones:
873+
874+
1. Config values are first loaded from the `include` path.
875+
* If `include` is an array,
876+
config values are loaded and merged from left to right for each path
877+
with later values taking precedence.
878+
* This step recurses if included config files also contain `include` keys.
879+
2. Then, the config file's own values are merged on top of the included config,
880+
taking highest precedence.
881+
831882
### `[install]`
832883

833884
The `[install]` table defines defaults for the [`cargo install`] command.

src/doc/src/reference/unstable.md

Lines changed: 6 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ Each new feature described below should explain how to use it.
119119
* [Build analysis](#build-analysis) --- Record and persist detailed build metrics across runs, with new commands to query past builds.
120120
* [`rustc-unicode`](#rustc-unicode) --- Enables `rustc`'s unicode error format in Cargo's error messages
121121
* Configuration
122-
* [config-include](#config-include) --- Adds the ability for config files to include other files.
123122
* [`cargo config`](#cargo-config) --- Adds a new subcommand for viewing config files.
124123
* Registries
125124
* [publish-timeout](#publish-timeout) --- Controls the timeout between uploading the crate and being available in the index
@@ -636,77 +635,9 @@ like to stabilize it somehow!
636635

637636
[rust-lang/rust#64158]: https://github.com/rust-lang/rust/pull/64158
638637

639-
## config-include
640-
* Tracking Issue: [#7723](https://github.com/rust-lang/cargo/issues/7723)
641-
642-
This feature requires the `-Zconfig-include` command-line option.
643-
644-
The `include` key in a config file can be used to load another config file.
645-
For example:
646-
647-
```toml
648-
# .cargo/config.toml
649-
include = "other-config.toml"
650-
651-
[build]
652-
jobs = 4
653-
```
654-
655-
```toml
656-
# .cargo/other-config.toml
657-
[build]
658-
rustflags = ["-W", "unsafe-code"]
659-
```
660638

661639
### Documentation updates
662640

663-
#### `include`
664-
665-
* Type: string, array of strings, or array of tables
666-
* Default: none
667-
668-
Loads additional config files. Paths are relative to the config file that
669-
includes them. Only paths ending with `.toml` are accepted.
670-
671-
Supports the following formats:
672-
673-
```toml
674-
# single path
675-
include = "path/to/mordor.toml"
676-
677-
# array of paths
678-
include = ["frodo.toml", "samwise.toml"]
679-
680-
# inline tables
681-
include = [
682-
"simple.toml",
683-
{ path = "optional.toml", optional = true }
684-
]
685-
686-
# array of tables
687-
[[include]]
688-
path = "required.toml"
689-
690-
[[include]]
691-
path = "optional.toml"
692-
optional = true
693-
```
694-
695-
When using table syntax (inline tables or array of tables), the following
696-
fields are supported:
697-
698-
* `path` (string, required): Path to the config file to include.
699-
* `optional` (boolean, default: false): If `true`, missing files are silently
700-
skipped instead of causing an error.
701-
702-
The merge behavior of `include` is different from other config values:
703-
704-
1. Config values are first loaded from the `include` path.
705-
* If `include` is an array, config values are loaded and merged from left
706-
to right for each path.
707-
* This step recurses if included config files also contain `include` keys.
708-
2. Then, the config file's own values are merged on top of the included config.
709-
710641
## target-applies-to-host
711642
* Original Pull Request: [#9322](https://github.com/rust-lang/cargo/pull/9322)
712643
* Tracking Issue: [#9453](https://github.com/rust-lang/cargo/issues/9453)
@@ -2327,3 +2258,9 @@ See the [config documentation](config.md#buildbuild-dir) for information about c
23272258

23282259
The `--build-plan` argument for the `build` command has been removed in 1.93.0-nightly.
23292260
See <https://github.com/rust-lang/cargo/issues/7614> for the reason for its removal.
2261+
2262+
## config-include
2263+
2264+
Support for including extra configuration files via the `include` config key
2265+
has been stabilized in 1.93.0.
2266+
See the [`include` config documentation](config.md#include) for more.

0 commit comments

Comments
 (0)