Skip to content

Commit 7ade57b

Browse files
authored
Added symlink resolution for workspace-path-hash (#15400)
### What does this PR try to resolve? This PR adds logic to resolve symlinks before hashing the `workspace-path-hash` template variable for `build.build-dir`. See #14125 (comment) cc: #14125 Note: The behavior on unix systems is unchanged, as the manifest_path was already canonicalized (at least on my system and in CI). However, the Windows behavior did not do this previous. ### How should we test and review this PR? I added a test which runs `cargo build` twice, once from the real directory and once from inside of a symlinked directory, then verifies that hashes match. The change is only a few lines. Most of the diffs are testing code r? @epage
2 parents 9bdf6b0 + 5053393 commit 7ade57b

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed

src/cargo/util/context/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,8 @@ impl GlobalContext {
672672
.to_string(),
673673
),
674674
("{workspace-path-hash}", {
675-
let hash = crate::util::hex::short_hash(&workspace_manifest_path);
675+
let real_path = std::fs::canonicalize(workspace_manifest_path)?;
676+
let hash = crate::util::hex::short_hash(&real_path);
676677
format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
677678
}),
678679
];

tests/testsuite/build_dir.rs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
1212
use std::path::PathBuf;
1313

14-
use cargo_test_support::prelude::*;
1514
use cargo_test_support::{paths, project, str};
15+
use cargo_test_support::{prelude::*, Project};
1616
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX, EXE_SUFFIX};
1717

1818
#[cargo_test]
@@ -569,7 +569,7 @@ fn template_cargo_cache_home() {
569569
}
570570

571571
#[cargo_test]
572-
fn template_workspace_manfiest_path_hash() {
572+
fn template_workspace_path_hash() {
573573
let p = project()
574574
.file("src/main.rs", r#"fn main() { println!("Hello, World!") }"#)
575575
.file(
@@ -609,6 +609,81 @@ fn template_workspace_manfiest_path_hash() {
609609
assert_exists(&p.root().join(&format!("target-dir/debug/foo{EXE_SUFFIX}")));
610610
}
611611

612+
/// Verify that the {workspace-path-hash} does not changes if cargo is run from inside of
613+
/// a symlinked directory.
614+
/// The test approach is to build a project twice from the non-symlinked directory and a symlinked
615+
/// directory and then compare the build-dir paths.
616+
#[cargo_test]
617+
fn template_workspace_path_hash_should_handle_symlink() {
618+
#[cfg(unix)]
619+
use std::os::unix::fs::symlink;
620+
#[cfg(windows)]
621+
use std::os::windows::fs::symlink_dir as symlink;
622+
623+
let p = project()
624+
.file("src/lib.rs", "")
625+
.file(
626+
"Cargo.toml",
627+
r#"
628+
[package]
629+
name = "foo"
630+
version = "1.0.0"
631+
authors = []
632+
edition = "2015"
633+
"#,
634+
)
635+
.file(
636+
".cargo/config.toml",
637+
r#"
638+
[build]
639+
build-dir = "foo/{workspace-path-hash}/build-dir"
640+
"#,
641+
)
642+
.build();
643+
644+
// Build from the non-symlinked directory
645+
p.cargo("check -Z build-dir")
646+
.masquerade_as_nightly_cargo(&["build-dir"])
647+
.enable_mac_dsym()
648+
.run();
649+
650+
// Parse and verify the hash dir created from the non-symlinked dir
651+
let foo_dir = p.root().join("foo");
652+
assert_exists(&foo_dir);
653+
let original_hash_dir = parse_workspace_manifest_path_hash(&foo_dir);
654+
verify_layouts(&p, &original_hash_dir);
655+
656+
// Create a symlink of the project root.
657+
let mut symlinked_dir = p.root().clone();
658+
symlinked_dir.pop();
659+
symlinked_dir = symlinked_dir.join("symlink-dir");
660+
symlink(p.root(), &symlinked_dir).unwrap();
661+
662+
// Remove the foo dir (which contains the build-dir) before we rebuild from a symlinked dir.
663+
foo_dir.rm_rf();
664+
665+
// Run cargo from the symlinked dir
666+
p.cargo("check -Z build-dir")
667+
.cwd(&symlinked_dir)
668+
.masquerade_as_nightly_cargo(&["build-dir"])
669+
.enable_mac_dsym()
670+
.run();
671+
672+
// Parse and verify the hash created from the symlinked dir
673+
assert_exists(&foo_dir);
674+
let symlink_hash_dir = parse_workspace_manifest_path_hash(&foo_dir);
675+
verify_layouts(&p, &symlink_hash_dir);
676+
677+
// Verify the hash dir created from the symlinked and non-symlinked dirs are the same.
678+
assert_eq!(original_hash_dir, symlink_hash_dir);
679+
680+
fn verify_layouts(p: &Project, build_dir_parent: &PathBuf) {
681+
let build_dir = build_dir_parent.as_path().join("build-dir");
682+
assert_build_dir_layout(build_dir, "debug");
683+
assert_artifact_dir_layout(p.root().join("target"), "debug");
684+
}
685+
}
686+
612687
fn parse_workspace_manifest_path_hash(hash_dir: &PathBuf) -> PathBuf {
613688
// Since the hash will change between test runs simply find the first directories and assume
614689
// that is the hash dir. The format is a 2 char directory followed by the remaining hash in the

0 commit comments

Comments
 (0)