Skip to content

Commit aeaf4b9

Browse files
committed
feat(config-include): add optional field support
When `optional=true` Cargo silently skip missing config-include files. Example config: ```toml [[include]] path = 'optional-config.toml' optional = true ```
1 parent e794520 commit aeaf4b9

File tree

2 files changed

+49
-23
lines changed

2 files changed

+49
-23
lines changed

src/cargo/util/context/mod.rs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,8 +1258,12 @@ impl GlobalContext {
12581258
) -> CargoResult<()> {
12591259
let includes = self.include_paths(cv, false)?;
12601260
for include in includes {
1261+
let Some(abs_path) = include.resolve_path(self) else {
1262+
continue;
1263+
};
1264+
12611265
let mut cv = self
1262-
._load_file(&include.abs_path(self), seen, false, WhyLoad::FileDiscovery)
1266+
._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
12631267
.with_context(|| {
12641268
format!(
12651269
"failed to load config include `{}` from `{}`",
@@ -1369,7 +1373,11 @@ impl GlobalContext {
13691373
// Accumulate all values here.
13701374
let mut root = CV::Table(HashMap::new(), value.definition().clone());
13711375
for include in includes {
1372-
self._load_file(&include.abs_path(self), seen, true, why_load)
1376+
let Some(abs_path) = include.resolve_path(self) else {
1377+
continue;
1378+
};
1379+
1380+
self._load_file(&abs_path, seen, true, why_load)
13731381
.and_then(|include| root.merge(include, true))
13741382
.with_context(|| {
13751383
format!(
@@ -1410,7 +1418,20 @@ impl GlobalContext {
14101418
),
14111419
None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
14121420
};
1413-
Ok(ConfigInclude::new(s, def))
1421+
1422+
// Extract optional `include.optional` field
1423+
let optional = match table.remove("optional") {
1424+
Some(CV::Boolean(b, _)) => b,
1425+
Some(other) => bail!(
1426+
"expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1427+
other.desc()
1428+
),
1429+
None => false,
1430+
};
1431+
1432+
let mut include = ConfigInclude::new(s, def);
1433+
include.optional = optional;
1434+
Ok(include)
14141435
}
14151436
other => bail!(
14161437
"expected a string or table, but found {} at `include[{idx}]` in {}",
@@ -2495,17 +2516,20 @@ struct ConfigInclude {
24952516
/// Could be either relative or absolute.
24962517
path: PathBuf,
24972518
def: Definition,
2519+
/// Whether this include is optional (missing files are silently ignored)
2520+
optional: bool,
24982521
}
24992522

25002523
impl ConfigInclude {
25012524
fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
25022525
Self {
25032526
path: p.into(),
25042527
def,
2528+
optional: false,
25052529
}
25062530
}
25072531

2508-
/// Gets the absolute path of the config-include config file.
2532+
/// Resolves the absolute path for this include.
25092533
///
25102534
/// For file based include,
25112535
/// it is relative to parent directory of the config file includes it.
@@ -2514,12 +2538,27 @@ impl ConfigInclude {
25142538
///
25152539
/// For CLI based include (e.g., `--config 'include = "foo.toml"'`),
25162540
/// it is relative to the current working directory.
2517-
fn abs_path(&self, gctx: &GlobalContext) -> PathBuf {
2518-
match &self.def {
2541+
///
2542+
/// Returns `None` if this is an optional include and the file doesn't exist.
2543+
/// Otherwise returns `Some(PathBuf)` with the absolute path.
2544+
fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2545+
let abs_path = match &self.def {
25192546
Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
25202547
Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
25212548
}
2522-
.join(&self.path)
2549+
.join(&self.path);
2550+
2551+
if self.optional && !abs_path.exists() {
2552+
tracing::info!(
2553+
"skipping optional include `{}` in `{}`: file not found at `{}`",
2554+
self.path.display(),
2555+
self.def,
2556+
abs_path.display(),
2557+
);
2558+
None
2559+
} else {
2560+
Some(abs_path)
2561+
}
25232562
}
25242563
}
25252564

tests/testsuite/config_include.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -647,22 +647,9 @@ fn optional_include_missing_and_existing() {
647647

648648
let gctx = GlobalContextBuilder::new()
649649
.unstable_flag("config-include")
650-
.build_err();
651-
assert_error(
652-
gctx.unwrap_err(),
653-
str![[r#"
654-
could not load Cargo configuration
655-
656-
Caused by:
657-
failed to load config include `missing.toml` from `[ROOT]/.cargo/config.toml`
658-
659-
Caused by:
660-
failed to read configuration file `[ROOT]/.cargo/missing.toml`
661-
662-
Caused by:
663-
[NOT_FOUND]
664-
"#]],
665-
);
650+
.build();
651+
assert_eq!(gctx.get::<i32>("key1").unwrap(), 1);
652+
assert_eq!(gctx.get::<i32>("key2").unwrap(), 2);
666653
}
667654

668655
#[cargo_test]

0 commit comments

Comments
 (0)