Skip to main content

cargo/core/
workspace.rs

1use std::cell::RefCell;
2use std::collections::hash_map::{Entry, HashMap};
3use std::collections::{BTreeMap, BTreeSet, HashSet};
4use std::path::{Path, PathBuf};
5use std::rc::Rc;
6
7use anyhow::{Context as _, anyhow, bail};
8use cargo_util_terminal::report::Level;
9use glob::glob;
10use itertools::Itertools;
11use tracing::debug;
12use url::Url;
13
14use crate::core::compiler::Unit;
15use crate::core::features::Features;
16use crate::core::registry::PackageRegistry;
17use crate::core::resolver::ResolveBehavior;
18use crate::core::resolver::features::CliFeatures;
19use crate::core::{
20    Dependency, Edition, FeatureValue, PackageId, PackageIdSpec, PackageIdSpecQuery, Patch,
21    PatchLocation,
22};
23use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
24use crate::diagnostics::DiagnosticStats;
25use crate::diagnostics::rules::blanket_hint_mostly_unused;
26use crate::diagnostics::rules::check_im_a_teapot;
27use crate::diagnostics::rules::implicit_minimum_version_req_pkg;
28use crate::diagnostics::rules::implicit_minimum_version_req_ws;
29use crate::diagnostics::rules::missing_lints_features;
30use crate::diagnostics::rules::missing_lints_inheritance;
31use crate::diagnostics::rules::non_kebab_case_bins;
32use crate::diagnostics::rules::non_kebab_case_features;
33use crate::diagnostics::rules::non_kebab_case_packages;
34use crate::diagnostics::rules::non_snake_case_features;
35use crate::diagnostics::rules::non_snake_case_packages;
36use crate::diagnostics::rules::redundant_homepage;
37use crate::diagnostics::rules::redundant_readme;
38use crate::diagnostics::rules::text_direction_codepoint_in_comment;
39use crate::diagnostics::rules::text_direction_codepoint_in_literal;
40use crate::diagnostics::rules::unknown_lints;
41use crate::diagnostics::rules::unused_build_dependencies_no_build_rs;
42use crate::diagnostics::rules::unused_workspace_dependencies;
43use crate::diagnostics::rules::unused_workspace_package_fields;
44use crate::ops;
45use crate::ops::lockfile::LOCKFILE_NAME;
46use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY, PathSource, SourceConfigMap};
47use crate::util::context;
48use crate::util::context::{FeatureUnification, Value};
49use crate::util::edit_distance;
50use crate::util::errors::{CargoResult, ManifestError};
51use crate::util::interning::InternedString;
52use crate::util::toml::{InheritableFields, read_manifest};
53use crate::util::{
54    Filesystem, GlobalContext, IntoUrl, closest_msg, context::CargoResolverConfig,
55    context::ConfigRelativePath, context::IncompatibleRustVersions,
56};
57
58use cargo_util::paths;
59use cargo_util::paths::normalize_path;
60use cargo_util_schemas::manifest;
61use cargo_util_schemas::manifest::RustVersion;
62use cargo_util_schemas::manifest::{TomlDependency, TomlManifest, TomlProfiles};
63use pathdiff::diff_paths;
64
65/// The core abstraction in Cargo for working with a workspace of crates.
66///
67/// A workspace is often created very early on and then threaded through all
68/// other functions. It's typically through this object that the current
69/// package is loaded and/or learned about.
70#[derive(Debug)]
71pub struct Workspace<'gctx> {
72    /// Cargo configuration information. See [`GlobalContext`].
73    gctx: &'gctx GlobalContext,
74
75    /// This path is a path to where the current cargo subcommand was invoked
76    /// from. That is the `--manifest-path` argument to Cargo, and
77    /// points to the "main crate" that we're going to worry about.
78    current_manifest: PathBuf,
79
80    /// A list of packages found in this workspace. Always includes at least the
81    /// package mentioned by `current_manifest`.
82    packages: Packages<'gctx>,
83
84    /// If this workspace includes more than one crate, this points to the root
85    /// of the workspace. This is `None` in the case that `[workspace]` is
86    /// missing, `package.workspace` is missing, and no `Cargo.toml` above
87    /// `current_manifest` was found on the filesystem with `[workspace]`.
88    root_manifest: Option<PathBuf>,
89
90    /// Shared target directory for all the packages of this workspace.
91    /// `None` if the default path of `root/target` should be used.
92    target_dir: Option<Filesystem>,
93
94    /// Shared build directory for intermediate build artifacts.
95    /// This directory may be shared between multiple workspaces.
96    build_dir: Option<Filesystem>,
97
98    /// List of members in this workspace with a listing of all their manifest
99    /// paths. The packages themselves can be looked up through the `packages`
100    /// set above.
101    members: Vec<PathBuf>,
102    /// Set of ids of workspace members
103    member_ids: HashSet<PackageId>,
104
105    /// The subset of `members` that are used by the
106    /// `build`, `check`, `test`, and `bench` subcommands
107    /// when no package is selected with `--package` / `-p` and `--workspace`
108    /// is not used.
109    ///
110    /// This is set by the `default-members` config
111    /// in the `[workspace]` section.
112    /// When unset, this is the same as `members` for virtual workspaces
113    /// (`--workspace` is implied)
114    /// or only the root package for non-virtual workspaces.
115    default_members: Vec<PathBuf>,
116
117    /// `true` if this is a temporary workspace created for the purposes of the
118    /// `cargo install` or `cargo package` commands.
119    is_ephemeral: bool,
120
121    /// `true` if this workspace should enforce optional dependencies even when
122    /// not needed; false if this workspace should only enforce dependencies
123    /// needed by the current configuration (such as in cargo install). In some
124    /// cases `false` also results in the non-enforcement of dev-dependencies.
125    require_optional_deps: bool,
126
127    /// A cache of loaded packages for particular paths which is disjoint from
128    /// `packages` up above, used in the `load` method down below.
129    loaded_packages: RefCell<HashMap<PathBuf, Package>>,
130
131    /// If `true`, then the resolver will ignore any existing `Cargo.lock`
132    /// file. This is set for `cargo install` without `--locked`.
133    ignore_lock: bool,
134
135    /// Requested path of the lockfile (i.e. passed as the cli flag)
136    requested_lockfile_path: Option<PathBuf>,
137
138    /// The resolver behavior specified with the `resolver` field.
139    resolve_behavior: ResolveBehavior,
140    /// If `true`, then workspace `rust_version` would be used in `cargo resolve`
141    /// and other places that use rust version.
142    /// This is set based on the resolver version, config settings, and CLI flags.
143    resolve_honors_rust_version: bool,
144    /// The feature unification mode used when building packages.
145    resolve_feature_unification: FeatureUnification,
146    /// Latest publish time allowed for packages
147    resolve_publish_time: Option<jiff::Timestamp>,
148    /// Workspace-level custom metadata
149    custom_metadata: Option<toml::Value>,
150
151    /// Local overlay configuration. See [`crate::sources::overlay`].
152    local_overlays: HashMap<SourceId, PathBuf>,
153}
154
155// Separate structure for tracking loaded packages (to avoid loading anything
156// twice), and this is separate to help appease the borrow checker.
157#[derive(Debug)]
158struct Packages<'gctx> {
159    gctx: &'gctx GlobalContext,
160    packages: HashMap<PathBuf, MaybePackage>,
161}
162
163#[derive(Debug)]
164pub enum MaybePackage {
165    Package(Package),
166    Virtual(VirtualManifest),
167}
168
169/// Configuration of a workspace in a manifest.
170#[derive(Debug, Clone)]
171pub enum WorkspaceConfig {
172    /// Indicates that `[workspace]` was present and the members were
173    /// optionally specified as well.
174    Root(WorkspaceRootConfig),
175
176    /// Indicates that `[workspace]` was present and the `root` field is the
177    /// optional value of `package.workspace`, if present.
178    Member { root: Option<String> },
179}
180
181impl WorkspaceConfig {
182    pub fn inheritable(&self) -> Option<&InheritableFields> {
183        match self {
184            WorkspaceConfig::Root(root) => Some(&root.inheritable_fields),
185            WorkspaceConfig::Member { .. } => None,
186        }
187    }
188
189    /// Returns the path of the workspace root based on this `[workspace]` configuration.
190    ///
191    /// Returns `None` if the root is not explicitly known.
192    ///
193    /// * `self_path` is the path of the manifest this `WorkspaceConfig` is located.
194    /// * `look_from` is the path where discovery started (usually the current
195    ///   working directory), used for `workspace.exclude` checking.
196    fn get_ws_root(&self, self_path: &Path, look_from: &Path) -> Option<PathBuf> {
197        match self {
198            WorkspaceConfig::Root(ances_root_config) => {
199                debug!("find_root - found a root checking exclusion");
200                if !ances_root_config.is_excluded(look_from) {
201                    debug!("find_root - found!");
202                    Some(self_path.to_owned())
203                } else {
204                    None
205                }
206            }
207            WorkspaceConfig::Member {
208                root: Some(path_to_root),
209            } => {
210                debug!("find_root - found pointer");
211                Some(read_root_pointer(self_path, path_to_root))
212            }
213            WorkspaceConfig::Member { .. } => None,
214        }
215    }
216}
217
218/// Intermediate configuration of a workspace root in a manifest.
219///
220/// Knows the Workspace Root path, as well as `members` and `exclude` lists of path patterns, which
221/// together tell if some path is recognized as a member by this root or not.
222#[derive(Debug, Clone)]
223pub struct WorkspaceRootConfig {
224    root_dir: PathBuf,
225    members: Option<Vec<String>>,
226    default_members: Option<Vec<String>>,
227    exclude: Vec<String>,
228    inheritable_fields: InheritableFields,
229    custom_metadata: Option<toml::Value>,
230}
231
232impl<'gctx> Workspace<'gctx> {
233    /// Creates a new workspace given the target manifest pointed to by
234    /// `manifest_path`.
235    ///
236    /// This function will construct the entire workspace by determining the
237    /// root and all member packages. It will then validate the workspace
238    /// before returning it, so `Ok` is only returned for valid workspaces.
239    pub fn new(manifest_path: &Path, gctx: &'gctx GlobalContext) -> CargoResult<Workspace<'gctx>> {
240        let mut ws = Workspace::new_default(manifest_path.to_path_buf(), gctx);
241
242        if manifest_path.is_relative() {
243            bail!(
244                "manifest_path:{:?} is not an absolute path. Please provide an absolute path.",
245                manifest_path
246            )
247        } else {
248            ws.root_manifest = ws.find_root(manifest_path)?;
249        }
250
251        ws.target_dir = gctx.target_dir()?;
252        ws.build_dir = gctx.build_dir(ws.root_manifest())?;
253
254        ws.custom_metadata = ws
255            .load_workspace_config()?
256            .and_then(|cfg| cfg.custom_metadata);
257        ws.find_members()?;
258        ws.set_resolve_behavior()?;
259        ws.validate()?;
260        Ok(ws)
261    }
262
263    fn new_default(current_manifest: PathBuf, gctx: &'gctx GlobalContext) -> Workspace<'gctx> {
264        Workspace {
265            gctx,
266            current_manifest,
267            packages: Packages {
268                gctx,
269                packages: HashMap::new(),
270            },
271            root_manifest: None,
272            target_dir: None,
273            build_dir: None,
274            members: Vec::new(),
275            member_ids: HashSet::new(),
276            default_members: Vec::new(),
277            is_ephemeral: false,
278            require_optional_deps: true,
279            loaded_packages: RefCell::new(HashMap::new()),
280            ignore_lock: false,
281            requested_lockfile_path: None,
282            resolve_behavior: ResolveBehavior::V1,
283            resolve_honors_rust_version: false,
284            resolve_feature_unification: FeatureUnification::Selected,
285            resolve_publish_time: None,
286            custom_metadata: None,
287            local_overlays: HashMap::new(),
288        }
289    }
290
291    /// Creates a "temporary workspace" from one package which only contains
292    /// that package.
293    ///
294    /// This constructor will not touch the filesystem and only creates an
295    /// in-memory workspace. That is, all configuration is ignored, it's just
296    /// intended for that one package.
297    ///
298    /// This is currently only used in niche situations like `cargo install` or
299    /// `cargo package`.
300    pub fn ephemeral(
301        package: Package,
302        gctx: &'gctx GlobalContext,
303        target_dir: Option<Filesystem>,
304        require_optional_deps: bool,
305    ) -> CargoResult<Workspace<'gctx>> {
306        let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), gctx);
307        ws.is_ephemeral = true;
308        ws.require_optional_deps = require_optional_deps;
309        let id = package.package_id();
310        let package = MaybePackage::Package(package);
311        ws.packages
312            .packages
313            .insert(ws.current_manifest.clone(), package);
314        ws.target_dir = if let Some(dir) = target_dir {
315            Some(dir)
316        } else {
317            ws.gctx.target_dir()?
318        };
319        ws.build_dir = ws.target_dir.clone();
320        ws.members.push(ws.current_manifest.clone());
321        ws.member_ids.insert(id);
322        ws.default_members.push(ws.current_manifest.clone());
323        ws.set_resolve_behavior()?;
324        Ok(ws)
325    }
326
327    /// Reloads the workspace.
328    ///
329    /// This is useful if the workspace has been updated, such as with `cargo
330    /// fix` modifying the `Cargo.toml` file.
331    pub fn reload(&self, gctx: &'gctx GlobalContext) -> CargoResult<Workspace<'gctx>> {
332        let mut ws = Workspace::new(&self.current_manifest, gctx)?;
333        ws.set_resolve_honors_rust_version(Some(self.resolve_honors_rust_version));
334        ws.set_resolve_feature_unification(self.resolve_feature_unification);
335        ws.set_requested_lockfile_path(self.requested_lockfile_path.clone());
336        Ok(ws)
337    }
338
339    fn set_resolve_behavior(&mut self) -> CargoResult<()> {
340        // - If resolver is specified in the workspace definition, use that.
341        // - If the root package specifies the resolver, use that.
342        // - If the root package specifies edition 2021, use v2.
343        // - Otherwise, use the default v1.
344        self.resolve_behavior = match self.root_maybe() {
345            MaybePackage::Package(p) => p
346                .manifest()
347                .resolve_behavior()
348                .unwrap_or_else(|| p.manifest().edition().default_resolve_behavior()),
349            MaybePackage::Virtual(vm) => vm.resolve_behavior().unwrap_or(ResolveBehavior::V1),
350        };
351
352        match self.resolve_behavior() {
353            ResolveBehavior::V1 | ResolveBehavior::V2 => {}
354            ResolveBehavior::V3 => {
355                if self.resolve_behavior == ResolveBehavior::V3 {
356                    self.resolve_honors_rust_version = true;
357                }
358            }
359        }
360        let config = self.gctx().get::<CargoResolverConfig>("resolver")?;
361        if let Some(incompatible_rust_versions) = config.incompatible_rust_versions {
362            self.resolve_honors_rust_version =
363                incompatible_rust_versions == IncompatibleRustVersions::Fallback;
364        }
365        if self.gctx().cli_unstable().feature_unification {
366            self.resolve_feature_unification = config
367                .feature_unification
368                .unwrap_or(FeatureUnification::Selected);
369        } else if config.feature_unification.is_some() {
370            self.gctx()
371                .shell()
372                .warn("ignoring `resolver.feature-unification` without `-Zfeature-unification`")?;
373        };
374
375        if let Some(lockfile_path) = config.lockfile_path {
376            // Reserve the ability to add templates in the future.
377            let replacements: [(&str, &str); 0] = [];
378            let path = lockfile_path
379                    .resolve_templated_path(self.gctx(), replacements)
380                    .map_err(|e| match e {
381                        context::ResolveTemplateError::UnexpectedVariable {
382                            variable,
383                            raw_template,
384                        } => {
385                            anyhow!(
386                                "unexpected variable `{variable}` in resolver.lockfile-path `{raw_template}`"
387                            )
388                        }
389                        context::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
390                            let (btype, literal) = match bracket_type {
391                                context::BracketType::Opening => ("opening", "{"),
392                                context::BracketType::Closing => ("closing", "}"),
393                            };
394
395                            anyhow!(
396                                "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
397                            )
398                        }
399                    })?;
400            if !path.ends_with(LOCKFILE_NAME) {
401                bail!("the `resolver.lockfile-path` must be a path to a {LOCKFILE_NAME} file");
402            }
403            if path.is_dir() {
404                bail!(
405                    "`resolver.lockfile-path` `{}` is a directory but expected a file",
406                    path.display()
407                );
408            }
409            self.requested_lockfile_path = Some(path);
410        }
411
412        Ok(())
413    }
414
415    /// Returns the current package of this workspace.
416    ///
417    /// Note that this can return an error if it the current manifest is
418    /// actually a "virtual Cargo.toml", in which case an error is returned
419    /// indicating that something else should be passed.
420    pub fn current(&self) -> CargoResult<&Package> {
421        let pkg = self.current_opt().ok_or_else(|| {
422            anyhow::format_err!(
423                "manifest path `{}` is a virtual manifest, but this \
424                 command requires running against an actual package in \
425                 this workspace",
426                self.current_manifest.display()
427            )
428        })?;
429        Ok(pkg)
430    }
431
432    pub fn current_mut(&mut self) -> CargoResult<&mut Package> {
433        let cm = self.current_manifest.clone();
434        let pkg = self.current_opt_mut().ok_or_else(|| {
435            anyhow::format_err!(
436                "manifest path `{}` is a virtual manifest, but this \
437                 command requires running against an actual package in \
438                 this workspace",
439                cm.display()
440            )
441        })?;
442        Ok(pkg)
443    }
444
445    pub fn current_opt(&self) -> Option<&Package> {
446        match *self.packages.get(&self.current_manifest) {
447            MaybePackage::Package(ref p) => Some(p),
448            MaybePackage::Virtual(..) => None,
449        }
450    }
451
452    pub fn current_opt_mut(&mut self) -> Option<&mut Package> {
453        match *self.packages.get_mut(&self.current_manifest) {
454            MaybePackage::Package(ref mut p) => Some(p),
455            MaybePackage::Virtual(..) => None,
456        }
457    }
458
459    pub fn is_virtual(&self) -> bool {
460        match *self.packages.get(&self.current_manifest) {
461            MaybePackage::Package(..) => false,
462            MaybePackage::Virtual(..) => true,
463        }
464    }
465
466    /// Returns the `GlobalContext` this workspace is associated with.
467    pub fn gctx(&self) -> &'gctx GlobalContext {
468        self.gctx
469    }
470
471    pub fn profiles(&self) -> Option<&TomlProfiles> {
472        self.root_maybe().profiles()
473    }
474
475    /// Returns the root path of this workspace.
476    ///
477    /// That is, this returns the path of the directory containing the
478    /// `Cargo.toml` which is the root of this workspace.
479    pub fn root(&self) -> &Path {
480        self.root_manifest().parent().unwrap()
481    }
482
483    /// Returns the path of the `Cargo.toml` which is the root of this
484    /// workspace.
485    pub fn root_manifest(&self) -> &Path {
486        self.root_manifest
487            .as_ref()
488            .unwrap_or(&self.current_manifest)
489    }
490
491    /// Returns the root Package or `VirtualManifest`.
492    pub fn root_maybe(&self) -> &MaybePackage {
493        self.packages.get(self.root_manifest())
494    }
495
496    pub fn target_dir(&self) -> Filesystem {
497        self.target_dir
498            .clone()
499            .unwrap_or_else(|| self.default_target_dir())
500    }
501
502    pub fn build_dir(&self) -> Filesystem {
503        self.build_dir
504            .clone()
505            .or_else(|| self.target_dir.clone())
506            .unwrap_or_else(|| self.default_build_dir())
507    }
508
509    fn default_target_dir(&self) -> Filesystem {
510        if self.root_maybe().is_embedded() {
511            self.build_dir().join("target")
512        } else {
513            Filesystem::new(self.root().join("target"))
514        }
515    }
516
517    fn default_build_dir(&self) -> Filesystem {
518        if self.root_maybe().is_embedded() {
519            let default = ConfigRelativePath::new(
520                "{cargo-cache-home}/build/{workspace-path-hash}"
521                    .to_owned()
522                    .into(),
523            );
524            self.gctx()
525                .custom_build_dir(&default, self.root_manifest())
526                .expect("template is correct")
527        } else {
528            self.default_target_dir()
529        }
530    }
531
532    /// Returns the root `[replace]` section of this workspace.
533    ///
534    /// This may be from a virtual crate or an actual crate.
535    pub fn root_replace(&self) -> &[(PackageIdSpec, Dependency)] {
536        match self.root_maybe() {
537            MaybePackage::Package(p) => p.manifest().replace(),
538            MaybePackage::Virtual(vm) => vm.replace(),
539        }
540    }
541
542    fn config_patch(&self) -> CargoResult<HashMap<Url, Vec<Patch>>> {
543        let config_patch: Option<
544            BTreeMap<String, BTreeMap<String, Value<TomlDependency<ConfigRelativePath>>>>,
545        > = self.gctx.get("patch")?;
546
547        let source = SourceId::for_manifest_path(self.root_manifest())?;
548
549        let mut warnings = Vec::new();
550
551        let mut patch = HashMap::new();
552        for (url, deps) in config_patch.into_iter().flatten() {
553            let url = match &url[..] {
554                CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
555                url => self
556                    .gctx
557                    .get_registry_index(url)
558                    .or_else(|_| url.into_url())
559                    .with_context(|| {
560                        format!("[patch] entry `{}` should be a URL or registry name", url)
561                    })?,
562            };
563            patch.insert(
564                url,
565                deps.iter()
566                    .map(|(name, dependency_cv)| {
567                        crate::util::toml::config_patch_to_dependency(
568                            &dependency_cv.val,
569                            name,
570                            source,
571                            self.gctx,
572                            &mut warnings,
573                        )
574                        .map(|dep| Patch {
575                            dep,
576                            loc: PatchLocation::Config(dependency_cv.definition.clone()),
577                        })
578                    })
579                    .collect::<CargoResult<Vec<_>>>()?,
580            );
581        }
582
583        for message in warnings {
584            self.gctx
585                .shell()
586                .warn(format!("[patch] in cargo config: {}", message))?
587        }
588
589        Ok(patch)
590    }
591
592    /// Returns the root `[patch]` section of this workspace.
593    ///
594    /// This may be from a virtual crate or an actual crate.
595    pub fn root_patch(&self) -> CargoResult<HashMap<Url, Vec<Patch>>> {
596        let from_manifest = match self.root_maybe() {
597            MaybePackage::Package(p) => p.manifest().patch(),
598            MaybePackage::Virtual(vm) => vm.patch(),
599        };
600
601        let from_config = self.config_patch()?;
602        if from_config.is_empty() {
603            return Ok(from_manifest.clone());
604        }
605        if from_manifest.is_empty() {
606            return Ok(from_config);
607        }
608
609        // We could just chain from_manifest and from_config,
610        // but that's not quite right as it won't deal with overlaps.
611        let mut combined = from_config;
612        for (url, deps_from_manifest) in from_manifest {
613            if let Some(deps_from_config) = combined.get_mut(url) {
614                // We want from_config to take precedence for each patched name.
615                // NOTE: This is inefficient if the number of patches is large!
616                let mut from_manifest_pruned = deps_from_manifest.clone();
617                for dep_from_config in &mut *deps_from_config {
618                    if let Some(i) = from_manifest_pruned.iter().position(|dep_from_manifest| {
619                        // XXX: should this also take into account version numbers?
620                        dep_from_config.dep.name_in_toml() == dep_from_manifest.dep.name_in_toml()
621                    }) {
622                        from_manifest_pruned.swap_remove(i);
623                    }
624                }
625                // Whatever is left does not exist in manifest dependencies.
626                deps_from_config.extend(from_manifest_pruned);
627            } else {
628                combined.insert(url.clone(), deps_from_manifest.clone());
629            }
630        }
631        Ok(combined)
632    }
633
634    /// Returns an iterator over all packages in this workspace
635    pub fn members(&self) -> impl Iterator<Item = &Package> {
636        let packages = &self.packages;
637        self.members
638            .iter()
639            .filter_map(move |path| match packages.get(path) {
640                MaybePackage::Package(p) => Some(p),
641                _ => None,
642            })
643    }
644
645    /// Returns a mutable iterator over all packages in this workspace
646    pub fn members_mut(&mut self) -> impl Iterator<Item = &mut Package> {
647        let packages = &mut self.packages.packages;
648        let members: HashSet<_> = self.members.iter().map(|path| path).collect();
649
650        packages.iter_mut().filter_map(move |(path, package)| {
651            if members.contains(path) {
652                if let MaybePackage::Package(p) = package {
653                    return Some(p);
654                }
655            }
656
657            None
658        })
659    }
660
661    /// Returns an iterator over default packages in this workspace
662    pub fn default_members<'a>(&'a self) -> impl Iterator<Item = &'a Package> {
663        let packages = &self.packages;
664        self.default_members
665            .iter()
666            .filter_map(move |path| match packages.get(path) {
667                MaybePackage::Package(p) => Some(p),
668                _ => None,
669            })
670    }
671
672    /// Returns an iterator over default packages in this workspace
673    pub fn default_members_mut(&mut self) -> impl Iterator<Item = &mut Package> {
674        let packages = &mut self.packages.packages;
675        let members: HashSet<_> = self
676            .default_members
677            .iter()
678            .map(|path| path.parent().unwrap().to_owned())
679            .collect();
680
681        packages.iter_mut().filter_map(move |(path, package)| {
682            if members.contains(path) {
683                if let MaybePackage::Package(p) = package {
684                    return Some(p);
685                }
686            }
687
688            None
689        })
690    }
691
692    /// Returns true if the package is a member of the workspace.
693    pub fn is_member(&self, pkg: &Package) -> bool {
694        self.member_ids.contains(&pkg.package_id())
695    }
696
697    /// Returns true if the given package_id is a member of the workspace.
698    pub fn is_member_id(&self, package_id: PackageId) -> bool {
699        self.member_ids.contains(&package_id)
700    }
701
702    pub fn is_ephemeral(&self) -> bool {
703        self.is_ephemeral
704    }
705
706    pub fn require_optional_deps(&self) -> bool {
707        self.require_optional_deps
708    }
709
710    pub fn set_require_optional_deps(
711        &mut self,
712        require_optional_deps: bool,
713    ) -> &mut Workspace<'gctx> {
714        self.require_optional_deps = require_optional_deps;
715        self
716    }
717
718    pub fn ignore_lock(&self) -> bool {
719        self.ignore_lock
720    }
721
722    pub fn set_ignore_lock(&mut self, ignore_lock: bool) -> &mut Workspace<'gctx> {
723        self.ignore_lock = ignore_lock;
724        self
725    }
726
727    /// Returns the directory where the lockfile is in.
728    pub fn lock_root(&self) -> Filesystem {
729        if let Some(requested) = self.requested_lockfile_path.as_ref() {
730            return Filesystem::new(
731                requested
732                    .parent()
733                    .expect("Lockfile path can't be root")
734                    .to_owned(),
735            );
736        }
737        self.default_lock_root()
738    }
739
740    fn default_lock_root(&self) -> Filesystem {
741        if self.root_maybe().is_embedded() {
742            // Include a workspace hash in case the user requests a shared build-dir so that
743            // scripts don't fight over the `Cargo.lock` content
744            let workspace_manifest_path = self.root_manifest();
745            let real_path = std::fs::canonicalize(workspace_manifest_path)
746                .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
747            let hash = crate::util::hex::short_hash(&real_path);
748            self.build_dir().join(hash)
749        } else {
750            Filesystem::new(self.root().to_owned())
751        }
752    }
753
754    // NOTE: may be removed once the deprecated `--lockfile-path` CLI flag is removed
755    pub fn set_requested_lockfile_path(&mut self, path: Option<PathBuf>) {
756        self.requested_lockfile_path = path;
757    }
758
759    pub fn requested_lockfile_path(&self) -> Option<&Path> {
760        self.requested_lockfile_path.as_deref()
761    }
762
763    /// Get the lowest-common denominator `package.rust-version` within the workspace, if specified
764    /// anywhere
765    pub fn lowest_rust_version(&self) -> Option<&RustVersion> {
766        self.members().filter_map(|pkg| pkg.rust_version()).min()
767    }
768
769    pub fn set_resolve_honors_rust_version(&mut self, honor_rust_version: Option<bool>) {
770        if let Some(honor_rust_version) = honor_rust_version {
771            self.resolve_honors_rust_version = honor_rust_version;
772        }
773    }
774
775    pub fn resolve_honors_rust_version(&self) -> bool {
776        self.resolve_honors_rust_version
777    }
778
779    pub fn set_resolve_feature_unification(&mut self, feature_unification: FeatureUnification) {
780        self.resolve_feature_unification = feature_unification;
781    }
782
783    pub fn resolve_feature_unification(&self) -> FeatureUnification {
784        self.resolve_feature_unification
785    }
786
787    pub fn set_resolve_publish_time(&mut self, publish_time: jiff::Timestamp) {
788        self.resolve_publish_time = Some(publish_time);
789    }
790
791    pub fn resolve_publish_time(&self) -> Option<jiff::Timestamp> {
792        self.resolve_publish_time
793    }
794
795    pub fn custom_metadata(&self) -> Option<&toml::Value> {
796        self.custom_metadata.as_ref()
797    }
798
799    pub fn load_workspace_config(&mut self) -> CargoResult<Option<WorkspaceRootConfig>> {
800        // If we didn't find a root, it must mean there is no [workspace] section, and thus no
801        // metadata.
802        if let Some(root_path) = &self.root_manifest {
803            let root_package = self.packages.load(root_path)?;
804            match root_package.workspace_config() {
805                WorkspaceConfig::Root(root_config) => {
806                    return Ok(Some(root_config.clone()));
807                }
808
809                _ => bail!(
810                    "root of a workspace inferred but wasn't a root: {}",
811                    root_path.display()
812                ),
813            }
814        }
815
816        Ok(None)
817    }
818
819    /// Finds the root of a workspace for the crate whose manifest is located
820    /// at `manifest_path`.
821    ///
822    /// This will parse the `Cargo.toml` at `manifest_path` and then interpret
823    /// the workspace configuration, optionally walking up the filesystem
824    /// looking for other workspace roots.
825    ///
826    /// Returns an error if `manifest_path` isn't actually a valid manifest or
827    /// if some other transient error happens.
828    fn find_root(&mut self, manifest_path: &Path) -> CargoResult<Option<PathBuf>> {
829        let current = self.packages.load(manifest_path)?;
830        match current
831            .workspace_config()
832            .get_ws_root(manifest_path, manifest_path)
833        {
834            Some(root_path) => {
835                debug!("find_root - is root {}", manifest_path.display());
836                Ok(Some(root_path))
837            }
838            None => find_workspace_root_with_loader(manifest_path, self.gctx, |self_path| {
839                Ok(self
840                    .packages
841                    .load(self_path)?
842                    .workspace_config()
843                    .get_ws_root(self_path, manifest_path))
844            }),
845        }
846    }
847
848    /// After the root of a workspace has been located, probes for all members
849    /// of a workspace.
850    ///
851    /// If the `workspace.members` configuration is present, then this just
852    /// verifies that those are all valid packages to point to. Otherwise, this
853    /// will transitively follow all `path` dependencies looking for members of
854    /// the workspace.
855    #[tracing::instrument(skip_all)]
856    fn find_members(&mut self) -> CargoResult<()> {
857        let Some(workspace_config) = self.load_workspace_config()? else {
858            debug!("find_members - only me as a member");
859            self.members.push(self.current_manifest.clone());
860            self.default_members.push(self.current_manifest.clone());
861            if let Ok(pkg) = self.current() {
862                let id = pkg.package_id();
863                self.member_ids.insert(id);
864            }
865            return Ok(());
866        };
867
868        // self.root_manifest must be Some to have retrieved workspace_config
869        let root_manifest_path = self.root_manifest.clone().unwrap();
870
871        let members_paths = workspace_config
872            .members_paths(workspace_config.members.as_deref().unwrap_or_default())?;
873        let default_members_paths = if root_manifest_path == self.current_manifest {
874            if let Some(ref default) = workspace_config.default_members {
875                Some(workspace_config.members_paths(default)?)
876            } else {
877                None
878            }
879        } else {
880            None
881        };
882
883        for (path, glob) in &members_paths {
884            self.find_path_deps(&path.join("Cargo.toml"), &root_manifest_path, false)
885                .with_context(|| {
886                    format!(
887                        "failed to load manifest for workspace member `{}`\n\
888                        referenced{} by workspace at `{}`",
889                        path.display(),
890                        glob.map(|g| format!(" via `{g}`")).unwrap_or_default(),
891                        root_manifest_path.display(),
892                    )
893                })?;
894        }
895
896        self.find_path_deps(&root_manifest_path, &root_manifest_path, false)?;
897
898        if let Some(default) = default_members_paths {
899            for (path, default_member_glob) in default {
900                let normalized_path = paths::normalize_path(&path);
901                let manifest_path = normalized_path.join("Cargo.toml");
902                if !self.members.contains(&manifest_path) {
903                    // default-members are allowed to be excluded, but they
904                    // still must be referred to by the original (unfiltered)
905                    // members list. Note that we aren't testing against the
906                    // manifest path, both because `members_paths` doesn't
907                    // include `/Cargo.toml`, and because excluded paths may not
908                    // be crates.
909                    let exclude = members_paths.iter().any(|(m, _)| *m == normalized_path)
910                        && workspace_config.is_excluded(&normalized_path);
911                    if exclude {
912                        continue;
913                    }
914                    bail!(
915                        "package `{}` is listed in default-members{} but is not a member\n\
916                        for workspace at `{}`.",
917                        path.display(),
918                        default_member_glob
919                            .map(|g| format!(" via `{g}`"))
920                            .unwrap_or_default(),
921                        root_manifest_path.display(),
922                    )
923                }
924                self.default_members.push(manifest_path)
925            }
926        } else if self.is_virtual() {
927            self.default_members = self.members.clone()
928        } else {
929            self.default_members.push(self.current_manifest.clone())
930        }
931
932        Ok(())
933    }
934
935    fn find_path_deps(
936        &mut self,
937        manifest_path: &Path,
938        root_manifest: &Path,
939        is_path_dep: bool,
940    ) -> CargoResult<()> {
941        let manifest_path = paths::normalize_path(manifest_path);
942        if self.members.contains(&manifest_path) {
943            return Ok(());
944        }
945        if is_path_dep && self.root_maybe().is_embedded() {
946            // Embedded manifests cannot have workspace members
947            return Ok(());
948        }
949        if is_path_dep
950            && !manifest_path.parent().unwrap().starts_with(self.root())
951            && self.find_root(&manifest_path)? != self.root_manifest
952        {
953            // If `manifest_path` is a path dependency outside of the workspace,
954            // don't add it, or any of its dependencies, as a members.
955            return Ok(());
956        }
957
958        if let WorkspaceConfig::Root(ref root_config) =
959            *self.packages.load(root_manifest)?.workspace_config()
960        {
961            if root_config.is_excluded(&manifest_path) {
962                return Ok(());
963            }
964        }
965
966        debug!("find_path_deps - {}", manifest_path.display());
967        self.members.push(manifest_path.clone());
968
969        let candidates = {
970            let pkg = match *self.packages.load(&manifest_path)? {
971                MaybePackage::Package(ref p) => p,
972                MaybePackage::Virtual(_) => return Ok(()),
973            };
974            self.member_ids.insert(pkg.package_id());
975            pkg.dependencies()
976                .iter()
977                .map(|d| (d.source_id(), d.package_name()))
978                .filter(|(s, _)| s.is_path())
979                .filter_map(|(s, n)| s.url().to_file_path().ok().map(|p| (p, n)))
980                .map(|(p, n)| (p.join("Cargo.toml"), n))
981                .collect::<Vec<_>>()
982        };
983        for (path, name) in candidates {
984            self.find_path_deps(&path, root_manifest, true)
985                .with_context(|| format!("failed to load manifest for dependency `{}`", name))
986                .map_err(|err| ManifestError::new(err, manifest_path.clone()))?;
987        }
988        Ok(())
989    }
990
991    /// Returns the unstable nightly-only features enabled via `cargo-features` in the manifest.
992    pub fn unstable_features(&self) -> &Features {
993        self.root_maybe().unstable_features()
994    }
995
996    pub fn resolve_behavior(&self) -> ResolveBehavior {
997        self.resolve_behavior
998    }
999
1000    /// Returns `true` if this workspace uses the new CLI features behavior.
1001    ///
1002    /// The old behavior only allowed choosing the features from the package
1003    /// in the current directory, regardless of which packages were chosen
1004    /// with the -p flags. The new behavior allows selecting features from the
1005    /// packages chosen on the command line (with -p or --workspace flags),
1006    /// ignoring whatever is in the current directory.
1007    pub fn allows_new_cli_feature_behavior(&self) -> bool {
1008        self.is_virtual()
1009            || match self.resolve_behavior() {
1010                ResolveBehavior::V1 => false,
1011                ResolveBehavior::V2 | ResolveBehavior::V3 => true,
1012            }
1013    }
1014
1015    /// Validates a workspace, ensuring that a number of invariants are upheld:
1016    ///
1017    /// 1. A workspace only has one root.
1018    /// 2. All workspace members agree on this one root as the root.
1019    /// 3. The current crate is a member of this workspace.
1020    #[tracing::instrument(skip_all)]
1021    fn validate(&mut self) -> CargoResult<()> {
1022        // The rest of the checks require a VirtualManifest or multiple members.
1023        if self.root_manifest.is_none() {
1024            return Ok(());
1025        }
1026
1027        self.validate_unique_names()?;
1028        self.validate_workspace_roots()?;
1029        self.validate_members()?;
1030        self.error_if_manifest_not_in_members()?;
1031        self.validate_manifest()
1032    }
1033
1034    fn validate_unique_names(&self) -> CargoResult<()> {
1035        let mut names = BTreeMap::new();
1036        for member in self.members.iter() {
1037            let package = self.packages.get(member);
1038            let name = match *package {
1039                MaybePackage::Package(ref p) => p.name(),
1040                MaybePackage::Virtual(_) => continue,
1041            };
1042            if let Some(prev) = names.insert(name, member) {
1043                bail!(
1044                    "two packages named `{}` in this workspace:\n\
1045                         - {}\n\
1046                         - {}",
1047                    name,
1048                    prev.display(),
1049                    member.display()
1050                );
1051            }
1052        }
1053        Ok(())
1054    }
1055
1056    fn validate_workspace_roots(&self) -> CargoResult<()> {
1057        let roots: Vec<PathBuf> = self
1058            .members
1059            .iter()
1060            .filter(|&member| {
1061                let config = self.packages.get(member).workspace_config();
1062                matches!(config, WorkspaceConfig::Root(_))
1063            })
1064            .map(|member| member.parent().unwrap().to_path_buf())
1065            .collect();
1066        match roots.len() {
1067            1 => Ok(()),
1068            0 => bail!(
1069                "`package.workspace` configuration points to a crate \
1070                 which is not configured with [workspace]: \n\
1071                 configuration at: {}\n\
1072                 points to: {}",
1073                self.current_manifest.display(),
1074                self.root_manifest.as_ref().unwrap().display()
1075            ),
1076            _ => {
1077                bail!(
1078                    "multiple workspace roots found in the same workspace:\n{}",
1079                    roots
1080                        .iter()
1081                        .map(|r| format!("  {}", r.display()))
1082                        .collect::<Vec<_>>()
1083                        .join("\n")
1084                );
1085            }
1086        }
1087    }
1088
1089    #[tracing::instrument(skip_all)]
1090    fn validate_members(&mut self) -> CargoResult<()> {
1091        for member in self.members.clone() {
1092            let root = self.find_root(&member)?;
1093            if root == self.root_manifest {
1094                continue;
1095            }
1096
1097            match root {
1098                Some(root) => {
1099                    bail!(
1100                        "package `{}` is a member of the wrong workspace\n\
1101                         expected: {}\n\
1102                         actual:   {}",
1103                        member.display(),
1104                        self.root_manifest.as_ref().unwrap().display(),
1105                        root.display()
1106                    );
1107                }
1108                None => {
1109                    bail!(
1110                        "workspace member `{}` is not hierarchically below \
1111                         the workspace root `{}`",
1112                        member.display(),
1113                        self.root_manifest.as_ref().unwrap().display()
1114                    );
1115                }
1116            }
1117        }
1118        Ok(())
1119    }
1120
1121    fn error_if_manifest_not_in_members(&mut self) -> CargoResult<()> {
1122        if self.members.contains(&self.current_manifest) {
1123            return Ok(());
1124        }
1125
1126        let root = self.root_manifest.as_ref().unwrap();
1127        let root_dir = root.parent().unwrap();
1128        let current_dir = self.current_manifest.parent().unwrap();
1129        let root_pkg = self.packages.get(root);
1130
1131        // Use pathdiff to handle finding the relative path between the current package
1132        // and the workspace root. This usually does a good job of handling `..` and
1133        // other weird things.
1134        // Normalize paths first to ensure `../` components are resolved if possible,
1135        // which helps `diff_paths` find the most direct relative path.
1136        let current_dir = paths::normalize_path(current_dir);
1137        let root_dir = paths::normalize_path(root_dir);
1138        let members_msg = match pathdiff::diff_paths(&current_dir, &root_dir) {
1139            Some(rel) => format!(
1140                "this may be fixable by adding `{}` to the \
1141                     `workspace.members` array of the manifest \
1142                     located at: {}",
1143                rel.display(),
1144                root.display()
1145            ),
1146            None => format!(
1147                "this may be fixable by adding a member to \
1148                     the `workspace.members` array of the \
1149                     manifest located at: {}",
1150                root.display()
1151            ),
1152        };
1153        let extra = match *root_pkg {
1154            MaybePackage::Virtual(_) => members_msg,
1155            MaybePackage::Package(ref p) => {
1156                let has_members_list = match *p.manifest().workspace_config() {
1157                    WorkspaceConfig::Root(ref root_config) => root_config.has_members_list(),
1158                    WorkspaceConfig::Member { .. } => unreachable!(),
1159                };
1160                if !has_members_list {
1161                    format!(
1162                        "this may be fixable by ensuring that this \
1163                             crate is depended on by the workspace \
1164                             root: {}",
1165                        root.display()
1166                    )
1167                } else {
1168                    members_msg
1169                }
1170            }
1171        };
1172        bail!(
1173            "current package believes it's in a workspace when it's not:\n\
1174                 current:   {}\n\
1175                 workspace: {}\n\n{}\n\
1176                 Alternatively, to keep it out of the workspace, add the package \
1177                 to the `workspace.exclude` array, or add an empty `[workspace]` \
1178                 table to the package's manifest.",
1179            self.current_manifest.display(),
1180            root.display(),
1181            extra
1182        );
1183    }
1184
1185    fn validate_manifest(&mut self) -> CargoResult<()> {
1186        if let Some(ref root_manifest) = self.root_manifest {
1187            for pkg in self
1188                .members()
1189                .filter(|p| p.manifest_path() != root_manifest)
1190            {
1191                let manifest = pkg.manifest();
1192                let emit_warning = |what| -> CargoResult<()> {
1193                    let msg = format!(
1194                        "{} for the non root package will be ignored, \
1195                         specify {} at the workspace root:\n\
1196                         package:   {}\n\
1197                         workspace: {}",
1198                        what,
1199                        what,
1200                        pkg.manifest_path().display(),
1201                        root_manifest.display(),
1202                    );
1203                    self.gctx.shell().warn(&msg)
1204                };
1205                if manifest.normalized_toml().has_profiles() {
1206                    emit_warning("profiles")?;
1207                }
1208                if !manifest.replace().is_empty() {
1209                    emit_warning("replace")?;
1210                }
1211                if !manifest.patch().is_empty() {
1212                    emit_warning("patch")?;
1213                }
1214                if let Some(behavior) = manifest.resolve_behavior() {
1215                    if behavior != self.resolve_behavior {
1216                        // Only warn if they don't match.
1217                        emit_warning("resolver")?;
1218                    }
1219                }
1220            }
1221            if let MaybePackage::Virtual(vm) = self.root_maybe() {
1222                if vm.resolve_behavior().is_none() {
1223                    if let Some(edition) = self
1224                        .members()
1225                        .filter(|p| p.manifest_path() != root_manifest)
1226                        .map(|p| p.manifest().edition())
1227                        .filter(|&e| e >= Edition::Edition2021)
1228                        .max()
1229                    {
1230                        let resolver = edition.default_resolve_behavior().to_manifest();
1231                        let report = &[Level::WARNING
1232                            .primary_title(format!(
1233                                "virtual workspace defaulting to `resolver = \"1\"` despite one or more workspace members being on edition {edition} which implies `resolver = \"{resolver}\"`"
1234                            ))
1235                            .elements([
1236                                Level::NOTE.message("to keep the current resolver, specify `workspace.resolver = \"1\"` in the workspace root's manifest"),
1237                                Level::NOTE.message(
1238                                    format!("to use the edition {edition} resolver, specify `workspace.resolver = \"{resolver}\"` in the workspace root's manifest"),
1239                                ),
1240                                Level::NOTE.message("for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions"),
1241                            ])];
1242                        self.gctx.shell().print_report(report, false)?;
1243                    }
1244                }
1245            }
1246        }
1247        Ok(())
1248    }
1249
1250    pub fn load(&self, manifest_path: &Path) -> CargoResult<Package> {
1251        match self.packages.maybe_get(manifest_path) {
1252            Some(MaybePackage::Package(p)) => return Ok(p.clone()),
1253            Some(&MaybePackage::Virtual(_)) => bail!("cannot load workspace root"),
1254            None => {}
1255        }
1256
1257        let mut loaded = self.loaded_packages.borrow_mut();
1258        if let Some(p) = loaded.get(manifest_path).cloned() {
1259            return Ok(p);
1260        }
1261        let source_id = SourceId::for_manifest_path(manifest_path)?;
1262        let package = ops::read_package(manifest_path, source_id, self.gctx)?;
1263        loaded.insert(manifest_path.to_path_buf(), package.clone());
1264        Ok(package)
1265    }
1266
1267    /// Preload the provided registry with already loaded packages.
1268    ///
1269    /// A workspace may load packages during construction/parsing/early phases
1270    /// for various operations, and this preload step avoids doubly-loading and
1271    /// parsing crates on the filesystem by inserting them all into the registry
1272    /// with their in-memory formats.
1273    pub fn preload(&self, registry: &mut PackageRegistry<'gctx>) {
1274        // These can get weird as this generally represents a workspace during
1275        // `cargo install`. Things like git repositories will actually have a
1276        // `PathSource` with multiple entries in it, so the logic below is
1277        // mostly just an optimization for normal `cargo build` in workspaces
1278        // during development.
1279        if self.is_ephemeral {
1280            return;
1281        }
1282
1283        for pkg in self.packages.packages.values() {
1284            let pkg = match *pkg {
1285                MaybePackage::Package(ref p) => p.clone(),
1286                MaybePackage::Virtual(_) => continue,
1287            };
1288            let src = PathSource::preload_with(pkg, self.gctx);
1289            registry.add_preloaded(Box::new(src));
1290        }
1291    }
1292
1293    pub fn emit_warnings(&self) -> CargoResult<()> {
1294        let mut first_emitted_error = None;
1295
1296        if let Err(e) = self.emit_ws_lints() {
1297            first_emitted_error = Some(e);
1298        }
1299
1300        for (path, maybe_pkg) in &self.packages.packages {
1301            if let MaybePackage::Package(pkg) = maybe_pkg {
1302                if let Err(e) = self.emit_pkg_lints(pkg, &path)
1303                    && first_emitted_error.is_none()
1304                {
1305                    first_emitted_error = Some(e);
1306                }
1307            }
1308            let warnings = match maybe_pkg {
1309                MaybePackage::Package(pkg) => pkg.manifest().warnings().warnings(),
1310                MaybePackage::Virtual(vm) => vm.warnings().warnings(),
1311            };
1312            for warning in warnings {
1313                if warning.is_critical {
1314                    let err = anyhow::format_err!("{}", warning.message);
1315                    let cx =
1316                        anyhow::format_err!("failed to parse manifest at `{}`", path.display());
1317                    if first_emitted_error.is_none() {
1318                        first_emitted_error = Some(err.context(cx));
1319                    }
1320                } else {
1321                    let msg = if self.root_manifest.is_none() {
1322                        warning.message.to_string()
1323                    } else {
1324                        // In a workspace, it can be confusing where a warning
1325                        // originated, so include the path.
1326                        format!("{}: {}", path.display(), warning.message)
1327                    };
1328                    self.gctx.shell().warn(msg)?
1329                }
1330            }
1331        }
1332
1333        if let Some(error) = first_emitted_error {
1334            Err(error)
1335        } else {
1336            Ok(())
1337        }
1338    }
1339
1340    pub fn emit_pkg_lints(&self, pkg: &Package, path: &Path) -> CargoResult<()> {
1341        let toml_lints = pkg
1342            .manifest()
1343            .normalized_toml()
1344            .lints
1345            .clone()
1346            .map(|lints| lints.lints)
1347            .unwrap_or(manifest::TomlLints::default());
1348        let cargo_lints = toml_lints
1349            .get("cargo")
1350            .cloned()
1351            .unwrap_or(manifest::TomlToolLints::default());
1352
1353        if self.gctx.cli_unstable().cargo_lints {
1354            let mut stats = DiagnosticStats::new();
1355
1356            missing_lints_features(pkg.into(), &path, &cargo_lints, &mut stats, self.gctx)?;
1357            unknown_lints(pkg.into(), &path, &cargo_lints, &mut stats, self.gctx)?;
1358
1359            check_im_a_teapot(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1360            implicit_minimum_version_req_pkg(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1361            non_kebab_case_packages(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1362            non_snake_case_packages(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1363            non_kebab_case_bins(self, pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1364            non_kebab_case_features(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1365            non_snake_case_features(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1366            unused_build_dependencies_no_build_rs(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1367            redundant_readme(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1368            redundant_homepage(pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1369            missing_lints_inheritance(self, pkg, &path, &cargo_lints, &mut stats, self.gctx)?;
1370            text_direction_codepoint_in_comment(
1371                pkg.into(),
1372                &path,
1373                &cargo_lints,
1374                &mut stats,
1375                self.gctx,
1376            )?;
1377            text_direction_codepoint_in_literal(
1378                pkg.into(),
1379                &path,
1380                &cargo_lints,
1381                &mut stats,
1382                self.gctx,
1383            )?;
1384
1385            stats.report_summary("parse", Some(&*pkg.name()), self.gctx)?;
1386        }
1387
1388        Ok(())
1389    }
1390
1391    pub fn emit_ws_lints(&self) -> CargoResult<()> {
1392        let mut stats = DiagnosticStats::new();
1393
1394        let cargo_lints = match self.root_maybe() {
1395            MaybePackage::Package(pkg) => {
1396                let toml = pkg.manifest().normalized_toml();
1397                if let Some(ws) = &toml.workspace {
1398                    ws.lints.as_ref()
1399                } else {
1400                    toml.lints.as_ref().map(|l| &l.lints)
1401                }
1402            }
1403            MaybePackage::Virtual(vm) => vm
1404                .normalized_toml()
1405                .workspace
1406                .as_ref()
1407                .unwrap()
1408                .lints
1409                .as_ref(),
1410        }
1411        .and_then(|t| t.get("cargo"))
1412        .cloned()
1413        .unwrap_or(manifest::TomlToolLints::default());
1414
1415        if self.gctx.cli_unstable().cargo_lints {
1416            missing_lints_features(
1417                (self, self.root_maybe()).into(),
1418                self.root_manifest(),
1419                &cargo_lints,
1420                &mut stats,
1421                self.gctx,
1422            )?;
1423            unknown_lints(
1424                (self, self.root_maybe()).into(),
1425                self.root_manifest(),
1426                &cargo_lints,
1427                &mut stats,
1428                self.gctx,
1429            )?;
1430
1431            unused_workspace_package_fields(
1432                self,
1433                self.root_maybe(),
1434                self.root_manifest(),
1435                &cargo_lints,
1436                &mut stats,
1437                self.gctx,
1438            )?;
1439            unused_workspace_dependencies(
1440                self,
1441                self.root_maybe(),
1442                self.root_manifest(),
1443                &cargo_lints,
1444                &mut stats,
1445                self.gctx,
1446            )?;
1447            implicit_minimum_version_req_ws(
1448                self,
1449                self.root_maybe(),
1450                self.root_manifest(),
1451                &cargo_lints,
1452                &mut stats,
1453                self.gctx,
1454            )?;
1455            text_direction_codepoint_in_comment(
1456                (self, self.root_maybe()).into(),
1457                self.root_manifest(),
1458                &cargo_lints,
1459                &mut stats,
1460                self.gctx,
1461            )?;
1462            text_direction_codepoint_in_literal(
1463                (self, self.root_maybe()).into(),
1464                self.root_manifest(),
1465                &cargo_lints,
1466                &mut stats,
1467                self.gctx,
1468            )?;
1469        }
1470
1471        // This is a short term hack to allow `blanket_hint_mostly_unused`
1472        // to run without requiring `-Zcargo-lints`, which should hopefully
1473        // improve the testing experience while we are collecting feedback
1474        if self.gctx.cli_unstable().profile_hint_mostly_unused {
1475            blanket_hint_mostly_unused(
1476                self,
1477                self.root_maybe(),
1478                self.root_manifest(),
1479                &cargo_lints,
1480                &mut stats,
1481                self.gctx,
1482            )?;
1483        }
1484
1485        stats.report_summary("parse", None, self.gctx)?;
1486        Ok(())
1487    }
1488
1489    pub fn set_target_dir(&mut self, target_dir: Filesystem) {
1490        self.target_dir = Some(target_dir);
1491    }
1492
1493    /// Returns a Vec of `(&Package, CliFeatures)` tuples that
1494    /// represent the workspace members that were requested on the command-line.
1495    ///
1496    /// `specs` may be empty, which indicates it should return all workspace
1497    /// members. In this case, `requested_features.all_features` must be
1498    /// `true`. This is used for generating `Cargo.lock`, which must include
1499    /// all members with all features enabled.
1500    pub fn members_with_features(
1501        &self,
1502        specs: &[PackageIdSpec],
1503        cli_features: &CliFeatures,
1504    ) -> CargoResult<Vec<(&Package, CliFeatures)>> {
1505        assert!(
1506            !specs.is_empty() || cli_features.all_features,
1507            "no specs requires all_features"
1508        );
1509        if specs.is_empty() {
1510            // When resolving the entire workspace, resolve each member with
1511            // all features enabled.
1512            return Ok(self
1513                .members()
1514                .map(|m| (m, CliFeatures::new_all(true)))
1515                .collect());
1516        }
1517        if self.allows_new_cli_feature_behavior() {
1518            self.members_with_features_new(specs, cli_features)
1519        } else {
1520            Ok(self.members_with_features_old(specs, cli_features))
1521        }
1522    }
1523
1524    /// Returns the requested features for the given member.
1525    /// This filters out any named features that the member does not have.
1526    fn collect_matching_features(
1527        member: &Package,
1528        cli_features: &CliFeatures,
1529        found_features: &mut BTreeSet<FeatureValue>,
1530    ) -> CliFeatures {
1531        if cli_features.features.is_empty() {
1532            return cli_features.clone();
1533        }
1534
1535        // Only include features this member defines.
1536        let summary = member.summary();
1537
1538        // Features defined in the manifest
1539        let summary_features = summary.features();
1540
1541        // Dependency name -> dependency
1542        let dependencies: BTreeMap<InternedString, &Dependency> = summary
1543            .dependencies()
1544            .iter()
1545            .map(|dep| (dep.name_in_toml(), dep))
1546            .collect();
1547
1548        // Features that enable optional dependencies
1549        let optional_dependency_names: BTreeSet<_> = dependencies
1550            .iter()
1551            .filter(|(_, dep)| dep.is_optional())
1552            .map(|(name, _)| name)
1553            .copied()
1554            .collect();
1555
1556        let mut features = BTreeSet::new();
1557
1558        // Checks if a member contains the given feature.
1559        let summary_or_opt_dependency_feature = |feature: &InternedString| -> bool {
1560            summary_features.contains_key(feature) || optional_dependency_names.contains(feature)
1561        };
1562
1563        for feature in cli_features.features.iter() {
1564            match feature {
1565                FeatureValue::Feature(f) => {
1566                    if summary_or_opt_dependency_feature(f) {
1567                        // feature exists in this member.
1568                        features.insert(feature.clone());
1569                        found_features.insert(feature.clone());
1570                    }
1571                }
1572                // This should be enforced by CliFeatures.
1573                FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1574                FeatureValue::DepFeature {
1575                    dep_name,
1576                    dep_feature,
1577                    weak: _,
1578                } => {
1579                    if dependencies.contains_key(dep_name) {
1580                        // pkg/feat for a dependency.
1581                        // Will rely on the dependency resolver to validate `dep_feature`.
1582                        features.insert(feature.clone());
1583                        found_features.insert(feature.clone());
1584                    } else if *dep_name == member.name()
1585                        && summary_or_opt_dependency_feature(dep_feature)
1586                    {
1587                        // member/feat where "feat" is a feature in member.
1588                        //
1589                        // `weak` can be ignored here, because the member
1590                        // either is or isn't being built.
1591                        features.insert(FeatureValue::Feature(*dep_feature));
1592                        found_features.insert(feature.clone());
1593                    }
1594                }
1595            }
1596        }
1597        CliFeatures {
1598            features: Rc::new(features),
1599            all_features: cli_features.all_features,
1600            uses_default_features: cli_features.uses_default_features,
1601        }
1602    }
1603
1604    fn missing_feature_spelling_suggestions(
1605        &self,
1606        selected_members: &[&Package],
1607        cli_features: &CliFeatures,
1608        found_features: &BTreeSet<FeatureValue>,
1609    ) -> Vec<String> {
1610        // Keeps track of which features were contained in summary of `member` to suggest similar features in errors
1611        let mut summary_features: Vec<InternedString> = Default::default();
1612
1613        // Keeps track of `member` dependencies (`dep/feature`) and their features names to suggest similar features in error
1614        let mut dependencies_features: BTreeMap<InternedString, &[InternedString]> =
1615            Default::default();
1616
1617        // Keeps track of `member` optional dependencies names (which can be enabled with feature) to suggest similar features in error
1618        let mut optional_dependency_names: Vec<InternedString> = Default::default();
1619
1620        // Keeps track of which features were contained in summary of `member` to suggest similar features in errors
1621        let mut summary_features_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
1622            Default::default();
1623
1624        // Keeps track of `member` optional dependencies (which can be enabled with feature) to suggest similar features in error
1625        let mut optional_dependency_names_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
1626            Default::default();
1627
1628        for &member in selected_members {
1629            // Only include features this member defines.
1630            let summary = member.summary();
1631
1632            // Features defined in the manifest
1633            summary_features.extend(summary.features().keys());
1634            summary_features_per_member
1635                .insert(member, summary.features().keys().copied().collect());
1636
1637            // Dependency name -> dependency
1638            let dependencies: BTreeMap<InternedString, &Dependency> = summary
1639                .dependencies()
1640                .iter()
1641                .map(|dep| (dep.name_in_toml(), dep))
1642                .collect();
1643
1644            dependencies_features.extend(
1645                dependencies
1646                    .iter()
1647                    .map(|(name, dep)| (*name, dep.features())),
1648            );
1649
1650            // Features that enable optional dependencies
1651            let optional_dependency_names_raw: BTreeSet<_> = dependencies
1652                .iter()
1653                .filter(|(_, dep)| dep.is_optional())
1654                .map(|(name, _)| name)
1655                .copied()
1656                .collect();
1657
1658            optional_dependency_names.extend(optional_dependency_names_raw.iter());
1659            optional_dependency_names_per_member.insert(member, optional_dependency_names_raw);
1660        }
1661
1662        let edit_distance_test = |a: InternedString, b: InternedString| {
1663            edit_distance(a.as_str(), b.as_str(), 3).is_some()
1664        };
1665
1666        cli_features
1667            .features
1668            .difference(found_features)
1669            .map(|feature| match feature {
1670                // Simple feature, check if any of the optional dependency features or member features are close enough
1671                FeatureValue::Feature(typo) => {
1672                    // Finds member features which are similar to the requested feature.
1673                    let summary_features = summary_features
1674                        .iter()
1675                        .filter(move |feature| edit_distance_test(**feature, *typo));
1676
1677                    // Finds optional dependencies which name is similar to the feature
1678                    let optional_dependency_features = optional_dependency_names
1679                        .iter()
1680                        .filter(move |feature| edit_distance_test(**feature, *typo));
1681
1682                    summary_features
1683                        .chain(optional_dependency_features)
1684                        .map(|s| s.to_string())
1685                        .collect::<Vec<_>>()
1686                }
1687                FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1688                FeatureValue::DepFeature {
1689                    dep_name,
1690                    dep_feature,
1691                    weak: _,
1692                } => {
1693                    // Finds set of `pkg/feat` that are very similar to current `pkg/feat`.
1694                    let pkg_feat_similar = dependencies_features
1695                        .iter()
1696                        .filter(|(name, _)| edit_distance_test(**name, *dep_name))
1697                        .map(|(name, features)| {
1698                            (
1699                                name,
1700                                features
1701                                    .iter()
1702                                    .filter(|feature| edit_distance_test(**feature, *dep_feature))
1703                                    .collect::<Vec<_>>(),
1704                            )
1705                        })
1706                        .map(|(name, features)| {
1707                            features
1708                                .into_iter()
1709                                .map(move |feature| format!("{}/{}", name, feature))
1710                        })
1711                        .flatten();
1712
1713                    // Finds set of `member/optional_dep` features which name is similar to current `pkg/feat`.
1714                    let optional_dependency_features = optional_dependency_names_per_member
1715                        .iter()
1716                        .filter(|(package, _)| edit_distance_test(package.name(), *dep_name))
1717                        .map(|(package, optional_dependencies)| {
1718                            optional_dependencies
1719                                .into_iter()
1720                                .filter(|optional_dependency| {
1721                                    edit_distance_test(**optional_dependency, *dep_name)
1722                                })
1723                                .map(move |optional_dependency| {
1724                                    format!("{}/{}", package.name(), optional_dependency)
1725                                })
1726                        })
1727                        .flatten();
1728
1729                    // Finds set of `member/feat` features which name is similar to current `pkg/feat`.
1730                    let summary_features = summary_features_per_member
1731                        .iter()
1732                        .filter(|(package, _)| edit_distance_test(package.name(), *dep_name))
1733                        .map(|(package, summary_features)| {
1734                            summary_features
1735                                .into_iter()
1736                                .filter(|summary_feature| {
1737                                    edit_distance_test(**summary_feature, *dep_feature)
1738                                })
1739                                .map(move |summary_feature| {
1740                                    format!("{}/{}", package.name(), summary_feature)
1741                                })
1742                        })
1743                        .flatten();
1744
1745                    pkg_feat_similar
1746                        .chain(optional_dependency_features)
1747                        .chain(summary_features)
1748                        .collect::<Vec<_>>()
1749                }
1750            })
1751            .map(|v| v.into_iter())
1752            .flatten()
1753            .unique()
1754            .filter(|element| {
1755                let feature = FeatureValue::new(element.into());
1756                !cli_features.features.contains(&feature) && !found_features.contains(&feature)
1757            })
1758            .sorted()
1759            .take(5)
1760            .collect()
1761    }
1762
1763    fn report_unknown_features_error(
1764        &self,
1765        specs: &[PackageIdSpec],
1766        cli_features: &CliFeatures,
1767        found_features: &BTreeSet<FeatureValue>,
1768    ) -> CargoResult<()> {
1769        let unknown: Vec<_> = cli_features
1770            .features
1771            .difference(found_features)
1772            .map(|feature| feature.to_string())
1773            .sorted()
1774            .collect();
1775
1776        let (selected_members, unselected_members): (Vec<_>, Vec<_>) = self
1777            .members()
1778            .partition(|member| specs.iter().any(|spec| spec.matches(member.package_id())));
1779
1780        let missing_packages_with_the_features = unselected_members
1781            .into_iter()
1782            .filter(|member| {
1783                unknown
1784                    .iter()
1785                    .any(|feature| member.summary().features().contains_key(&**feature))
1786            })
1787            .map(|m| m.name())
1788            .collect_vec();
1789
1790        let these_features = if unknown.len() == 1 {
1791            "this feature"
1792        } else {
1793            "these features"
1794        };
1795        let mut msg = if let [singular] = &selected_members[..] {
1796            format!(
1797                "the package '{}' does not contain {these_features}: {}",
1798                singular.name(),
1799                unknown.join(", ")
1800            )
1801        } else {
1802            let names = selected_members.iter().map(|m| m.name()).join(", ");
1803            format!(
1804                "none of the selected packages contains {these_features}: {}\nselected packages: {names}",
1805                unknown.join(", ")
1806            )
1807        };
1808
1809        use std::fmt::Write;
1810        if !missing_packages_with_the_features.is_empty() {
1811            write!(
1812                &mut msg,
1813                "\nhelp: package{} with the missing feature{}: {}",
1814                if missing_packages_with_the_features.len() != 1 {
1815                    "s"
1816                } else {
1817                    ""
1818                },
1819                if unknown.len() != 1 { "s" } else { "" },
1820                missing_packages_with_the_features.join(", ")
1821            )?;
1822        } else {
1823            let suggestions = self.missing_feature_spelling_suggestions(
1824                &selected_members,
1825                cli_features,
1826                found_features,
1827            );
1828            if !suggestions.is_empty() {
1829                write!(
1830                    &mut msg,
1831                    "\nhelp: there {}: {}",
1832                    if suggestions.len() == 1 {
1833                        "is a similarly named feature"
1834                    } else {
1835                        "are similarly named features"
1836                    },
1837                    suggestions.join(", ")
1838                )?;
1839            }
1840        }
1841
1842        bail!("{msg}")
1843    }
1844
1845    /// New command-line feature selection behavior with resolver = "2" or the
1846    /// root of a virtual workspace. See `allows_new_cli_feature_behavior`.
1847    fn members_with_features_new(
1848        &self,
1849        specs: &[PackageIdSpec],
1850        cli_features: &CliFeatures,
1851    ) -> CargoResult<Vec<(&Package, CliFeatures)>> {
1852        // Keeps track of which features matched `member` to produce an error
1853        // if any of them did not match anywhere.
1854        let mut found_features = Default::default();
1855
1856        let members: Vec<(&Package, CliFeatures)> = self
1857            .members()
1858            .filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
1859            .map(|m| {
1860                (
1861                    m,
1862                    Workspace::collect_matching_features(m, cli_features, &mut found_features),
1863                )
1864            })
1865            .collect();
1866
1867        if members.is_empty() {
1868            // `cargo build -p foo`, where `foo` is not a member.
1869            // Do not allow any command-line flags (defaults only).
1870            if !(cli_features.features.is_empty()
1871                && !cli_features.all_features
1872                && cli_features.uses_default_features)
1873            {
1874                let hint = specs
1875                    .iter()
1876                    .map(|spec| {
1877                        closest_msg(
1878                            spec.name(),
1879                            self.members(),
1880                            |m| m.name().as_str(),
1881                            "workspace member",
1882                        )
1883                    })
1884                    .find(|msg| !msg.is_empty())
1885                    .unwrap_or_default();
1886                bail!("cannot specify features for packages outside of workspace{hint}");
1887            }
1888            // Add all members from the workspace so we can ensure `-p nonmember`
1889            // is in the resolve graph.
1890            return Ok(self
1891                .members()
1892                .map(|m| (m, CliFeatures::new_all(false)))
1893                .collect());
1894        }
1895        if *cli_features.features != found_features {
1896            self.report_unknown_features_error(specs, cli_features, &found_features)?;
1897        }
1898        Ok(members)
1899    }
1900
1901    /// This is the "old" behavior for command-line feature selection.
1902    /// See `allows_new_cli_feature_behavior`.
1903    fn members_with_features_old(
1904        &self,
1905        specs: &[PackageIdSpec],
1906        cli_features: &CliFeatures,
1907    ) -> Vec<(&Package, CliFeatures)> {
1908        // Split off any features with the syntax `member-name/feature-name` into a map
1909        // so that those features can be applied directly to those workspace-members.
1910        let mut member_specific_features: HashMap<InternedString, BTreeSet<FeatureValue>> =
1911            HashMap::new();
1912        // Features for the member in the current directory.
1913        let mut cwd_features = BTreeSet::new();
1914        for feature in cli_features.features.iter() {
1915            match feature {
1916                FeatureValue::Feature(_) => {
1917                    cwd_features.insert(feature.clone());
1918                }
1919                // This should be enforced by CliFeatures.
1920                FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1921                FeatureValue::DepFeature {
1922                    dep_name,
1923                    dep_feature,
1924                    weak: _,
1925                } => {
1926                    // I think weak can be ignored here.
1927                    // * With `--features member?/feat -p member`, the ? doesn't
1928                    //   really mean anything (either the member is built or it isn't).
1929                    // * With `--features nonmember?/feat`, cwd_features will
1930                    //   handle processing it correctly.
1931                    let is_member = self.members().any(|member| {
1932                        // Check if `dep_name` is member of the workspace, but isn't associated with current package.
1933                        self.current_opt() != Some(member) && member.name() == *dep_name
1934                    });
1935                    if is_member && specs.iter().any(|spec| spec.name() == dep_name.as_str()) {
1936                        member_specific_features
1937                            .entry(*dep_name)
1938                            .or_default()
1939                            .insert(FeatureValue::Feature(*dep_feature));
1940                    } else {
1941                        cwd_features.insert(feature.clone());
1942                    }
1943                }
1944            }
1945        }
1946
1947        let ms: Vec<_> = self
1948            .members()
1949            .filter_map(|member| {
1950                let member_id = member.package_id();
1951                match self.current_opt() {
1952                    // The features passed on the command-line only apply to
1953                    // the "current" package (determined by the cwd).
1954                    Some(current) if member_id == current.package_id() => {
1955                        let feats = CliFeatures {
1956                            features: Rc::new(cwd_features.clone()),
1957                            all_features: cli_features.all_features,
1958                            uses_default_features: cli_features.uses_default_features,
1959                        };
1960                        Some((member, feats))
1961                    }
1962                    _ => {
1963                        // Ignore members that are not enabled on the command-line.
1964                        if specs.iter().any(|spec| spec.matches(member_id)) {
1965                            // -p for a workspace member that is not the "current"
1966                            // one.
1967                            //
1968                            // The odd behavior here is due to backwards
1969                            // compatibility. `--features` and
1970                            // `--no-default-features` used to only apply to the
1971                            // "current" package. As an extension, this allows
1972                            // member-name/feature-name to set member-specific
1973                            // features, which should be backwards-compatible.
1974                            let feats = CliFeatures {
1975                                features: Rc::new(
1976                                    member_specific_features
1977                                        .remove(member.name().as_str())
1978                                        .unwrap_or_default(),
1979                                ),
1980                                uses_default_features: true,
1981                                all_features: cli_features.all_features,
1982                            };
1983                            Some((member, feats))
1984                        } else {
1985                            // This member was not requested on the command-line, skip.
1986                            None
1987                        }
1988                    }
1989                }
1990            })
1991            .collect();
1992
1993        // If any member specific features were not removed while iterating over members
1994        // some features will be ignored.
1995        assert!(member_specific_features.is_empty());
1996
1997        ms
1998    }
1999
2000    /// Returns true if `unit` should depend on the output of Docscrape units.
2001    pub fn unit_needs_doc_scrape(&self, unit: &Unit) -> bool {
2002        // We do not add scraped units for Host units, as they're either build scripts
2003        // (not documented) or proc macros (have no scrape-able exports). Additionally,
2004        // naively passing a proc macro's unit_for to new_unit_dep will currently cause
2005        // Cargo to panic, see issue #10545.
2006        self.is_member(&unit.pkg) && !(unit.target.for_host() || unit.pkg.proc_macro())
2007    }
2008
2009    /// Adds a local package registry overlaying a `SourceId`.
2010    ///
2011    /// See [`crate::sources::overlay::DependencyConfusionThreatOverlaySource`] for why you shouldn't use this.
2012    pub fn add_local_overlay(&mut self, id: SourceId, registry_path: PathBuf) {
2013        self.local_overlays.insert(id, registry_path);
2014    }
2015
2016    /// Builds a package registry that reflects this workspace configuration.
2017    pub fn package_registry(&self) -> CargoResult<PackageRegistry<'gctx>> {
2018        let source_config =
2019            SourceConfigMap::new_with_overlays(self.gctx(), self.local_overlays()?)?;
2020        PackageRegistry::new_with_source_config(self.gctx(), source_config)
2021    }
2022
2023    /// Returns all the configured local overlays, including the ones from our secret environment variable.
2024    fn local_overlays(&self) -> CargoResult<impl Iterator<Item = (SourceId, SourceId)>> {
2025        let mut ret = self
2026            .local_overlays
2027            .iter()
2028            .map(|(id, path)| Ok((*id, SourceId::for_local_registry(path)?)))
2029            .collect::<CargoResult<Vec<_>>>()?;
2030
2031        if let Ok(overlay) = self
2032            .gctx
2033            .get_env("__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS")
2034        {
2035            let (url, path) = overlay.split_once('=').ok_or(anyhow::anyhow!(
2036                "invalid overlay format. I won't tell you why; you shouldn't be using it anyway"
2037            ))?;
2038            ret.push((
2039                SourceId::from_url(url)?,
2040                SourceId::for_local_registry(path.as_ref())?,
2041            ));
2042        }
2043
2044        Ok(ret.into_iter())
2045    }
2046}
2047
2048impl<'gctx> Packages<'gctx> {
2049    fn get(&self, manifest_path: &Path) -> &MaybePackage {
2050        self.maybe_get(manifest_path).unwrap()
2051    }
2052
2053    fn get_mut(&mut self, manifest_path: &Path) -> &mut MaybePackage {
2054        self.maybe_get_mut(manifest_path).unwrap()
2055    }
2056
2057    fn maybe_get(&self, manifest_path: &Path) -> Option<&MaybePackage> {
2058        self.packages.get(manifest_path)
2059    }
2060
2061    fn maybe_get_mut(&mut self, manifest_path: &Path) -> Option<&mut MaybePackage> {
2062        self.packages.get_mut(manifest_path)
2063    }
2064
2065    fn load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage> {
2066        match self.packages.entry(manifest_path.to_path_buf()) {
2067            Entry::Occupied(e) => Ok(e.into_mut()),
2068            Entry::Vacant(v) => {
2069                let source_id = SourceId::for_manifest_path(manifest_path)?;
2070                let manifest = read_manifest(manifest_path, source_id, self.gctx)?;
2071                Ok(v.insert(match manifest {
2072                    EitherManifest::Real(manifest) => {
2073                        MaybePackage::Package(Package::new(manifest, manifest_path))
2074                    }
2075                    EitherManifest::Virtual(vm) => MaybePackage::Virtual(vm),
2076                }))
2077            }
2078        }
2079    }
2080}
2081
2082impl MaybePackage {
2083    fn workspace_config(&self) -> &WorkspaceConfig {
2084        match *self {
2085            MaybePackage::Package(ref p) => p.manifest().workspace_config(),
2086            MaybePackage::Virtual(ref vm) => vm.workspace_config(),
2087        }
2088    }
2089
2090    /// Has an embedded manifest (single-file package)
2091    pub fn is_embedded(&self) -> bool {
2092        match self {
2093            MaybePackage::Package(p) => p.manifest().is_embedded(),
2094            MaybePackage::Virtual(_) => false,
2095        }
2096    }
2097
2098    pub fn contents(&self) -> Option<&str> {
2099        match self {
2100            MaybePackage::Package(p) => p.manifest().contents(),
2101            MaybePackage::Virtual(v) => v.contents(),
2102        }
2103    }
2104
2105    pub fn document(&self) -> Option<&toml::Spanned<toml::de::DeTable<'static>>> {
2106        match self {
2107            MaybePackage::Package(p) => p.manifest().document(),
2108            MaybePackage::Virtual(v) => v.document(),
2109        }
2110    }
2111
2112    pub fn original_toml(&self) -> Option<&TomlManifest> {
2113        match self {
2114            MaybePackage::Package(p) => p.manifest().original_toml(),
2115            MaybePackage::Virtual(v) => v.original_toml(),
2116        }
2117    }
2118
2119    pub fn normalized_toml(&self) -> &TomlManifest {
2120        match self {
2121            MaybePackage::Package(p) => p.manifest().normalized_toml(),
2122            MaybePackage::Virtual(v) => v.normalized_toml(),
2123        }
2124    }
2125
2126    pub fn edition(&self) -> Edition {
2127        match self {
2128            MaybePackage::Package(p) => p.manifest().edition(),
2129            MaybePackage::Virtual(_) => Edition::default(),
2130        }
2131    }
2132
2133    pub fn profiles(&self) -> Option<&TomlProfiles> {
2134        match self {
2135            MaybePackage::Package(p) => p.manifest().profiles(),
2136            MaybePackage::Virtual(v) => v.profiles(),
2137        }
2138    }
2139
2140    pub fn unstable_features(&self) -> &Features {
2141        match self {
2142            MaybePackage::Package(p) => p.manifest().unstable_features(),
2143            MaybePackage::Virtual(vm) => vm.unstable_features(),
2144        }
2145    }
2146}
2147
2148impl WorkspaceRootConfig {
2149    /// Creates a new Intermediate Workspace Root configuration.
2150    pub fn new(
2151        root_dir: &Path,
2152        members: &Option<Vec<String>>,
2153        default_members: &Option<Vec<String>>,
2154        exclude: &Option<Vec<String>>,
2155        inheritable: &Option<InheritableFields>,
2156        custom_metadata: &Option<toml::Value>,
2157    ) -> WorkspaceRootConfig {
2158        WorkspaceRootConfig {
2159            root_dir: root_dir.to_path_buf(),
2160            members: members.clone(),
2161            default_members: default_members.clone(),
2162            exclude: exclude.clone().unwrap_or_default(),
2163            inheritable_fields: inheritable.clone().unwrap_or_default(),
2164            custom_metadata: custom_metadata.clone(),
2165        }
2166    }
2167    /// Checks the path against the `excluded` list.
2168    ///
2169    /// This method does **not** consider the `members` list.
2170    fn is_excluded(&self, manifest_path: &Path) -> bool {
2171        let excluded = self
2172            .exclude
2173            .iter()
2174            .any(|ex| manifest_path.starts_with(self.root_dir.join(ex)));
2175
2176        let explicit_member = match self.members {
2177            Some(ref members) => members
2178                .iter()
2179                .any(|mem| manifest_path.starts_with(self.root_dir.join(mem))),
2180            None => false,
2181        };
2182
2183        !explicit_member && excluded
2184    }
2185
2186    /// Checks if the path is explicitly listed as a workspace member.
2187    ///
2188    /// Returns `true` ONLY if:
2189    /// - The path is the workspace root manifest itself, or
2190    /// - The path matches one of the explicit `members` patterns
2191    ///
2192    /// NOTE: This does NOT check for implicit path dependency membership.
2193    /// A `false` return does NOT mean the package is definitely not a member -
2194    /// it could still be a member via path dependencies. Callers should fallback
2195    /// to full workspace loading when this returns `false`.
2196    fn is_explicitly_listed_member(&self, manifest_path: &Path) -> bool {
2197        let root_manifest = self.root_dir.join("Cargo.toml");
2198        if manifest_path == root_manifest {
2199            return true;
2200        }
2201        match self.members {
2202            Some(ref members) => {
2203                // Use members_paths to properly expand glob patterns
2204                let Ok(expanded_members) = self.members_paths(members) else {
2205                    return false;
2206                };
2207                // Normalize the manifest path for comparison
2208                let normalized_manifest = paths::normalize_path(manifest_path);
2209                expanded_members.iter().any(|(member_path, _)| {
2210                    // Normalize the member path as glob expansion may leave ".." components
2211                    let normalized_member = paths::normalize_path(member_path);
2212                    // Compare the manifest's parent directory with the member path exactly
2213                    // instead of using starts_with to avoid matching nested directories
2214                    normalized_manifest.parent() == Some(normalized_member.as_path())
2215                })
2216            }
2217            None => false,
2218        }
2219    }
2220
2221    fn has_members_list(&self) -> bool {
2222        self.members.is_some()
2223    }
2224
2225    /// Returns true if this workspace config has default-members defined.
2226    fn has_default_members(&self) -> bool {
2227        self.default_members.is_some()
2228    }
2229
2230    /// Returns expanded paths along with the glob that they were expanded from.
2231    /// The glob is `None` if the path matched exactly.
2232    #[tracing::instrument(skip_all)]
2233    fn members_paths<'g>(
2234        &self,
2235        globs: &'g [String],
2236    ) -> CargoResult<Vec<(PathBuf, Option<&'g str>)>> {
2237        let mut expanded_list = Vec::new();
2238
2239        for glob in globs {
2240            let pathbuf = self.root_dir.join(glob);
2241            let expanded_paths = Self::expand_member_path(&pathbuf)?;
2242
2243            // If glob does not find any valid paths, then put the original
2244            // path in the expanded list to maintain backwards compatibility.
2245            if expanded_paths.is_empty() {
2246                expanded_list.push((pathbuf, None));
2247            } else {
2248                let used_glob_pattern = expanded_paths.len() > 1 || expanded_paths[0] != pathbuf;
2249                let glob = used_glob_pattern.then_some(glob.as_str());
2250
2251                // Some OS can create system support files anywhere.
2252                // (e.g. macOS creates `.DS_Store` file if you visit a directory using Finder.)
2253                // Such files can be reported as a member path unexpectedly.
2254                // Check and filter out non-directory paths to prevent pushing such accidental unwanted path
2255                // as a member.
2256                for expanded_path in expanded_paths {
2257                    if expanded_path.is_dir() {
2258                        expanded_list.push((expanded_path, glob));
2259                    }
2260                }
2261            }
2262        }
2263
2264        Ok(expanded_list)
2265    }
2266
2267    fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> {
2268        let Some(path) = path.to_str() else {
2269            return Ok(Vec::new());
2270        };
2271        let res = glob(path).with_context(|| format!("could not parse pattern `{}`", &path))?;
2272        let res = res
2273            .map(|p| p.with_context(|| format!("unable to match path to pattern `{}`", &path)))
2274            .collect::<Result<Vec<_>, _>>()?;
2275        Ok(res)
2276    }
2277
2278    pub fn inheritable(&self) -> &InheritableFields {
2279        &self.inheritable_fields
2280    }
2281}
2282
2283pub fn resolve_relative_path(
2284    label: &str,
2285    old_root: &Path,
2286    new_root: &Path,
2287    rel_path: &str,
2288) -> CargoResult<String> {
2289    let joined_path = normalize_path(&old_root.join(rel_path));
2290    match diff_paths(joined_path, new_root) {
2291        None => Err(anyhow!(
2292            "`{}` was defined in {} but could not be resolved with {}",
2293            label,
2294            old_root.display(),
2295            new_root.display()
2296        )),
2297        Some(path) => Ok(path
2298            .to_str()
2299            .ok_or_else(|| {
2300                anyhow!(
2301                    "`{}` resolved to non-UTF value (`{}`)",
2302                    label,
2303                    path.display()
2304                )
2305            })?
2306            .to_owned()),
2307    }
2308}
2309
2310/// Finds the path of the root of the workspace.
2311pub fn find_workspace_root(
2312    manifest_path: &Path,
2313    gctx: &GlobalContext,
2314) -> CargoResult<Option<PathBuf>> {
2315    find_workspace_root_with_loader(manifest_path, gctx, |self_path| {
2316        let source_id = SourceId::for_manifest_path(self_path)?;
2317        let manifest = read_manifest(self_path, source_id, gctx)?;
2318        Ok(manifest
2319            .workspace_config()
2320            .get_ws_root(self_path, manifest_path))
2321    })
2322}
2323
2324/// Finds the workspace root for a manifest, with minimal verification.
2325///
2326/// This is similar to `find_workspace_root`, but additionally verifies that the
2327/// package and workspace agree on each other:
2328/// - If the package has an explicit `package.workspace` pointer, it is trusted
2329/// - Otherwise, the workspace must include the package in its `members` list
2330pub fn find_workspace_root_with_membership_check(
2331    manifest_path: &Path,
2332    gctx: &GlobalContext,
2333) -> CargoResult<Option<PathBuf>> {
2334    let source_id = SourceId::for_manifest_path(manifest_path)?;
2335    let current_manifest = read_manifest(manifest_path, source_id, gctx)?;
2336
2337    match current_manifest.workspace_config() {
2338        WorkspaceConfig::Root(root_config) => {
2339            // This manifest is a workspace root itself
2340            // If default-members are defined, fall back to full loading for proper validation
2341            if root_config.has_default_members() {
2342                Ok(None)
2343            } else {
2344                Ok(Some(manifest_path.to_path_buf()))
2345            }
2346        }
2347        WorkspaceConfig::Member {
2348            root: Some(path_to_root),
2349        } => {
2350            // Has explicit `package.workspace` pointer - verify the workspace agrees
2351            let ws_manifest_path = read_root_pointer(manifest_path, path_to_root);
2352            let ws_source_id = SourceId::for_manifest_path(&ws_manifest_path)?;
2353            let ws_manifest = read_manifest(&ws_manifest_path, ws_source_id, gctx)?;
2354
2355            // Verify the workspace includes this package in its members
2356            if let WorkspaceConfig::Root(ref root_config) = *ws_manifest.workspace_config() {
2357                if root_config.is_explicitly_listed_member(manifest_path)
2358                    && !root_config.is_excluded(manifest_path)
2359                {
2360                    return Ok(Some(ws_manifest_path));
2361                }
2362            }
2363            // Workspace doesn't agree with the pointer - not a valid workspace root
2364            Ok(None)
2365        }
2366        WorkspaceConfig::Member { root: None } => {
2367            // No explicit pointer, walk up with membership validation
2368            find_workspace_root_with_loader(manifest_path, gctx, |candidate_manifest_path| {
2369                let source_id = SourceId::for_manifest_path(candidate_manifest_path)?;
2370                let manifest = read_manifest(candidate_manifest_path, source_id, gctx)?;
2371                if let WorkspaceConfig::Root(ref root_config) = *manifest.workspace_config() {
2372                    if root_config.is_explicitly_listed_member(manifest_path)
2373                        && !root_config.is_excluded(manifest_path)
2374                    {
2375                        return Ok(Some(candidate_manifest_path.to_path_buf()));
2376                    }
2377                }
2378                Ok(None)
2379            })
2380        }
2381    }
2382}
2383
2384/// Finds the path of the root of the workspace.
2385///
2386/// This uses a callback to determine if the given path tells us what the
2387/// workspace root is.
2388fn find_workspace_root_with_loader(
2389    manifest_path: &Path,
2390    gctx: &GlobalContext,
2391    mut loader: impl FnMut(&Path) -> CargoResult<Option<PathBuf>>,
2392) -> CargoResult<Option<PathBuf>> {
2393    // Check if there are any workspace roots that have already been found that would work
2394    {
2395        let roots = gctx.ws_roots();
2396        // Iterate through the manifests parent directories until we find a workspace
2397        // root. Note we skip the first item since that is just the path itself
2398        for current in manifest_path.ancestors().skip(1) {
2399            if let Some(ws_config) = roots.get(current) {
2400                if !ws_config.is_excluded(manifest_path) {
2401                    // Add `Cargo.toml` since ws_root is the root and not the file
2402                    return Ok(Some(current.join("Cargo.toml")));
2403                }
2404            }
2405        }
2406    }
2407
2408    for ances_manifest_path in find_root_iter(manifest_path, gctx) {
2409        debug!("find_root - trying {}", ances_manifest_path.display());
2410        let ws_root_path = loader(&ances_manifest_path).with_context(|| {
2411            format!(
2412                "failed searching for potential workspace\n\
2413                 package manifest: `{}`\n\
2414                 invalid potential workspace manifest: `{}`\n\
2415                 \n\
2416                 help: to avoid searching for a non-existent workspace, add \
2417                 `[workspace]` to the package manifest",
2418                manifest_path.display(),
2419                ances_manifest_path.display(),
2420            )
2421        })?;
2422        if let Some(ws_root_path) = ws_root_path {
2423            return Ok(Some(ws_root_path));
2424        }
2425    }
2426    Ok(None)
2427}
2428
2429fn read_root_pointer(member_manifest: &Path, root_link: &str) -> PathBuf {
2430    let path = member_manifest
2431        .parent()
2432        .unwrap()
2433        .join(root_link)
2434        .join("Cargo.toml");
2435    debug!("find_root - pointer {}", path.display());
2436    paths::normalize_path(&path)
2437}
2438
2439fn find_root_iter<'a>(
2440    manifest_path: &'a Path,
2441    gctx: &'a GlobalContext,
2442) -> impl Iterator<Item = PathBuf> + 'a {
2443    LookBehind::new(paths::ancestors(manifest_path, None).skip(2))
2444        .take_while(|path| !path.curr.ends_with("target/package"))
2445        // Don't walk across `CARGO_HOME` when we're looking for the
2446        // workspace root. Sometimes a package will be organized with
2447        // `CARGO_HOME` pointing inside of the workspace root or in the
2448        // current package, but we don't want to mistakenly try to put
2449        // crates.io crates into the workspace by accident.
2450        .take_while(|path| {
2451            if let Some(last) = path.last {
2452                gctx.home() != last
2453            } else {
2454                true
2455            }
2456        })
2457        .map(|path| path.curr.join("Cargo.toml"))
2458        .filter(|ances_manifest_path| ances_manifest_path.exists())
2459}
2460
2461struct LookBehindWindow<'a, T: ?Sized> {
2462    curr: &'a T,
2463    last: Option<&'a T>,
2464}
2465
2466struct LookBehind<'a, T: ?Sized, K: Iterator<Item = &'a T>> {
2467    iter: K,
2468    last: Option<&'a T>,
2469}
2470
2471impl<'a, T: ?Sized, K: Iterator<Item = &'a T>> LookBehind<'a, T, K> {
2472    fn new(items: K) -> Self {
2473        Self {
2474            iter: items,
2475            last: None,
2476        }
2477    }
2478}
2479
2480impl<'a, T: ?Sized, K: Iterator<Item = &'a T>> Iterator for LookBehind<'a, T, K> {
2481    type Item = LookBehindWindow<'a, T>;
2482
2483    fn next(&mut self) -> Option<Self::Item> {
2484        match self.iter.next() {
2485            None => None,
2486            Some(next) => {
2487                let last = self.last;
2488                self.last = Some(next);
2489                Some(LookBehindWindow { curr: next, last })
2490            }
2491        }
2492    }
2493}