1 use std
::collections
::{HashMap, HashSet}
;
4 use std
::path
::{Path, PathBuf}
;
6 use crate::core
::{EitherManifest, Package, PackageId, SourceId}
;
7 use crate::util
::errors
::CargoResult
;
8 use crate::util
::important_paths
::find_project_manifest_exact
;
9 use crate::util
::toml
::read_manifest
;
10 use crate::util
::Config
;
11 use cargo_util
::paths
;
12 use log
::{info, trace}
;
18 ) -> CargoResult
<(Package
, Vec
<PathBuf
>)> {
20 "read_package; path={}; source-id={}",
24 let (manifest
, nested
) = read_manifest(path
, source_id
, config
)?
;
25 let manifest
= match manifest
{
26 EitherManifest
::Real(manifest
) => manifest
,
27 EitherManifest
::Virtual(..) => anyhow
::bail
!(
28 "found a virtual manifest at `{}` instead of a package \
34 Ok((Package
::new(manifest
, path
), nested
))
41 ) -> CargoResult
<Vec
<Package
>> {
42 let mut all_packages
= HashMap
::new();
43 let mut visited
= HashSet
::<PathBuf
>::new();
44 let mut errors
= Vec
::<anyhow
::Error
>::new();
47 "looking for root package: {}, source_id={}",
52 walk(path
, &mut |dir
| {
53 trace
!("looking for child package: {}", dir
.display());
55 // Don't recurse into hidden/dot directories unless we're at the toplevel
57 let name
= dir
.file_name().and_then(|s
| s
.to_str());
58 if name
.map(|s
| s
.starts_with('
.'
)) == Some(true) {
62 // Don't automatically discover packages across git submodules
63 if dir
.join(".git").exists() {
68 // Don't ever look at target directories
69 if dir
.file_name().and_then(|s
| s
.to_str()) == Some("target")
70 && has_manifest(dir
.parent().unwrap())
75 if has_manifest(dir
) {
88 if all_packages
.is_empty() {
90 Some(err
) => Err(err
),
91 None
=> Err(anyhow
::format_err
!(
92 "Could not find Cargo.toml in `{}`",
97 Ok(all_packages
.into_iter().map(|(_
, v
)| v
).collect())
101 fn walk(path
: &Path
, callback
: &mut dyn FnMut(&Path
) -> CargoResult
<bool
>) -> CargoResult
<()> {
102 if !callback(path
)?
{
103 trace
!("not processing {}", path
.display());
107 // Ignore any permission denied errors because temporary directories
108 // can often have some weird permissions on them.
109 let dirs
= match fs
::read_dir(path
) {
111 Err(ref e
) if e
.kind() == io
::ErrorKind
::PermissionDenied
=> return Ok(()),
113 let cx
= format
!("failed to read directory `{}`", path
.display());
114 let e
= anyhow
::Error
::from(e
);
115 return Err(e
.context(cx
));
120 if dir
.file_type()?
.is_dir() {
121 walk(&dir
.path(), callback
)?
;
127 fn has_manifest(path
: &Path
) -> bool
{
128 find_project_manifest_exact(path
, "Cargo.toml").is_ok()
131 fn read_nested_packages(
133 all_packages
: &mut HashMap
<PackageId
, Package
>,
136 visited
: &mut HashSet
<PathBuf
>,
137 errors
: &mut Vec
<anyhow
::Error
>,
138 ) -> CargoResult
<()> {
139 if !visited
.insert(path
.to_path_buf()) {
143 let manifest_path
= find_project_manifest_exact(path
, "Cargo.toml")?
;
145 let (manifest
, nested
) = match read_manifest(&manifest_path
, source_id
, config
) {
147 // Ignore malformed manifests found on git repositories
149 // git source try to find and read all manifests from the repository
150 // but since it's not possible to exclude folders from this search
151 // it's safer to ignore malformed manifests to avoid
153 // TODO: Add a way to exclude folders?
155 "skipping malformed package found at `{}`",
156 path
.to_string_lossy()
158 errors
.push(err
.into());
164 let manifest
= match manifest
{
165 EitherManifest
::Real(manifest
) => manifest
,
166 EitherManifest
::Virtual(..) => return Ok(()),
168 let pkg
= Package
::new(manifest
, &manifest_path
);
170 let pkg_id
= pkg
.package_id();
171 use std
::collections
::hash_map
::Entry
;
172 match all_packages
.entry(pkg_id
) {
173 Entry
::Vacant(v
) => {
176 Entry
::Occupied(_
) => {
178 "skipping nested package `{}` found at `{}`",
180 path
.to_string_lossy()
185 // Registry sources are not allowed to have `path=` dependencies because
186 // they're all translated to actual registry dependencies.
188 // We normalize the path here ensure that we don't infinitely walk around
189 // looking for crates. By normalizing we ensure that we visit this crate at
192 // TODO: filesystem/symlink implications?
193 if !source_id
.is_registry() {
194 for p
in nested
.iter() {
195 let path
= paths
::normalize_path(&path
.join(p
));
197 read_nested_packages(&path
, all_packages
, source_id
, config
, visited
, errors
);
198 // Ignore broken manifests found on git repositories.
200 // A well formed manifest might still fail to load due to reasons
201 // like referring to a "path" that requires an extra build step.
203 // See https://github.com/rust-lang/cargo/issues/6822.
204 if let Err(err
) = result
{
205 if source_id
.is_git() {
207 "skipping nested package found at `{}`: {:?}",