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