Skip to content

Commit

Permalink
feat(source): wrap PathBuf with PathEntry
Browse files Browse the repository at this point in the history
This gives us more room to store file metadata.
For example, knowing a source file is a symlink
and resolving it when packaging.
  • Loading branch information
weihanglo committed Dec 25, 2024
1 parent 0276088 commit d68eeb0
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 16 deletions.
5 changes: 3 additions & 2 deletions src/cargo/ops/cargo_package/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -396,7 +397,7 @@ fn prepare_archive(
fn build_ar_list(
ws: &Workspace<'_>,
pkg: &Package,
src_files: Vec<PathBuf>,
src_files: Vec<PathEntry>,
vcs_info: Option<vcs::VcsInfo>,
) -> CargoResult<Vec<ArchiveFile>> {
let mut result = HashMap::new();
Expand All @@ -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()),
});
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/cargo/ops/cargo_package/vcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use serde::Serialize;
use tracing::debug;

use crate::core::Package;
use crate::sources::PathEntry;
use crate::CargoResult;
use crate::GlobalContext;

Expand Down Expand Up @@ -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<Option<VcsInfo>> {
Expand Down Expand Up @@ -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<Option<GitVcsInfo>> {
Expand All @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/cargo/ops/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -315,13 +316,14 @@ fn sync(
fn cp_sources(
pkg: &Package,
src: &Path,
paths: &[PathBuf],
paths: &[PathEntry],
dst: &Path,
cksums: &mut BTreeMap<String, String>,
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() {
Expand Down
1 change: 1 addition & 0 deletions src/cargo/sources/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
113 changes: 102 additions & 11 deletions src/cargo/sources/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<PathBuf>> {
pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathEntry>> {
list_files(pkg, self.gctx)
}

Expand Down Expand Up @@ -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<Vec<PathBuf>> {
pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathEntry>> {
list_files(pkg, self.gctx)
}

Expand Down Expand Up @@ -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<fs::FileType> 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<gix::dir::entry::Kind> 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<Package>,
Expand Down Expand Up @@ -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<Vec<PathBuf>> {
pub fn list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathEntry>> {
_list_files(pkg, gctx).with_context(|| {
format!(
"failed to determine list of files in {}",
Expand All @@ -456,7 +534,7 @@ pub fn list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathBu
}

/// See [`PathSource::list_files`].
fn _list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathBuf>> {
fn _list_files(pkg: &Package, gctx: &GlobalContext) -> CargoResult<Vec<PathEntry>> {
let root = pkg.root();
let no_include_option = pkg.manifest().include().is_empty();
let git_repo = if no_include_option {
Expand Down Expand Up @@ -580,7 +658,7 @@ fn list_files_gix(
repo: &gix::Repository,
filter: &dyn Fn(&Path, bool) -> bool,
gctx: &GlobalContext,
) -> CargoResult<Vec<PathBuf>> {
) -> CargoResult<Vec<PathEntry>> {
debug!("list_files_gix {}", pkg.package_id());
let options = repo
.dirwalk_options()?
Expand Down Expand Up @@ -619,7 +697,7 @@ fn list_files_gix(
vec![include, exclude]
};

let mut files = Vec::<PathBuf>::new();
let mut files = Vec::<PathEntry>::new();
let mut subpackages_found = Vec::new();
for item in repo
.dirwalk_iter(index.clone(), pathspec, Default::default(), options)?
Expand Down Expand Up @@ -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),
});
}
}

Expand All @@ -715,7 +796,7 @@ fn list_files_gix(
/// is not tracked under a Git repository.
fn list_files_walk(
path: &Path,
ret: &mut Vec<PathBuf>,
ret: &mut Vec<PathEntry>,
is_root: bool,
filter: &dyn Fn(&Path, bool) -> bool,
gctx: &GlobalContext,
Expand Down Expand Up @@ -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() => {
Expand All @@ -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()),
},
}
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit d68eeb0

Please sign in to comment.