1 use std::cell::RefCell;
2 use std::collections::hash_map::{Entry, HashMap};
3 use std::collections::{BTreeMap, BTreeSet, HashSet};
4 use std::path::{Path, PathBuf};
5 use std::rc::Rc;
6 
7 use anyhow::{bail, Context as _};
8 use glob::glob;
9 use itertools::Itertools;
10 use log::debug;
11 use url::Url;
12 
13 use crate::core::features::Features;
14 use crate::core::registry::PackageRegistry;
15 use crate::core::resolver::features::CliFeatures;
16 use crate::core::resolver::ResolveBehavior;
17 use crate::core::{Dependency, Edition, FeatureValue, PackageId, PackageIdSpec};
18 use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
19 use crate::ops;
20 use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
21 use crate::util::errors::{CargoResult, ManifestError};
22 use crate::util::interning::InternedString;
23 use crate::util::lev_distance;
24 use crate::util::toml::{read_manifest, TomlDependency, TomlProfiles};
25 use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl};
26 use cargo_util::paths;
27 
28 /// The core abstraction in Cargo for working with a workspace of crates.
29 ///
30 /// A workspace is often created very early on and then threaded through all
31 /// other functions. It's typically through this object that the current
32 /// package is loaded and/or learned about.
33 #[derive(Debug)]
34 pub struct Workspace<'cfg> {
35     config: &'cfg Config,
36 
37     // This path is a path to where the current cargo subcommand was invoked
38     // from. That is the `--manifest-path` argument to Cargo, and
39     // points to the "main crate" that we're going to worry about.
40     current_manifest: PathBuf,
41 
42     // A list of packages found in this workspace. Always includes at least the
43     // package mentioned by `current_manifest`.
44     packages: Packages<'cfg>,
45 
46     // If this workspace includes more than one crate, this points to the root
47     // of the workspace. This is `None` in the case that `[workspace]` is
48     // missing, `package.workspace` is missing, and no `Cargo.toml` above
49     // `current_manifest` was found on the filesystem with `[workspace]`.
50     root_manifest: Option<PathBuf>,
51 
52     // Shared target directory for all the packages of this workspace.
53     // `None` if the default path of `root/target` should be used.
54     target_dir: Option<Filesystem>,
55 
56     // List of members in this workspace with a listing of all their manifest
57     // paths. The packages themselves can be looked up through the `packages`
58     // set above.
59     members: Vec<PathBuf>,
60     member_ids: HashSet<PackageId>,
61 
62     // The subset of `members` that are used by the
63     // `build`, `check`, `test`, and `bench` subcommands
64     // when no package is selected with `--package` / `-p` and `--workspace`
65     // is not used.
66     //
67     // This is set by the `default-members` config
68     // in the `[workspace]` section.
69     // When unset, this is the same as `members` for virtual workspaces
70     // (`--workspace` is implied)
71     // or only the root package for non-virtual workspaces.
72     default_members: Vec<PathBuf>,
73 
74     // `true` if this is a temporary workspace created for the purposes of the
75     // `cargo install` or `cargo package` commands.
76     is_ephemeral: bool,
77 
78     // `true` if this workspace should enforce optional dependencies even when
79     // not needed; false if this workspace should only enforce dependencies
80     // needed by the current configuration (such as in cargo install). In some
81     // cases `false` also results in the non-enforcement of dev-dependencies.
82     require_optional_deps: bool,
83 
84     // A cache of loaded packages for particular paths which is disjoint from
85     // `packages` up above, used in the `load` method down below.
86     loaded_packages: RefCell<HashMap<PathBuf, Package>>,
87 
88     // If `true`, then the resolver will ignore any existing `Cargo.lock`
89     // file. This is set for `cargo install` without `--locked`.
90     ignore_lock: bool,
91 
92     /// The resolver behavior specified with the `resolver` field.
93     resolve_behavior: ResolveBehavior,
94 
95     /// Workspace-level custom metadata
96     custom_metadata: Option<toml::Value>,
97 }
98 
99 // Separate structure for tracking loaded packages (to avoid loading anything
100 // twice), and this is separate to help appease the borrow checker.
101 #[derive(Debug)]
102 struct Packages<'cfg> {
103     config: &'cfg Config,
104     packages: HashMap<PathBuf, MaybePackage>,
105 }
106 
107 #[derive(Debug)]
108 pub enum MaybePackage {
109     Package(Package),
110     Virtual(VirtualManifest),
111 }
112 
113 /// Configuration of a workspace in a manifest.
114 #[derive(Debug, Clone)]
115 pub enum WorkspaceConfig {
116     /// Indicates that `[workspace]` was present and the members were
117     /// optionally specified as well.
118     Root(WorkspaceRootConfig),
119 
120     /// Indicates that `[workspace]` was present and the `root` field is the
121     /// optional value of `package.workspace`, if present.
122     Member { root: Option<String> },
123 }
124 
125 /// Intermediate configuration of a workspace root in a manifest.
126 ///
127 /// Knows the Workspace Root path, as well as `members` and `exclude` lists of path patterns, which
128 /// together tell if some path is recognized as a member by this root or not.
129 #[derive(Debug, Clone)]
130 pub struct WorkspaceRootConfig {
131     root_dir: PathBuf,
132     members: Option<Vec<String>>,
133     default_members: Option<Vec<String>>,
134     exclude: Vec<String>,
135     custom_metadata: Option<toml::Value>,
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.
new(manifest_path: &Path, config: &'cfg Config) -> CargoResult<Workspace<'cfg>>145     pub fn new(manifest_path: &Path, config: &'cfg Config) -> CargoResult<Workspace<'cfg>> {
146         let mut ws = Workspace::new_default(manifest_path.to_path_buf(), config);
147         ws.target_dir = config.target_dir()?;
148 
149         if manifest_path.is_relative() {
150             bail!(
151                 "manifest_path:{:?} is not an absolute path. Please provide an absolute path.",
152                 manifest_path
153             )
154         } else {
155             ws.root_manifest = ws.find_root(manifest_path)?;
156         }
157 
158         ws.custom_metadata = ws
159             .load_workspace_config()?
160             .and_then(|cfg| cfg.custom_metadata);
161         ws.find_members()?;
162         ws.set_resolve_behavior();
163         ws.validate()?;
164         Ok(ws)
165     }
166 
new_default(current_manifest: PathBuf, config: &'cfg Config) -> Workspace<'cfg>167     fn new_default(current_manifest: PathBuf, config: &'cfg Config) -> Workspace<'cfg> {
168         Workspace {
169             config,
170             current_manifest,
171             packages: Packages {
172                 config,
173                 packages: HashMap::new(),
174             },
175             root_manifest: None,
176             target_dir: None,
177             members: Vec::new(),
178             member_ids: HashSet::new(),
179             default_members: Vec::new(),
180             is_ephemeral: false,
181             require_optional_deps: true,
182             loaded_packages: RefCell::new(HashMap::new()),
183             ignore_lock: false,
184             resolve_behavior: ResolveBehavior::V1,
185             custom_metadata: None,
186         }
187     }
188 
new_virtual( root_path: PathBuf, current_manifest: PathBuf, manifest: VirtualManifest, config: &'cfg Config, ) -> CargoResult<Workspace<'cfg>>189     pub fn new_virtual(
190         root_path: PathBuf,
191         current_manifest: PathBuf,
192         manifest: VirtualManifest,
193         config: &'cfg Config,
194     ) -> CargoResult<Workspace<'cfg>> {
195         let mut ws = Workspace::new_default(current_manifest, config);
196         ws.root_manifest = Some(root_path.join("Cargo.toml"));
197         ws.target_dir = config.target_dir()?;
198         ws.packages
199             .packages
200             .insert(root_path, MaybePackage::Virtual(manifest));
201         ws.find_members()?;
202         ws.set_resolve_behavior();
203         // TODO: validation does not work because it walks up the directory
204         // tree looking for the root which is a fake file that doesn't exist.
205         Ok(ws)
206     }
207 
208     /// Creates a "temporary workspace" from one package which only contains
209     /// that package.
210     ///
211     /// This constructor will not touch the filesystem and only creates an
212     /// in-memory workspace. That is, all configuration is ignored, it's just
213     /// intended for that one package.
214     ///
215     /// This is currently only used in niche situations like `cargo install` or
216     /// `cargo package`.
ephemeral( package: Package, config: &'cfg Config, target_dir: Option<Filesystem>, require_optional_deps: bool, ) -> CargoResult<Workspace<'cfg>>217     pub fn ephemeral(
218         package: Package,
219         config: &'cfg Config,
220         target_dir: Option<Filesystem>,
221         require_optional_deps: bool,
222     ) -> CargoResult<Workspace<'cfg>> {
223         let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), config);
224         ws.is_ephemeral = true;
225         ws.require_optional_deps = require_optional_deps;
226         let key = ws.current_manifest.parent().unwrap();
227         let id = package.package_id();
228         let package = MaybePackage::Package(package);
229         ws.packages.packages.insert(key.to_path_buf(), package);
230         ws.target_dir = if let Some(dir) = target_dir {
231             Some(dir)
232         } else {
233             ws.config.target_dir()?
234         };
235         ws.members.push(ws.current_manifest.clone());
236         ws.member_ids.insert(id);
237         ws.default_members.push(ws.current_manifest.clone());
238         ws.set_resolve_behavior();
239         Ok(ws)
240     }
241 
set_resolve_behavior(&mut self)242     fn set_resolve_behavior(&mut self) {
243         // - If resolver is specified in the workspace definition, use that.
244         // - If the root package specifies the resolver, use that.
245         // - If the root package specifies edition 2021, use v2.
246         // - Otherwise, use the default v1.
247         self.resolve_behavior = match self.root_maybe() {
248             MaybePackage::Package(p) => p.manifest().resolve_behavior().or_else(|| {
249                 if p.manifest().edition() >= Edition::Edition2021 {
250                     Some(ResolveBehavior::V2)
251                 } else {
252                     None
253                 }
254             }),
255             MaybePackage::Virtual(vm) => vm.resolve_behavior(),
256         }
257         .unwrap_or(ResolveBehavior::V1);
258     }
259 
260     /// Returns the current package of this workspace.
261     ///
262     /// Note that this can return an error if it the current manifest is
263     /// actually a "virtual Cargo.toml", in which case an error is returned
264     /// indicating that something else should be passed.
current(&self) -> CargoResult<&Package>265     pub fn current(&self) -> CargoResult<&Package> {
266         let pkg = self.current_opt().ok_or_else(|| {
267             anyhow::format_err!(
268                 "manifest path `{}` is a virtual manifest, but this \
269                  command requires running against an actual package in \
270                  this workspace",
271                 self.current_manifest.display()
272             )
273         })?;
274         Ok(pkg)
275     }
276 
current_mut(&mut self) -> CargoResult<&mut Package>277     pub fn current_mut(&mut self) -> CargoResult<&mut Package> {
278         let cm = self.current_manifest.clone();
279         let pkg = self.current_opt_mut().ok_or_else(|| {
280             anyhow::format_err!(
281                 "manifest path `{}` is a virtual manifest, but this \
282                  command requires running against an actual package in \
283                  this workspace",
284                 cm.display()
285             )
286         })?;
287         Ok(pkg)
288     }
289 
current_opt(&self) -> Option<&Package>290     pub fn current_opt(&self) -> Option<&Package> {
291         match *self.packages.get(&self.current_manifest) {
292             MaybePackage::Package(ref p) => Some(p),
293             MaybePackage::Virtual(..) => None,
294         }
295     }
296 
current_opt_mut(&mut self) -> Option<&mut Package>297     pub fn current_opt_mut(&mut self) -> Option<&mut Package> {
298         match *self.packages.get_mut(&self.current_manifest) {
299             MaybePackage::Package(ref mut p) => Some(p),
300             MaybePackage::Virtual(..) => None,
301         }
302     }
303 
is_virtual(&self) -> bool304     pub fn is_virtual(&self) -> bool {
305         match *self.packages.get(&self.current_manifest) {
306             MaybePackage::Package(..) => false,
307             MaybePackage::Virtual(..) => true,
308         }
309     }
310 
311     /// Returns the `Config` this workspace is associated with.
config(&self) -> &'cfg Config312     pub fn config(&self) -> &'cfg Config {
313         self.config
314     }
315 
profiles(&self) -> Option<&TomlProfiles>316     pub fn profiles(&self) -> Option<&TomlProfiles> {
317         match self.root_maybe() {
318             MaybePackage::Package(p) => p.manifest().profiles(),
319             MaybePackage::Virtual(vm) => vm.profiles(),
320         }
321     }
322 
323     /// Returns the root path of this workspace.
324     ///
325     /// That is, this returns the path of the directory containing the
326     /// `Cargo.toml` which is the root of this workspace.
root(&self) -> &Path327     pub fn root(&self) -> &Path {
328         self.root_manifest().parent().unwrap()
329     }
330 
331     /// Returns the path of the `Cargo.toml` which is the root of this
332     /// workspace.
root_manifest(&self) -> &Path333     pub fn root_manifest(&self) -> &Path {
334         self.root_manifest
335             .as_ref()
336             .unwrap_or(&self.current_manifest)
337     }
338 
339     /// Returns the root Package or VirtualManifest.
root_maybe(&self) -> &MaybePackage340     pub fn root_maybe(&self) -> &MaybePackage {
341         self.packages.get(self.root_manifest())
342     }
343 
target_dir(&self) -> Filesystem344     pub fn target_dir(&self) -> Filesystem {
345         self.target_dir
346             .clone()
347             .unwrap_or_else(|| Filesystem::new(self.root().join("target")))
348     }
349 
350     /// Returns the root `[replace]` section of this workspace.
351     ///
352     /// This may be from a virtual crate or an actual crate.
root_replace(&self) -> &[(PackageIdSpec, Dependency)]353     pub fn root_replace(&self) -> &[(PackageIdSpec, Dependency)] {
354         match self.root_maybe() {
355             MaybePackage::Package(p) => p.manifest().replace(),
356             MaybePackage::Virtual(vm) => vm.replace(),
357         }
358     }
359 
config_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>>360     fn config_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
361         let config_patch: Option<
362             BTreeMap<String, BTreeMap<String, TomlDependency<ConfigRelativePath>>>,
363         > = self.config.get("patch")?;
364 
365         let source = SourceId::for_path(self.root())?;
366 
367         let mut warnings = Vec::new();
368         let mut nested_paths = Vec::new();
369 
370         let mut patch = HashMap::new();
371         for (url, deps) in config_patch.into_iter().flatten() {
372             let url = match &url[..] {
373                 CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
374                 url => self
375                     .config
376                     .get_registry_index(url)
377                     .or_else(|_| url.into_url())
378                     .with_context(|| {
379                         format!("[patch] entry `{}` should be a URL or registry name", url)
380                     })?,
381             };
382             patch.insert(
383                 url,
384                 deps.iter()
385                     .map(|(name, dep)| {
386                         dep.to_dependency_split(
387                             name,
388                             source,
389                             &mut nested_paths,
390                             self.config,
391                             &mut warnings,
392                             /* platform */ None,
393                             // NOTE: Since we use ConfigRelativePath, this root isn't used as
394                             // any relative paths are resolved before they'd be joined with root.
395                             Path::new("unused-relative-path"),
396                             self.unstable_features(),
397                             /* kind */ None,
398                         )
399                     })
400                     .collect::<CargoResult<Vec<_>>>()?,
401             );
402         }
403 
404         for message in warnings {
405             self.config
406                 .shell()
407                 .warn(format!("[patch] in cargo config: {}", message))?
408         }
409 
410         Ok(patch)
411     }
412 
413     /// Returns the root `[patch]` section of this workspace.
414     ///
415     /// This may be from a virtual crate or an actual crate.
root_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>>416     pub fn root_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
417         let from_manifest = match self.root_maybe() {
418             MaybePackage::Package(p) => p.manifest().patch(),
419             MaybePackage::Virtual(vm) => vm.patch(),
420         };
421 
422         let from_config = self.config_patch()?;
423         if from_config.is_empty() {
424             return Ok(from_manifest.clone());
425         }
426         if from_manifest.is_empty() {
427             return Ok(from_config);
428         }
429 
430         // We could just chain from_manifest and from_config,
431         // but that's not quite right as it won't deal with overlaps.
432         let mut combined = from_config;
433         for (url, deps_from_manifest) in from_manifest {
434             if let Some(deps_from_config) = combined.get_mut(url) {
435                 // We want from_config to take precedence for each patched name.
436                 // NOTE: This is inefficient if the number of patches is large!
437                 let mut from_manifest_pruned = deps_from_manifest.clone();
438                 for dep_from_config in &mut *deps_from_config {
439                     if let Some(i) = from_manifest_pruned.iter().position(|dep_from_manifest| {
440                         // XXX: should this also take into account version numbers?
441                         dep_from_config.name_in_toml() == dep_from_manifest.name_in_toml()
442                     }) {
443                         from_manifest_pruned.swap_remove(i);
444                     }
445                 }
446                 // Whatever is left does not exist in manifest dependencies.
447                 deps_from_config.extend(from_manifest_pruned);
448             } else {
449                 combined.insert(url.clone(), deps_from_manifest.clone());
450             }
451         }
452         Ok(combined)
453     }
454 
455     /// Returns an iterator over all packages in this workspace
members(&self) -> impl Iterator<Item = &Package>456     pub fn members(&self) -> impl Iterator<Item = &Package> {
457         let packages = &self.packages;
458         self.members
459             .iter()
460             .filter_map(move |path| match packages.get(path) {
461                 &MaybePackage::Package(ref p) => Some(p),
462                 _ => None,
463             })
464     }
465 
466     /// Returns a mutable iterator over all packages in this workspace
members_mut(&mut self) -> impl Iterator<Item = &mut Package>467     pub fn members_mut(&mut self) -> impl Iterator<Item = &mut Package> {
468         let packages = &mut self.packages.packages;
469         let members: HashSet<_> = self
470             .members
471             .iter()
472             .map(|path| path.parent().unwrap().to_owned())
473             .collect();
474 
475         packages.iter_mut().filter_map(move |(path, package)| {
476             if members.contains(path) {
477                 if let MaybePackage::Package(ref mut p) = package {
478                     return Some(p);
479                 }
480             }
481 
482             None
483         })
484     }
485 
486     /// Returns an iterator over default packages in this workspace
default_members<'a>(&'a self) -> impl Iterator<Item = &Package>487     pub fn default_members<'a>(&'a self) -> impl Iterator<Item = &Package> {
488         let packages = &self.packages;
489         self.default_members
490             .iter()
491             .filter_map(move |path| match packages.get(path) {
492                 &MaybePackage::Package(ref p) => Some(p),
493                 _ => None,
494             })
495     }
496 
497     /// Returns an iterator over default packages in this workspace
default_members_mut(&mut self) -> impl Iterator<Item = &mut Package>498     pub fn default_members_mut(&mut self) -> impl Iterator<Item = &mut Package> {
499         let packages = &mut self.packages.packages;
500         let members: HashSet<_> = self
501             .default_members
502             .iter()
503             .map(|path| path.parent().unwrap().to_owned())
504             .collect();
505 
506         packages.iter_mut().filter_map(move |(path, package)| {
507             if members.contains(path) {
508                 if let MaybePackage::Package(ref mut p) = package {
509                     return Some(p);
510                 }
511             }
512 
513             None
514         })
515     }
516 
517     /// Returns true if the package is a member of the workspace.
is_member(&self, pkg: &Package) -> bool518     pub fn is_member(&self, pkg: &Package) -> bool {
519         self.member_ids.contains(&pkg.package_id())
520     }
521 
is_ephemeral(&self) -> bool522     pub fn is_ephemeral(&self) -> bool {
523         self.is_ephemeral
524     }
525 
require_optional_deps(&self) -> bool526     pub fn require_optional_deps(&self) -> bool {
527         self.require_optional_deps
528     }
529 
set_require_optional_deps( &mut self, require_optional_deps: bool, ) -> &mut Workspace<'cfg>530     pub fn set_require_optional_deps(
531         &mut self,
532         require_optional_deps: bool,
533     ) -> &mut Workspace<'cfg> {
534         self.require_optional_deps = require_optional_deps;
535         self
536     }
537 
ignore_lock(&self) -> bool538     pub fn ignore_lock(&self) -> bool {
539         self.ignore_lock
540     }
541 
set_ignore_lock(&mut self, ignore_lock: bool) -> &mut Workspace<'cfg>542     pub fn set_ignore_lock(&mut self, ignore_lock: bool) -> &mut Workspace<'cfg> {
543         self.ignore_lock = ignore_lock;
544         self
545     }
546 
custom_metadata(&self) -> Option<&toml::Value>547     pub fn custom_metadata(&self) -> Option<&toml::Value> {
548         self.custom_metadata.as_ref()
549     }
550 
load_workspace_config(&mut self) -> CargoResult<Option<WorkspaceRootConfig>>551     pub fn load_workspace_config(&mut self) -> CargoResult<Option<WorkspaceRootConfig>> {
552         // If we didn't find a root, it must mean there is no [workspace] section, and thus no
553         // metadata.
554         if let Some(root_path) = &self.root_manifest {
555             let root_package = self.packages.load(root_path)?;
556             match root_package.workspace_config() {
557                 WorkspaceConfig::Root(ref root_config) => {
558                     return Ok(Some(root_config.clone()));
559                 }
560 
561                 _ => bail!(
562                     "root of a workspace inferred but wasn't a root: {}",
563                     root_path.display()
564                 ),
565             }
566         }
567 
568         Ok(None)
569     }
570 
571     /// Finds the root of a workspace for the crate whose manifest is located
572     /// at `manifest_path`.
573     ///
574     /// This will parse the `Cargo.toml` at `manifest_path` and then interpret
575     /// the workspace configuration, optionally walking up the filesystem
576     /// looking for other workspace roots.
577     ///
578     /// Returns an error if `manifest_path` isn't actually a valid manifest or
579     /// if some other transient error happens.
find_root(&mut self, manifest_path: &Path) -> CargoResult<Option<PathBuf>>580     fn find_root(&mut self, manifest_path: &Path) -> CargoResult<Option<PathBuf>> {
581         fn read_root_pointer(member_manifest: &Path, root_link: &str) -> PathBuf {
582             let path = member_manifest
583                 .parent()
584                 .unwrap()
585                 .join(root_link)
586                 .join("Cargo.toml");
587             debug!("find_root - pointer {}", path.display());
588             paths::normalize_path(&path)
589         }
590 
591         {
592             let current = self.packages.load(manifest_path)?;
593             match *current.workspace_config() {
594                 WorkspaceConfig::Root(_) => {
595                     debug!("find_root - is root {}", manifest_path.display());
596                     return Ok(Some(manifest_path.to_path_buf()));
597                 }
598                 WorkspaceConfig::Member {
599                     root: Some(ref path_to_root),
600                 } => return Ok(Some(read_root_pointer(manifest_path, path_to_root))),
601                 WorkspaceConfig::Member { root: None } => {}
602             }
603         }
604 
605         for path in paths::ancestors(manifest_path, None).skip(2) {
606             if path.ends_with("target/package") {
607                 break;
608             }
609 
610             let ances_manifest_path = path.join("Cargo.toml");
611             debug!("find_root - trying {}", ances_manifest_path.display());
612             if ances_manifest_path.exists() {
613                 match *self.packages.load(&ances_manifest_path)?.workspace_config() {
614                     WorkspaceConfig::Root(ref ances_root_config) => {
615                         debug!("find_root - found a root checking exclusion");
616                         if !ances_root_config.is_excluded(manifest_path) {
617                             debug!("find_root - found!");
618                             return Ok(Some(ances_manifest_path));
619                         }
620                     }
621                     WorkspaceConfig::Member {
622                         root: Some(ref path_to_root),
623                     } => {
624                         debug!("find_root - found pointer");
625                         return Ok(Some(read_root_pointer(&ances_manifest_path, path_to_root)));
626                     }
627                     WorkspaceConfig::Member { .. } => {}
628                 }
629             }
630 
631             // Don't walk across `CARGO_HOME` when we're looking for the
632             // workspace root. Sometimes a package will be organized with
633             // `CARGO_HOME` pointing inside of the workspace root or in the
634             // current package, but we don't want to mistakenly try to put
635             // crates.io crates into the workspace by accident.
636             if self.config.home() == path {
637                 break;
638             }
639         }
640 
641         Ok(None)
642     }
643 
644     /// After the root of a workspace has been located, probes for all members
645     /// of a workspace.
646     ///
647     /// If the `workspace.members` configuration is present, then this just
648     /// verifies that those are all valid packages to point to. Otherwise, this
649     /// will transitively follow all `path` dependencies looking for members of
650     /// the workspace.
find_members(&mut self) -> CargoResult<()>651     fn find_members(&mut self) -> CargoResult<()> {
652         let workspace_config = match self.load_workspace_config()? {
653             Some(workspace_config) => workspace_config,
654             None => {
655                 debug!("find_members - only me as a member");
656                 self.members.push(self.current_manifest.clone());
657                 self.default_members.push(self.current_manifest.clone());
658                 if let Ok(pkg) = self.current() {
659                     let id = pkg.package_id();
660                     self.member_ids.insert(id);
661                 }
662                 return Ok(());
663             }
664         };
665 
666         // self.root_manifest must be Some to have retrieved workspace_config
667         let root_manifest_path = self.root_manifest.clone().unwrap();
668 
669         let members_paths =
670             workspace_config.members_paths(workspace_config.members.as_ref().unwrap_or(&vec![]))?;
671         let default_members_paths = if root_manifest_path == self.current_manifest {
672             if let Some(ref default) = workspace_config.default_members {
673                 Some(workspace_config.members_paths(default)?)
674             } else {
675                 None
676             }
677         } else {
678             None
679         };
680 
681         for path in &members_paths {
682             self.find_path_deps(&path.join("Cargo.toml"), &root_manifest_path, false)
683                 .with_context(|| {
684                     format!(
685                         "failed to load manifest for workspace member `{}`",
686                         path.display()
687                     )
688                 })?;
689         }
690 
691         if let Some(default) = default_members_paths {
692             for path in default {
693                 let normalized_path = paths::normalize_path(&path);
694                 let manifest_path = normalized_path.join("Cargo.toml");
695                 if !self.members.contains(&manifest_path) {
696                     // default-members are allowed to be excluded, but they
697                     // still must be referred to by the original (unfiltered)
698                     // members list. Note that we aren't testing against the
699                     // manifest path, both because `members_paths` doesn't
700                     // include `/Cargo.toml`, and because excluded paths may not
701                     // be crates.
702                     let exclude = members_paths.contains(&normalized_path)
703                         && workspace_config.is_excluded(&normalized_path);
704                     if exclude {
705                         continue;
706                     }
707                     bail!(
708                         "package `{}` is listed in workspace’s default-members \
709                          but is not a member.",
710                         path.display()
711                     )
712                 }
713                 self.default_members.push(manifest_path)
714             }
715         } else if self.is_virtual() {
716             self.default_members = self.members.clone()
717         } else {
718             self.default_members.push(self.current_manifest.clone())
719         }
720 
721         self.find_path_deps(&root_manifest_path, &root_manifest_path, false)
722     }
723 
find_path_deps( &mut self, manifest_path: &Path, root_manifest: &Path, is_path_dep: bool, ) -> CargoResult<()>724     fn find_path_deps(
725         &mut self,
726         manifest_path: &Path,
727         root_manifest: &Path,
728         is_path_dep: bool,
729     ) -> CargoResult<()> {
730         let manifest_path = paths::normalize_path(manifest_path);
731         if self.members.contains(&manifest_path) {
732             return Ok(());
733         }
734         if is_path_dep
735             && !manifest_path.parent().unwrap().starts_with(self.root())
736             && self.find_root(&manifest_path)? != self.root_manifest
737         {
738             // If `manifest_path` is a path dependency outside of the workspace,
739             // don't add it, or any of its dependencies, as a members.
740             return Ok(());
741         }
742 
743         if let WorkspaceConfig::Root(ref root_config) =
744             *self.packages.load(root_manifest)?.workspace_config()
745         {
746             if root_config.is_excluded(&manifest_path) {
747                 return Ok(());
748             }
749         }
750 
751         debug!("find_members - {}", manifest_path.display());
752         self.members.push(manifest_path.clone());
753 
754         let candidates = {
755             let pkg = match *self.packages.load(&manifest_path)? {
756                 MaybePackage::Package(ref p) => p,
757                 MaybePackage::Virtual(_) => return Ok(()),
758             };
759             self.member_ids.insert(pkg.package_id());
760             pkg.dependencies()
761                 .iter()
762                 .map(|d| (d.source_id(), d.package_name()))
763                 .filter(|(s, _)| s.is_path())
764                 .filter_map(|(s, n)| s.url().to_file_path().ok().map(|p| (p, n)))
765                 .map(|(p, n)| (p.join("Cargo.toml"), n))
766                 .collect::<Vec<_>>()
767         };
768         for (path, name) in candidates {
769             self.find_path_deps(&path, root_manifest, true)
770                 .with_context(|| format!("failed to load manifest for dependency `{}`", name))
771                 .map_err(|err| ManifestError::new(err, manifest_path.clone()))?;
772         }
773         Ok(())
774     }
775 
776     /// Returns the unstable nightly-only features enabled via `cargo-features` in the manifest.
unstable_features(&self) -> &Features777     pub fn unstable_features(&self) -> &Features {
778         match self.root_maybe() {
779             MaybePackage::Package(p) => p.manifest().unstable_features(),
780             MaybePackage::Virtual(vm) => vm.unstable_features(),
781         }
782     }
783 
resolve_behavior(&self) -> ResolveBehavior784     pub fn resolve_behavior(&self) -> ResolveBehavior {
785         self.resolve_behavior
786     }
787 
788     /// Returns `true` if this workspace uses the new CLI features behavior.
789     ///
790     /// The old behavior only allowed choosing the features from the package
791     /// in the current directory, regardless of which packages were chosen
792     /// with the -p flags. The new behavior allows selecting features from the
793     /// packages chosen on the command line (with -p or --workspace flags),
794     /// ignoring whatever is in the current directory.
allows_new_cli_feature_behavior(&self) -> bool795     pub fn allows_new_cli_feature_behavior(&self) -> bool {
796         self.is_virtual()
797             || match self.resolve_behavior() {
798                 ResolveBehavior::V1 => false,
799                 ResolveBehavior::V2 => true,
800             }
801     }
802 
803     /// Validates a workspace, ensuring that a number of invariants are upheld:
804     ///
805     /// 1. A workspace only has one root.
806     /// 2. All workspace members agree on this one root as the root.
807     /// 3. The current crate is a member of this workspace.
validate(&mut self) -> CargoResult<()>808     fn validate(&mut self) -> CargoResult<()> {
809         // The rest of the checks require a VirtualManifest or multiple members.
810         if self.root_manifest.is_none() {
811             return Ok(());
812         }
813 
814         self.validate_unique_names()?;
815         self.validate_workspace_roots()?;
816         self.validate_members()?;
817         self.error_if_manifest_not_in_members()?;
818         self.validate_manifest()
819     }
820 
validate_unique_names(&self) -> CargoResult<()>821     fn validate_unique_names(&self) -> CargoResult<()> {
822         let mut names = BTreeMap::new();
823         for member in self.members.iter() {
824             let package = self.packages.get(member);
825             let name = match *package {
826                 MaybePackage::Package(ref p) => p.name(),
827                 MaybePackage::Virtual(_) => continue,
828             };
829             if let Some(prev) = names.insert(name, member) {
830                 bail!(
831                     "two packages named `{}` in this workspace:\n\
832                          - {}\n\
833                          - {}",
834                     name,
835                     prev.display(),
836                     member.display()
837                 );
838             }
839         }
840         Ok(())
841     }
842 
validate_workspace_roots(&self) -> CargoResult<()>843     fn validate_workspace_roots(&self) -> CargoResult<()> {
844         let roots: Vec<PathBuf> = self
845             .members
846             .iter()
847             .filter(|&member| {
848                 let config = self.packages.get(member).workspace_config();
849                 matches!(config, WorkspaceConfig::Root(_))
850             })
851             .map(|member| member.parent().unwrap().to_path_buf())
852             .collect();
853         match roots.len() {
854             1 => Ok(()),
855             0 => bail!(
856                 "`package.workspace` configuration points to a crate \
857                  which is not configured with [workspace]: \n\
858                  configuration at: {}\n\
859                  points to: {}",
860                 self.current_manifest.display(),
861                 self.root_manifest.as_ref().unwrap().display()
862             ),
863             _ => {
864                 bail!(
865                     "multiple workspace roots found in the same workspace:\n{}",
866                     roots
867                         .iter()
868                         .map(|r| format!("  {}", r.display()))
869                         .collect::<Vec<_>>()
870                         .join("\n")
871                 );
872             }
873         }
874     }
875 
validate_members(&mut self) -> CargoResult<()>876     fn validate_members(&mut self) -> CargoResult<()> {
877         for member in self.members.clone() {
878             let root = self.find_root(&member)?;
879             if root == self.root_manifest {
880                 continue;
881             }
882 
883             match root {
884                 Some(root) => {
885                     bail!(
886                         "package `{}` is a member of the wrong workspace\n\
887                          expected: {}\n\
888                          actual:   {}",
889                         member.display(),
890                         self.root_manifest.as_ref().unwrap().display(),
891                         root.display()
892                     );
893                 }
894                 None => {
895                     bail!(
896                         "workspace member `{}` is not hierarchically below \
897                          the workspace root `{}`",
898                         member.display(),
899                         self.root_manifest.as_ref().unwrap().display()
900                     );
901                 }
902             }
903         }
904         Ok(())
905     }
906 
error_if_manifest_not_in_members(&mut self) -> CargoResult<()>907     fn error_if_manifest_not_in_members(&mut self) -> CargoResult<()> {
908         if self.members.contains(&self.current_manifest) {
909             return Ok(());
910         }
911 
912         let root = self.root_manifest.as_ref().unwrap();
913         let root_dir = root.parent().unwrap();
914         let current_dir = self.current_manifest.parent().unwrap();
915         let root_pkg = self.packages.get(root);
916 
917         // FIXME: Make this more generic by using a relative path resolver between member and root.
918         let members_msg = match current_dir.strip_prefix(root_dir) {
919             Ok(rel) => format!(
920                 "this may be fixable by adding `{}` to the \
921                      `workspace.members` array of the manifest \
922                      located at: {}",
923                 rel.display(),
924                 root.display()
925             ),
926             Err(_) => format!(
927                 "this may be fixable by adding a member to \
928                      the `workspace.members` array of the \
929                      manifest located at: {}",
930                 root.display()
931             ),
932         };
933         let extra = match *root_pkg {
934             MaybePackage::Virtual(_) => members_msg,
935             MaybePackage::Package(ref p) => {
936                 let has_members_list = match *p.manifest().workspace_config() {
937                     WorkspaceConfig::Root(ref root_config) => root_config.has_members_list(),
938                     WorkspaceConfig::Member { .. } => unreachable!(),
939                 };
940                 if !has_members_list {
941                     format!(
942                         "this may be fixable by ensuring that this \
943                              crate is depended on by the workspace \
944                              root: {}",
945                         root.display()
946                     )
947                 } else {
948                     members_msg
949                 }
950             }
951         };
952         bail!(
953             "current package believes it's in a workspace when it's not:\n\
954                  current:   {}\n\
955                  workspace: {}\n\n{}\n\
956                  Alternatively, to keep it out of the workspace, add the package \
957                  to the `workspace.exclude` array, or add an empty `[workspace]` \
958                  table to the package's manifest.",
959             self.current_manifest.display(),
960             root.display(),
961             extra
962         );
963     }
964 
validate_manifest(&mut self) -> CargoResult<()>965     fn validate_manifest(&mut self) -> CargoResult<()> {
966         if let Some(ref root_manifest) = self.root_manifest {
967             for pkg in self
968                 .members()
969                 .filter(|p| p.manifest_path() != root_manifest)
970             {
971                 let manifest = pkg.manifest();
972                 let emit_warning = |what| -> CargoResult<()> {
973                     let msg = format!(
974                         "{} for the non root package will be ignored, \
975                          specify {} at the workspace root:\n\
976                          package:   {}\n\
977                          workspace: {}",
978                         what,
979                         what,
980                         pkg.manifest_path().display(),
981                         root_manifest.display(),
982                     );
983                     self.config.shell().warn(&msg)
984                 };
985                 if manifest.original().has_profiles() {
986                     emit_warning("profiles")?;
987                 }
988                 if !manifest.replace().is_empty() {
989                     emit_warning("replace")?;
990                 }
991                 if !manifest.patch().is_empty() {
992                     emit_warning("patch")?;
993                 }
994                 if let Some(behavior) = manifest.resolve_behavior() {
995                     if behavior != self.resolve_behavior {
996                         // Only warn if they don't match.
997                         emit_warning("resolver")?;
998                     }
999                 }
1000             }
1001         }
1002         Ok(())
1003     }
1004 
load(&self, manifest_path: &Path) -> CargoResult<Package>1005     pub fn load(&self, manifest_path: &Path) -> CargoResult<Package> {
1006         match self.packages.maybe_get(manifest_path) {
1007             Some(&MaybePackage::Package(ref p)) => return Ok(p.clone()),
1008             Some(&MaybePackage::Virtual(_)) => bail!("cannot load workspace root"),
1009             None => {}
1010         }
1011 
1012         let mut loaded = self.loaded_packages.borrow_mut();
1013         if let Some(p) = loaded.get(manifest_path).cloned() {
1014             return Ok(p);
1015         }
1016         let source_id = SourceId::for_path(manifest_path.parent().unwrap())?;
1017         let (package, _nested_paths) = ops::read_package(manifest_path, source_id, self.config)?;
1018         loaded.insert(manifest_path.to_path_buf(), package.clone());
1019         Ok(package)
1020     }
1021 
1022     /// Preload the provided registry with already loaded packages.
1023     ///
1024     /// A workspace may load packages during construction/parsing/early phases
1025     /// for various operations, and this preload step avoids doubly-loading and
1026     /// parsing crates on the filesystem by inserting them all into the registry
1027     /// with their in-memory formats.
preload(&self, registry: &mut PackageRegistry<'cfg>)1028     pub fn preload(&self, registry: &mut PackageRegistry<'cfg>) {
1029         // These can get weird as this generally represents a workspace during
1030         // `cargo install`. Things like git repositories will actually have a
1031         // `PathSource` with multiple entries in it, so the logic below is
1032         // mostly just an optimization for normal `cargo build` in workspaces
1033         // during development.
1034         if self.is_ephemeral {
1035             return;
1036         }
1037 
1038         for pkg in self.packages.packages.values() {
1039             let pkg = match *pkg {
1040                 MaybePackage::Package(ref p) => p.clone(),
1041                 MaybePackage::Virtual(_) => continue,
1042             };
1043             let mut src = PathSource::new(pkg.root(), pkg.package_id().source_id(), self.config);
1044             src.preload_with(pkg);
1045             registry.add_preloaded(Box::new(src));
1046         }
1047     }
1048 
emit_warnings(&self) -> CargoResult<()>1049     pub fn emit_warnings(&self) -> CargoResult<()> {
1050         for (path, maybe_pkg) in &self.packages.packages {
1051             let warnings = match maybe_pkg {
1052                 MaybePackage::Package(pkg) => pkg.manifest().warnings().warnings(),
1053                 MaybePackage::Virtual(vm) => vm.warnings().warnings(),
1054             };
1055             let path = path.join("Cargo.toml");
1056             for warning in warnings {
1057                 if warning.is_critical {
1058                     let err = anyhow::format_err!("{}", warning.message);
1059                     let cx =
1060                         anyhow::format_err!("failed to parse manifest at `{}`", path.display());
1061                     return Err(err.context(cx));
1062                 } else {
1063                     let msg = if self.root_manifest.is_none() {
1064                         warning.message.to_string()
1065                     } else {
1066                         // In a workspace, it can be confusing where a warning
1067                         // originated, so include the path.
1068                         format!("{}: {}", path.display(), warning.message)
1069                     };
1070                     self.config.shell().warn(msg)?
1071                 }
1072             }
1073         }
1074         Ok(())
1075     }
1076 
set_target_dir(&mut self, target_dir: Filesystem)1077     pub fn set_target_dir(&mut self, target_dir: Filesystem) {
1078         self.target_dir = Some(target_dir);
1079     }
1080 
1081     /// Returns a Vec of `(&Package, RequestedFeatures)` tuples that
1082     /// represent the workspace members that were requested on the command-line.
1083     ///
1084     /// `specs` may be empty, which indicates it should return all workspace
1085     /// members. In this case, `requested_features.all_features` must be
1086     /// `true`. This is used for generating `Cargo.lock`, which must include
1087     /// all members with all features enabled.
members_with_features( &self, specs: &[PackageIdSpec], cli_features: &CliFeatures, ) -> CargoResult<Vec<(&Package, CliFeatures)>>1088     pub fn members_with_features(
1089         &self,
1090         specs: &[PackageIdSpec],
1091         cli_features: &CliFeatures,
1092     ) -> CargoResult<Vec<(&Package, CliFeatures)>> {
1093         assert!(
1094             !specs.is_empty() || cli_features.all_features,
1095             "no specs requires all_features"
1096         );
1097         if specs.is_empty() {
1098             // When resolving the entire workspace, resolve each member with
1099             // all features enabled.
1100             return Ok(self
1101                 .members()
1102                 .map(|m| (m, CliFeatures::new_all(true)))
1103                 .collect());
1104         }
1105         if self.allows_new_cli_feature_behavior() {
1106             self.members_with_features_new(specs, cli_features)
1107         } else {
1108             Ok(self.members_with_features_old(specs, cli_features))
1109         }
1110     }
1111 
1112     /// Returns the requested features for the given member.
1113     /// This filters out any named features that the member does not have.
collect_matching_features( member: &Package, cli_features: &CliFeatures, found_features: &mut BTreeSet<FeatureValue>, ) -> CliFeatures1114     fn collect_matching_features(
1115         member: &Package,
1116         cli_features: &CliFeatures,
1117         found_features: &mut BTreeSet<FeatureValue>,
1118     ) -> CliFeatures {
1119         if cli_features.features.is_empty() || cli_features.all_features {
1120             return cli_features.clone();
1121         }
1122 
1123         // Only include features this member defines.
1124         let summary = member.summary();
1125 
1126         // Features defined in the manifest
1127         let summary_features = summary.features();
1128 
1129         // Dependency name -> dependency
1130         let dependencies: BTreeMap<InternedString, &Dependency> = summary
1131             .dependencies()
1132             .iter()
1133             .map(|dep| (dep.name_in_toml(), dep))
1134             .collect();
1135 
1136         // Features that enable optional dependencies
1137         let optional_dependency_names: BTreeSet<_> = dependencies
1138             .iter()
1139             .filter(|(_, dep)| dep.is_optional())
1140             .map(|(name, _)| name)
1141             .copied()
1142             .collect();
1143 
1144         let mut features = BTreeSet::new();
1145 
1146         // Checks if a member contains the given feature.
1147         let summary_or_opt_dependency_feature = |feature: &InternedString| -> bool {
1148             summary_features.contains_key(feature) || optional_dependency_names.contains(feature)
1149         };
1150 
1151         for feature in cli_features.features.iter() {
1152             match feature {
1153                 FeatureValue::Feature(f) => {
1154                     if summary_or_opt_dependency_feature(f) {
1155                         // feature exists in this member.
1156                         features.insert(feature.clone());
1157                         found_features.insert(feature.clone());
1158                     }
1159                 }
1160                 // This should be enforced by CliFeatures.
1161                 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1162                 FeatureValue::DepFeature {
1163                     dep_name,
1164                     dep_feature,
1165                     weak: _,
1166                 } => {
1167                     if dependencies.contains_key(dep_name) {
1168                         // pkg/feat for a dependency.
1169                         // Will rely on the dependency resolver to validate `dep_feature`.
1170                         features.insert(feature.clone());
1171                         found_features.insert(feature.clone());
1172                     } else if *dep_name == member.name()
1173                         && summary_or_opt_dependency_feature(dep_feature)
1174                     {
1175                         // member/feat where "feat" is a feature in member.
1176                         //
1177                         // `weak` can be ignored here, because the member
1178                         // either is or isn't being built.
1179                         features.insert(FeatureValue::Feature(*dep_feature));
1180                         found_features.insert(feature.clone());
1181                     }
1182                 }
1183             }
1184         }
1185         CliFeatures {
1186             features: Rc::new(features),
1187             all_features: false,
1188             uses_default_features: cli_features.uses_default_features,
1189         }
1190     }
1191 
report_unknown_features_error( &self, specs: &[PackageIdSpec], cli_features: &CliFeatures, found_features: &BTreeSet<FeatureValue>, ) -> CargoResult<()>1192     fn report_unknown_features_error(
1193         &self,
1194         specs: &[PackageIdSpec],
1195         cli_features: &CliFeatures,
1196         found_features: &BTreeSet<FeatureValue>,
1197     ) -> CargoResult<()> {
1198         // Keeps track of which features were contained in summary of `member` to suggest similar features in errors
1199         let mut summary_features: Vec<InternedString> = Default::default();
1200 
1201         // Keeps track of `member` dependencies (`dep/feature`) and their features names to suggest similar features in error
1202         let mut dependencies_features: BTreeMap<InternedString, &[InternedString]> =
1203             Default::default();
1204 
1205         // Keeps track of `member` optional dependencies names (which can be enabled with feature) to suggest similar features in error
1206         let mut optional_dependency_names: Vec<InternedString> = Default::default();
1207 
1208         // Keeps track of which features were contained in summary of `member` to suggest similar features in errors
1209         let mut summary_features_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
1210             Default::default();
1211 
1212         // Keeps track of `member` optional dependencies (which can be enabled with feature) to suggest similar features in error
1213         let mut optional_dependency_names_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
1214             Default::default();
1215 
1216         for member in self
1217             .members()
1218             .filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
1219         {
1220             // Only include features this member defines.
1221             let summary = member.summary();
1222 
1223             // Features defined in the manifest
1224             summary_features.extend(summary.features().keys());
1225             summary_features_per_member
1226                 .insert(member, summary.features().keys().copied().collect());
1227 
1228             // Dependency name -> dependency
1229             let dependencies: BTreeMap<InternedString, &Dependency> = summary
1230                 .dependencies()
1231                 .iter()
1232                 .map(|dep| (dep.name_in_toml(), dep))
1233                 .collect();
1234 
1235             dependencies_features.extend(
1236                 dependencies
1237                     .iter()
1238                     .map(|(name, dep)| (*name, dep.features())),
1239             );
1240 
1241             // Features that enable optional dependencies
1242             let optional_dependency_names_raw: BTreeSet<_> = dependencies
1243                 .iter()
1244                 .filter(|(_, dep)| dep.is_optional())
1245                 .map(|(name, _)| name)
1246                 .copied()
1247                 .collect();
1248 
1249             optional_dependency_names.extend(optional_dependency_names_raw.iter());
1250             optional_dependency_names_per_member.insert(member, optional_dependency_names_raw);
1251         }
1252 
1253         let levenshtein_test =
1254             |a: InternedString, b: InternedString| lev_distance(a.as_str(), b.as_str()) < 4;
1255 
1256         let suggestions: Vec<_> = cli_features
1257             .features
1258             .difference(found_features)
1259             .map(|feature| match feature {
1260                 // Simple feature, check if any of the optional dependency features or member features are close enough
1261                 FeatureValue::Feature(typo) => {
1262                     // Finds member features which are similar to the requested feature.
1263                     let summary_features = summary_features
1264                         .iter()
1265                         .filter(move |feature| levenshtein_test(**feature, *typo));
1266 
1267                     // Finds optional dependencies which name is similar to the feature
1268                     let optional_dependency_features = optional_dependency_names
1269                         .iter()
1270                         .filter(move |feature| levenshtein_test(**feature, *typo));
1271 
1272                     summary_features
1273                         .chain(optional_dependency_features)
1274                         .map(|s| s.to_string())
1275                         .collect::<Vec<_>>()
1276                 }
1277                 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1278                 FeatureValue::DepFeature {
1279                     dep_name,
1280                     dep_feature,
1281                     weak: _,
1282                 } => {
1283                     // Finds set of `pkg/feat` that are very similar to current `pkg/feat`.
1284                     let pkg_feat_similar = dependencies_features
1285                         .iter()
1286                         .filter(|(name, _)| levenshtein_test(**name, *dep_name))
1287                         .map(|(name, features)| {
1288                             (
1289                                 name,
1290                                 features
1291                                     .iter()
1292                                     .filter(|feature| levenshtein_test(**feature, *dep_feature))
1293                                     .collect::<Vec<_>>(),
1294                             )
1295                         })
1296                         .map(|(name, features)| {
1297                             features
1298                                 .into_iter()
1299                                 .map(move |feature| format!("{}/{}", name, feature))
1300                         })
1301                         .flatten();
1302 
1303                     // Finds set of `member/optional_dep` features which name is similar to current `pkg/feat`.
1304                     let optional_dependency_features = optional_dependency_names_per_member
1305                         .iter()
1306                         .filter(|(package, _)| levenshtein_test(package.name(), *dep_name))
1307                         .map(|(package, optional_dependencies)| {
1308                             optional_dependencies
1309                                 .into_iter()
1310                                 .filter(|optional_dependency| {
1311                                     levenshtein_test(**optional_dependency, *dep_name)
1312                                 })
1313                                 .map(move |optional_dependency| {
1314                                     format!("{}/{}", package.name(), optional_dependency)
1315                                 })
1316                         })
1317                         .flatten();
1318 
1319                     // Finds set of `member/feat` features which name is similar to current `pkg/feat`.
1320                     let summary_features = summary_features_per_member
1321                         .iter()
1322                         .filter(|(package, _)| levenshtein_test(package.name(), *dep_name))
1323                         .map(|(package, summary_features)| {
1324                             summary_features
1325                                 .into_iter()
1326                                 .filter(|summary_feature| {
1327                                     levenshtein_test(**summary_feature, *dep_feature)
1328                                 })
1329                                 .map(move |summary_feature| {
1330                                     format!("{}/{}", package.name(), summary_feature)
1331                                 })
1332                         })
1333                         .flatten();
1334 
1335                     pkg_feat_similar
1336                         .chain(optional_dependency_features)
1337                         .chain(summary_features)
1338                         .collect::<Vec<_>>()
1339                 }
1340             })
1341             .map(|v| v.into_iter())
1342             .flatten()
1343             .unique()
1344             .filter(|element| {
1345                 let feature = FeatureValue::new(InternedString::new(element));
1346                 !cli_features.features.contains(&feature) && !found_features.contains(&feature)
1347             })
1348             .sorted()
1349             .take(5)
1350             .collect();
1351 
1352         let unknown: Vec<_> = cli_features
1353             .features
1354             .difference(found_features)
1355             .map(|feature| feature.to_string())
1356             .sorted()
1357             .collect();
1358 
1359         if suggestions.is_empty() {
1360             bail!(
1361                 "none of the selected packages contains these features: {}",
1362                 unknown.join(", ")
1363             );
1364         } else {
1365             bail!(
1366                 "none of the selected packages contains these features: {}, did you mean: {}?",
1367                 unknown.join(", "),
1368                 suggestions.join(", ")
1369             );
1370         }
1371     }
1372 
1373     /// New command-line feature selection behavior with resolver = "2" or the
1374     /// root of a virtual workspace. See `allows_new_cli_feature_behavior`.
members_with_features_new( &self, specs: &[PackageIdSpec], cli_features: &CliFeatures, ) -> CargoResult<Vec<(&Package, CliFeatures)>>1375     fn members_with_features_new(
1376         &self,
1377         specs: &[PackageIdSpec],
1378         cli_features: &CliFeatures,
1379     ) -> CargoResult<Vec<(&Package, CliFeatures)>> {
1380         // Keeps track of which features matched `member` to produce an error
1381         // if any of them did not match anywhere.
1382         let mut found_features = Default::default();
1383 
1384         let members: Vec<(&Package, CliFeatures)> = self
1385             .members()
1386             .filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
1387             .map(|m| {
1388                 (
1389                     m,
1390                     Workspace::collect_matching_features(m, cli_features, &mut found_features),
1391                 )
1392             })
1393             .collect();
1394 
1395         if members.is_empty() {
1396             // `cargo build -p foo`, where `foo` is not a member.
1397             // Do not allow any command-line flags (defaults only).
1398             if !(cli_features.features.is_empty()
1399                 && !cli_features.all_features
1400                 && cli_features.uses_default_features)
1401             {
1402                 bail!("cannot specify features for packages outside of workspace");
1403             }
1404             // Add all members from the workspace so we can ensure `-p nonmember`
1405             // is in the resolve graph.
1406             return Ok(self
1407                 .members()
1408                 .map(|m| (m, CliFeatures::new_all(false)))
1409                 .collect());
1410         }
1411         if *cli_features.features != found_features {
1412             self.report_unknown_features_error(specs, cli_features, &found_features)?;
1413         }
1414         Ok(members)
1415     }
1416 
1417     /// This is the "old" behavior for command-line feature selection.
1418     /// See `allows_new_cli_feature_behavior`.
members_with_features_old( &self, specs: &[PackageIdSpec], cli_features: &CliFeatures, ) -> Vec<(&Package, CliFeatures)>1419     fn members_with_features_old(
1420         &self,
1421         specs: &[PackageIdSpec],
1422         cli_features: &CliFeatures,
1423     ) -> Vec<(&Package, CliFeatures)> {
1424         // Split off any features with the syntax `member-name/feature-name` into a map
1425         // so that those features can be applied directly to those workspace-members.
1426         let mut member_specific_features: HashMap<InternedString, BTreeSet<FeatureValue>> =
1427             HashMap::new();
1428         // Features for the member in the current directory.
1429         let mut cwd_features = BTreeSet::new();
1430         for feature in cli_features.features.iter() {
1431             match feature {
1432                 FeatureValue::Feature(_) => {
1433                     cwd_features.insert(feature.clone());
1434                 }
1435                 // This should be enforced by CliFeatures.
1436                 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1437                 FeatureValue::DepFeature {
1438                     dep_name,
1439                     dep_feature,
1440                     weak: _,
1441                 } => {
1442                     // I think weak can be ignored here.
1443                     // * With `--features member?/feat -p member`, the ? doesn't
1444                     //   really mean anything (either the member is built or it isn't).
1445                     // * With `--features nonmember?/feat`, cwd_features will
1446                     //   handle processing it correctly.
1447                     let is_member = self.members().any(|member| {
1448                         // Check if `dep_name` is member of the workspace, but isn't associated with current package.
1449                         self.current_opt() != Some(member) && member.name() == *dep_name
1450                     });
1451                     if is_member && specs.iter().any(|spec| spec.name() == *dep_name) {
1452                         member_specific_features
1453                             .entry(*dep_name)
1454                             .or_default()
1455                             .insert(FeatureValue::Feature(*dep_feature));
1456                     } else {
1457                         cwd_features.insert(feature.clone());
1458                     }
1459                 }
1460             }
1461         }
1462 
1463         let ms: Vec<_> = self
1464             .members()
1465             .filter_map(|member| {
1466                 let member_id = member.package_id();
1467                 match self.current_opt() {
1468                     // The features passed on the command-line only apply to
1469                     // the "current" package (determined by the cwd).
1470                     Some(current) if member_id == current.package_id() => {
1471                         let feats = CliFeatures {
1472                             features: Rc::new(cwd_features.clone()),
1473                             all_features: cli_features.all_features,
1474                             uses_default_features: cli_features.uses_default_features,
1475                         };
1476                         Some((member, feats))
1477                     }
1478                     _ => {
1479                         // Ignore members that are not enabled on the command-line.
1480                         if specs.iter().any(|spec| spec.matches(member_id)) {
1481                             // -p for a workspace member that is not the "current"
1482                             // one.
1483                             //
1484                             // The odd behavior here is due to backwards
1485                             // compatibility. `--features` and
1486                             // `--no-default-features` used to only apply to the
1487                             // "current" package. As an extension, this allows
1488                             // member-name/feature-name to set member-specific
1489                             // features, which should be backwards-compatible.
1490                             let feats = CliFeatures {
1491                                 features: Rc::new(
1492                                     member_specific_features
1493                                         .remove(member.name().as_str())
1494                                         .unwrap_or_default(),
1495                                 ),
1496                                 uses_default_features: true,
1497                                 all_features: cli_features.all_features,
1498                             };
1499                             Some((member, feats))
1500                         } else {
1501                             // This member was not requested on the command-line, skip.
1502                             None
1503                         }
1504                     }
1505                 }
1506             })
1507             .collect();
1508 
1509         // If any member specific features were not removed while iterating over members
1510         // some features will be ignored.
1511         assert!(member_specific_features.is_empty());
1512 
1513         ms
1514     }
1515 }
1516 
1517 impl<'cfg> Packages<'cfg> {
get(&self, manifest_path: &Path) -> &MaybePackage1518     fn get(&self, manifest_path: &Path) -> &MaybePackage {
1519         self.maybe_get(manifest_path).unwrap()
1520     }
1521 
get_mut(&mut self, manifest_path: &Path) -> &mut MaybePackage1522     fn get_mut(&mut self, manifest_path: &Path) -> &mut MaybePackage {
1523         self.maybe_get_mut(manifest_path).unwrap()
1524     }
1525 
maybe_get(&self, manifest_path: &Path) -> Option<&MaybePackage>1526     fn maybe_get(&self, manifest_path: &Path) -> Option<&MaybePackage> {
1527         self.packages.get(manifest_path.parent().unwrap())
1528     }
1529 
maybe_get_mut(&mut self, manifest_path: &Path) -> Option<&mut MaybePackage>1530     fn maybe_get_mut(&mut self, manifest_path: &Path) -> Option<&mut MaybePackage> {
1531         self.packages.get_mut(manifest_path.parent().unwrap())
1532     }
1533 
load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage>1534     fn load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage> {
1535         let key = manifest_path.parent().unwrap();
1536         match self.packages.entry(key.to_path_buf()) {
1537             Entry::Occupied(e) => Ok(e.into_mut()),
1538             Entry::Vacant(v) => {
1539                 let source_id = SourceId::for_path(key)?;
1540                 let (manifest, _nested_paths) =
1541                     read_manifest(manifest_path, source_id, self.config)?;
1542                 Ok(v.insert(match manifest {
1543                     EitherManifest::Real(manifest) => {
1544                         MaybePackage::Package(Package::new(manifest, manifest_path))
1545                     }
1546                     EitherManifest::Virtual(vm) => MaybePackage::Virtual(vm),
1547                 }))
1548             }
1549         }
1550     }
1551 }
1552 
1553 impl MaybePackage {
workspace_config(&self) -> &WorkspaceConfig1554     fn workspace_config(&self) -> &WorkspaceConfig {
1555         match *self {
1556             MaybePackage::Package(ref p) => p.manifest().workspace_config(),
1557             MaybePackage::Virtual(ref vm) => vm.workspace_config(),
1558         }
1559     }
1560 }
1561 
1562 impl WorkspaceRootConfig {
1563     /// Creates a new Intermediate Workspace Root configuration.
new( root_dir: &Path, members: &Option<Vec<String>>, default_members: &Option<Vec<String>>, exclude: &Option<Vec<String>>, custom_metadata: &Option<toml::Value>, ) -> WorkspaceRootConfig1564     pub fn new(
1565         root_dir: &Path,
1566         members: &Option<Vec<String>>,
1567         default_members: &Option<Vec<String>>,
1568         exclude: &Option<Vec<String>>,
1569         custom_metadata: &Option<toml::Value>,
1570     ) -> WorkspaceRootConfig {
1571         WorkspaceRootConfig {
1572             root_dir: root_dir.to_path_buf(),
1573             members: members.clone(),
1574             default_members: default_members.clone(),
1575             exclude: exclude.clone().unwrap_or_default(),
1576             custom_metadata: custom_metadata.clone(),
1577         }
1578     }
1579 
1580     /// Checks the path against the `excluded` list.
1581     ///
1582     /// This method does **not** consider the `members` list.
is_excluded(&self, manifest_path: &Path) -> bool1583     fn is_excluded(&self, manifest_path: &Path) -> bool {
1584         let excluded = self
1585             .exclude
1586             .iter()
1587             .any(|ex| manifest_path.starts_with(self.root_dir.join(ex)));
1588 
1589         let explicit_member = match self.members {
1590             Some(ref members) => members
1591                 .iter()
1592                 .any(|mem| manifest_path.starts_with(self.root_dir.join(mem))),
1593             None => false,
1594         };
1595 
1596         !explicit_member && excluded
1597     }
1598 
has_members_list(&self) -> bool1599     fn has_members_list(&self) -> bool {
1600         self.members.is_some()
1601     }
1602 
members_paths(&self, globs: &[String]) -> CargoResult<Vec<PathBuf>>1603     fn members_paths(&self, globs: &[String]) -> CargoResult<Vec<PathBuf>> {
1604         let mut expanded_list = Vec::new();
1605 
1606         for glob in globs {
1607             let pathbuf = self.root_dir.join(glob);
1608             let expanded_paths = Self::expand_member_path(&pathbuf)?;
1609 
1610             // If glob does not find any valid paths, then put the original
1611             // path in the expanded list to maintain backwards compatibility.
1612             if expanded_paths.is_empty() {
1613                 expanded_list.push(pathbuf);
1614             } else {
1615                 // Some OS can create system support files anywhere.
1616                 // (e.g. macOS creates `.DS_Store` file if you visit a directory using Finder.)
1617                 // Such files can be reported as a member path unexpectedly.
1618                 // Check and filter out non-directory paths to prevent pushing such accidental unwanted path
1619                 // as a member.
1620                 for expanded_path in expanded_paths {
1621                     if expanded_path.is_dir() {
1622                         expanded_list.push(expanded_path);
1623                     }
1624                 }
1625             }
1626         }
1627 
1628         Ok(expanded_list)
1629     }
1630 
expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>>1631     fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> {
1632         let path = match path.to_str() {
1633             Some(p) => p,
1634             None => return Ok(Vec::new()),
1635         };
1636         let res = glob(path).with_context(|| format!("could not parse pattern `{}`", &path))?;
1637         let res = res
1638             .map(|p| p.with_context(|| format!("unable to match path to pattern `{}`", &path)))
1639             .collect::<Result<Vec<_>, _>>()?;
1640         Ok(res)
1641     }
1642 }
1643