use crate::util::data_structures::{HashMap, HashSet};
use std::cell::{Cell, RefCell};
use std::fmt::{self, Debug, Formatter};
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use crate::ops;
use crate::sources::IndexSummary;
use crate::sources::source::MaybePackage;
use crate::sources::source::QueryKind;
use crate::sources::source::Source;
use crate::util::GlobalContext;
use crate::util::errors::CargoResult;
use crate::util::important_paths::find_project_manifest_exact;
use crate::util::internal;
use crate::workspace::parser::read_manifest;
use crate::workspace::{Dependency, EitherManifest, Manifest, Package, PackageId, SourceId};
use anyhow::Context as _;
use cargo_util::paths;
use filetime::FileTime;
use gix::bstr::{BString, ByteVec};
use gix::dir::entry::Status;
use gix::index::entry::Stage;
use ignore::gitignore::GitignoreBuilder;
use tracing::{debug, info, trace, warn};
use walkdir::WalkDir;
pub struct PathSource<'gctx> {
source_id: SourceId,
path: PathBuf,
package: RefCell<Option<Option<Package>>>,
gctx: &'gctx GlobalContext,
}
impl<'gctx> PathSource<'gctx> {
pub fn new(path: &Path, source_id: SourceId, gctx: &'gctx GlobalContext) -> Self {
Self {
source_id,
path: path.to_path_buf(),
package: RefCell::new(None),
gctx,
}
}
pub fn preload_with(pkg: Package, gctx: &'gctx GlobalContext) -> Self {
let source_id = pkg.package_id().source_id();
let path = pkg.root().to_owned();
Self {
source_id,
path,
package: RefCell::new(Some(Some(pkg))),
gctx,
}
}
pub fn root_package(&self) -> CargoResult<Package> {
trace!("root_package; source={:?}", self);
self.load()?;
match &*self.package.borrow() {
Some(Some(pkg)) => Ok(pkg.clone()),
Some(None) | None => Err(anyhow::format_err!(
"failed to read `{}`",
self.path.join("Cargo.toml").display()
)),
}
}
#[tracing::instrument(skip_all)]
pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathEntry>> {
list_files(pkg, self.gctx)
}
fn last_modified_file(&self, pkg: &Package) -> CargoResult<(FileTime, PathBuf)> {
if self.package.borrow().is_none() {
return Err(internal(format!(
"BUG: source `{:?}` was not loaded",
self.path
)));
}
last_modified_file(&self.path, pkg, self.gctx)
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn load(&self) -> CargoResult<()> {
let mut package = self.package.borrow_mut();
if package.is_none() {
*package = Some(self.read_package()?);
}
Ok(())
}
fn read_package(&self) -> CargoResult<Option<Package>> {
let path = self.path.join("Cargo.toml");
if !path.exists() {
return Ok(None);
}
Ok(Some(ops::read_package(&path, self.source_id, self.gctx)?))
}
}
impl<'gctx> Debug for PathSource<'gctx> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "the paths source")
}
}
#[async_trait::async_trait(?Send)]
impl<'gctx> Source for PathSource<'gctx> {
async fn query(
&self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()> {
self.load()?;
if let Some(Some(p)) = &*self.package.borrow() {
let s = p.summary();
let matched = match kind {
QueryKind::Exact | QueryKind::RejectedVersions => dep.matches(s),
QueryKind::AlternativeNames => true,
QueryKind::Normalized => dep.matches(s),
};
if matched {
f(IndexSummary::Candidate(s.clone()))
}
}
Ok(())
}
fn supports_checksums(&self) -> bool {
false
}
fn requires_precise(&self) -> bool {
false
}
fn source_id(&self) -> SourceId {
self.source_id
}
async fn download(&self, id: PackageId) -> CargoResult<MaybePackage> {
trace!("getting packages; id={}", id);
self.load()?;
let pkg = self.package.borrow();
let pkg = pkg
.as_ref()
.and_then(|p| p.as_ref())
.filter(|pkg| pkg.package_id() == id);
pkg.cloned()
.map(MaybePackage::Ready)
.ok_or_else(|| internal(format!("failed to find {} in path source", id)))
}
async fn finish_download(&self, _id: PackageId, _data: Vec<u8>) -> CargoResult<Package> {
panic!("no download should have started")
}
fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
let (max, max_path) = self.last_modified_file(pkg)?;
let max_path = max_path.strip_prefix(&self.path).unwrap_or(&max_path);
Ok(format!("{} ({})", max, max_path.display()))
}
fn describe(&self) -> String {
match self.source_id.url().to_file_path() {
Ok(path) => path.display().to_string(),
Err(_) => self.source_id.to_string(),
}
}
fn invalidate_cache(&self) {
}
fn set_quiet(&mut self, _quiet: bool) {
}
}
pub struct RecursivePathSource<'gctx> {
source_id: SourceId,
path: PathBuf,
loaded: Cell<bool>,
packages: RefCell<HashMap<PackageId, Vec<Package>>>,
warned_duplicate: RefCell<HashSet<PackageId>>,
gctx: &'gctx GlobalContext,
}
impl<'gctx> RecursivePathSource<'gctx> {
pub fn new(root: &Path, source_id: SourceId, gctx: &'gctx GlobalContext) -> Self {
Self {
source_id,
path: root.to_path_buf(),
loaded: Cell::new(false),
packages: Default::default(),
warned_duplicate: Default::default(),
gctx,
}
}
pub fn read_packages(&self) -> CargoResult<Vec<Package>> {
self.load()?;
Ok(self
.packages
.borrow()
.iter()
.map(|(pkg_id, v)| {
first_package(
*pkg_id,
v,
&mut self.warned_duplicate.borrow_mut(),
self.gctx,
)
.clone()
})
.collect())
}
pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<PathEntry>> {
list_files(pkg, self.gctx)
}
fn last_modified_file(&self, pkg: &Package) -> CargoResult<(FileTime, PathBuf)> {
if !self.loaded.get() {
return Err(internal(format!(
"BUG: source `{:?}` was not loaded",
self.path
)));
}
last_modified_file(&self.path, pkg, self.gctx)
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn load(&self) -> CargoResult<()> {
if !self.loaded.get() {
self.packages
.replace(read_packages(&self.path, self.source_id, self.gctx)?);
self.loaded.set(true);
}
Ok(())
}
}
impl<'gctx> Debug for RecursivePathSource<'gctx> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "the paths source")
}
}
#[async_trait::async_trait(?Send)]
impl<'gctx> Source for RecursivePathSource<'gctx> {
async fn query(
&self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()> {
self.load()?;
for s in self
.packages
.borrow()
.iter()
.filter(|(pkg_id, _)| pkg_id.name() == dep.package_name())
.map(|(pkg_id, pkgs)| {
first_package(
*pkg_id,
pkgs,
&mut self.warned_duplicate.borrow_mut(),
self.gctx,
)
})
.map(|p| p.summary())
{
let matched = match kind {
QueryKind::Exact | QueryKind::RejectedVersions => dep.matches(s),
QueryKind::AlternativeNames => true,
QueryKind::Normalized => dep.matches(s),
};
if matched {
f(IndexSummary::Candidate(s.clone()))
}
}
Ok(())
}
fn supports_checksums(&self) -> bool {
false
}
fn requires_precise(&self) -> bool {
false
}
fn source_id(&self) -> SourceId {
self.source_id
}
async fn download(&self, id: PackageId) -> CargoResult<MaybePackage> {
trace!("getting packages; id={}", id);
self.load()?;
let pkgs = self.packages.borrow();
let pkg = pkgs.get(&id);
pkg.map(|pkgs| {
first_package(id, pkgs, &mut self.warned_duplicate.borrow_mut(), self.gctx).clone()
})
.map(MaybePackage::Ready)
.ok_or_else(|| internal(format!("failed to find {} in path source", id)))
}
async fn finish_download(&self, _id: PackageId, _data: Vec<u8>) -> CargoResult<Package> {
panic!("no download should have started")
}
fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
let (max, max_path) = self.last_modified_file(pkg)?;
let max_path = max_path.strip_prefix(&self.path).unwrap_or(&max_path);
Ok(format!("{} ({})", max, max_path.display()))
}
fn describe(&self) -> String {
match self.source_id.url().to_file_path() {
Ok(path) => path.display().to_string(),
Err(_) => self.source_id.to_string(),
}
}
fn invalidate_cache(&self) {
}
fn set_quiet(&mut self, _quiet: bool) {
}
}
#[derive(Debug, Clone, Copy)]
enum FileType {
File { maybe_symlink: bool },
Dir,
Symlink,
Other,
}
impl From<fs::FileType> for FileType {
fn from(value: fs::FileType) -> Self {
if value.is_file() {
FileType::File {
maybe_symlink: false,
}
} 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 {
maybe_symlink: false,
},
Kind::Symlink => FileType::Symlink,
Kind::Directory | Kind::Repository => FileType::Dir,
}
}
}
#[derive(Clone, Debug)]
pub struct PathEntry {
path: PathBuf,
ty: FileType,
under_symlink_dir: bool,
}
impl PathEntry {
pub fn into_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)
}
pub fn is_symlink_or_under_symlink(&self) -> bool {
self.is_symlink() || self.under_symlink_dir
}
pub fn maybe_plain_text_symlink(&self) -> bool {
matches!(
self.ty,
FileType::File {
maybe_symlink: true
}
)
}
}
impl std::ops::Deref for PathEntry {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.path.as_path()
}
}
impl AsRef<PathBuf> for PathEntry {
fn as_ref(&self) -> &PathBuf {
&self.path
}
}
fn first_package<'p>(
pkg_id: PackageId,
pkgs: &'p Vec<Package>,
warned_duplicate: &mut HashSet<PackageId>,
gctx: &GlobalContext,
) -> &'p Package {
if pkgs.len() != 1 && warned_duplicate.insert(pkg_id) {
let ignored = pkgs[1..]
.iter()
.filter(|pkg| pkg.publish().is_none())
.collect::<Vec<_>>();
if !ignored.is_empty() {
use std::fmt::Write as _;
let plural = if ignored.len() == 1 { "" } else { "s" };
let mut msg = String::new();
let _ = writeln!(&mut msg, "skipping duplicate package{plural} `{pkg_id}`:");
for ignored in ignored {
let manifest_path = ignored.manifest_path().display();
let _ = writeln!(&mut msg, " {manifest_path}");
}
let manifest_path = pkgs[0].manifest_path().display();
let _ = writeln!(&mut msg, "in favor of {manifest_path}");
let _ = gctx.shell().warn(msg);
}
}
&pkgs[0]
}
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 {}",
pkg.root().display()
)
})
}
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 {
discover_gix_repo(root)?
} else {
None
};
let mut exclude_builder = GitignoreBuilder::new(root);
if no_include_option && git_repo.is_none() {
exclude_builder.add_line(None, ".*")?;
}
for rule in pkg.manifest().exclude() {
exclude_builder.add_line(None, rule)?;
}
let ignore_exclude = exclude_builder.build()?;
let mut include_builder = GitignoreBuilder::new(root);
for rule in pkg.manifest().include() {
include_builder.add_line(None, rule)?;
}
let ignore_include = include_builder.build()?;
let ignore_should_package = |relative_path: &Path, is_dir: bool| {
if no_include_option {
!ignore_exclude
.matched_path_or_any_parents(relative_path, is_dir)
.is_ignore()
} else {
if is_dir {
return true;
}
ignore_include
.matched_path_or_any_parents(relative_path, false)
.is_ignore()
}
};
let filter = |path: &Path, is_dir: bool| {
let Ok(relative_path) = path.strip_prefix(root) else {
return false;
};
let rel = relative_path.as_os_str();
if rel == "Cargo.lock" || rel == "Cargo.toml" {
return true;
}
ignore_should_package(relative_path, is_dir)
};
if no_include_option {
if let Some(repo) = git_repo {
return list_files_gix(pkg, &repo, &filter, gctx);
}
}
let mut ret = Vec::new();
list_files_walk(pkg.root(), &mut ret, true, &filter, gctx)?;
Ok(ret)
}
fn discover_gix_repo(root: &Path) -> CargoResult<Option<gix::Repository>> {
let repo = match gix::ThreadSafeRepository::discover(root) {
Ok(repo) => repo.to_thread_local(),
Err(e) => {
tracing::debug!(
"could not discover git repo at or above {}: {}",
root.display(),
e
);
return Ok(None);
}
};
let index = repo
.index_or_empty()
.with_context(|| format!("failed to open git index at {}", repo.path().display()))?;
let repo_root = repo.workdir().ok_or_else(|| {
anyhow::format_err!(
"did not expect repo at {} to be bare",
repo.path().display()
)
})?;
let repo_relative_path = match paths::strip_prefix_canonical(root, repo_root) {
Ok(p) => p,
Err(e) => {
warn!(
"cannot determine if path `{:?}` is in git repo `{:?}`: {:?}",
root, repo_root, e
);
return Ok(None);
}
};
let manifest_path = gix::path::join_bstr_unix_pathsep(
gix::path::to_unix_separators_on_windows(gix::path::into_bstr(repo_relative_path)),
"Cargo.toml",
);
if index.entry_index_by_path(&manifest_path).is_ok() {
return Ok(Some(repo));
}
Ok(None)
}
fn list_files_gix(
pkg: &Package,
repo: &gix::Repository,
filter: &dyn Fn(&Path, bool) -> bool,
gctx: &GlobalContext,
) -> CargoResult<Vec<PathEntry>> {
debug!("list_files_gix {}", pkg.package_id());
let options = repo
.dirwalk_options()?
.emit_untracked(gix::dir::walk::EmissionMode::Matching)
.emit_ignored(None)
.emit_tracked(true)
.recurse_repositories(false)
.symlinks_to_directories_are_ignored_like_directories(true)
.emit_empty_directories(false);
let index = repo.index_or_empty()?;
let root = repo
.workdir()
.ok_or_else(|| anyhow::format_err!("can't list files on a bare repository"))?;
assert!(
root.is_absolute(),
"BUG: paths used internally are absolute, and the repo inherits that"
);
let pkg_path = pkg.root();
let repo_relative_pkg_path = pkg_path.strip_prefix(root).unwrap_or(Path::new(""));
let target_prefix = gix::path::to_unix_separators_on_windows(gix::path::into_bstr(
repo_relative_pkg_path.join("target/"),
));
let package_prefix =
gix::path::to_unix_separators_on_windows(gix::path::into_bstr(repo_relative_pkg_path));
let pathspec = {
let mut include = BString::from(":(top)");
include.push_str(package_prefix.as_ref());
let mut exclude = BString::from(":!(exclude,top)");
exclude.push_str(target_prefix.as_ref());
vec![include, exclude]
};
let mut files = Vec::<PathEntry>::new();
let mut subpackages_found = Vec::new();
for item in repo
.dirwalk_iter(index.clone(), pathspec, Default::default(), options)?
.filter(|res| {
res.as_ref().map_or(true, |item| {
item.entry.disk_kind != Some(gix::dir::entry::Kind::Untrackable)
&& !(item.entry.status == Status::Untracked
&& item.entry.rela_path == "Cargo.lock")
})
})
.map(|res| {
res.map(|item| {
let maybe_plain_text_symlink = item.entry.index_kind
== Some(gix::dir::entry::Kind::Symlink)
&& item.entry.disk_kind == Some(gix::dir::entry::Kind::File);
(
item.entry.rela_path,
item.entry.disk_kind,
maybe_plain_text_symlink,
)
})
})
.chain(
index
.prefixed_entries(target_prefix.as_ref())
.unwrap_or_default()
.iter()
.filter(|entry| {
entry.stage() == Stage::Unconflicted
})
.map(|entry| {
(
entry.path(&index).to_owned(),
None,
false,
)
})
.map(Ok),
)
{
let (rela_path, kind, maybe_plain_text_symlink) = item?;
let file_path = root.join(gix::path::from_bstr(rela_path));
if file_path.file_name().and_then(|name| name.to_str()) == Some("Cargo.toml") {
let path = file_path.parent().unwrap();
if path != pkg_path {
debug!("subpackage found: {}", path.display());
files.retain(|p| !p.starts_with(path));
subpackages_found.push(path.to_path_buf());
continue;
}
}
if subpackages_found.iter().any(|p| file_path.starts_with(p)) {
continue;
}
let is_dir = kind.map_or(false, |kind| {
if kind == gix::dir::entry::Kind::Symlink {
file_path.is_dir()
} else {
kind.is_dir()
}
});
if is_dir {
match gix::open(&file_path) {
Ok(sub_repo) => {
files.extend(list_files_gix(pkg, &sub_repo, filter, gctx)?);
}
Err(_) => {
list_files_walk(&file_path, &mut files, false, filter, gctx)?;
}
}
} else if (filter)(&file_path, is_dir) {
assert!(!is_dir);
trace!(" found {}", file_path.display());
let ty = match kind.map(Into::into) {
Some(FileType::File { .. }) => FileType::File {
maybe_symlink: maybe_plain_text_symlink,
},
Some(ty) => ty,
None => FileType::Other,
};
files.push(PathEntry {
path: file_path,
ty,
under_symlink_dir: false,
});
}
}
return Ok(files);
}
fn list_files_walk(
path: &Path,
ret: &mut Vec<PathEntry>,
is_root: bool,
filter: &dyn Fn(&Path, bool) -> bool,
gctx: &GlobalContext,
) -> CargoResult<()> {
let walkdir = WalkDir::new(path)
.follow_links(true)
.contents_first(false)
.into_iter()
.filter_entry(|entry| {
let path = entry.path();
let at_root = is_root && entry.depth() == 0;
let is_dir = entry.file_type().is_dir();
if !at_root && !filter(path, is_dir) {
return false;
}
if !is_dir {
return true;
}
if !at_root && path.join("Cargo.toml").exists() {
return false;
}
if is_root
&& entry.depth() == 1
&& path.file_name().and_then(|s| s.to_str()) == Some("target")
{
return false;
}
true
});
let mut current_symlink_dir = None;
for entry in walkdir {
match entry {
Ok(entry) => {
let file_type = entry.file_type();
match current_symlink_dir.as_ref() {
Some(dir) if entry.path().starts_with(dir) => {
}
Some(_) | None => {
current_symlink_dir = if file_type.is_dir() && entry.path_is_symlink() {
Some(entry.path().to_path_buf())
} else {
None
};
}
}
if file_type.is_file() || file_type.is_symlink() {
let ty = if entry.path_is_symlink() {
FileType::Symlink
} else {
file_type.into()
};
ret.push(PathEntry {
path: entry.into_path(),
ty,
under_symlink_dir: current_symlink_dir.is_some(),
});
}
}
Err(err) if err.loop_ancestor().is_some() => {
gctx.shell().warn(err)?;
}
Err(err) => match err.path() {
Some(path) if !filter(path, path.is_dir()) => {}
Some(path) => ret.push(PathEntry {
path: path.to_path_buf(),
ty: FileType::Other,
under_symlink_dir: false,
}),
None => return Err(err.into()),
},
}
}
Ok(())
}
fn last_modified_file(
path: &Path,
pkg: &Package,
gctx: &GlobalContext,
) -> CargoResult<(FileTime, PathBuf)> {
let mut max = FileTime::zero();
let mut max_path = PathBuf::new();
for file in list_files(pkg, gctx).with_context(|| {
format!(
"failed to determine the most recently modified file in {}",
pkg.root().display()
)
})? {
let mtime = paths::mtime(&file).unwrap_or_else(|_| FileTime::zero());
if mtime > max {
max = mtime;
max_path = file.into_path_buf();
}
}
trace!("last modified file {}: {}", path.display(), max);
Ok((max, max_path))
}
fn read_packages(
path: &Path,
source_id: SourceId,
gctx: &GlobalContext,
) -> CargoResult<HashMap<PackageId, Vec<Package>>> {
let mut all_packages = HashMap::default();
let mut visited = HashSet::<PathBuf>::default();
let mut errors = Vec::<anyhow::Error>::new();
trace!(
"looking for root package: {}, source_id={}",
path.display(),
source_id
);
walk(path, &mut |dir| {
trace!("looking for child package: {}", dir.display());
if dir != path {
let name = dir.file_name().and_then(|s| s.to_str());
if name.map(|s| s.starts_with('.')) == Some(true) {
return Ok(false);
}
if dir.join(".git").exists() {
return Ok(false);
}
}
if dir.file_name().and_then(|s| s.to_str()) == Some("target")
&& has_manifest(dir.parent().unwrap())
{
return Ok(false);
}
if has_manifest(dir) {
read_nested_packages(
dir,
&mut all_packages,
source_id,
gctx,
&mut visited,
&mut errors,
)?;
}
Ok(true)
})?;
if all_packages.is_empty() {
match errors.pop() {
Some(err) => Err(err),
None => {
if find_project_manifest_exact(path, "cargo.toml").is_ok() {
Err(anyhow::format_err!(
"could not find `Cargo.toml` in `{}`
help: found `cargo.toml`, consider renaming it to `Cargo.toml`",
path.display()
))
} else {
Err(anyhow::format_err!(
"could not find `Cargo.toml` in `{}`",
path.display()
))
}
}
}
} else {
Ok(all_packages)
}
}
fn nested_paths(manifest: &Manifest) -> Vec<PathBuf> {
let mut nested_paths = Vec::new();
let normalized = manifest.normalized_toml();
let dependencies = normalized
.dependencies
.iter()
.chain(normalized.build_dependencies())
.chain(normalized.dev_dependencies())
.chain(
normalized
.target
.as_ref()
.into_iter()
.flat_map(|t| t.values())
.flat_map(|t| {
t.dependencies
.iter()
.chain(t.build_dependencies())
.chain(t.dev_dependencies())
}),
);
for dep_table in dependencies {
for dep in dep_table.values() {
let cargo_util_schemas::manifest::InheritableDependency::Value(dep) = dep else {
continue;
};
let cargo_util_schemas::manifest::TomlDependency::Detailed(dep) = dep else {
continue;
};
let Some(path) = dep.path.as_ref() else {
continue;
};
nested_paths.push(PathBuf::from(path.as_str()));
}
}
nested_paths
}
fn walk(path: &Path, callback: &mut dyn FnMut(&Path) -> CargoResult<bool>) -> CargoResult<()> {
if !callback(path)? {
trace!("not processing {}", path.display());
return Ok(());
}
let dirs = match fs::read_dir(path) {
Ok(dirs) => dirs,
Err(ref e) if e.kind() == io::ErrorKind::PermissionDenied => return Ok(()),
Err(e) => {
let cx = format!("failed to read directory `{}`", path.display());
let e = anyhow::Error::from(e);
return Err(e.context(cx));
}
};
let mut dirs = dirs.collect::<Vec<_>>();
dirs.sort_unstable_by_key(|d| d.as_ref().ok().map(|d| d.file_name()));
for dir in dirs {
let dir = dir?;
if dir.file_type()?.is_dir() {
walk(&dir.path(), callback)?;
}
}
Ok(())
}
fn has_manifest(path: &Path) -> bool {
find_project_manifest_exact(path, "Cargo.toml").is_ok()
}
fn read_nested_packages(
path: &Path,
all_packages: &mut HashMap<PackageId, Vec<Package>>,
source_id: SourceId,
gctx: &GlobalContext,
visited: &mut HashSet<PathBuf>,
errors: &mut Vec<anyhow::Error>,
) -> CargoResult<()> {
if !visited.insert(path.to_path_buf()) {
return Ok(());
}
let manifest_path = find_project_manifest_exact(path, "Cargo.toml")?;
let manifest = match read_manifest(&manifest_path, source_id, gctx) {
Err(err) => {
info!(
"skipping malformed package found at `{}`",
path.to_string_lossy()
);
errors.push(err.into());
return Ok(());
}
Ok(tuple) => tuple,
};
let manifest = match manifest {
EitherManifest::Real(manifest) => manifest,
EitherManifest::Virtual(..) => return Ok(()),
};
let nested = nested_paths(&manifest);
let pkg = Package::new(manifest, &manifest_path);
let pkg_id = pkg.package_id();
all_packages.entry(pkg_id).or_default().push(pkg);
if !source_id.is_registry() {
for p in nested.iter() {
let path = paths::normalize_path(&path.join(p));
let result =
read_nested_packages(&path, all_packages, source_id, gctx, visited, errors);
if let Err(err) = result {
if source_id.is_git() {
info!(
"skipping nested package found at `{}`: {:?}",
path.display(),
&err,
);
errors.push(err);
} else {
return Err(err);
}
}
}
}
Ok(())
}