]>
Commit | Line | Data |
---|---|---|
9659f560 | 1 | use std::cell::RefCell; |
9659f560 | 2 | use std::collections::hash_map::{Entry, HashMap}; |
c17bfcbd | 3 | use std::collections::{BTreeMap, BTreeSet, HashSet}; |
58ddb28a | 4 | use std::path::{Path, PathBuf}; |
c17bfcbd | 5 | use std::rc::Rc; |
58ddb28a AC |
6 | use std::slice; |
7 | ||
b3a747cf | 8 | use glob::glob; |
9ed82b57 | 9 | use log::debug; |
61a3c68b | 10 | use url::Url; |
b3a747cf | 11 | |
87f1a4b2 | 12 | use crate::core::features::Features; |
04ddd4d0 | 13 | use crate::core::registry::PackageRegistry; |
76f0d68d | 14 | use crate::core::resolver::features::RequestedFeatures; |
787e75b7 | 15 | use crate::core::resolver::ResolveBehavior; |
c17bfcbd | 16 | use crate::core::{Dependency, InternedString, PackageId, PackageIdSpec}; |
04ddd4d0 DW |
17 | use crate::core::{EitherManifest, Package, SourceId, VirtualManifest}; |
18 | use crate::ops; | |
19 | use crate::sources::PathSource; | |
20 | use crate::util::errors::{CargoResult, CargoResultExt, ManifestError}; | |
21 | use crate::util::paths; | |
77ee608d | 22 | use crate::util::toml::{read_manifest, TomlProfiles}; |
04ddd4d0 | 23 | use crate::util::{Config, Filesystem}; |
58ddb28a AC |
24 | |
25 | /// The core abstraction in Cargo for working with a workspace of crates. | |
26 | /// | |
27 | /// A workspace is often created very early on and then threaded through all | |
28 | /// other functions. It's typically through this object that the current | |
29 | /// package is loaded and/or learned about. | |
c946944b | 30 | #[derive(Debug)] |
58ddb28a AC |
31 | pub struct Workspace<'cfg> { |
32 | config: &'cfg Config, | |
33 | ||
34 | // This path is a path to where the current cargo subcommand was invoked | |
4f0e1361 | 35 | // from. That is the `--manifest-path` argument to Cargo, and |
58ddb28a AC |
36 | // points to the "main crate" that we're going to worry about. |
37 | current_manifest: PathBuf, | |
38 | ||
39 | // A list of packages found in this workspace. Always includes at least the | |
40 | // package mentioned by `current_manifest`. | |
41 | packages: Packages<'cfg>, | |
42 | ||
43 | // If this workspace includes more than one crate, this points to the root | |
44 | // of the workspace. This is `None` in the case that `[workspace]` is | |
45 | // missing, `package.workspace` is missing, and no `Cargo.toml` above | |
46 | // `current_manifest` was found on the filesystem with `[workspace]`. | |
47 | root_manifest: Option<PathBuf>, | |
48 | ||
a2cc38dc AK |
49 | // Shared target directory for all the packages of this workspace. |
50 | // `None` if the default path of `root/target` should be used. | |
3c9b362b AK |
51 | target_dir: Option<Filesystem>, |
52 | ||
58ddb28a AC |
53 | // List of members in this workspace with a listing of all their manifest |
54 | // paths. The packages themselves can be looked up through the `packages` | |
55 | // set above. | |
56 | members: Vec<PathBuf>, | |
f660b3e4 | 57 | member_ids: HashSet<PackageId>, |
68c6f2b0 | 58 | |
499a6788 SS |
59 | // The subset of `members` that are used by the |
60 | // `build`, `check`, `test`, and `bench` subcommands | |
ecf824f7 | 61 | // when no package is selected with `--package` / `-p` and `--workspace` |
499a6788 SS |
62 | // is not used. |
63 | // | |
64 | // This is set by the `default-members` config | |
65 | // in the `[workspace]` section. | |
66 | // When unset, this is the same as `members` for virtual workspaces | |
ecf824f7 | 67 | // (`--workspace` is implied) |
499a6788 | 68 | // or only the root package for non-virtual workspaces. |
ba7911dd SS |
69 | default_members: Vec<PathBuf>, |
70 | ||
f7c91ba6 AR |
71 | // `true` if this is a temporary workspace created for the purposes of the |
72 | // `cargo install` or `cargo package` commands. | |
68c6f2b0 | 73 | is_ephemeral: bool, |
db71d878 | 74 | |
f7c91ba6 | 75 | // `true` if this workspace should enforce optional dependencies even when |
db71d878 | 76 | // not needed; false if this workspace should only enforce dependencies |
df5f7d68 XL |
77 | // needed by the current configuration (such as in cargo install). In some |
78 | // cases `false` also results in the non-enforcement of dev-dependencies. | |
db71d878 | 79 | require_optional_deps: bool, |
9659f560 | 80 | |
57a7e126 | 81 | // A cache of loaded packages for particular paths which is disjoint from |
51d23560 | 82 | // `packages` up above, used in the `load` method down below. |
9659f560 | 83 | loaded_packages: RefCell<HashMap<PathBuf, Package>>, |
3d893793 EH |
84 | |
85 | // If `true`, then the resolver will ignore any existing `Cargo.lock` | |
86 | // file. This is set for `cargo install` without `--locked`. | |
87 | ignore_lock: bool, | |
787e75b7 EH |
88 | |
89 | /// The resolver behavior specified with the `resolver` field. | |
323d7a88 | 90 | resolve_behavior: Option<ResolveBehavior>, |
58ddb28a AC |
91 | } |
92 | ||
93 | // Separate structure for tracking loaded packages (to avoid loading anything | |
94 | // twice), and this is separate to help appease the borrow checker. | |
c946944b | 95 | #[derive(Debug)] |
58ddb28a AC |
96 | struct Packages<'cfg> { |
97 | config: &'cfg Config, | |
98 | packages: HashMap<PathBuf, MaybePackage>, | |
99 | } | |
100 | ||
c946944b | 101 | #[derive(Debug)] |
58ddb28a AC |
102 | enum MaybePackage { |
103 | Package(Package), | |
104 | Virtual(VirtualManifest), | |
105 | } | |
106 | ||
107 | /// Configuration of a workspace in a manifest. | |
108 | #[derive(Debug, Clone)] | |
109 | pub enum WorkspaceConfig { | |
110 | /// Indicates that `[workspace]` was present and the members were | |
111 | /// optionally specified as well. | |
f320997f | 112 | Root(WorkspaceRootConfig), |
58ddb28a AC |
113 | |
114 | /// Indicates that `[workspace]` was present and the `root` field is the | |
115 | /// optional value of `package.workspace`, if present. | |
116 | Member { root: Option<String> }, | |
117 | } | |
118 | ||
fd07cfd0 BE |
119 | /// Intermediate configuration of a workspace root in a manifest. |
120 | /// | |
121 | /// Knows the Workspace Root path, as well as `members` and `exclude` lists of path patterns, which | |
122 | /// together tell if some path is recognized as a member by this root or not. | |
f320997f BE |
123 | #[derive(Debug, Clone)] |
124 | pub struct WorkspaceRootConfig { | |
125 | root_dir: PathBuf, | |
126 | members: Option<Vec<String>>, | |
ba7911dd | 127 | default_members: Option<Vec<String>>, |
f320997f BE |
128 | exclude: Vec<String>, |
129 | } | |
130 | ||
58ddb28a AC |
131 | /// An iterator over the member packages of a workspace, returned by |
132 | /// `Workspace::members` | |
46615d29 | 133 | pub struct Members<'a, 'cfg> { |
58ddb28a AC |
134 | ws: &'a Workspace<'cfg>, |
135 | iter: slice::Iter<'a, PathBuf>, | |
136 | } | |
137 | ||
138 | impl<'cfg> Workspace<'cfg> { | |
139 | /// Creates a new workspace given the target manifest pointed to by | |
140 | /// `manifest_path`. | |
141 | /// | |
142 | /// This function will construct the entire workspace by determining the | |
143 | /// root and all member packages. It will then validate the workspace | |
144 | /// before returning it, so `Ok` is only returned for valid workspaces. | |
1e682848 | 145 | pub fn new(manifest_path: &Path, config: &'cfg Config) -> CargoResult<Workspace<'cfg>> { |
1f14fa31 EH |
146 | let mut ws = Workspace::new_default(manifest_path.to_path_buf(), config); |
147 | ws.target_dir = config.target_dir()?; | |
148 | ws.root_manifest = ws.find_root(manifest_path)?; | |
149 | ws.find_members()?; | |
787e75b7 EH |
150 | ws.resolve_behavior = match ws.root_maybe() { |
151 | MaybePackage::Package(p) => p.manifest().resolve_behavior(), | |
152 | MaybePackage::Virtual(vm) => vm.resolve_behavior(), | |
323d7a88 | 153 | }; |
1f14fa31 EH |
154 | ws.validate()?; |
155 | Ok(ws) | |
156 | } | |
a2cc38dc | 157 | |
1f14fa31 EH |
158 | fn new_default(current_manifest: PathBuf, config: &'cfg Config) -> Workspace<'cfg> { |
159 | Workspace { | |
0247dc42 | 160 | config, |
1f14fa31 | 161 | current_manifest, |
58ddb28a | 162 | packages: Packages { |
0247dc42 | 163 | config, |
58ddb28a AC |
164 | packages: HashMap::new(), |
165 | }, | |
166 | root_manifest: None, | |
1f14fa31 | 167 | target_dir: None, |
58ddb28a | 168 | members: Vec::new(), |
f660b3e4 | 169 | member_ids: HashSet::new(), |
ba7911dd | 170 | default_members: Vec::new(), |
68c6f2b0 | 171 | is_ephemeral: false, |
db71d878 | 172 | require_optional_deps: true, |
9659f560 | 173 | loaded_packages: RefCell::new(HashMap::new()), |
3d893793 | 174 | ignore_lock: false, |
323d7a88 | 175 | resolve_behavior: None, |
1f14fa31 EH |
176 | } |
177 | } | |
178 | ||
179 | pub fn new_virtual( | |
180 | root_path: PathBuf, | |
181 | current_manifest: PathBuf, | |
182 | manifest: VirtualManifest, | |
183 | config: &'cfg Config, | |
184 | ) -> CargoResult<Workspace<'cfg>> { | |
185 | let mut ws = Workspace::new_default(current_manifest, config); | |
186 | ws.root_manifest = Some(root_path.join("Cargo.toml")); | |
187 | ws.target_dir = config.target_dir()?; | |
323d7a88 | 188 | ws.resolve_behavior = manifest.resolve_behavior(); |
1f14fa31 EH |
189 | ws.packages |
190 | .packages | |
191 | .insert(root_path, MaybePackage::Virtual(manifest)); | |
82655b46 | 192 | ws.find_members()?; |
1f14fa31 EH |
193 | // TODO: validation does not work because it walks up the directory |
194 | // tree looking for the root which is a fake file that doesn't exist. | |
58ddb28a AC |
195 | Ok(ws) |
196 | } | |
197 | ||
015e7972 | 198 | /// Creates a "temporary workspace" from one package which only contains |
58ddb28a AC |
199 | /// that package. |
200 | /// | |
201 | /// This constructor will not touch the filesystem and only creates an | |
202 | /// in-memory workspace. That is, all configuration is ignored, it's just | |
203 | /// intended for that one package. | |
204 | /// | |
205 | /// This is currently only used in niche situations like `cargo install` or | |
206 | /// `cargo package`. | |
1e682848 AC |
207 | pub fn ephemeral( |
208 | package: Package, | |
209 | config: &'cfg Config, | |
210 | target_dir: Option<Filesystem>, | |
211 | require_optional_deps: bool, | |
212 | ) -> CargoResult<Workspace<'cfg>> { | |
e137ee69 | 213 | let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), config); |
1f14fa31 EH |
214 | ws.is_ephemeral = true; |
215 | ws.require_optional_deps = require_optional_deps; | |
323d7a88 | 216 | ws.resolve_behavior = package.manifest().resolve_behavior(); |
1f14fa31 EH |
217 | let key = ws.current_manifest.parent().unwrap(); |
218 | let id = package.package_id(); | |
219 | let package = MaybePackage::Package(package); | |
220 | ws.packages.packages.insert(key.to_path_buf(), package); | |
221 | ws.target_dir = if let Some(dir) = target_dir { | |
222 | Some(dir) | |
223 | } else { | |
224 | ws.config.target_dir()? | |
58ddb28a | 225 | }; |
1f14fa31 EH |
226 | ws.members.push(ws.current_manifest.clone()); |
227 | ws.member_ids.insert(id); | |
228 | ws.default_members.push(ws.current_manifest.clone()); | |
23591fe5 | 229 | Ok(ws) |
58ddb28a AC |
230 | } |
231 | ||
232 | /// Returns the current package of this workspace. | |
233 | /// | |
234 | /// Note that this can return an error if it the current manifest is | |
235 | /// actually a "virtual Cargo.toml", in which case an error is returned | |
236 | /// indicating that something else should be passed. | |
237 | pub fn current(&self) -> CargoResult<&Package> { | |
37cffbe0 | 238 | let pkg = self.current_opt().ok_or_else(|| { |
3a18c89a | 239 | anyhow::format_err!( |
1e682848 AC |
240 | "manifest path `{}` is a virtual manifest, but this \ |
241 | command requires running against an actual package in \ | |
242 | this workspace", | |
243 | self.current_manifest.display() | |
244 | ) | |
37cffbe0 AC |
245 | })?; |
246 | Ok(pkg) | |
a2f687a5 AK |
247 | } |
248 | ||
d5eeab84 LB |
249 | pub fn current_mut(&mut self) -> CargoResult<&mut Package> { |
250 | let cm = self.current_manifest.clone(); | |
251 | let pkg = self.current_opt_mut().ok_or_else(|| { | |
252 | anyhow::format_err!( | |
253 | "manifest path `{}` is a virtual manifest, but this \ | |
254 | command requires running against an actual package in \ | |
255 | this workspace", | |
256 | cm.display() | |
257 | ) | |
258 | })?; | |
259 | Ok(pkg) | |
260 | } | |
261 | ||
a2f687a5 | 262 | pub fn current_opt(&self) -> Option<&Package> { |
58ddb28a | 263 | match *self.packages.get(&self.current_manifest) { |
a2f687a5 | 264 | MaybePackage::Package(ref p) => Some(p), |
1e682848 | 265 | MaybePackage::Virtual(..) => None, |
58ddb28a AC |
266 | } |
267 | } | |
268 | ||
d5eeab84 LB |
269 | pub fn current_opt_mut(&mut self) -> Option<&mut Package> { |
270 | match *self.packages.get_mut(&self.current_manifest) { | |
271 | MaybePackage::Package(ref mut p) => Some(p), | |
272 | MaybePackage::Virtual(..) => None, | |
273 | } | |
274 | } | |
275 | ||
193a95a2 RS |
276 | pub fn is_virtual(&self) -> bool { |
277 | match *self.packages.get(&self.current_manifest) { | |
278 | MaybePackage::Package(..) => false, | |
1e682848 | 279 | MaybePackage::Virtual(..) => true, |
193a95a2 RS |
280 | } |
281 | } | |
282 | ||
58ddb28a AC |
283 | /// Returns the `Config` this workspace is associated with. |
284 | pub fn config(&self) -> &'cfg Config { | |
285 | self.config | |
286 | } | |
287 | ||
77ee608d | 288 | pub fn profiles(&self) -> Option<&TomlProfiles> { |
2b6fd6f0 EH |
289 | match self.root_maybe() { |
290 | MaybePackage::Package(p) => p.manifest().profiles(), | |
291 | MaybePackage::Virtual(vm) => vm.profiles(), | |
151f12fc AK |
292 | } |
293 | } | |
294 | ||
58ddb28a AC |
295 | /// Returns the root path of this workspace. |
296 | /// | |
297 | /// That is, this returns the path of the directory containing the | |
298 | /// `Cargo.toml` which is the root of this workspace. | |
299 | pub fn root(&self) -> &Path { | |
300 | match self.root_manifest { | |
301 | Some(ref p) => p, | |
1e682848 | 302 | None => &self.current_manifest, |
e5a11190 E |
303 | } |
304 | .parent() | |
305 | .unwrap() | |
58ddb28a AC |
306 | } |
307 | ||
2b6fd6f0 EH |
308 | /// Returns the root Package or VirtualManifest. |
309 | fn root_maybe(&self) -> &MaybePackage { | |
310 | let root = self | |
311 | .root_manifest | |
312 | .as_ref() | |
313 | .unwrap_or(&self.current_manifest); | |
314 | self.packages.get(root) | |
315 | } | |
316 | ||
3c9b362b | 317 | pub fn target_dir(&self) -> Filesystem { |
1e682848 AC |
318 | self.target_dir |
319 | .clone() | |
320 | .unwrap_or_else(|| Filesystem::new(self.root().join("target"))) | |
3c9b362b AK |
321 | } |
322 | ||
e26ef017 | 323 | /// Returns the root `[replace]` section of this workspace. |
58ddb28a AC |
324 | /// |
325 | /// This may be from a virtual crate or an actual crate. | |
326 | pub fn root_replace(&self) -> &[(PackageIdSpec, Dependency)] { | |
2b6fd6f0 EH |
327 | match self.root_maybe() { |
328 | MaybePackage::Package(p) => p.manifest().replace(), | |
329 | MaybePackage::Virtual(vm) => vm.replace(), | |
58ddb28a AC |
330 | } |
331 | } | |
332 | ||
e26ef017 | 333 | /// Returns the root `[patch]` section of this workspace. |
61a3c68b AC |
334 | /// |
335 | /// This may be from a virtual crate or an actual crate. | |
336 | pub fn root_patch(&self) -> &HashMap<Url, Vec<Dependency>> { | |
2b6fd6f0 EH |
337 | match self.root_maybe() { |
338 | MaybePackage::Package(p) => p.manifest().patch(), | |
339 | MaybePackage::Virtual(vm) => vm.patch(), | |
61a3c68b AC |
340 | } |
341 | } | |
342 | ||
58ddb28a AC |
343 | /// Returns an iterator over all packages in this workspace |
344 | pub fn members<'a>(&'a self) -> Members<'a, 'cfg> { | |
345 | Members { | |
346 | ws: self, | |
347 | iter: self.members.iter(), | |
348 | } | |
349 | } | |
350 | ||
ba7911dd SS |
351 | /// Returns an iterator over default packages in this workspace |
352 | pub fn default_members<'a>(&'a self) -> Members<'a, 'cfg> { | |
353 | Members { | |
354 | ws: self, | |
355 | iter: self.default_members.iter(), | |
356 | } | |
357 | } | |
358 | ||
575d6e81 EH |
359 | /// Returns true if the package is a member of the workspace. |
360 | pub fn is_member(&self, pkg: &Package) -> bool { | |
f660b3e4 | 361 | self.member_ids.contains(&pkg.package_id()) |
575d6e81 EH |
362 | } |
363 | ||
68c6f2b0 AK |
364 | pub fn is_ephemeral(&self) -> bool { |
365 | self.is_ephemeral | |
366 | } | |
367 | ||
db71d878 JT |
368 | pub fn require_optional_deps(&self) -> bool { |
369 | self.require_optional_deps | |
370 | } | |
371 | ||
3d893793 EH |
372 | pub fn set_require_optional_deps( |
373 | &mut self, | |
1e682848 AC |
374 | require_optional_deps: bool, |
375 | ) -> &mut Workspace<'cfg> { | |
ee78456a XL |
376 | self.require_optional_deps = require_optional_deps; |
377 | self | |
378 | } | |
379 | ||
3d893793 EH |
380 | pub fn ignore_lock(&self) -> bool { |
381 | self.ignore_lock | |
382 | } | |
383 | ||
384 | pub fn set_ignore_lock(&mut self, ignore_lock: bool) -> &mut Workspace<'cfg> { | |
385 | self.ignore_lock = ignore_lock; | |
386 | self | |
387 | } | |
388 | ||
58ddb28a AC |
389 | /// Finds the root of a workspace for the crate whose manifest is located |
390 | /// at `manifest_path`. | |
391 | /// | |
392 | /// This will parse the `Cargo.toml` at `manifest_path` and then interpret | |
393 | /// the workspace configuration, optionally walking up the filesystem | |
394 | /// looking for other workspace roots. | |
395 | /// | |
396 | /// Returns an error if `manifest_path` isn't actually a valid manifest or | |
397 | /// if some other transient error happens. | |
1e682848 | 398 | fn find_root(&mut self, manifest_path: &Path) -> CargoResult<Option<PathBuf>> { |
3435414e | 399 | fn read_root_pointer(member_manifest: &Path, root_link: &str) -> CargoResult<PathBuf> { |
1e682848 AC |
400 | let path = member_manifest |
401 | .parent() | |
402 | .unwrap() | |
3435414e AK |
403 | .join(root_link) |
404 | .join("Cargo.toml"); | |
405 | debug!("find_root - pointer {}", path.display()); | |
23591fe5 | 406 | Ok(paths::normalize_path(&path)) |
3435414e AK |
407 | }; |
408 | ||
58ddb28a | 409 | { |
23591fe5 | 410 | let current = self.packages.load(manifest_path)?; |
58ddb28a | 411 | match *current.workspace_config() { |
f320997f | 412 | WorkspaceConfig::Root(_) => { |
58ddb28a | 413 | debug!("find_root - is root {}", manifest_path.display()); |
1e682848 | 414 | return Ok(Some(manifest_path.to_path_buf())); |
58ddb28a | 415 | } |
1e682848 AC |
416 | WorkspaceConfig::Member { |
417 | root: Some(ref path_to_root), | |
418 | } => return Ok(Some(read_root_pointer(manifest_path, path_to_root)?)), | |
58ddb28a AC |
419 | WorkspaceConfig::Member { root: None } => {} |
420 | } | |
421 | } | |
422 | ||
290f6ab7 | 423 | for path in paths::ancestors(manifest_path).skip(2) { |
4f0e1361 | 424 | if path.ends_with("target/package") { |
425 | break; | |
426 | } | |
427 | ||
fd07cfd0 BE |
428 | let ances_manifest_path = path.join("Cargo.toml"); |
429 | debug!("find_root - trying {}", ances_manifest_path.display()); | |
430 | if ances_manifest_path.exists() { | |
431 | match *self.packages.load(&ances_manifest_path)?.workspace_config() { | |
432 | WorkspaceConfig::Root(ref ances_root_config) => { | |
67364baa | 433 | debug!("find_root - found a root checking exclusion"); |
676edacf | 434 | if !ances_root_config.is_excluded(manifest_path) { |
67364baa | 435 | debug!("find_root - found!"); |
1e682848 | 436 | return Ok(Some(ances_manifest_path)); |
67364baa | 437 | } |
58ddb28a | 438 | } |
1e682848 AC |
439 | WorkspaceConfig::Member { |
440 | root: Some(ref path_to_root), | |
441 | } => { | |
67364baa | 442 | debug!("find_root - found pointer"); |
1e682848 | 443 | return Ok(Some(read_root_pointer(&ances_manifest_path, path_to_root)?)); |
3435414e | 444 | } |
58ddb28a AC |
445 | WorkspaceConfig::Member { .. } => {} |
446 | } | |
447 | } | |
4f28bc1a AC |
448 | |
449 | // Don't walk across `CARGO_HOME` when we're looking for the | |
3492a390 | 450 | // workspace root. Sometimes a package will be organized with |
4f28bc1a | 451 | // `CARGO_HOME` pointing inside of the workspace root or in the |
3492a390 | 452 | // current package, but we don't want to mistakenly try to put |
4f28bc1a AC |
453 | // crates.io crates into the workspace by accident. |
454 | if self.config.home() == path { | |
1e682848 | 455 | break; |
4f28bc1a | 456 | } |
58ddb28a AC |
457 | } |
458 | ||
459 | Ok(None) | |
460 | } | |
461 | ||
462 | /// After the root of a workspace has been located, probes for all members | |
463 | /// of a workspace. | |
464 | /// | |
465 | /// If the `workspace.members` configuration is present, then this just | |
466 | /// verifies that those are all valid packages to point to. Otherwise, this | |
467 | /// will transitively follow all `path` dependencies looking for members of | |
468 | /// the workspace. | |
469 | fn find_members(&mut self) -> CargoResult<()> { | |
f320997f | 470 | let root_manifest_path = match self.root_manifest { |
58ddb28a AC |
471 | Some(ref path) => path.clone(), |
472 | None => { | |
473 | debug!("find_members - only me as a member"); | |
474 | self.members.push(self.current_manifest.clone()); | |
ba7911dd | 475 | self.default_members.push(self.current_manifest.clone()); |
f660b3e4 AC |
476 | if let Ok(pkg) = self.current() { |
477 | let id = pkg.package_id(); | |
478 | self.member_ids.insert(id); | |
479 | } | |
1e682848 | 480 | return Ok(()); |
58ddb28a AC |
481 | } |
482 | }; | |
f320997f | 483 | |
ba7911dd SS |
484 | let members_paths; |
485 | let default_members_paths; | |
486 | { | |
f320997f BE |
487 | let root_package = self.packages.load(&root_manifest_path)?; |
488 | match *root_package.workspace_config() { | |
ba7911dd | 489 | WorkspaceConfig::Root(ref root_config) => { |
e5a11190 E |
490 | members_paths = root_config |
491 | .members_paths(root_config.members.as_ref().unwrap_or(&vec![]))?; | |
53e84c57 WL |
492 | default_members_paths = if root_manifest_path == self.current_manifest { |
493 | if let Some(ref default) = root_config.default_members { | |
494 | Some(root_config.members_paths(default)?) | |
495 | } else { | |
496 | None | |
497 | } | |
ba7911dd SS |
498 | } else { |
499 | None | |
53e84c57 | 500 | }; |
ba7911dd | 501 | } |
3a18c89a | 502 | _ => anyhow::bail!( |
1e682848 AC |
503 | "root of a workspace inferred but wasn't a root: {}", |
504 | root_manifest_path.display() | |
505 | ), | |
58ddb28a | 506 | } |
ba7911dd | 507 | } |
58ddb28a | 508 | |
f320997f BE |
509 | for path in members_paths { |
510 | self.find_path_deps(&path.join("Cargo.toml"), &root_manifest_path, false)?; | |
58ddb28a AC |
511 | } |
512 | ||
ba7911dd SS |
513 | if let Some(default) = default_members_paths { |
514 | for path in default { | |
515 | let manifest_path = paths::normalize_path(&path.join("Cargo.toml")); | |
516 | if !self.members.contains(&manifest_path) { | |
3a18c89a | 517 | anyhow::bail!( |
1e682848 AC |
518 | "package `{}` is listed in workspace’s default-members \ |
519 | but is not a member.", | |
520 | path.display() | |
521 | ) | |
ba7911dd SS |
522 | } |
523 | self.default_members.push(manifest_path) | |
524 | } | |
8d5e73c6 | 525 | } else if self.is_virtual() { |
ba7911dd | 526 | self.default_members = self.members.clone() |
8d5e73c6 SS |
527 | } else { |
528 | self.default_members.push(self.current_manifest.clone()) | |
ba7911dd SS |
529 | } |
530 | ||
f320997f | 531 | self.find_path_deps(&root_manifest_path, &root_manifest_path, false) |
58ddb28a AC |
532 | } |
533 | ||
1e682848 AC |
534 | fn find_path_deps( |
535 | &mut self, | |
536 | manifest_path: &Path, | |
537 | root_manifest: &Path, | |
538 | is_path_dep: bool, | |
539 | ) -> CargoResult<()> { | |
083da141 | 540 | let manifest_path = paths::normalize_path(manifest_path); |
ba7911dd | 541 | if self.members.contains(&manifest_path) { |
1e682848 | 542 | return Ok(()); |
58ddb28a | 543 | } |
e5a11190 E |
544 | if is_path_dep |
545 | && !manifest_path.parent().unwrap().starts_with(self.root()) | |
1e682848 AC |
546 | && self.find_root(&manifest_path)? != self.root_manifest |
547 | { | |
07c667fb AK |
548 | // If `manifest_path` is a path dependency outside of the workspace, |
549 | // don't add it, or any of its dependencies, as a members. | |
1e682848 | 550 | return Ok(()); |
07c667fb | 551 | } |
58ddb28a | 552 | |
1e682848 AC |
553 | if let WorkspaceConfig::Root(ref root_config) = |
554 | *self.packages.load(root_manifest)?.workspace_config() | |
555 | { | |
676edacf | 556 | if root_config.is_excluded(&manifest_path) { |
1e682848 | 557 | return Ok(()); |
67364baa | 558 | } |
67364baa AC |
559 | } |
560 | ||
58ddb28a | 561 | debug!("find_members - {}", manifest_path.display()); |
083da141 | 562 | self.members.push(manifest_path.clone()); |
58ddb28a AC |
563 | |
564 | let candidates = { | |
25b7ad26 | 565 | let pkg = match *self.packages.load(&manifest_path)? { |
10b3ebf0 AB |
566 | MaybePackage::Package(ref p) => p, |
567 | MaybePackage::Virtual(_) => return Ok(()), | |
58ddb28a | 568 | }; |
f660b3e4 | 569 | self.member_ids.insert(pkg.package_id()); |
58ddb28a | 570 | pkg.dependencies() |
1e682848 AC |
571 | .iter() |
572 | .map(|d| d.source_id()) | |
573 | .filter(|d| d.is_path()) | |
574 | .filter_map(|d| d.url().to_file_path().ok()) | |
575 | .map(|p| p.join("Cargo.toml")) | |
576 | .collect::<Vec<_>>() | |
58ddb28a AC |
577 | }; |
578 | for candidate in candidates { | |
49ab03e3 AB |
579 | self.find_path_deps(&candidate, root_manifest, true) |
580 | .map_err(|err| ManifestError::new(err, manifest_path.clone()))?; | |
58ddb28a AC |
581 | } |
582 | Ok(()) | |
583 | } | |
584 | ||
87f1a4b2 AH |
585 | pub fn features(&self) -> &Features { |
586 | match self.root_maybe() { | |
587 | MaybePackage::Package(p) => p.manifest().features(), | |
588 | MaybePackage::Virtual(vm) => vm.features(), | |
589 | } | |
590 | } | |
591 | ||
787e75b7 | 592 | pub fn resolve_behavior(&self) -> ResolveBehavior { |
323d7a88 | 593 | self.resolve_behavior.unwrap_or(ResolveBehavior::V1) |
787e75b7 EH |
594 | } |
595 | ||
596 | pub fn allows_unstable_package_features(&self) -> bool { | |
597 | self.config().cli_unstable().package_features | |
598 | || match self.resolve_behavior() { | |
599 | ResolveBehavior::V1 => false, | |
600 | ResolveBehavior::V2 => true, | |
601 | } | |
602 | } | |
603 | ||
58ddb28a AC |
604 | /// Validates a workspace, ensuring that a number of invariants are upheld: |
605 | /// | |
606 | /// 1. A workspace only has one root. | |
607 | /// 2. All workspace members agree on this one root as the root. | |
608 | /// 3. The current crate is a member of this workspace. | |
609 | fn validate(&mut self) -> CargoResult<()> { | |
2b6fd6f0 | 610 | // The rest of the checks require a VirtualManifest or multiple members. |
58ddb28a | 611 | if self.root_manifest.is_none() { |
1e682848 | 612 | return Ok(()); |
58ddb28a AC |
613 | } |
614 | ||
065f821f AT |
615 | self.validate_unique_names()?; |
616 | self.validate_workspace_roots()?; | |
617 | self.validate_members()?; | |
618 | self.error_if_manifest_not_in_members()?; | |
619 | self.validate_manifest() | |
620 | } | |
621 | ||
622 | fn validate_unique_names(&self) -> CargoResult<()> { | |
623 | let mut names = BTreeMap::new(); | |
624 | for member in self.members.iter() { | |
625 | let package = self.packages.get(member); | |
626 | let name = match *package { | |
627 | MaybePackage::Package(ref p) => p.name(), | |
628 | MaybePackage::Virtual(_) => continue, | |
629 | }; | |
630 | if let Some(prev) = names.insert(name, member) { | |
631 | anyhow::bail!( | |
632 | "two packages named `{}` in this workspace:\n\ | |
1e682848 AC |
633 | - {}\n\ |
634 | - {}", | |
065f821f AT |
635 | name, |
636 | prev.display(), | |
637 | member.display() | |
638 | ); | |
58ddb28a AC |
639 | } |
640 | } | |
065f821f AT |
641 | Ok(()) |
642 | } | |
58ddb28a | 643 | |
065f821f AT |
644 | fn validate_workspace_roots(&self) -> CargoResult<()> { |
645 | let roots: Vec<PathBuf> = self | |
646 | .members | |
647 | .iter() | |
648 | .filter(|&member| { | |
649 | let config = self.packages.get(member).workspace_config(); | |
650 | matches!(config, WorkspaceConfig::Root(_)) | |
651 | }) | |
652 | .map(|member| member.parent().unwrap().to_path_buf()) | |
653 | .collect(); | |
58ddb28a | 654 | match roots.len() { |
065f821f | 655 | 1 => Ok(()), |
3a18c89a | 656 | 0 => anyhow::bail!( |
1e682848 AC |
657 | "`package.workspace` configuration points to a crate \ |
658 | which is not configured with [workspace]: \n\ | |
659 | configuration at: {}\n\ | |
660 | points to: {}", | |
661 | self.current_manifest.display(), | |
662 | self.root_manifest.as_ref().unwrap().display() | |
663 | ), | |
58ddb28a | 664 | _ => { |
3a18c89a | 665 | anyhow::bail!( |
1e682848 AC |
666 | "multiple workspace roots found in the same workspace:\n{}", |
667 | roots | |
668 | .iter() | |
669 | .map(|r| format!(" {}", r.display())) | |
670 | .collect::<Vec<_>>() | |
671 | .join("\n") | |
672 | ); | |
58ddb28a AC |
673 | } |
674 | } | |
065f821f | 675 | } |
58ddb28a | 676 | |
065f821f | 677 | fn validate_members(&mut self) -> CargoResult<()> { |
58ddb28a | 678 | for member in self.members.clone() { |
82655b46 | 679 | let root = self.find_root(&member)?; |
58ddb28a | 680 | if root == self.root_manifest { |
1e682848 | 681 | continue; |
58ddb28a AC |
682 | } |
683 | ||
684 | match root { | |
685 | Some(root) => { | |
3a18c89a | 686 | anyhow::bail!( |
1e682848 AC |
687 | "package `{}` is a member of the wrong workspace\n\ |
688 | expected: {}\n\ | |
689 | actual: {}", | |
690 | member.display(), | |
691 | self.root_manifest.as_ref().unwrap().display(), | |
692 | root.display() | |
693 | ); | |
58ddb28a AC |
694 | } |
695 | None => { | |
3a18c89a | 696 | anyhow::bail!( |
1e682848 AC |
697 | "workspace member `{}` is not hierarchically below \ |
698 | the workspace root `{}`", | |
699 | member.display(), | |
700 | self.root_manifest.as_ref().unwrap().display() | |
701 | ); | |
58ddb28a AC |
702 | } |
703 | } | |
704 | } | |
065f821f AT |
705 | Ok(()) |
706 | } | |
707 | ||
708 | fn error_if_manifest_not_in_members(&mut self) -> CargoResult<()> { | |
709 | if self.members.contains(&self.current_manifest) { | |
710 | return Ok(()); | |
711 | } | |
58ddb28a | 712 | |
065f821f AT |
713 | let root = self.root_manifest.as_ref().unwrap(); |
714 | let root_dir = root.parent().unwrap(); | |
715 | let current_dir = self.current_manifest.parent().unwrap(); | |
716 | let root_pkg = self.packages.get(root); | |
717 | ||
718 | // FIXME: Make this more generic by using a relative path resolver between member and root. | |
719 | let members_msg = match current_dir.strip_prefix(root_dir) { | |
720 | Ok(rel) => format!( | |
721 | "this may be fixable by adding `{}` to the \ | |
1e682848 AC |
722 | `workspace.members` array of the manifest \ |
723 | located at: {}", | |
065f821f AT |
724 | rel.display(), |
725 | root.display() | |
726 | ), | |
727 | Err(_) => format!( | |
728 | "this may be fixable by adding a member to \ | |
1e682848 AC |
729 | the `workspace.members` array of the \ |
730 | manifest located at: {}", | |
065f821f AT |
731 | root.display() |
732 | ), | |
733 | }; | |
734 | let extra = match *root_pkg { | |
735 | MaybePackage::Virtual(_) => members_msg, | |
736 | MaybePackage::Package(ref p) => { | |
737 | let has_members_list = match *p.manifest().workspace_config() { | |
738 | WorkspaceConfig::Root(ref root_config) => root_config.has_members_list(), | |
739 | WorkspaceConfig::Member { .. } => unreachable!(), | |
740 | }; | |
741 | if !has_members_list { | |
742 | format!( | |
743 | "this may be fixable by ensuring that this \ | |
1e682848 AC |
744 | crate is depended on by the workspace \ |
745 | root: {}", | |
065f821f AT |
746 | root.display() |
747 | ) | |
748 | } else { | |
749 | members_msg | |
58ddb28a | 750 | } |
065f821f AT |
751 | } |
752 | }; | |
753 | anyhow::bail!( | |
754 | "current package believes it's in a workspace when it's not:\n\ | |
1e682848 | 755 | current: {}\n\ |
d7a92124 EH |
756 | workspace: {}\n\n{}\n\ |
757 | Alternatively, to keep it out of the workspace, add the package \ | |
758 | to the `workspace.exclude` array, or add an empty `[workspace]` \ | |
759 | table to the package's manifest.", | |
065f821f AT |
760 | self.current_manifest.display(), |
761 | root.display(), | |
762 | extra | |
763 | ); | |
764 | } | |
58ddb28a | 765 | |
065f821f | 766 | fn validate_manifest(&mut self) -> CargoResult<()> { |
151f12fc | 767 | if let Some(ref root_manifest) = self.root_manifest { |
e5a11190 E |
768 | for pkg in self |
769 | .members() | |
1e682848 AC |
770 | .filter(|p| p.manifest_path() != root_manifest) |
771 | { | |
5b2b5f95 EH |
772 | let manifest = pkg.manifest(); |
773 | let emit_warning = |what| -> CargoResult<()> { | |
774 | let msg = format!( | |
775 | "{} for the non root package will be ignored, \ | |
776 | specify {} at the workspace root:\n\ | |
1e682848 AC |
777 | package: {}\n\ |
778 | workspace: {}", | |
5b2b5f95 EH |
779 | what, |
780 | what, | |
1e682848 | 781 | pkg.manifest_path().display(), |
5b2b5f95 | 782 | root_manifest.display(), |
1e682848 | 783 | ); |
5b2b5f95 EH |
784 | self.config.shell().warn(&msg) |
785 | }; | |
786 | if manifest.original().has_profiles() { | |
787 | emit_warning("profiles")?; | |
788 | } | |
789 | if !manifest.replace().is_empty() { | |
790 | emit_warning("replace")?; | |
791 | } | |
792 | if !manifest.patch().is_empty() { | |
793 | emit_warning("patch")?; | |
151f12fc | 794 | } |
5907db65 EH |
795 | if manifest.resolve_behavior().is_some() |
796 | && manifest.resolve_behavior() != self.resolve_behavior | |
797 | { | |
787e75b7 | 798 | // Only warn if they don't match. |
323d7a88 | 799 | emit_warning("resolver")?; |
787e75b7 | 800 | } |
151f12fc AK |
801 | } |
802 | } | |
58ddb28a AC |
803 | Ok(()) |
804 | } | |
9659f560 AC |
805 | |
806 | pub fn load(&self, manifest_path: &Path) -> CargoResult<Package> { | |
807 | match self.packages.maybe_get(manifest_path) { | |
808 | Some(&MaybePackage::Package(ref p)) => return Ok(p.clone()), | |
3a18c89a | 809 | Some(&MaybePackage::Virtual(_)) => anyhow::bail!("cannot load workspace root"), |
9659f560 AC |
810 | None => {} |
811 | } | |
812 | ||
813 | let mut loaded = self.loaded_packages.borrow_mut(); | |
814 | if let Some(p) = loaded.get(manifest_path).cloned() { | |
815 | return Ok(p); | |
816 | } | |
817 | let source_id = SourceId::for_path(manifest_path.parent().unwrap())?; | |
e5a11190 | 818 | let (package, _nested_paths) = ops::read_package(manifest_path, source_id, self.config)?; |
9659f560 AC |
819 | loaded.insert(manifest_path.to_path_buf(), package.clone()); |
820 | Ok(package) | |
821 | } | |
51d23560 AC |
822 | |
823 | /// Preload the provided registry with already loaded packages. | |
824 | /// | |
825 | /// A workspace may load packages during construction/parsing/early phases | |
826 | /// for various operations, and this preload step avoids doubly-loading and | |
827 | /// parsing crates on the filesystem by inserting them all into the registry | |
828 | /// with their in-memory formats. | |
829 | pub fn preload(&self, registry: &mut PackageRegistry<'cfg>) { | |
830 | // These can get weird as this generally represents a workspace during | |
831 | // `cargo install`. Things like git repositories will actually have a | |
832 | // `PathSource` with multiple entries in it, so the logic below is | |
833 | // mostly just an optimization for normal `cargo build` in workspaces | |
834 | // during development. | |
835 | if self.is_ephemeral { | |
836 | return; | |
837 | } | |
838 | ||
839 | for pkg in self.packages.packages.values() { | |
840 | let pkg = match *pkg { | |
841 | MaybePackage::Package(ref p) => p.clone(), | |
842 | MaybePackage::Virtual(_) => continue, | |
843 | }; | |
844 | let mut src = PathSource::new( | |
845 | pkg.manifest_path(), | |
846 | pkg.package_id().source_id(), | |
847 | self.config, | |
848 | ); | |
849 | src.preload_with(pkg); | |
850 | registry.add_preloaded(Box::new(src)); | |
851 | } | |
852 | } | |
688a4fa6 EH |
853 | |
854 | pub fn emit_warnings(&self) -> CargoResult<()> { | |
855 | for (path, maybe_pkg) in &self.packages.packages { | |
856 | let warnings = match maybe_pkg { | |
857 | MaybePackage::Package(pkg) => pkg.manifest().warnings().warnings(), | |
858 | MaybePackage::Virtual(vm) => vm.warnings().warnings(), | |
859 | }; | |
6ad77940 | 860 | let path = path.join("Cargo.toml"); |
688a4fa6 EH |
861 | for warning in warnings { |
862 | if warning.is_critical { | |
3a18c89a | 863 | let err = anyhow::format_err!("{}", warning.message); |
54c42142 | 864 | let cx = |
3a18c89a | 865 | anyhow::format_err!("failed to parse manifest at `{}`", path.display()); |
6eefe3c2 | 866 | return Err(err.context(cx)); |
688a4fa6 | 867 | } else { |
6ad77940 EH |
868 | let msg = if self.root_manifest.is_none() { |
869 | warning.message.to_string() | |
870 | } else { | |
871 | // In a workspace, it can be confusing where a warning | |
872 | // originated, so include the path. | |
873 | format!("{}: {}", path.display(), warning.message) | |
874 | }; | |
875 | self.config.shell().warn(msg)? | |
688a4fa6 EH |
876 | } |
877 | } | |
878 | } | |
879 | Ok(()) | |
880 | } | |
e137ee69 CD |
881 | |
882 | pub fn set_target_dir(&mut self, target_dir: Filesystem) { | |
883 | self.target_dir = Some(target_dir); | |
884 | } | |
76f0d68d EH |
885 | |
886 | /// Returns a Vec of `(&Package, RequestedFeatures)` tuples that | |
887 | /// represent the workspace members that were requested on the command-line. | |
888 | /// | |
889 | /// `specs` may be empty, which indicates it should return all workspace | |
890 | /// members. In this case, `requested_features.all_features` must be | |
891 | /// `true`. This is used for generating `Cargo.lock`, which must include | |
892 | /// all members with all features enabled. | |
893 | pub fn members_with_features( | |
894 | &self, | |
895 | specs: &[PackageIdSpec], | |
896 | requested_features: &RequestedFeatures, | |
897 | ) -> CargoResult<Vec<(&Package, RequestedFeatures)>> { | |
898 | assert!( | |
899 | !specs.is_empty() || requested_features.all_features, | |
900 | "no specs requires all_features" | |
901 | ); | |
902 | if specs.is_empty() { | |
903 | // When resolving the entire workspace, resolve each member with | |
904 | // all features enabled. | |
905 | return Ok(self | |
906 | .members() | |
907 | .map(|m| (m, RequestedFeatures::new_all(true))) | |
908 | .collect()); | |
909 | } | |
787e75b7 | 910 | if self.allows_unstable_package_features() { |
c17bfcbd EH |
911 | self.members_with_features_pf(specs, requested_features) |
912 | } else { | |
913 | self.members_with_features_stable(specs, requested_features) | |
914 | } | |
915 | } | |
916 | ||
54ace8af | 917 | /// New command-line feature selection with -Zpackage-features. |
c17bfcbd EH |
918 | fn members_with_features_pf( |
919 | &self, | |
920 | specs: &[PackageIdSpec], | |
921 | requested_features: &RequestedFeatures, | |
922 | ) -> CargoResult<Vec<(&Package, RequestedFeatures)>> { | |
c17bfcbd EH |
923 | // Keep track of which features matched *any* member, to produce an error |
924 | // if any of them did not match anywhere. | |
925 | let mut found: BTreeSet<InternedString> = BTreeSet::new(); | |
926 | ||
927 | // Returns the requested features for the given member. | |
928 | // This filters out any named features that the member does not have. | |
929 | let mut matching_features = |member: &Package| -> RequestedFeatures { | |
c17bfcbd EH |
930 | if requested_features.features.is_empty() || requested_features.all_features { |
931 | return requested_features.clone(); | |
76f0d68d | 932 | } |
c17bfcbd EH |
933 | // Only include features this member defines. |
934 | let summary = member.summary(); | |
935 | let member_features = summary.features(); | |
936 | let mut features = BTreeSet::new(); | |
937 | ||
938 | // Checks if a member contains the given feature. | |
939 | let contains = |feature: InternedString| -> bool { | |
940 | member_features.contains_key(&feature) | |
941 | || summary | |
942 | .dependencies() | |
943 | .iter() | |
944 | .any(|dep| dep.is_optional() && dep.name_in_toml() == feature) | |
945 | }; | |
946 | ||
947 | for feature in requested_features.features.iter() { | |
948 | let mut split = feature.splitn(2, '/'); | |
949 | let split = (split.next().unwrap(), split.next()); | |
950 | if let (pkg, Some(pkg_feature)) = split { | |
951 | let pkg = InternedString::new(pkg); | |
952 | let pkg_feature = InternedString::new(pkg_feature); | |
953 | if summary | |
954 | .dependencies() | |
955 | .iter() | |
956 | .any(|dep| dep.name_in_toml() == pkg) | |
957 | { | |
958 | // pkg/feat for a dependency. | |
959 | // Will rely on the dependency resolver to validate `feat`. | |
960 | features.insert(*feature); | |
961 | found.insert(*feature); | |
962 | } else if pkg == member.name() && contains(pkg_feature) { | |
963 | // member/feat where "feat" is a feature in member. | |
964 | features.insert(pkg_feature); | |
965 | found.insert(*feature); | |
966 | } | |
967 | } else if contains(*feature) { | |
968 | // feature exists in this member. | |
969 | features.insert(*feature); | |
970 | found.insert(*feature); | |
971 | } | |
972 | } | |
973 | RequestedFeatures { | |
974 | features: Rc::new(features), | |
975 | all_features: false, | |
976 | uses_default_features: requested_features.uses_default_features, | |
977 | } | |
978 | }; | |
979 | ||
980 | let members: Vec<(&Package, RequestedFeatures)> = self | |
981 | .members() | |
982 | .filter(|m| specs.iter().any(|spec| spec.matches(m.package_id()))) | |
983 | .map(|m| (m, matching_features(m))) | |
984 | .collect(); | |
985 | if members.is_empty() { | |
986 | // `cargo build -p foo`, where `foo` is not a member. | |
987 | // Do not allow any command-line flags (defaults only). | |
988 | if !(requested_features.features.is_empty() | |
989 | && !requested_features.all_features | |
990 | && requested_features.uses_default_features) | |
991 | { | |
992 | anyhow::bail!("cannot specify features for packages outside of workspace"); | |
993 | } | |
994 | // Add all members from the workspace so we can ensure `-p nonmember` | |
995 | // is in the resolve graph. | |
996 | return Ok(self | |
76f0d68d | 997 | .members() |
c17bfcbd EH |
998 | .map(|m| (m, RequestedFeatures::new_all(false))) |
999 | .collect()); | |
1000 | } | |
54ace8af | 1001 | if *requested_features.features != found { |
c17bfcbd EH |
1002 | let missing: Vec<_> = requested_features |
1003 | .features | |
1004 | .difference(&found) | |
1005 | .copied() | |
76f0d68d | 1006 | .collect(); |
c17bfcbd EH |
1007 | // TODO: typo suggestions would be good here. |
1008 | anyhow::bail!( | |
1009 | "none of the selected packages contains these features: {}", | |
1010 | missing.join(", ") | |
1011 | ); | |
1012 | } | |
1013 | Ok(members) | |
1014 | } | |
1015 | ||
1016 | /// This is the current "stable" behavior for command-line feature selection. | |
1017 | fn members_with_features_stable( | |
1018 | &self, | |
1019 | specs: &[PackageIdSpec], | |
1020 | requested_features: &RequestedFeatures, | |
1021 | ) -> CargoResult<Vec<(&Package, RequestedFeatures)>> { | |
1022 | let ms = self.members().filter_map(|member| { | |
1023 | let member_id = member.package_id(); | |
1024 | match self.current_opt() { | |
1025 | // The features passed on the command-line only apply to | |
1026 | // the "current" package (determined by the cwd). | |
1027 | Some(current) if member_id == current.package_id() => { | |
1028 | Some((member, requested_features.clone())) | |
76f0d68d | 1029 | } |
c17bfcbd EH |
1030 | _ => { |
1031 | // Ignore members that are not enabled on the command-line. | |
1032 | if specs.iter().any(|spec| spec.matches(member_id)) { | |
1033 | // -p for a workspace member that is not the | |
1034 | // "current" one, don't use the local | |
1035 | // `--features`, only allow `--all-features`. | |
1036 | Some(( | |
1037 | member, | |
1038 | RequestedFeatures::new_all(requested_features.all_features), | |
1039 | )) | |
1040 | } else { | |
1041 | // This member was not requested on the command-line, skip. | |
1042 | None | |
76f0d68d EH |
1043 | } |
1044 | } | |
c17bfcbd EH |
1045 | } |
1046 | }); | |
1047 | Ok(ms.collect()) | |
76f0d68d | 1048 | } |
58ddb28a AC |
1049 | } |
1050 | ||
1051 | impl<'cfg> Packages<'cfg> { | |
1052 | fn get(&self, manifest_path: &Path) -> &MaybePackage { | |
9659f560 AC |
1053 | self.maybe_get(manifest_path).unwrap() |
1054 | } | |
1055 | ||
d5eeab84 LB |
1056 | fn get_mut(&mut self, manifest_path: &Path) -> &mut MaybePackage { |
1057 | self.maybe_get_mut(manifest_path).unwrap() | |
1058 | } | |
1059 | ||
9659f560 AC |
1060 | fn maybe_get(&self, manifest_path: &Path) -> Option<&MaybePackage> { |
1061 | self.packages.get(manifest_path.parent().unwrap()) | |
58ddb28a AC |
1062 | } |
1063 | ||
d5eeab84 LB |
1064 | fn maybe_get_mut(&mut self, manifest_path: &Path) -> Option<&mut MaybePackage> { |
1065 | self.packages.get_mut(manifest_path.parent().unwrap()) | |
1066 | } | |
1067 | ||
58ddb28a AC |
1068 | fn load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage> { |
1069 | let key = manifest_path.parent().unwrap(); | |
1070 | match self.packages.entry(key.to_path_buf()) { | |
1071 | Entry::Occupied(e) => Ok(e.into_mut()), | |
1072 | Entry::Vacant(v) => { | |
82655b46 | 1073 | let source_id = SourceId::for_path(key)?; |
9671c682 | 1074 | let (manifest, _nested_paths) = |
e5a11190 | 1075 | read_manifest(manifest_path, source_id, self.config)?; |
58ddb28a AC |
1076 | Ok(v.insert(match manifest { |
1077 | EitherManifest::Real(manifest) => { | |
fd07cfd0 | 1078 | MaybePackage::Package(Package::new(manifest, manifest_path)) |
58ddb28a | 1079 | } |
1e682848 | 1080 | EitherManifest::Virtual(vm) => MaybePackage::Virtual(vm), |
58ddb28a AC |
1081 | })) |
1082 | } | |
1083 | } | |
1084 | } | |
1085 | } | |
1086 | ||
1087 | impl<'a, 'cfg> Iterator for Members<'a, 'cfg> { | |
1088 | type Item = &'a Package; | |
1089 | ||
1090 | fn next(&mut self) -> Option<&'a Package> { | |
1091 | loop { | |
1e682848 | 1092 | let next = self.iter.next().map(|path| self.ws.packages.get(path)); |
58ddb28a AC |
1093 | match next { |
1094 | Some(&MaybePackage::Package(ref p)) => return Some(p), | |
1095 | Some(&MaybePackage::Virtual(_)) => {} | |
1096 | None => return None, | |
1097 | } | |
1098 | } | |
1099 | } | |
3d1b950e P |
1100 | |
1101 | fn size_hint(&self) -> (usize, Option<usize>) { | |
1102 | let (_, upper) = self.iter.size_hint(); | |
1103 | (0, upper) | |
1104 | } | |
58ddb28a AC |
1105 | } |
1106 | ||
1107 | impl MaybePackage { | |
1108 | fn workspace_config(&self) -> &WorkspaceConfig { | |
1109 | match *self { | |
fd07cfd0 BE |
1110 | MaybePackage::Package(ref p) => p.manifest().workspace_config(), |
1111 | MaybePackage::Virtual(ref vm) => vm.workspace_config(), | |
58ddb28a AC |
1112 | } |
1113 | } | |
1114 | } | |
f320997f BE |
1115 | |
1116 | impl WorkspaceRootConfig { | |
f7c91ba6 | 1117 | /// Creates a new Intermediate Workspace Root configuration. |
f320997f BE |
1118 | pub fn new( |
1119 | root_dir: &Path, | |
1120 | members: &Option<Vec<String>>, | |
ba7911dd | 1121 | default_members: &Option<Vec<String>>, |
f320997f BE |
1122 | exclude: &Option<Vec<String>>, |
1123 | ) -> WorkspaceRootConfig { | |
1124 | WorkspaceRootConfig { | |
1125 | root_dir: root_dir.to_path_buf(), | |
1126 | members: members.clone(), | |
ba7911dd | 1127 | default_members: default_members.clone(), |
f320997f BE |
1128 | exclude: exclude.clone().unwrap_or_default(), |
1129 | } | |
1130 | } | |
1131 | ||
fd07cfd0 BE |
1132 | /// Checks the path against the `excluded` list. |
1133 | /// | |
f7c91ba6 | 1134 | /// This method does **not** consider the `members` list. |
f320997f | 1135 | fn is_excluded(&self, manifest_path: &Path) -> bool { |
e5a11190 E |
1136 | let excluded = self |
1137 | .exclude | |
1e682848 AC |
1138 | .iter() |
1139 | .any(|ex| manifest_path.starts_with(self.root_dir.join(ex))); | |
f320997f BE |
1140 | |
1141 | let explicit_member = match self.members { | |
1e682848 AC |
1142 | Some(ref members) => members |
1143 | .iter() | |
1144 | .any(|mem| manifest_path.starts_with(self.root_dir.join(mem))), | |
f320997f BE |
1145 | None => false, |
1146 | }; | |
1147 | ||
1148 | !explicit_member && excluded | |
1149 | } | |
1150 | ||
1151 | fn has_members_list(&self) -> bool { | |
1152 | self.members.is_some() | |
1153 | } | |
1154 | ||
ba7911dd | 1155 | fn members_paths(&self, globs: &[String]) -> CargoResult<Vec<PathBuf>> { |
f320997f BE |
1156 | let mut expanded_list = Vec::new(); |
1157 | ||
ba7911dd SS |
1158 | for glob in globs { |
1159 | let pathbuf = self.root_dir.join(glob); | |
1160 | let expanded_paths = Self::expand_member_path(&pathbuf)?; | |
1161 | ||
1162 | // If glob does not find any valid paths, then put the original | |
1163 | // path in the expanded list to maintain backwards compatibility. | |
1164 | if expanded_paths.is_empty() { | |
1165 | expanded_list.push(pathbuf); | |
1166 | } else { | |
1167 | expanded_list.extend(expanded_paths); | |
f320997f BE |
1168 | } |
1169 | } | |
1170 | ||
1171 | Ok(expanded_list) | |
1172 | } | |
1173 | ||
1174 | fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> { | |
1175 | let path = match path.to_str() { | |
1176 | Some(p) => p, | |
1177 | None => return Ok(Vec::new()), | |
1178 | }; | |
54c42142 | 1179 | let res = |
3a18c89a | 1180 | glob(path).chain_err(|| anyhow::format_err!("could not parse pattern `{}`", &path))?; |
e5a11190 | 1181 | let res = res |
54c42142 | 1182 | .map(|p| { |
3a18c89a | 1183 | p.chain_err(|| anyhow::format_err!("unable to match path to pattern `{}`", &path)) |
54c42142 | 1184 | }) |
e5a11190 | 1185 | .collect::<Result<Vec<_>, _>>()?; |
37cffbe0 | 1186 | Ok(res) |
f320997f BE |
1187 | } |
1188 | } |