diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index cd0330913..52cdcb822 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -19,13 +19,12 @@ use std::env; use std::ffi::{OsStr, OsString}; use std::fs::{self, File}; use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::Arc; use failure::Error; use lazycell::LazyCell; use log::debug; -use same_file::is_same_file; use serde::Serialize; pub use self::build_config::{BuildConfig, CompileMode, MessageFormat}; @@ -443,12 +442,12 @@ fn link_targets<'a, 'cfg>( } }; destinations.push(dst.clone()); - hardlink_or_copy(src, dst)?; + paths::link_or_copy(src, dst)?; if let Some(ref path) = output.export_path { let export_dir = export_dir.as_ref().unwrap(); paths::create_dir_all(export_dir)?; - hardlink_or_copy(src, path)?; + paths::link_or_copy(src, path)?; } } @@ -477,54 +476,6 @@ fn link_targets<'a, 'cfg>( })) } -/// Hardlink (file) or symlink (dir) src to dst if possible, otherwise copy it. -fn hardlink_or_copy(src: &Path, dst: &Path) -> CargoResult<()> { - debug!("linking {} to {}", src.display(), dst.display()); - if is_same_file(src, dst).unwrap_or(false) { - return Ok(()); - } - - // NB: we can't use dst.exists(), as if dst is a broken symlink, - // dst.exists() will return false. This is problematic, as we still need to - // unlink dst in this case. symlink_metadata(dst).is_ok() will tell us - // whether dst exists *without* following symlinks, which is what we want. - if fs::symlink_metadata(dst).is_ok() { - paths::remove_file(&dst)?; - } - - let link_result = if src.is_dir() { - #[cfg(target_os = "redox")] - use std::os::redox::fs::symlink; - #[cfg(unix)] - use std::os::unix::fs::symlink; - #[cfg(windows)] - use std::os::windows::fs::symlink_dir as symlink; - - let dst_dir = dst.parent().unwrap(); - let src = if src.starts_with(dst_dir) { - src.strip_prefix(dst_dir).unwrap() - } else { - src - }; - symlink(src, dst) - } else { - fs::hard_link(src, dst) - }; - link_result - .or_else(|err| { - debug!("link failed {}. falling back to fs::copy", err); - fs::copy(src, dst).map(|_| ()) - }) - .chain_err(|| { - format!( - "failed to link or copy `{}` to `{}`", - src.display(), - dst.display() - ) - })?; - Ok(()) -} - // For all plugin dependencies, add their -L paths (now calculated and present // in `build_script_outputs`) to the dynamic library load path for the command // to execute. diff --git a/src/cargo/util/paths.rs b/src/cargo/util/paths.rs index 1e400f130..b2327e325 100644 --- a/src/cargo/util/paths.rs +++ b/src/cargo/util/paths.rs @@ -340,3 +340,63 @@ fn set_not_readonly(p: &Path) -> io::Result { fs::set_permissions(p, perms)?; Ok(true) } + +/// Hardlink (file) or symlink (dir) src to dst if possible, otherwise copy it. +/// +/// If the destination already exists, it is removed before linking. +pub fn link_or_copy(src: impl AsRef, dst: impl AsRef) -> CargoResult<()> { + let src = src.as_ref(); + let dst = dst.as_ref(); + _link_or_copy(src, dst) +} + +fn _link_or_copy(src: &Path, dst: &Path) -> CargoResult<()> { + log::debug!("linking {} to {}", src.display(), dst.display()); + if same_file::is_same_file(src, dst).unwrap_or(false) { + return Ok(()); + } + + // NB: we can't use dst.exists(), as if dst is a broken symlink, + // dst.exists() will return false. This is problematic, as we still need to + // unlink dst in this case. symlink_metadata(dst).is_ok() will tell us + // whether dst exists *without* following symlinks, which is what we want. + if fs::symlink_metadata(dst).is_ok() { + remove_file(&dst)?; + } + + let link_result = if src.is_dir() { + #[cfg(target_os = "redox")] + use std::os::redox::fs::symlink; + #[cfg(unix)] + use std::os::unix::fs::symlink; + #[cfg(windows)] + // FIXME: This should probably panic or have a copy fallback. Symlinks + // are not supported in all windows environments. Currently symlinking + // is only used for .dSYM directories on macos, but this shouldn't be + // accidentally relied upon. + use std::os::windows::fs::symlink_dir as symlink; + + let dst_dir = dst.parent().unwrap(); + let src = if src.starts_with(dst_dir) { + src.strip_prefix(dst_dir).unwrap() + } else { + src + }; + symlink(src, dst) + } else { + fs::hard_link(src, dst) + }; + link_result + .or_else(|err| { + log::debug!("link failed {}. falling back to fs::copy", err); + fs::copy(src, dst).map(|_| ()) + }) + .chain_err(|| { + format!( + "failed to link or copy `{}` to `{}`", + src.display(), + dst.display() + ) + })?; + Ok(()) +}