Skip to content

Conversation

@ehuss
Copy link
Contributor

@ehuss ehuss commented Dec 3, 2019

This is a collection of changes to config handling. I intended to split this into separate PRs, but they all built on one another so I decided to do it as one. However, I can still split this up if desired.

High level overview:

  • Refactorings, mainly to remove pub from Config::get_table and use serde API instead.
  • Add --config CLI option.
  • Add config include to include other files.

This makes some progress on #5416.
Closes #6699.

This makes a minor user-visible change in regards to StringList types. If an array is specified in a config as a list, and also as an env var, they will now be merged. Previously the environment variable overrode the file value. But if it is a string, then it won't join (env var takes precedence). I can probably change this, but I'm not sure if the old behavior is desired, or if it should merge all the time?

Future plans
This lays the groundwork for some more changes:

  • Work on Can't set debug-assertions via -Z config-profile #7253 (debug-assertions and debug fails in environment vars). I have some ideas to try.
  • Consider removing use of get_list for paths, and use a Vec<ConfigRelativePath>. This will require some non-trivial changes to how ConfigSeqAccess works. This is one of the last parts that does not use the serde API.
  • Possibly change [source] to load config values in a lazy fashion. This will unlock the ability to use environment variables with source definitions (like CARGO_SOURCE_CRATES_IO_REPLACE_WITH).
  • Possibly change [profile] to load config profiles in a lazy fashion. This will make it easier to use environment variables with profiles, particularly with arbitrarily named profiles.
  • Possibly remove the case-sensitive environment variables in -Zadvanced-env. I think they are just too awkward, and prone to problems. Instead, drive people towards using --config instead of env vars.
  • Add support for TOML tables in env vars (like CARGO_PROFILES={my-profile={opt-level=1}})). I started implementing it, but then looking at the use cases, it didn't seem as useful as I initially thought. However, it's still an option to try.

Refactoring overview

  • [source] table now uses the serde API.
  • [target] table now uses the serde API. This is complicated since the 'cfg()' entries are different from the triple entries. The 'cfg()' tables are loaded separately, and are accessed from Config::target_cfgs. Otherwise, it just uses config.get of the specific target.TRIPLE.
    • Moved the target config stuff into config/target.rs.
  • Various changes to make this work:
    • Added PathAndArgs type which replaces config.get_path_and_args.
    • Changed ConfigKey to track the key parts as a list (instead of a string). This fixes an issue where quoted keys weren't handled properly (like [foo.'a.b'.bar]). This also seems to make a little more sense (it was joining parts into a string only to immediately call split on it). Changed various APIs to take a ConfigKey object instead of a string to avoid that splitting behavior.
    • ValueDeserializer now pre-computes the Definition so that it can provide a better error message when a value fails to deserialize.

Overall, there shouldn't be significant user-visible changes. Some error messages have changed and warnings have been added for some ignored keys. -Zadvanced-env now works for source and target tables, though I'm not really happy with that feature.

@rust-highfive
Copy link

r? @alexcrichton

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Dec 3, 2019
@bors
Copy link
Contributor

bors commented Dec 12, 2019

