]>
Commit | Line | Data |
---|---|---|
06a0605f | 1 | use std::collections::{HashMap, HashSet}; |
f28c7872 | 2 | use std::fs; |
a6dad622 AC |
3 | use std::io; |
4 | use std::path::{Path, PathBuf}; | |
a09ad635 | 5 | |
04ddd4d0 DW |
6 | use crate::core::{EitherManifest, Package, PackageId, SourceId}; |
7 | use crate::util::errors::{CargoError, CargoResult}; | |
8 | use crate::util::important_paths::find_project_manifest_exact; | |
9 | use crate::util::toml::read_manifest; | |
10 | use crate::util::{self, Config}; | |
b3c23503 | 11 | |
1e682848 AC |
12 | pub fn read_package( |
13 | path: &Path, | |
e5a11190 | 14 | source_id: SourceId, |
1e682848 AC |
15 | config: &Config, |
16 | ) -> CargoResult<(Package, Vec<PathBuf>)> { | |
17 | trace!( | |
18 | "read_package; path={}; source-id={}", | |
19 | path.display(), | |
20 | source_id | |
21 | ); | |
82655b46 | 22 | let (manifest, nested) = read_manifest(path, source_id, config)?; |
58ddb28a AC |
23 | let manifest = match manifest { |
24 | EitherManifest::Real(manifest) => manifest, | |
1e682848 AC |
25 | EitherManifest::Virtual(..) => bail!( |
26 | "found a virtual manifest at `{}` instead of a package \ | |
27 | manifest", | |
28 | path.display() | |
29 | ), | |
58ddb28a | 30 | }; |
bbbf2dea | 31 | |
346df29a | 32 | Ok((Package::new(manifest, path), nested)) |
62bff631 | 33 | } |
bcf90287 | 34 | |
1e682848 AC |
35 | pub fn read_packages( |
36 | path: &Path, | |
e5a11190 | 37 | source_id: SourceId, |
1e682848 AC |
38 | config: &Config, |
39 | ) -> CargoResult<Vec<Package>> { | |
06a0605f | 40 | let mut all_packages = HashMap::new(); |
a6dad622 | 41 | let mut visited = HashSet::<PathBuf>::new(); |
4b827bf5 | 42 | let mut errors = Vec::<CargoError>::new(); |
bcf90287 | 43 | |
1e682848 AC |
44 | trace!( |
45 | "looking for root package: {}, source_id={}", | |
46 | path.display(), | |
47 | source_id | |
48 | ); | |
71e4252a | 49 | |
82655b46 | 50 | walk(path, &mut |dir| { |
98854f6f | 51 | trace!("looking for child package: {}", dir.display()); |
c84bc16b | 52 | |
4cd3c567 PG |
53 | // Don't recurse into hidden/dot directories unless we're at the toplevel |
54 | if dir != path { | |
55 | let name = dir.file_name().and_then(|s| s.to_str()); | |
23591fe5 | 56 | if name.map(|s| s.starts_with('.')) == Some(true) { |
1e682848 | 57 | return Ok(false); |
4cd3c567 | 58 | } |
c84bc16b | 59 | |
4cd3c567 PG |
60 | // Don't automatically discover packages across git submodules |
61 | if fs::metadata(&dir.join(".git")).is_ok() { | |
1e682848 | 62 | return Ok(false); |
4cd3c567 | 63 | } |
97a2f271 | 64 | } |
c84bc16b AC |
65 | |
66 | // Don't ever look at target directories | |
1e682848 AC |
67 | if dir.file_name().and_then(|s| s.to_str()) == Some("target") |
68 | && has_manifest(dir.parent().unwrap()) | |
69 | { | |
70 | return Ok(false); | |
c84bc16b AC |
71 | } |
72 | ||
73 | if has_manifest(dir) { | |
1e682848 AC |
74 | read_nested_packages( |
75 | dir, | |
76 | &mut all_packages, | |
77 | source_id, | |
78 | config, | |
79 | &mut visited, | |
80 | &mut errors, | |
81 | )?; | |
c84bc16b | 82 | } |
ea3cb31c | 83 | Ok(true) |
82655b46 | 84 | })?; |
bcf90287 | 85 | |
ea3cb31c | 86 | if all_packages.is_empty() { |
0275ca06 | 87 | match errors.pop() { |
88 | Some(err) => Err(err), | |
1e682848 AC |
89 | None => Err(format_err!( |
90 | "Could not find Cargo.toml in `{}`", | |
91 | path.display() | |
92 | )), | |
4b827bf5 | 93 | } |
ea3cb31c | 94 | } else { |
06a0605f | 95 | Ok(all_packages.into_iter().map(|(_, v)| v).collect()) |
ea3cb31c YK |
96 | } |
97 | } | |
98 | ||
1e682848 | 99 | fn walk(path: &Path, callback: &mut FnMut(&Path) -> CargoResult<bool>) -> CargoResult<()> { |
82655b46 | 100 | if !callback(path)? { |
a6dad622 | 101 | trace!("not processing {}", path.display()); |
1e682848 | 102 | return Ok(()); |
a6dad622 | 103 | } |
71e4252a | 104 | |
a6dad622 AC |
105 | // Ignore any permission denied errors because temporary directories |
106 | // can often have some weird permissions on them. | |
107 | let dirs = match fs::read_dir(path) { | |
108 | Ok(dirs) => dirs, | |
1e682848 | 109 | Err(ref e) if e.kind() == io::ErrorKind::PermissionDenied => return Ok(()), |
d1e640d7 | 110 | Err(e) => { |
37cffbe0 AC |
111 | let cx = format!("failed to read directory `{}`", path.display()); |
112 | let e = CargoError::from(e); | |
1e682848 | 113 | return Err(e.context(cx).into()); |
d1e640d7 | 114 | } |
a6dad622 AC |
115 | }; |
116 | for dir in dirs { | |
82655b46 SG |
117 | let dir = dir?; |
118 | if dir.file_type()?.is_dir() { | |
119 | walk(&dir.path(), callback)?; | |
e595e877 | 120 | } |
ea3cb31c | 121 | } |
ea3cb31c YK |
122 | Ok(()) |
123 | } | |
124 | ||
ea3cb31c YK |
125 | fn has_manifest(path: &Path) -> bool { |
126 | find_project_manifest_exact(path, "Cargo.toml").is_ok() | |
127 | } | |
128 | ||
1e682848 AC |
129 | fn read_nested_packages( |
130 | path: &Path, | |
131 | all_packages: &mut HashMap<PackageId, Package>, | |
e5a11190 | 132 | source_id: SourceId, |
1e682848 AC |
133 | config: &Config, |
134 | visited: &mut HashSet<PathBuf>, | |
135 | errors: &mut Vec<CargoError>, | |
136 | ) -> CargoResult<()> { | |
137 | if !visited.insert(path.to_path_buf()) { | |
138 | return Ok(()); | |
139 | } | |
ea3cb31c | 140 | |
82655b46 | 141 | let manifest_path = find_project_manifest_exact(path, "Cargo.toml")?; |
9243f06d | 142 | |
2b416a0d | 143 | let (manifest, nested) = match read_manifest(&manifest_path, source_id, config) { |
4b827bf5 | 144 | Err(err) => { |
2b416a0d FA |
145 | // Ignore malformed manifests found on git repositories |
146 | // | |
147 | // git source try to find and read all manifests from the repository | |
148 | // but since it's not possible to exclude folders from this search | |
149 | // it's safer to ignore malformed manifests to avoid | |
150 | // | |
151 | // TODO: Add a way to exclude folders? | |
1e682848 AC |
152 | info!( |
153 | "skipping malformed package found at `{}`", | |
154 | path.to_string_lossy() | |
155 | ); | |
25b7ad26 | 156 | errors.push(err.into()); |
2b416a0d FA |
157 | return Ok(()); |
158 | } | |
1e682848 | 159 | Ok(tuple) => tuple, |
2b416a0d | 160 | }; |
3d6de417 | 161 | |
9243f06d AC |
162 | let manifest = match manifest { |
163 | EitherManifest::Real(manifest) => manifest, | |
164 | EitherManifest::Virtual(..) => return Ok(()), | |
165 | }; | |
166 | let pkg = Package::new(manifest, &manifest_path); | |
ea3cb31c | 167 | |
dae87a26 | 168 | let pkg_id = pkg.package_id(); |
1e682848 | 169 | use std::collections::hash_map::Entry; |
836fdacd | 170 | match all_packages.entry(pkg_id) { |
1e682848 AC |
171 | Entry::Vacant(v) => { |
172 | v.insert(pkg); | |
173 | } | |
836fdacd | 174 | Entry::Occupied(_) => { |
1e682848 AC |
175 | info!( |
176 | "skipping nested package `{}` found at `{}`", | |
177 | pkg.name(), | |
178 | path.to_string_lossy() | |
179 | ); | |
836fdacd | 180 | } |
cdbaa749 | 181 | } |
ea3cb31c | 182 | |
9fba127e AC |
183 | // Registry sources are not allowed to have `path=` dependencies because |
184 | // they're all translated to actual registry dependencies. | |
a6dad622 AC |
185 | // |
186 | // We normalize the path here ensure that we don't infinitely walk around | |
187 | // looking for crates. By normalizing we ensure that we visit this crate at | |
188 | // most once. | |
189 | // | |
190 | // TODO: filesystem/symlink implications? | |
9fba127e AC |
191 | if !source_id.is_registry() { |
192 | for p in nested.iter() { | |
a6dad622 | 193 | let path = util::normalize_path(&path.join(p)); |
1e682848 | 194 | read_nested_packages(&path, all_packages, source_id, config, visited, errors)?; |
9fba127e | 195 | } |
ea3cb31c YK |
196 | } |
197 | ||
c84bc16b | 198 | Ok(()) |
bcf90287 | 199 | } |