1 use std::collections::{HashMap, HashSet};
2 use std::fs;
3 use std::io;
4 use std::path::{Path, PathBuf};
5 
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};
13 
read_package( path: &Path, source_id: SourceId, config: &Config, ) -> CargoResult<(Package, Vec<PathBuf>)>14 pub fn read_package(
15     path: &Path,
16     source_id: SourceId,
17     config: &Config,
18 ) -> CargoResult<(Package, Vec<PathBuf>)> {
19     trace!(
20         "read_package; path={}; source-id={}",
21         path.display(),
22         source_id
23     );
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 \
29              manifest",
30             path.display()
31         ),
32     };
33 
34     Ok((Package::new(manifest, path), nested))
35 }
36 
read_packages( path: &Path, source_id: SourceId, config: &Config, ) -> CargoResult<Vec<Package>>37 pub fn read_packages(
38     path: &Path,
39     source_id: SourceId,
40     config: &Config,
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();
45 
46     trace!(
47         "looking for root package: {}, source_id={}",
48         path.display(),
49         source_id
50     );
51 
52     walk(path, &mut |dir| {
53         trace!("looking for child package: {}", dir.display());
54 
55         // Don't recurse into hidden/dot directories unless we're at the toplevel
56         if dir != path {
57             let name = dir.file_name().and_then(|s| s.to_str());
58             if name.map(|s| s.starts_with('.')) == Some(true) {
59                 return Ok(false);
60             }
61 
62             // Don't automatically discover packages across git submodules
63             if dir.join(".git").exists() {
64                 return Ok(false);
65             }
66         }
67 
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())
71         {
72             return Ok(false);
73         }
74 
75         if has_manifest(dir) {
76             read_nested_packages(
77                 dir,
78                 &mut all_packages,
79                 source_id,
80                 config,
81                 &mut visited,
82                 &mut errors,
83             )?;
84         }
85         Ok(true)
86     })?;
87 
88     if all_packages.is_empty() {
89         match errors.pop() {
90             Some(err) => Err(err),
91             None => {
92                 if find_project_manifest_exact(path, "cargo.toml").is_ok() {
93                     Err(anyhow::format_err!(
94                 "Could not find Cargo.toml in `{}`, but found cargo.toml please try to rename it to Cargo.toml",
95                 path.display()
96             ))
97                 } else {
98                     Err(anyhow::format_err!(
99                         "Could not find Cargo.toml in `{}`",
100                         path.display()
101                     ))
102                 }
103             }
104         }
105     } else {
106         Ok(all_packages.into_iter().map(|(_, v)| v).collect())
107     }
108 }
109 
walk(path: &Path, callback: &mut dyn FnMut(&Path) -> CargoResult<bool>) -> CargoResult<()>110 fn walk(path: &Path, callback: &mut dyn FnMut(&Path) -> CargoResult<bool>) -> CargoResult<()> {
111     if !callback(path)? {
112         trace!("not processing {}", path.display());
113         return Ok(());
114     }
115 
116     // Ignore any permission denied errors because temporary directories
117     // can often have some weird permissions on them.
118     let dirs = match fs::read_dir(path) {
119         Ok(dirs) => dirs,
120         Err(ref e) if e.kind() == io::ErrorKind::PermissionDenied => return Ok(()),
121         Err(e) => {
122             let cx = format!("failed to read directory `{}`", path.display());
123             let e = anyhow::Error::from(e);
124             return Err(e.context(cx));
125         }
126     };
127     for dir in dirs {
128         let dir = dir?;
129         if dir.file_type()?.is_dir() {
130             walk(&dir.path(), callback)?;
131         }
132     }
133     Ok(())
134 }
135 
has_manifest(path: &Path) -> bool136 fn has_manifest(path: &Path) -> bool {
137     find_project_manifest_exact(path, "Cargo.toml").is_ok()
138 }
139 
read_nested_packages( path: &Path, all_packages: &mut HashMap<PackageId, Package>, source_id: SourceId, config: &Config, visited: &mut HashSet<PathBuf>, errors: &mut Vec<anyhow::Error>, ) -> CargoResult<()>140 fn read_nested_packages(
141     path: &Path,
142     all_packages: &mut HashMap<PackageId, Package>,
143     source_id: SourceId,
144     config: &Config,
145     visited: &mut HashSet<PathBuf>,
146     errors: &mut Vec<anyhow::Error>,
147 ) -> CargoResult<()> {
148     if !visited.insert(path.to_path_buf()) {
149         return Ok(());
150     }
151 
152     let manifest_path = find_project_manifest_exact(path, "Cargo.toml")?;
153 
154     let (manifest, nested) = match read_manifest(&manifest_path, source_id, config) {
155         Err(err) => {
156             // Ignore malformed manifests found on git repositories
157             //
158             // git source try to find and read all manifests from the repository
159             // but since it's not possible to exclude folders from this search
160             // it's safer to ignore malformed manifests to avoid
161             //
162             // TODO: Add a way to exclude folders?
163             info!(
164                 "skipping malformed package found at `{}`",
165                 path.to_string_lossy()
166             );
167             errors.push(err.into());
168             return Ok(());
169         }
170         Ok(tuple) => tuple,
171     };
172 
173     let manifest = match manifest {
174         EitherManifest::Real(manifest) => manifest,
175         EitherManifest::Virtual(..) => return Ok(()),
176     };
177     let pkg = Package::new(manifest, &manifest_path);
178 
179     let pkg_id = pkg.package_id();
180     use std::collections::hash_map::Entry;
181     match all_packages.entry(pkg_id) {
182         Entry::Vacant(v) => {
183             v.insert(pkg);
184         }
185         Entry::Occupied(_) => {
186             info!(
187                 "skipping nested package `{}` found at `{}`",
188                 pkg.name(),
189                 path.to_string_lossy()
190             );
191         }
192     }
193 
194     // Registry sources are not allowed to have `path=` dependencies because
195     // they're all translated to actual registry dependencies.
196     //
197     // We normalize the path here ensure that we don't infinitely walk around
198     // looking for crates. By normalizing we ensure that we visit this crate at
199     // most once.
200     //
201     // TODO: filesystem/symlink implications?
202     if !source_id.is_registry() {
203         for p in nested.iter() {
204             let path = paths::normalize_path(&path.join(p));
205             let result =
206                 read_nested_packages(&path, all_packages, source_id, config, visited, errors);
207             // Ignore broken manifests found on git repositories.
208             //
209             // A well formed manifest might still fail to load due to reasons
210             // like referring to a "path" that requires an extra build step.
211             //
212             // See https://github.com/rust-lang/cargo/issues/6822.
213             if let Err(err) = result {
214                 if source_id.is_git() {
215                     info!(
216                         "skipping nested package found at `{}`: {:?}",
217                         path.display(),
218                         &err,
219                     );
220                     errors.push(err);
221                 } else {
222                     return Err(err);
223                 }
224             }
225         }
226     }
227 
228     Ok(())
229 }
230