☔ The latest upstream changes (presumably #7699) made this pull request unmergeable. Please resolve the merge conflicts.

@alexcrichton
Copy link
Member

Ok talked about this with @ehuss over video today and I'm quite happy with this change. Everything here looks great! I didn't do a super-close code review but I'm relatively certain that any behavior changes right now are backwards compatible and desired. In light of all that I'm gonna...

@bors: r+

@bors
Copy link
Contributor

bors commented Dec 18, 2019

📌 Commit 7f14aeaaba4165d0465bc212b95b7dd5b01f7f8e has been approved by alexcrichton

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Dec 18, 2019
@ehuss
Copy link
Contributor Author

ehuss commented Dec 19, 2019

@bors retry

@ehuss
Copy link
Contributor Author

ehuss commented Dec 19, 2019

@bors r=alexcrichton

@bors
Copy link
Contributor

bors commented Dec 19, 2019

📌 Commit d2b25cf3a6026fc487b21bdbd4ec1296318f2196 has been approved by alexcrichton

@bors
Copy link
Contributor

bors commented Dec 19, 2019

⌛ Testing commit d2b25cf3a6026fc487b21bdbd4ec1296318f2196 with merge 137e18f617bc21be68882dfd7b71375cfbbd3506...

@bors
Copy link
Contributor

bors commented Dec 19, 2019

💔 Test failed - checks-azure

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Dec 19, 2019
@ehuss
Copy link
Contributor Author

ehuss commented Dec 19, 2019

@bors r=alexcrichton
merge conflict

@bors
Copy link
Contributor

bors commented Dec 19, 2019

📌 Commit ce7fd68 has been approved by alexcrichton

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Dec 19, 2019
bors added a commit that referenced this pull request Dec 19, 2019
Config enhancements.

This is a collection of changes to config handling. I intended to split this into separate PRs, but they all built on one another so I decided to do it as one. However, I can still split this up if desired.

High level overview:

- Refactorings, mainly to remove `pub` from `Config::get_table` and use serde API instead.
- Add `--config` CLI option.
- Add config `include` to include other files.

This makes some progress on #5416.
Closes #6699.

This makes a minor user-visible change in regards to `StringList` types. If an array is specified in a config as a list, and also as an env var, they will now be merged. Previously the environment variable overrode the file value. But if it is a string, then it won't join (env var takes precedence). I can probably change this, but I'm not sure if the old behavior is desired, or if it should merge all the time?

**Future plans**
This lays the groundwork for some more changes:
- Work on #7253 (`debug-assertions` and `debug` fails in environment vars). I have some ideas to try.
- Consider removing use of `get_list` for `paths`, and use a `Vec<ConfigRelativePath>`. This will require some non-trivial changes to how `ConfigSeqAccess` works. This is one of the last parts that does not use the serde API.
- Possibly change `[source]` to load config values in a lazy fashion. This will unlock the ability to use environment variables with source definitions (like CARGO_SOURCE_CRATES_IO_REPLACE_WITH).
- Possibly change `[profile]` to load config profiles in a lazy fashion. This will make it easier to use environment variables with profiles, particularly with arbitrarily named profiles.
- Possibly remove the case-sensitive environment variables in `-Zadvanced-env`. I think they are just too awkward, and prone to problems. Instead, drive people towards using `--config` instead of env vars.
- Add support for TOML tables in env vars (like `CARGO_PROFILES={my-profile={opt-level=1}})`). I started implementing it, but then looking at the use cases, it didn't seem as useful as I initially thought. However, it's still an option to try.

**Refactoring overview**

- `[source]` table now uses the serde API.
- `[target]` table now uses the serde API. This is complicated since the 'cfg()' entries are different from the triple entries. The 'cfg()' tables are loaded separately, and are accessed from `Config::target_cfgs`. Otherwise, it just uses `config.get` of the specific target.TRIPLE.
    - Moved the target config stuff into `config/target.rs`.
- Various changes to make this work:
    - Added `PathAndArgs` type which replaces `config.get_path_and_args`.
    - Changed `ConfigKey` to track the key parts as a list (instead of a string). This fixes an issue where quoted keys weren't handled properly (like `[foo.'a.b'.bar]`). This also seems to make a little more sense (it was joining parts into a string only to immediately call `split` on it). Changed various APIs to take a `ConfigKey` object instead of a string to avoid that splitting behavior.
    - `ValueDeserializer` now pre-computes the `Definition` so that it can provide a better error message when a value fails to deserialize.

Overall, there shouldn't be significant user-visible changes. Some error messages have changed and warnings have been added for some ignored keys. `-Zadvanced-env` now works for source and target tables, though I'm not really happy with that feature.
@bors
Copy link
Contributor

bors commented Dec 19, 2019

⌛ Testing commit ce7fd68 with merge e37f62f...

@bors
Copy link
Contributor

bors commented Dec 19, 2019

☀️ Test successful - checks-azure
Approved by: alexcrichton
Pushing e37f62f to master...

@bors bors merged commit ce7fd68 into rust-lang:master Dec 19, 2019
bors added a commit that referenced this pull request Jan 6, 2020
Fix CARGO_TARGET_triple_LINKER environment variable.

#7649 caused an unfortunate regression where the `CARGO_TARGET_triple_LINKER` environment variable stopped working. I did not realize `serde(flatten)` caused serde to switch to `deserialize_map` which does not support environment variables.

The solution here is to essentially revert back to how the `[target]` table used to be loaded by loading each key individually.

This also removes the `ar` field which is not used by `rustc`.
@ehuss ehuss added this to the 1.42.0 milestone Feb 6, 2022
weihanglo added a commit to weihanglo/cargo that referenced this pull request Nov 21, 2025
# 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: rust-lang#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`
- 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"`
* Conditional include: conditions like gitconfig's `includeIf`
* Variable substitution and template: placeholders like `{CONFIG_DIR}`
  or `{CARGO_HOME}`
* Implicit-include: like `.cargo/config.user.toml` or
  `.cargo/config.d` for config fragments

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** (rust-lang#12298, 2023-06-21)

The team decided 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** (rust-lang#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** (rust-lang#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
5. The `.toml` file extension requirement
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 precendence if people already
include those paths.

## 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.

- **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.
- **Per-project profile overrides**: 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
  `.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 rust-lang#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 (rust-lang#6699)
- 2019-12-19: initial implementation (rust-lang#7649)
- 2023-06-21: file extension restriction added (rust-lang#12298)
- 2025-10-30: table syntax support added (rust-lang#16174)
- 2025-10-31: optional field support added (rust-lang#16180)

## 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 rust-lang#7723 (not going to tag each of them)
weihanglo added a commit to weihanglo/cargo that referenced this pull request Nov 21, 2025
# 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: rust-lang#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`
- 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"`
* Conditional include: conditions like gitconfig's `includeIf`
* Variable substitution and template: placeholders like `{CONFIG_DIR}`
  or `{CARGO_HOME}`
* Implicit-include: like `.cargo/config.user.toml` or
  `.cargo/config.d` for config fragments

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** (rust-lang#12298, 2023-06-21)

The team decided 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** (rust-lang#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** (rust-lang#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
5. The `.toml` file extension requirement
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 precendence if people already
include those paths.

## 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.

- **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.
- **Per-project profile overrides**: 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
  `.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 rust-lang#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 (rust-lang#6699)
- 2019-12-19: initial implementation (rust-lang#7649)
- 2023-06-21: file extension restriction added (rust-lang#12298)
- 2025-10-30: table syntax support added (rust-lang#16174)
- 2025-10-31: optional field support added (rust-lang#16180)

## 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 rust-lang#7723 (not going to tag each of them)
weihanglo added a commit to weihanglo/cargo that referenced this pull request Nov 21, 2025
# 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: rust-lang#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`
- 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"`
* Conditional include: conditions like gitconfig's `includeIf`
* Variable substitution and template: placeholders like `{CONFIG_DIR}`
  or `{CARGO_HOME}`
* Implicit-include: like `.cargo/config.user.toml` or
  `.cargo/config.d` for config fragments

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** (rust-lang#12298, 2023-06-21)

The team decided 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** (rust-lang#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** (rust-lang#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
5. The `.toml` file extension requirement
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 precendence if people already
include those paths.

## 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.

- **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.
- **Per-project profile overrides**: 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
  `.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 rust-lang#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 (rust-lang#6699)
- 2019-12-19: initial implementation (rust-lang#7649)
- 2023-06-21: file extension restriction added (rust-lang#12298)
- 2025-10-30: table syntax support added (rust-lang#16174)
- 2025-10-31: optional field support added (rust-lang#16180)

## 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 rust-lang#7723 (not going to tag each of them)
weihanglo added a commit to weihanglo/cargo that referenced this pull request Nov 21, 2025
# 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: rust-lang#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"` — rust-lang#9306
* Conditional include: conditions like gitconfig's `includeIf` —
  rust-lang#7723 (comment)
* Variable substitution and template: placeholders like `{CONFIG_DIR}`
  or `{CARGO_HOME}` — rust-lang#15769
* Implicit-include: like `.cargo/config.user.toml` or `.cargo/config.d`
  for config fragments — [#t-cargo > Built-in
  &rust-lang#96;.cargo/config.local.toml&rust-lang#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` —
  rust-lang#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** (rust-lang#12298, 2023-06-21) (rust-lang#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** (rust-lang#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** (rust-lang#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 rust-lang#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 (rust-lang#6699)
- 2019-12-19: initial implementation (rust-lang#7649)
- 2023-06-21: file extension restriction added (rust-lang#12298)
- 2025-10-30: table syntax support added (rust-lang#16174)
- 2025-10-31: optional field support added (rust-lang#16180)
- 2025-11-21: glob and template syntax restriction added (rust-lang#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 rust-lang#7723
weihanglo added a commit to weihanglo/cargo that referenced this pull request Nov 21, 2025
# 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: rust-lang#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"` — rust-lang#9306
* Conditional include: conditions like gitconfig's `includeIf` —
  rust-lang#7723 (comment)
* Variable substitution and template: placeholders like `{CONFIG_DIR}`
  or `{CARGO_HOME}` — rust-lang#15769
* Implicit-include: like `.cargo/config.user.toml` or `.cargo/config.d`
  for config fragments — [#t-cargo > Built-in
  &rust-lang#96;.cargo/config.local.toml&rust-lang#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` —
  rust-lang#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** (rust-lang#12298, 2023-06-21) (rust-lang#16285,
2025-11-21)

The syntax has a couple restrictions:

* Path must end with `.toml` extension
* Path must 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** (rust-lang#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** (rust-lang#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
5. The `path` and `optional` fields in table syntax

**This does NOT prevent**:

- Adding glob/wildcard support
- Adding conditional includes
- Adding variable substitution and template

**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.

The only possible way to support it is accepting `.cargo/config.toml/`
as a directory and treat it as a config fragments directory.
`.cargo/config.toml` (and the legacy `.cargo/config`) is the only path
Cargo reserves.

## 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 rust-lang#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 (rust-lang#6699)
- 2019-12-19: initial implementation (rust-lang#7649)
- 2023-06-21: file extension restriction added (rust-lang#12298)
- 2025-10-30: table syntax support added (rust-lang#16174)
- 2025-10-31: optional field support added (rust-lang#16180)
- 2025-11-21: glob and template syntax restriction added (rust-lang#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 rust-lang#7723
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Command-line config

4 participants