diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index 31be1eb781c..cf406e8ddb2 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -197,13 +197,18 @@ impl<'cfg> PathSource<'cfg> { repo.path().display() ) })?; - let repo_relative_path = root.strip_prefix(repo_root).chain_err(|| { - format!( - "expected git repo {} to be parent of package {}", - repo.path().display(), - root.display() - ) - })?; + let repo_relative_path = match paths::strip_prefix_canonical(root, repo_root) { + Ok(p) => p, + Err(e) => { + log::warn!( + "cannot determine if path `{:?}` is in git repo `{:?}`: {:?}", + root, + repo_root, + e + ); + return Ok(None); + } + }; let manifest_path = repo_relative_path.join("Cargo.toml"); if index.get_path(&manifest_path, 0).is_some() { return Ok(Some(self.list_files_git(pkg, &repo, filter)?)); diff --git a/src/cargo/util/paths.rs b/src/cargo/util/paths.rs index 22a0229a961..7b907128e13 100644 --- a/src/cargo/util/paths.rs +++ b/src/cargo/util/paths.rs @@ -411,3 +411,25 @@ pub fn set_file_time_no_err>(path: P, time: FileTime) { ), } } + +/// Strips `base` from `path`. +/// +/// This canonicalizes both paths before stripping. This is useful if the +/// paths are obtained in different ways, and one or the other may or may not +/// have been normalized in some way. +pub fn strip_prefix_canonical>( + path: P, + base: P, +) -> Result { + // Not all filesystems support canonicalize. Just ignore if it doesn't work. + let safe_canonicalize = |path: &Path| match path.canonicalize() { + Ok(p) => p, + Err(e) => { + log::warn!("cannot canonicalize {:?}: {:?}", path, e); + path.to_path_buf() + } + }; + let canon_path = safe_canonicalize(path.as_ref()); + let canon_base = safe_canonicalize(base.as_ref()); + canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf()) +} diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs index b6f046da664..5ede0bb4870 100644 --- a/tests/testsuite/install.rs +++ b/tests/testsuite/install.rs @@ -10,7 +10,9 @@ use cargo_test_support::install::{ }; use cargo_test_support::paths; use cargo_test_support::registry::Package; -use cargo_test_support::{basic_manifest, cargo_process, project, NO_SUCH_FILE_ERR_MSG}; +use cargo_test_support::{ + basic_manifest, cargo_process, project, symlink_supported, t, NO_SUCH_FILE_ERR_MSG, +}; fn pkg(name: &str, vers: &str) { Package::new(name, vers) @@ -1458,3 +1460,40 @@ fn git_install_reads_workspace_manifest() { .with_stderr_contains(" invalid type: integer `3`[..]") .run(); } + +#[cargo_test] +fn install_git_with_symlink_home() { + // Ensure that `cargo install` with a git repo is OK when CARGO_HOME is a + // symlink, and uses an build script. + if !symlink_supported() { + return; + } + let p = git::new("foo", |p| { + p.file("Cargo.toml", &basic_manifest("foo", "1.0.0")) + .file("src/main.rs", "fn main() {}") + // This triggers discover_git_and_list_files for detecting changed files. + .file("build.rs", "fn main() {}") + }); + #[cfg(unix)] + use std::os::unix::fs::symlink; + #[cfg(windows)] + use std::os::windows::fs::symlink_dir as symlink; + + let actual = paths::root().join("actual-home"); + t!(std::fs::create_dir(&actual)); + t!(symlink(&actual, paths::home().join(".cargo"))); + cargo_process("install --git") + .arg(p.url().to_string()) + .with_stderr( + "\ +[UPDATING] git repository [..] +[INSTALLING] foo v1.0.0 [..] +[COMPILING] foo v1.0.0 [..] +[FINISHED] [..] +[INSTALLING] [..]home/.cargo/bin/foo[..] +[INSTALLED] package `foo [..] +[WARNING] be sure to add [..] +", + ) + .run(); +}