diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs index 07ba919c98af..6c920eec4e35 100644 --- a/src/cargo/ops/cargo_package/mod.rs +++ b/src/cargo/ops/cargo_package/mod.rs @@ -16,6 +16,7 @@ use crate::core::Workspace; use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId}; use crate::ops::lockfile::LOCKFILE_NAME; use crate::ops::registry::{infer_registry, RegistryOrIndex}; +use crate::sources::path::PathEntry; use crate::sources::registry::index::{IndexPackage, RegistryDependency}; use crate::sources::{PathSource, CRATES_IO_REGISTRY}; use crate::util::cache_lock::CacheLockMode; @@ -396,7 +397,7 @@ fn prepare_archive( fn build_ar_list( ws: &Workspace<'_>, pkg: &Package, - src_files: Vec, + src_files: Vec, vcs_info: Option, ) -> CargoResult> { let mut result = HashMap::new(); @@ -420,7 +421,7 @@ fn build_ar_list( .push(ArchiveFile { rel_path: rel_path.to_owned(), rel_str: rel_str.to_owned(), - contents: FileContents::OnDisk(src_file.clone()), + contents: FileContents::OnDisk(src_file.to_path_buf()), }); } } diff --git a/src/cargo/ops/cargo_package/vcs.rs b/src/cargo/ops/cargo_package/vcs.rs index 44adfd85a1ac..20dbedc7cb43 100644 --- a/src/cargo/ops/cargo_package/vcs.rs +++ b/src/cargo/ops/cargo_package/vcs.rs @@ -9,6 +9,7 @@ use serde::Serialize; use tracing::debug; use crate::core::Package; +use crate::sources::PathEntry; use crate::CargoResult; use crate::GlobalContext; @@ -41,7 +42,7 @@ pub struct GitVcsInfo { #[tracing::instrument(skip_all)] pub fn check_repo_state( p: &Package, - src_files: &[PathBuf], + src_files: &[PathEntry], gctx: &GlobalContext, opts: &PackageOpts<'_>, ) -> CargoResult> { @@ -114,7 +115,7 @@ pub fn check_repo_state( fn git( pkg: &Package, gctx: &GlobalContext, - src_files: &[PathBuf], + src_files: &[PathEntry], repo: &git2::Repository, opts: &PackageOpts<'_>, ) -> CargoResult> { @@ -136,6 +137,7 @@ fn git( let mut dirty_src_files: Vec<_> = src_files .iter() .filter(|src_file| dirty_files.iter().any(|path| src_file.starts_with(path))) + .map(|p| p.as_path_buf()) .chain(dirty_metadata_paths(pkg, repo)?.iter()) .map(|path| { pathdiff::diff_paths(path, cwd) diff --git a/src/cargo/ops/vendor.rs b/src/cargo/ops/vendor.rs index 35d0e0c9417c..63e4b436dc92 100644 --- a/src/cargo/ops/vendor.rs +++ b/src/cargo/ops/vendor.rs @@ -2,6 +2,7 @@ use crate::core::shell::Verbosity; use crate::core::{GitReference, Package, Workspace}; use crate::ops; use crate::sources::path::PathSource; +use crate::sources::PathEntry; use crate::sources::CRATES_IO_REGISTRY; use crate::util::cache_lock::CacheLockMode; use crate::util::{try_canonicalize, CargoResult, GlobalContext}; @@ -315,13 +316,14 @@ fn sync( fn cp_sources( pkg: &Package, src: &Path, - paths: &[PathBuf], + paths: &[PathEntry], dst: &Path, cksums: &mut BTreeMap, tmp_buf: &mut [u8], gctx: &GlobalContext, ) -> CargoResult<()> { for p in paths { + let p = p.as_path(); let relative = p.strip_prefix(&src).unwrap(); match relative.to_str() { diff --git a/src/cargo/sources/mod.rs b/src/cargo/sources/mod.rs index 9c98cc49eaa5..dfbf79c76bc4 100644 --- a/src/cargo/sources/mod.rs +++ b/src/cargo/sources/mod.rs @@ -29,6 +29,7 @@ pub use self::config::SourceConfigMap; pub use self::directory::DirectorySource; pub use self::git::GitSource; +pub use self::path::PathEntry; pub use self::path::PathSource; pub use self::path::RecursivePathSource; pub use self::registry::{ diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index ee2e6fea47f2..7a4e3c303d2a 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -94,7 +94,7 @@ impl<'gctx> PathSource<'gctx> { /// use other methods like `.gitignore`, `package.include`, or /// `package.exclude` to filter the list of files. #[tracing::instrument(skip_all)] - pub fn list_files(&self, pkg: &Package) -> CargoResult> { + pub fn list_files(&self, pkg: &Package) -> CargoResult> { list_files(pkg, self.gctx) } @@ -278,7 +278,7 @@ impl<'gctx> RecursivePathSource<'gctx> { /// are relevant for building this package, but it also contains logic to /// use other methods like `.gitignore`, `package.include`, or /// `package.exclude` to filter the list of files. - pub fn list_files(&self, pkg: &Package) -> CargoResult> { + pub fn list_files(&self, pkg: &Package) -> CargoResult> { list_files(pkg, self.gctx) } @@ -404,6 +404,84 @@ impl<'gctx> Source for RecursivePathSource<'gctx> { } } +/// Type that abstracts over [`gix::dir::entry::Kind`] and [`fs::FileType`]. +#[derive(Debug, Clone, Copy)] +enum FileType { + File, + Dir, + Symlink, + Other, +} + +impl From for FileType { + fn from(value: fs::FileType) -> Self { + if value.is_file() { + FileType::File + } else if value.is_dir() { + FileType::Dir + } else if value.is_symlink() { + FileType::Symlink + } else { + FileType::Other + } + } +} + +impl From for FileType { + fn from(value: gix::dir::entry::Kind) -> Self { + use gix::dir::entry::Kind; + match value { + Kind::Untrackable => FileType::Other, + Kind::File => FileType::File, + Kind::Symlink => FileType::Symlink, + Kind::Directory | Kind::Repository => FileType::Dir, + } + } +} + +/// [`PathBuf`] with extra metadata. +#[derive(Clone)] +pub struct PathEntry { + path: PathBuf, + ty: FileType, +} + +impl PathEntry { + pub fn into_path_buf(self) -> PathBuf { + self.path + } + + pub fn as_path_buf(&self) -> &PathBuf { + &self.path + } + + pub fn is_file(&self) -> bool { + matches!(self.ty, FileType::File) + } + + pub fn is_dir(&self) -> bool { + matches!(self.ty, FileType::Dir) + } + + pub fn is_symlink(&self) -> bool { + matches!(self.ty, FileType::Symlink) + } +} + +impl fmt::Debug for PathEntry { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.path.fmt(f) + } +} + +impl std::ops::Deref for PathEntry { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.path + } +} + fn first_package<'p>( pkg_id: PackageId, pkgs: &'p Vec, @@ -446,7 +524,7 @@ fn first_package<'p>( /// are relevant for building this package, but it also contains logic to /// use other methods like `.gitignore`, `package.include`, or /// `package.exclude` to filter the list of files. -pub fn list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult> { +pub fn list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult> { _list_files(pkg, gctx).with_context(|| { format!( "failed to determine list of files in {}", @@ -456,7 +534,7 @@ pub fn list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult CargoResult> { +fn _list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult> { let root = pkg.root(); let no_include_option = pkg.manifest().include().is_empty(); let git_repo = if no_include_option { @@ -580,7 +658,7 @@ fn list_files_gix( repo: &gix::Repository, filter: &dyn Fn(&Path, bool) -> bool, gctx: &GlobalContext, -) -> CargoResult> { +) -> CargoResult> { debug!("list_files_gix {}", pkg.package_id()); let options = repo .dirwalk_options()? @@ -619,7 +697,7 @@ fn list_files_gix( vec![include, exclude] }; - let mut files = Vec::::new(); + let mut files = Vec::::new(); let mut subpackages_found = Vec::new(); for item in repo .dirwalk_iter(index.clone(), pathspec, Default::default(), options)? @@ -701,7 +779,10 @@ fn list_files_gix( } else if (filter)(&file_path, is_dir) { assert!(!is_dir); trace!(" found {}", file_path.display()); - files.push(file_path); + files.push(PathEntry { + path: file_path, + ty: kind.map(Into::into).unwrap_or(FileType::Other), + }); } } @@ -715,7 +796,7 @@ fn list_files_gix( /// is not tracked under a Git repository. fn list_files_walk( path: &Path, - ret: &mut Vec, + ret: &mut Vec, is_root: bool, filter: &dyn Fn(&Path, bool) -> bool, gctx: &GlobalContext, @@ -756,7 +837,14 @@ fn list_files_walk( Ok(entry) => { let file_type = entry.file_type(); if file_type.is_file() || file_type.is_symlink() { - ret.push(entry.into_path()); + // We follow_links(true) here so check if entry was created from a symlink + let ty = if entry.path_is_symlink() { + FileType::Symlink + } else { + entry.file_type().into() + }; + let path = entry.into_path(); + ret.push(PathEntry { path, ty }); } } Err(err) if err.loop_ancestor().is_some() => { @@ -770,7 +858,10 @@ fn list_files_walk( // Otherwise, simply recover from it. // Don't worry about error skipping here, the callers would // still hit the IO error if they do access it thereafter. - Some(path) => ret.push(path.to_path_buf()), + Some(path) => ret.push(PathEntry { + path: path.to_path_buf(), + ty: FileType::Other, + }), None => return Err(err.into()), }, } @@ -801,7 +892,7 @@ fn last_modified_file( let mtime = paths::mtime(&file).unwrap_or_else(|_| FileTime::zero()); if mtime > max { max = mtime; - max_path = file; + max_path = file.into_path_buf(); } } trace!("last modified file {}: {}", path.display(), max);