]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/cargo_read_manifest.rs
Auto merge of #9467 - ehuss:install-metadata, r=alexcrichton
[cargo.git] / src / cargo / ops / cargo_read_manifest.rs
CommitLineData
06a0605f 1use std::collections::{HashMap, HashSet};
f28c7872 2use std::fs;
a6dad622
AC
3use std::io;
4use std::path::{Path, PathBuf};
a09ad635 5
04ddd4d0 6use crate::core::{EitherManifest, Package, PackageId, SourceId};
920d5527 7use crate::util::errors::CargoResult;
04ddd4d0
DW
8use crate::util::important_paths::find_project_manifest_exact;
9use crate::util::toml::read_manifest;
1dae5acb
EH
10use crate::util::Config;
11use cargo_util::paths;
12use log::{info, trace};
b3c23503 13
1e682848
AC
14pub fn read_package(
15 path: &Path,
e5a11190 16 source_id: SourceId,
1e682848
AC
17 config: &Config,
18) -> CargoResult<(Package, Vec<PathBuf>)> {
19 trace!(
20 "read_package; path={}; source-id={}",
21 path.display(),
22 source_id
23 );
82655b46 24 let (manifest, nested) = read_manifest(path, source_id, config)?;
58ddb28a
AC
25 let manifest = match manifest {
26 EitherManifest::Real(manifest) => manifest,
3a18c89a 27 EitherManifest::Virtual(..) => anyhow::bail!(
1e682848
AC
28 "found a virtual manifest at `{}` instead of a package \
29 manifest",
30 path.display()
31 ),
58ddb28a 32 };
bbbf2dea 33
346df29a 34 Ok((Package::new(manifest, path), nested))
62bff631 35}
bcf90287 36
1e682848
AC
37pub fn read_packages(
38 path: &Path,
e5a11190 39 source_id: SourceId,
1e682848
AC
40 config: &Config,
41) -> CargoResult<Vec<Package>> {
06a0605f 42 let mut all_packages = HashMap::new();
a6dad622 43 let mut visited = HashSet::<PathBuf>::new();
3a18c89a 44 let mut errors = Vec::<anyhow::Error>::new();
bcf90287 45
1e682848
AC
46 trace!(
47 "looking for root package: {}, source_id={}",
48 path.display(),
49 source_id
50 );
71e4252a 51
82655b46 52 walk(path, &mut |dir| {
98854f6f 53 trace!("looking for child package: {}", dir.display());
c84bc16b 54
4cd3c567
PG
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());
23591fe5 58 if name.map(|s| s.starts_with('.')) == Some(true) {
1e682848 59 return Ok(false);
4cd3c567 60 }
c84bc16b 61
4cd3c567 62 // Don't automatically discover packages across git submodules
4367ec4d 63 if dir.join(".git").exists() {
1e682848 64 return Ok(false);
4cd3c567 65 }
97a2f271 66 }
c84bc16b
AC
67
68 // Don't ever look at target directories
1e682848
AC
69 if dir.file_name().and_then(|s| s.to_str()) == Some("target")
70 && has_manifest(dir.parent().unwrap())
71 {
72 return Ok(false);
c84bc16b
AC
73 }
74
75 if has_manifest(dir) {
1e682848
AC
76 read_nested_packages(
77 dir,
78 &mut all_packages,
79 source_id,
80 config,
81 &mut visited,
82 &mut errors,
83 )?;
c84bc16b 84 }
ea3cb31c 85 Ok(true)
82655b46 86 })?;
bcf90287 87
ea3cb31c 88 if all_packages.is_empty() {
0275ca06 89 match errors.pop() {
90 Some(err) => Err(err),
3a18c89a 91 None => Err(anyhow::format_err!(
1e682848
AC
92 "Could not find Cargo.toml in `{}`",
93 path.display()
94 )),
4b827bf5 95 }
ea3cb31c 96 } else {
06a0605f 97 Ok(all_packages.into_iter().map(|(_, v)| v).collect())
ea3cb31c
YK
98 }
99}
100
b8b7faee 101fn walk(path: &Path, callback: &mut dyn FnMut(&Path) -> CargoResult<bool>) -> CargoResult<()> {
82655b46 102 if !callback(path)? {
a6dad622 103 trace!("not processing {}", path.display());
1e682848 104 return Ok(());
a6dad622 105 }
71e4252a 106
a6dad622
AC
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) {
110 Ok(dirs) => dirs,
1e682848 111 Err(ref e) if e.kind() == io::ErrorKind::PermissionDenied => return Ok(()),
d1e640d7 112 Err(e) => {
37cffbe0 113 let cx = format!("failed to read directory `{}`", path.display());
3a18c89a 114 let e = anyhow::Error::from(e);
6eefe3c2 115 return Err(e.context(cx));
d1e640d7 116 }
a6dad622
AC
117 };
118 for dir in dirs {
82655b46
SG
119 let dir = dir?;
120 if dir.file_type()?.is_dir() {
121 walk(&dir.path(), callback)?;
e595e877 122 }
ea3cb31c 123 }
ea3cb31c
YK
124 Ok(())
125}
126
ea3cb31c
YK
127fn has_manifest(path: &Path) -> bool {
128 find_project_manifest_exact(path, "Cargo.toml").is_ok()
129}
130
1e682848
AC
131fn read_nested_packages(
132 path: &Path,
133 all_packages: &mut HashMap<PackageId, Package>,
e5a11190 134 source_id: SourceId,
1e682848
AC
135 config: &Config,
136 visited: &mut HashSet<PathBuf>,
3a18c89a 137 errors: &mut Vec<anyhow::Error>,
1e682848
AC
138) -> CargoResult<()> {
139 if !visited.insert(path.to_path_buf()) {
140 return Ok(());
141 }
ea3cb31c 142
82655b46 143 let manifest_path = find_project_manifest_exact(path, "Cargo.toml")?;
9243f06d 144
2b416a0d 145 let (manifest, nested) = match read_manifest(&manifest_path, source_id, config) {
4b827bf5 146 Err(err) => {
2b416a0d
FA
147 // Ignore malformed manifests found on git repositories
148 //
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
152 //
153 // TODO: Add a way to exclude folders?
1e682848
AC
154 info!(
155 "skipping malformed package found at `{}`",
156 path.to_string_lossy()
157 );
25b7ad26 158 errors.push(err.into());
2b416a0d
FA
159 return Ok(());
160 }
1e682848 161 Ok(tuple) => tuple,
2b416a0d 162 };
3d6de417 163
9243f06d
AC
164 let manifest = match manifest {
165 EitherManifest::Real(manifest) => manifest,
166 EitherManifest::Virtual(..) => return Ok(()),
167 };
168 let pkg = Package::new(manifest, &manifest_path);
ea3cb31c 169
dae87a26 170 let pkg_id = pkg.package_id();
1e682848 171 use std::collections::hash_map::Entry;
836fdacd 172 match all_packages.entry(pkg_id) {
1e682848
AC
173 Entry::Vacant(v) => {
174 v.insert(pkg);
175 }
836fdacd 176 Entry::Occupied(_) => {
1e682848
AC
177 info!(
178 "skipping nested package `{}` found at `{}`",
179 pkg.name(),
180 path.to_string_lossy()
181 );
836fdacd 182 }
cdbaa749 183 }
ea3cb31c 184
9fba127e
AC
185 // Registry sources are not allowed to have `path=` dependencies because
186 // they're all translated to actual registry dependencies.
a6dad622
AC
187 //
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
190 // most once.
191 //
192 // TODO: filesystem/symlink implications?
9fba127e
AC
193 if !source_id.is_registry() {
194 for p in nested.iter() {
1dae5acb 195 let path = paths::normalize_path(&path.join(p));
a2cddeeb
JW
196 let result =
197 read_nested_packages(&path, all_packages, source_id, config, visited, errors);
198 // Ignore broken manifests found on git repositories.
199 //
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.
202 //
203 // See https://github.com/rust-lang/cargo/issues/6822.
204 if let Err(err) = result {
205 if source_id.is_git() {
206 info!(
207 "skipping nested package found at `{}`: {:?}",
208 path.display(),
209 &err,
210 );
211 errors.push(err);
212 } else {
213 return Err(err);
214 }
215 }
9fba127e 216 }
ea3cb31c
YK
217 }
218
c84bc16b 219 Ok(())
bcf90287 220}