From c14ccdf9f0b78a52b202c726b2db6c0f34a76738 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Mon, 4 May 2026 17:39:04 -0400 Subject: [PATCH 1/2] test(config): `[env]` relative paths definition https://github.com/rust-lang/cargo/issues/16954 --- tests/testsuite/config_include.rs | 58 +++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/testsuite/config_include.rs b/tests/testsuite/config_include.rs index 8ddb58935..20dc0f4e2 100644 --- a/tests/testsuite/config_include.rs +++ b/tests/testsuite/config_include.rs @@ -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]/foo/.cargo/../../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]/outer/foo/.cargo/../val"], + ); +} From e4e626924c15324ead964468293388d7d2f0d0a9 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Mon, 4 May 2026 17:39:55 -0400 Subject: [PATCH 2/2] fix(config): normalize included config paths 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. --- src/cargo/util/context/mod.rs | 1 + tests/testsuite/config_include.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cargo/util/context/mod.rs b/src/cargo/util/context/mod.rs index e6e41ef71..bcf095f01 100644 --- a/src/cargo/util/context/mod.rs +++ b/src/cargo/util/context/mod.rs @@ -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!( diff --git a/tests/testsuite/config_include.rs b/tests/testsuite/config_include.rs index 20dc0f4e2..abd88d9a0 100644 --- a/tests/testsuite/config_include.rs +++ b/tests/testsuite/config_include.rs @@ -617,7 +617,7 @@ fn env_relative_path_included_from_same_level() { ); assert_e2e().eq( env.get("OUTER").unwrap().to_str().unwrap(), - str!["[ROOT]/foo/.cargo/../../outer-val"], + str!["[ROOT]/outer-val"], ); } @@ -642,6 +642,6 @@ fn env_relative_path_included_from_upper_level() { assert_e2e().eq( env.get("MY_ENV").unwrap().to_str().unwrap(), - str!["[ROOT]/outer/foo/.cargo/../val"], + str!["[ROOT]/val"], ); }