From abc8d357090d20153576f82c57d1ac2b480fe794 Mon Sep 17 00:00:00 2001 From: Fahd Ashour Date: Mon, 8 Dec 2025 15:59:17 +0200 Subject: [PATCH 1/2] add test for cargo_home symlink duplicate load --- tests/testsuite/config.rs | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index 8d6d0478f6f..7ae1b9d9e57 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -2589,3 +2589,71 @@ fn mixed_type_array() { } ); } + +#[cargo_test] +fn config_symlink_home_duplicate_load_bug() { + // Test that when CARGO_HOME is accessed via a symlink that points to a directory + // already in the config search path, the config file is not loaded twice. + + use cargo_test_support::basic_manifest; + + #[cfg(unix)] + use std::os::unix::fs::symlink; + + #[cfg(windows)] + use std::os::windows::fs::symlink_dir as symlink; + + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("src/lib.rs", "") + .build(); + + // Create directory structure a/b/ and symlink c -> a + let a_dir = p.root().join("a"); + let b_dir = a_dir.join("b"); + let c_symlink = p.root().join("c"); + + fs::create_dir_all(&b_dir).unwrap(); + symlink(&a_dir, &c_symlink).unwrap(); + + // Create config file in a/.cargo/ + let cargo_config_dir = a_dir.join(".cargo"); + fs::create_dir(&cargo_config_dir).unwrap(); + let config_path = cargo_config_dir.join("config.toml"); + fs::write( + &config_path, + r#" +[build] +rustdocflags = ["--default-theme=dark"] +"#, + ) + .unwrap(); + + // Move the project into a/b/ + let project_in_b = b_dir.join("foo"); + fs::create_dir(&project_in_b).unwrap(); + fs::write( + project_in_b.join("Cargo.toml"), + &basic_manifest("foo", "0.1.0"), + ) + .unwrap(); + fs::create_dir(project_in_b.join("src")).unwrap(); + fs::write(project_in_b.join("src/lib.rs"), "").unwrap(); + + // Set CARGO_HOME to ../../c/.cargo (which is really a/.cargo via symlink) + let cargo_home = c_symlink.join(".cargo"); + + // If config is loaded twice, rustdocflags will be duplicated and cause an error + p.cargo("doc") + .cwd(&project_in_b) + .env("CARGO_HOME", &cargo_home) + .with_status(101) + .with_stderr_data(str![[r#" +[DOCUMENTING] foo v0.1.0 ([ROOT]/foo/a/b/foo) +[ERROR] Option 'default-theme' given more than once + +[ERROR] could not document `foo` + +"#]]) + .run(); +} From e5889cfc753e75c5c3432d4ffde0681a26b4c691 Mon Sep 17 00:00:00 2001 From: Fahd Ashour Date: Mon, 8 Dec 2025 16:01:23 +0200 Subject: [PATCH 2/2] add canonical_home and compare against seen_dir --- src/cargo/util/context/mod.rs | 8 ++++++-- tests/testsuite/config.rs | 10 +--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/cargo/util/context/mod.rs b/src/cargo/util/context/mod.rs index 7ae15c17ac6..a3f2e470553 100644 --- a/src/cargo/util/context/mod.rs +++ b/src/cargo/util/context/mod.rs @@ -1678,13 +1678,17 @@ impl GlobalContext { if let Some(path) = self.get_file_path(&config_root, "config", true)? { walk(&path)?; } - seen_dir.insert(config_root); + + let canonical_root = config_root.canonicalize().unwrap_or(config_root); + seen_dir.insert(canonical_root); } + let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf()); + // Once we're done, also be sure to walk the home directory even if it's not // in our history to be sure we pick up that standard location for // information. - if !seen_dir.contains(home) { + if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) { if let Some(path) = self.get_file_path(home, "config", true)? { walk(&path)?; } diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index 7ae1b9d9e57..648958f3326 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -2591,7 +2591,7 @@ fn mixed_type_array() { } #[cargo_test] -fn config_symlink_home_duplicate_load_bug() { +fn config_symlink_home_duplicate_load() { // Test that when CARGO_HOME is accessed via a symlink that points to a directory // already in the config search path, the config file is not loaded twice. @@ -2647,13 +2647,5 @@ rustdocflags = ["--default-theme=dark"] p.cargo("doc") .cwd(&project_in_b) .env("CARGO_HOME", &cargo_home) - .with_status(101) - .with_stderr_data(str![[r#" -[DOCUMENTING] foo v0.1.0 ([ROOT]/foo/a/b/foo) -[ERROR] Option 'default-theme' given more than once - -[ERROR] could not document `foo` - -"#]]) .run(); }