From dc0d1133f93c7c1dfbf4a0397ba29ce6cfb98738 Mon Sep 17 00:00:00 2001 From: Ross Sullivan Date: Sun, 6 Apr 2025 15:36:32 +0900 Subject: [PATCH 1/2] test(build-dir): Added test for workspace-path-hash symlink handling --- tests/testsuite/build_dir.rs | 84 +++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/tests/testsuite/build_dir.rs b/tests/testsuite/build_dir.rs index bb0abe33d..c5a1ecd45 100644 --- a/tests/testsuite/build_dir.rs +++ b/tests/testsuite/build_dir.rs @@ -11,8 +11,8 @@ use std::path::PathBuf; -use cargo_test_support::prelude::*; use cargo_test_support::{paths, project, str}; +use cargo_test_support::{prelude::*, Project}; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX, EXE_SUFFIX}; #[cargo_test] @@ -569,7 +569,7 @@ fn template_cargo_cache_home() { } #[cargo_test] -fn template_workspace_manfiest_path_hash() { +fn template_workspace_path_hash() { let p = project() .file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#) .file( @@ -609,6 +609,86 @@ fn template_workspace_manfiest_path_hash() { assert_exists(&p.root().join(&format!("target-dir/debug/foo{EXE_SUFFIX}"))); } +/// Verify that the {workspace-path-hash} does not changes if cargo is run from inside of +/// a symlinked directory. +/// The test approach is to build a project twice from the non-symlinked directory and a symlinked +/// directory and then compare the build-dir paths. +#[cargo_test] +fn template_workspace_path_hash_should_handle_symlink() { + #[cfg(unix)] + use std::os::unix::fs::symlink; + #[cfg(windows)] + use std::os::windows::fs::symlink_dir as symlink; + + let p = project() + .file("src/lib.rs", "") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + authors = [] + edition = "2015" + "#, + ) + .file( + ".cargo/config.toml", + r#" + [build] + build-dir = "foo/{workspace-path-hash}/build-dir" + "#, + ) + .build(); + + // Build from the non-symlinked directory + p.cargo("check -Z build-dir") + .masquerade_as_nightly_cargo(&["build-dir"]) + .enable_mac_dsym() + .run(); + + // Parse and verify the hash dir created from the non-symlinked dir + let foo_dir = p.root().join("foo"); + assert_exists(&foo_dir); + let original_hash_dir = parse_workspace_manifest_path_hash(&foo_dir); + verify_layouts(&p, &original_hash_dir); + + // Create a symlink of the project root. + let mut symlinked_dir = p.root().clone(); + symlinked_dir.pop(); + symlinked_dir = symlinked_dir.join("symlink-dir"); + symlink(p.root(), &symlinked_dir).unwrap(); + + // Remove the foo dir (which contains the build-dir) before we rebuild from a symlinked dir. + foo_dir.rm_rf(); + + // Run cargo from the symlinked dir + p.cargo("check -Z build-dir") + .cwd(&symlinked_dir) + .masquerade_as_nightly_cargo(&["build-dir"]) + .enable_mac_dsym() + .run(); + + // Parse and verify the hash created from the symlinked dir + assert_exists(&foo_dir); + let symlink_hash_dir = parse_workspace_manifest_path_hash(&foo_dir); + verify_layouts(&p, &symlink_hash_dir); + + // Verify the current behavior: + // unix: hash dir created from the symlinked and non-symlinked dirs are the same. + #[cfg(unix)] + assert_eq!(original_hash_dir, symlink_hash_dir); + // windows: hash dir created from the symlinked and non-symlinked dirs are different. + #[cfg(windows)] + assert_ne!(original_hash_dir, symlink_hash_dir); + + fn verify_layouts(p: &Project, build_dir_parent: &PathBuf) { + let build_dir = build_dir_parent.as_path().join("build-dir"); + assert_build_dir_layout(build_dir, "debug"); + assert_artifact_dir_layout(p.root().join("target"), "debug"); + } +} + fn parse_workspace_manifest_path_hash(hash_dir: &PathBuf) -> PathBuf { // Since the hash will change between test runs simply find the first directories and assume // that is the hash dir. The format is a 2 char directory followed by the remaining hash in the From 50533934d0c73b86750c2a7ac76e154f00b99e9a Mon Sep 17 00:00:00 2001 From: Ross Sullivan Date: Sun, 6 Apr 2025 16:32:43 +0900 Subject: [PATCH 2/2] feat(build-dir): Resolve symlinks before hashing `workspace-path-hash` This commit resolves symlinks in the manifest path before hashing it. --- src/cargo/util/context/mod.rs | 3 ++- tests/testsuite/build_dir.rs | 7 +------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/cargo/util/context/mod.rs b/src/cargo/util/context/mod.rs index 6764e5961..8ad723293 100644 --- a/src/cargo/util/context/mod.rs +++ b/src/cargo/util/context/mod.rs @@ -672,7 +672,8 @@ impl GlobalContext { .to_string(), ), ("{workspace-path-hash}", { - let hash = crate::util::hex::short_hash(&workspace_manifest_path); + let real_path = std::fs::canonicalize(workspace_manifest_path)?; + let hash = crate::util::hex::short_hash(&real_path); format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..]) }), ]; diff --git a/tests/testsuite/build_dir.rs b/tests/testsuite/build_dir.rs index c5a1ecd45..7854bdebb 100644 --- a/tests/testsuite/build_dir.rs +++ b/tests/testsuite/build_dir.rs @@ -674,13 +674,8 @@ fn template_workspace_path_hash_should_handle_symlink() { let symlink_hash_dir = parse_workspace_manifest_path_hash(&foo_dir); verify_layouts(&p, &symlink_hash_dir); - // Verify the current behavior: - // unix: hash dir created from the symlinked and non-symlinked dirs are the same. - #[cfg(unix)] + // Verify the hash dir created from the symlinked and non-symlinked dirs are the same. assert_eq!(original_hash_dir, symlink_hash_dir); - // windows: hash dir created from the symlinked and non-symlinked dirs are different. - #[cfg(windows)] - assert_ne!(original_hash_dir, symlink_hash_dir); fn verify_layouts(p: &Project, build_dir_parent: &PathBuf) { let build_dir = build_dir_parent.as_path().join("build-dir");