fix(config): normalize included config paths (#16964)

### What does this PR try to resolve?

Extracted from <https://github.com/rust-lang/cargo/pull/16957>.

Without normalizing included config paths,
`Definition::root()` will do `parent().parent()`
on non-normalized paths containing `..` segments.
And that will remove `..` and result in wrong path resolution.

### How to test and review this PR?

Two tested are added to showcase the buggy behavior, especially
`env_relative_path_included_from_upper_level` which was a wrong path
resolution.
This commit is contained in:
Ed Page
2026-05-04 23:25:34 +00:00
committed by GitHub
2 changed files with 59 additions and 0 deletions
+1
View File
@@ -2347,6 +2347,7 @@ impl ConfigInclude {
Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
}
.join(&self.path);
let abs_path = paths::normalize_path(&abs_path);
if self.optional && !abs_path.exists() {
tracing::info!(
+58
View File
@@ -1,6 +1,7 @@
//! Tests for `include` config field.
use crate::prelude::*;
use cargo_test_support::compare::assert_e2e;
use cargo_test_support::str;
use super::config::GlobalContextBuilder;
@@ -587,3 +588,60 @@ Caused by:
"#]],
);
}
#[cargo_test]
fn env_relative_path_included_from_same_level() {
// See https://github.com/rust-lang/cargo/issues/16954
write_config_at(
"foo/.cargo/config.toml",
"
include = ['../../inc/inc.toml']
[env]
INNER = { value = 'inner-val', relative = true }
",
);
write_config_at(
"inc/inc.toml",
"
[env]
OUTER = { value = 'outer-val', relative = true }
",
);
let gctx = GlobalContextBuilder::new().cwd("foo").build();
let env = gctx.env_config().unwrap();
assert_e2e().eq(
env.get("INNER").unwrap().to_str().unwrap(),
str!["[ROOT]/foo/inner-val"],
);
assert_e2e().eq(
env.get("OUTER").unwrap().to_str().unwrap(),
str!["[ROOT]/outer-val"],
);
}
#[cargo_test]
fn env_relative_path_included_from_upper_level() {
// See https://github.com/rust-lang/cargo/issues/16954
write_config_at(
"outer/foo/.cargo/config.toml",
"
include = ['../../inc.toml']
",
);
write_config_at(
"outer/inc.toml",
"
[env]
MY_ENV = { value = 'val', relative = true }
",
);
let gctx = GlobalContextBuilder::new().cwd("outer/foo").build();
let env = gctx.env_config().unwrap();
assert_e2e().eq(
env.get("MY_ENV").unwrap().to_str().unwrap(),
str!["[ROOT]/val"],
);
}