cargo/core/
workspace.rs

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