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 annotate_snippets::Level;
8use anyhow::{Context as _, anyhow, bail};
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,
21};
22use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
23use crate::lints::analyze_cargo_lints_table;
24use crate::lints::rules::blanket_hint_mostly_unused;
25use crate::lints::rules::check_im_a_teapot;
26use crate::lints::rules::implicit_minimum_version_req;
27use crate::ops;
28use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY, PathSource, SourceConfigMap};
29use crate::util::context::FeatureUnification;
30use crate::util::edit_distance;
31use crate::util::errors::{CargoResult, ManifestError};
32use crate::util::interning::InternedString;
33use crate::util::toml::{InheritableFields, read_manifest};
34use crate::util::{
35 Filesystem, GlobalContext, IntoUrl, context::CargoResolverConfig, context::ConfigRelativePath,
36 context::IncompatibleRustVersions,
37};
38
39use cargo_util::paths;
40use cargo_util::paths::normalize_path;
41use cargo_util_schemas::manifest;
42use cargo_util_schemas::manifest::RustVersion;
43use cargo_util_schemas::manifest::{TomlDependency, TomlProfiles};
44use pathdiff::diff_paths;
45
46#[derive(Debug)]
52pub struct Workspace<'gctx> {
53 gctx: &'gctx GlobalContext,
55
56 current_manifest: PathBuf,
60
61 packages: Packages<'gctx>,
64
65 root_manifest: Option<PathBuf>,
70
71 target_dir: Option<Filesystem>,
74
75 build_dir: Option<Filesystem>,
78
79 members: Vec<PathBuf>,
83 member_ids: HashSet<PackageId>,
85
86 default_members: Vec<PathBuf>,
97
98 is_ephemeral: bool,
101
102 require_optional_deps: bool,
107
108 loaded_packages: RefCell<HashMap<PathBuf, Package>>,
111
112 ignore_lock: bool,
115
116 requested_lockfile_path: Option<PathBuf>,
118
119 resolve_behavior: ResolveBehavior,
121 resolve_honors_rust_version: bool,
125 resolve_feature_unification: FeatureUnification,
127 resolve_publish_time: Option<jiff::Timestamp>,
129 custom_metadata: Option<toml::Value>,
131
132 local_overlays: HashMap<SourceId, PathBuf>,
134}
135
136#[derive(Debug)]
139struct Packages<'gctx> {
140 gctx: &'gctx GlobalContext,
141 packages: HashMap<PathBuf, MaybePackage>,
142}
143
144#[derive(Debug)]
145pub enum MaybePackage {
146 Package(Package),
147 Virtual(VirtualManifest),
148}
149
150#[derive(Debug, Clone)]
152pub enum WorkspaceConfig {
153 Root(WorkspaceRootConfig),
156
157 Member { root: Option<String> },
160}
161
162impl WorkspaceConfig {
163 pub fn inheritable(&self) -> Option<&InheritableFields> {
164 match self {
165 WorkspaceConfig::Root(root) => Some(&root.inheritable_fields),
166 WorkspaceConfig::Member { .. } => None,
167 }
168 }
169
170 fn get_ws_root(&self, self_path: &Path, look_from: &Path) -> Option<PathBuf> {
178 match self {
179 WorkspaceConfig::Root(ances_root_config) => {
180 debug!("find_root - found a root checking exclusion");
181 if !ances_root_config.is_excluded(look_from) {
182 debug!("find_root - found!");
183 Some(self_path.to_owned())
184 } else {
185 None
186 }
187 }
188 WorkspaceConfig::Member {
189 root: Some(path_to_root),
190 } => {
191 debug!("find_root - found pointer");
192 Some(read_root_pointer(self_path, path_to_root))
193 }
194 WorkspaceConfig::Member { .. } => None,
195 }
196 }
197}
198
199#[derive(Debug, Clone)]
204pub struct WorkspaceRootConfig {
205 root_dir: PathBuf,
206 members: Option<Vec<String>>,
207 default_members: Option<Vec<String>>,
208 exclude: Vec<String>,
209 inheritable_fields: InheritableFields,
210 custom_metadata: Option<toml::Value>,
211}
212
213impl<'gctx> Workspace<'gctx> {
214 pub fn new(manifest_path: &Path, gctx: &'gctx GlobalContext) -> CargoResult<Workspace<'gctx>> {
221 let mut ws = Workspace::new_default(manifest_path.to_path_buf(), gctx);
222
223 if manifest_path.is_relative() {
224 bail!(
225 "manifest_path:{:?} is not an absolute path. Please provide an absolute path.",
226 manifest_path
227 )
228 } else {
229 ws.root_manifest = ws.find_root(manifest_path)?;
230 }
231
232 ws.target_dir = gctx.target_dir()?;
233 ws.build_dir = gctx.build_dir(ws.root_manifest())?;
234
235 ws.custom_metadata = ws
236 .load_workspace_config()?
237 .and_then(|cfg| cfg.custom_metadata);
238 ws.find_members()?;
239 ws.set_resolve_behavior()?;
240 ws.validate()?;
241 Ok(ws)
242 }
243
244 fn new_default(current_manifest: PathBuf, gctx: &'gctx GlobalContext) -> Workspace<'gctx> {
245 Workspace {
246 gctx,
247 current_manifest,
248 packages: Packages {
249 gctx,
250 packages: HashMap::new(),
251 },
252 root_manifest: None,
253 target_dir: None,
254 build_dir: None,
255 members: Vec::new(),
256 member_ids: HashSet::new(),
257 default_members: Vec::new(),
258 is_ephemeral: false,
259 require_optional_deps: true,
260 loaded_packages: RefCell::new(HashMap::new()),
261 ignore_lock: false,
262 requested_lockfile_path: None,
263 resolve_behavior: ResolveBehavior::V1,
264 resolve_honors_rust_version: false,
265 resolve_feature_unification: FeatureUnification::Selected,
266 resolve_publish_time: None,
267 custom_metadata: None,
268 local_overlays: HashMap::new(),
269 }
270 }
271
272 pub fn ephemeral(
282 package: Package,
283 gctx: &'gctx GlobalContext,
284 target_dir: Option<Filesystem>,
285 require_optional_deps: bool,
286 ) -> CargoResult<Workspace<'gctx>> {
287 let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), gctx);
288 ws.is_ephemeral = true;
289 ws.require_optional_deps = require_optional_deps;
290 let id = package.package_id();
291 let package = MaybePackage::Package(package);
292 ws.packages
293 .packages
294 .insert(ws.current_manifest.clone(), package);
295 ws.target_dir = if let Some(dir) = target_dir {
296 Some(dir)
297 } else {
298 ws.gctx.target_dir()?
299 };
300 ws.build_dir = ws.target_dir.clone();
301 ws.members.push(ws.current_manifest.clone());
302 ws.member_ids.insert(id);
303 ws.default_members.push(ws.current_manifest.clone());
304 ws.set_resolve_behavior()?;
305 Ok(ws)
306 }
307
308 pub fn reload(&self, gctx: &'gctx GlobalContext) -> CargoResult<Workspace<'gctx>> {
313 let mut ws = Workspace::new(&self.current_manifest, gctx)?;
314 ws.set_resolve_honors_rust_version(Some(self.resolve_honors_rust_version));
315 ws.set_resolve_feature_unification(self.resolve_feature_unification);
316 ws.set_requested_lockfile_path(self.requested_lockfile_path.clone());
317 Ok(ws)
318 }
319
320 fn set_resolve_behavior(&mut self) -> CargoResult<()> {
321 self.resolve_behavior = match self.root_maybe() {
326 MaybePackage::Package(p) => p
327 .manifest()
328 .resolve_behavior()
329 .unwrap_or_else(|| p.manifest().edition().default_resolve_behavior()),
330 MaybePackage::Virtual(vm) => vm.resolve_behavior().unwrap_or(ResolveBehavior::V1),
331 };
332
333 match self.resolve_behavior() {
334 ResolveBehavior::V1 | ResolveBehavior::V2 => {}
335 ResolveBehavior::V3 => {
336 if self.resolve_behavior == ResolveBehavior::V3 {
337 self.resolve_honors_rust_version = true;
338 }
339 }
340 }
341 let config = self.gctx().get::<CargoResolverConfig>("resolver")?;
342 if let Some(incompatible_rust_versions) = config.incompatible_rust_versions {
343 self.resolve_honors_rust_version =
344 incompatible_rust_versions == IncompatibleRustVersions::Fallback;
345 }
346 if self.gctx().cli_unstable().feature_unification {
347 self.resolve_feature_unification = config
348 .feature_unification
349 .unwrap_or(FeatureUnification::Selected);
350 } else if config.feature_unification.is_some() {
351 self.gctx()
352 .shell()
353 .warn("ignoring `resolver.feature-unification` without `-Zfeature-unification`")?;
354 };
355
356 Ok(())
357 }
358
359 pub fn current(&self) -> CargoResult<&Package> {
365 let pkg = self.current_opt().ok_or_else(|| {
366 anyhow::format_err!(
367 "manifest path `{}` is a virtual manifest, but this \
368 command requires running against an actual package in \
369 this workspace",
370 self.current_manifest.display()
371 )
372 })?;
373 Ok(pkg)
374 }
375
376 pub fn current_mut(&mut self) -> CargoResult<&mut Package> {
377 let cm = self.current_manifest.clone();
378 let pkg = self.current_opt_mut().ok_or_else(|| {
379 anyhow::format_err!(
380 "manifest path `{}` is a virtual manifest, but this \
381 command requires running against an actual package in \
382 this workspace",
383 cm.display()
384 )
385 })?;
386 Ok(pkg)
387 }
388
389 pub fn current_opt(&self) -> Option<&Package> {
390 match *self.packages.get(&self.current_manifest) {
391 MaybePackage::Package(ref p) => Some(p),
392 MaybePackage::Virtual(..) => None,
393 }
394 }
395
396 pub fn current_opt_mut(&mut self) -> Option<&mut Package> {
397 match *self.packages.get_mut(&self.current_manifest) {
398 MaybePackage::Package(ref mut p) => Some(p),
399 MaybePackage::Virtual(..) => None,
400 }
401 }
402
403 pub fn is_virtual(&self) -> bool {
404 match *self.packages.get(&self.current_manifest) {
405 MaybePackage::Package(..) => false,
406 MaybePackage::Virtual(..) => true,
407 }
408 }
409
410 pub fn gctx(&self) -> &'gctx GlobalContext {
412 self.gctx
413 }
414
415 pub fn profiles(&self) -> Option<&TomlProfiles> {
416 self.root_maybe().profiles()
417 }
418
419 pub fn root(&self) -> &Path {
424 self.root_manifest().parent().unwrap()
425 }
426
427 pub fn root_manifest(&self) -> &Path {
430 self.root_manifest
431 .as_ref()
432 .unwrap_or(&self.current_manifest)
433 }
434
435 pub fn root_maybe(&self) -> &MaybePackage {
437 self.packages.get(self.root_manifest())
438 }
439
440 pub fn target_dir(&self) -> Filesystem {
441 self.target_dir
442 .clone()
443 .unwrap_or_else(|| self.default_target_dir())
444 }
445
446 pub fn build_dir(&self) -> Filesystem {
447 self.build_dir
448 .clone()
449 .or_else(|| self.target_dir.clone())
450 .unwrap_or_else(|| self.default_build_dir())
451 }
452
453 fn default_target_dir(&self) -> Filesystem {
454 if self.root_maybe().is_embedded() {
455 self.build_dir().join("target")
456 } else {
457 Filesystem::new(self.root().join("target"))
458 }
459 }
460
461 fn default_build_dir(&self) -> Filesystem {
462 if self.root_maybe().is_embedded() {
463 let default = ConfigRelativePath::new(
464 "{cargo-cache-home}/build/{workspace-path-hash}"
465 .to_owned()
466 .into(),
467 );
468 self.gctx()
469 .custom_build_dir(&default, self.root_manifest())
470 .expect("template is correct")
471 } else {
472 self.default_target_dir()
473 }
474 }
475
476 pub fn root_replace(&self) -> &[(PackageIdSpec, Dependency)] {
480 match self.root_maybe() {
481 MaybePackage::Package(p) => p.manifest().replace(),
482 MaybePackage::Virtual(vm) => vm.replace(),
483 }
484 }
485
486 fn config_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
487 let config_patch: Option<
488 BTreeMap<String, BTreeMap<String, TomlDependency<ConfigRelativePath>>>,
489 > = self.gctx.get("patch")?;
490
491 let source = SourceId::for_manifest_path(self.root_manifest())?;
492
493 let mut warnings = Vec::new();
494
495 let mut patch = HashMap::new();
496 for (url, deps) in config_patch.into_iter().flatten() {
497 let url = match &url[..] {
498 CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
499 url => self
500 .gctx
501 .get_registry_index(url)
502 .or_else(|_| url.into_url())
503 .with_context(|| {
504 format!("[patch] entry `{}` should be a URL or registry name", url)
505 })?,
506 };
507 patch.insert(
508 url,
509 deps.iter()
510 .map(|(name, dep)| {
511 crate::util::toml::to_dependency(
512 dep,
513 name,
514 source,
515 self.gctx,
516 &mut warnings,
517 None,
518 Path::new("unused-relative-path"),
521 None,
522 )
523 })
524 .collect::<CargoResult<Vec<_>>>()?,
525 );
526 }
527
528 for message in warnings {
529 self.gctx
530 .shell()
531 .warn(format!("[patch] in cargo config: {}", message))?
532 }
533
534 Ok(patch)
535 }
536
537 pub fn root_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
541 let from_manifest = match self.root_maybe() {
542 MaybePackage::Package(p) => p.manifest().patch(),
543 MaybePackage::Virtual(vm) => vm.patch(),
544 };
545
546 let from_config = self.config_patch()?;
547 if from_config.is_empty() {
548 return Ok(from_manifest.clone());
549 }
550 if from_manifest.is_empty() {
551 return Ok(from_config);
552 }
553
554 let mut combined = from_config;
557 for (url, deps_from_manifest) in from_manifest {
558 if let Some(deps_from_config) = combined.get_mut(url) {
559 let mut from_manifest_pruned = deps_from_manifest.clone();
562 for dep_from_config in &mut *deps_from_config {
563 if let Some(i) = from_manifest_pruned.iter().position(|dep_from_manifest| {
564 dep_from_config.name_in_toml() == dep_from_manifest.name_in_toml()
566 }) {
567 from_manifest_pruned.swap_remove(i);
568 }
569 }
570 deps_from_config.extend(from_manifest_pruned);
572 } else {
573 combined.insert(url.clone(), deps_from_manifest.clone());
574 }
575 }
576 Ok(combined)
577 }
578
579 pub fn members(&self) -> impl Iterator<Item = &Package> {
581 let packages = &self.packages;
582 self.members
583 .iter()
584 .filter_map(move |path| match packages.get(path) {
585 MaybePackage::Package(p) => Some(p),
586 _ => None,
587 })
588 }
589
590 pub fn members_mut(&mut self) -> impl Iterator<Item = &mut Package> {
592 let packages = &mut self.packages.packages;
593 let members: HashSet<_> = self.members.iter().map(|path| path).collect();
594
595 packages.iter_mut().filter_map(move |(path, package)| {
596 if members.contains(path) {
597 if let MaybePackage::Package(p) = package {
598 return Some(p);
599 }
600 }
601
602 None
603 })
604 }
605
606 pub fn default_members<'a>(&'a self) -> impl Iterator<Item = &'a Package> {
608 let packages = &self.packages;
609 self.default_members
610 .iter()
611 .filter_map(move |path| match packages.get(path) {
612 MaybePackage::Package(p) => Some(p),
613 _ => None,
614 })
615 }
616
617 pub fn default_members_mut(&mut self) -> impl Iterator<Item = &mut Package> {
619 let packages = &mut self.packages.packages;
620 let members: HashSet<_> = self
621 .default_members
622 .iter()
623 .map(|path| path.parent().unwrap().to_owned())
624 .collect();
625
626 packages.iter_mut().filter_map(move |(path, package)| {
627 if members.contains(path) {
628 if let MaybePackage::Package(p) = package {
629 return Some(p);
630 }
631 }
632
633 None
634 })
635 }
636
637 pub fn is_member(&self, pkg: &Package) -> bool {
639 self.member_ids.contains(&pkg.package_id())
640 }
641
642 pub fn is_member_id(&self, package_id: PackageId) -> bool {
644 self.member_ids.contains(&package_id)
645 }
646
647 pub fn is_ephemeral(&self) -> bool {
648 self.is_ephemeral
649 }
650
651 pub fn require_optional_deps(&self) -> bool {
652 self.require_optional_deps
653 }
654
655 pub fn set_require_optional_deps(
656 &mut self,
657 require_optional_deps: bool,
658 ) -> &mut Workspace<'gctx> {
659 self.require_optional_deps = require_optional_deps;
660 self
661 }
662
663 pub fn ignore_lock(&self) -> bool {
664 self.ignore_lock
665 }
666
667 pub fn set_ignore_lock(&mut self, ignore_lock: bool) -> &mut Workspace<'gctx> {
668 self.ignore_lock = ignore_lock;
669 self
670 }
671
672 pub fn lock_root(&self) -> Filesystem {
674 if let Some(requested) = self.requested_lockfile_path.as_ref() {
675 return Filesystem::new(
676 requested
677 .parent()
678 .expect("Lockfile path can't be root")
679 .to_owned(),
680 );
681 }
682 self.default_lock_root()
683 }
684
685 fn default_lock_root(&self) -> Filesystem {
686 if self.root_maybe().is_embedded() {
687 self.build_dir()
688 } else {
689 Filesystem::new(self.root().to_owned())
690 }
691 }
692
693 pub fn set_requested_lockfile_path(&mut self, path: Option<PathBuf>) {
694 self.requested_lockfile_path = path;
695 }
696
697 pub fn requested_lockfile_path(&self) -> Option<&Path> {
698 self.requested_lockfile_path.as_deref()
699 }
700
701 pub fn lowest_rust_version(&self) -> Option<&RustVersion> {
704 self.members().filter_map(|pkg| pkg.rust_version()).min()
705 }
706
707 pub fn set_resolve_honors_rust_version(&mut self, honor_rust_version: Option<bool>) {
708 if let Some(honor_rust_version) = honor_rust_version {
709 self.resolve_honors_rust_version = honor_rust_version;
710 }
711 }
712
713 pub fn resolve_honors_rust_version(&self) -> bool {
714 self.resolve_honors_rust_version
715 }
716
717 pub fn set_resolve_feature_unification(&mut self, feature_unification: FeatureUnification) {
718 self.resolve_feature_unification = feature_unification;
719 }
720
721 pub fn resolve_feature_unification(&self) -> FeatureUnification {
722 self.resolve_feature_unification
723 }
724
725 pub fn set_resolve_publish_time(&mut self, publish_time: jiff::Timestamp) {
726 self.resolve_publish_time = Some(publish_time);
727 }
728
729 pub fn resolve_publish_time(&self) -> Option<jiff::Timestamp> {
730 self.resolve_publish_time
731 }
732
733 pub fn custom_metadata(&self) -> Option<&toml::Value> {
734 self.custom_metadata.as_ref()
735 }
736
737 pub fn load_workspace_config(&mut self) -> CargoResult<Option<WorkspaceRootConfig>> {
738 if let Some(root_path) = &self.root_manifest {
741 let root_package = self.packages.load(root_path)?;
742 match root_package.workspace_config() {
743 WorkspaceConfig::Root(root_config) => {
744 return Ok(Some(root_config.clone()));
745 }
746
747 _ => bail!(
748 "root of a workspace inferred but wasn't a root: {}",
749 root_path.display()
750 ),
751 }
752 }
753
754 Ok(None)
755 }
756
757 fn find_root(&mut self, manifest_path: &Path) -> CargoResult<Option<PathBuf>> {
767 let current = self.packages.load(manifest_path)?;
768 match current
769 .workspace_config()
770 .get_ws_root(manifest_path, manifest_path)
771 {
772 Some(root_path) => {
773 debug!("find_root - is root {}", manifest_path.display());
774 Ok(Some(root_path))
775 }
776 None => find_workspace_root_with_loader(manifest_path, self.gctx, |self_path| {
777 Ok(self
778 .packages
779 .load(self_path)?
780 .workspace_config()
781 .get_ws_root(self_path, manifest_path))
782 }),
783 }
784 }
785
786 #[tracing::instrument(skip_all)]
794 fn find_members(&mut self) -> CargoResult<()> {
795 let Some(workspace_config) = self.load_workspace_config()? else {
796 debug!("find_members - only me as a member");
797 self.members.push(self.current_manifest.clone());
798 self.default_members.push(self.current_manifest.clone());
799 if let Ok(pkg) = self.current() {
800 let id = pkg.package_id();
801 self.member_ids.insert(id);
802 }
803 return Ok(());
804 };
805
806 let root_manifest_path = self.root_manifest.clone().unwrap();
808
809 let members_paths = workspace_config
810 .members_paths(workspace_config.members.as_deref().unwrap_or_default())?;
811 let default_members_paths = if root_manifest_path == self.current_manifest {
812 if let Some(ref default) = workspace_config.default_members {
813 Some(workspace_config.members_paths(default)?)
814 } else {
815 None
816 }
817 } else {
818 None
819 };
820
821 for (path, glob) in &members_paths {
822 self.find_path_deps(&path.join("Cargo.toml"), &root_manifest_path, false)
823 .with_context(|| {
824 format!(
825 "failed to load manifest for workspace member `{}`\n\
826 referenced{} by workspace at `{}`",
827 path.display(),
828 glob.map(|g| format!(" via `{g}`")).unwrap_or_default(),
829 root_manifest_path.display(),
830 )
831 })?;
832 }
833
834 self.find_path_deps(&root_manifest_path, &root_manifest_path, false)?;
835
836 if let Some(default) = default_members_paths {
837 for (path, default_member_glob) in default {
838 let normalized_path = paths::normalize_path(&path);
839 let manifest_path = normalized_path.join("Cargo.toml");
840 if !self.members.contains(&manifest_path) {
841 let exclude = members_paths.iter().any(|(m, _)| *m == normalized_path)
848 && workspace_config.is_excluded(&normalized_path);
849 if exclude {
850 continue;
851 }
852 bail!(
853 "package `{}` is listed in default-members{} but is not a member\n\
854 for workspace at `{}`.",
855 path.display(),
856 default_member_glob
857 .map(|g| format!(" via `{g}`"))
858 .unwrap_or_default(),
859 root_manifest_path.display(),
860 )
861 }
862 self.default_members.push(manifest_path)
863 }
864 } else if self.is_virtual() {
865 self.default_members = self.members.clone()
866 } else {
867 self.default_members.push(self.current_manifest.clone())
868 }
869
870 Ok(())
871 }
872
873 fn find_path_deps(
874 &mut self,
875 manifest_path: &Path,
876 root_manifest: &Path,
877 is_path_dep: bool,
878 ) -> CargoResult<()> {
879 let manifest_path = paths::normalize_path(manifest_path);
880 if self.members.contains(&manifest_path) {
881 return Ok(());
882 }
883 if is_path_dep && self.root_maybe().is_embedded() {
884 return Ok(());
886 }
887 if is_path_dep
888 && !manifest_path.parent().unwrap().starts_with(self.root())
889 && self.find_root(&manifest_path)? != self.root_manifest
890 {
891 return Ok(());
894 }
895
896 if let WorkspaceConfig::Root(ref root_config) =
897 *self.packages.load(root_manifest)?.workspace_config()
898 {
899 if root_config.is_excluded(&manifest_path) {
900 return Ok(());
901 }
902 }
903
904 debug!("find_path_deps - {}", manifest_path.display());
905 self.members.push(manifest_path.clone());
906
907 let candidates = {
908 let pkg = match *self.packages.load(&manifest_path)? {
909 MaybePackage::Package(ref p) => p,
910 MaybePackage::Virtual(_) => return Ok(()),
911 };
912 self.member_ids.insert(pkg.package_id());
913 pkg.dependencies()
914 .iter()
915 .map(|d| (d.source_id(), d.package_name()))
916 .filter(|(s, _)| s.is_path())
917 .filter_map(|(s, n)| s.url().to_file_path().ok().map(|p| (p, n)))
918 .map(|(p, n)| (p.join("Cargo.toml"), n))
919 .collect::<Vec<_>>()
920 };
921 for (path, name) in candidates {
922 self.find_path_deps(&path, root_manifest, true)
923 .with_context(|| format!("failed to load manifest for dependency `{}`", name))
924 .map_err(|err| ManifestError::new(err, manifest_path.clone()))?;
925 }
926 Ok(())
927 }
928
929 pub fn unstable_features(&self) -> &Features {
931 self.root_maybe().unstable_features()
932 }
933
934 pub fn resolve_behavior(&self) -> ResolveBehavior {
935 self.resolve_behavior
936 }
937
938 pub fn allows_new_cli_feature_behavior(&self) -> bool {
946 self.is_virtual()
947 || match self.resolve_behavior() {
948 ResolveBehavior::V1 => false,
949 ResolveBehavior::V2 | ResolveBehavior::V3 => true,
950 }
951 }
952
953 #[tracing::instrument(skip_all)]
959 fn validate(&mut self) -> CargoResult<()> {
960 if self.root_manifest.is_none() {
962 return Ok(());
963 }
964
965 self.validate_unique_names()?;
966 self.validate_workspace_roots()?;
967 self.validate_members()?;
968 self.error_if_manifest_not_in_members()?;
969 self.validate_manifest()
970 }
971
972 fn validate_unique_names(&self) -> CargoResult<()> {
973 let mut names = BTreeMap::new();
974 for member in self.members.iter() {
975 let package = self.packages.get(member);
976 let name = match *package {
977 MaybePackage::Package(ref p) => p.name(),
978 MaybePackage::Virtual(_) => continue,
979 };
980 if let Some(prev) = names.insert(name, member) {
981 bail!(
982 "two packages named `{}` in this workspace:\n\
983 - {}\n\
984 - {}",
985 name,
986 prev.display(),
987 member.display()
988 );
989 }
990 }
991 Ok(())
992 }
993
994 fn validate_workspace_roots(&self) -> CargoResult<()> {
995 let roots: Vec<PathBuf> = self
996 .members
997 .iter()
998 .filter(|&member| {
999 let config = self.packages.get(member).workspace_config();
1000 matches!(config, WorkspaceConfig::Root(_))
1001 })
1002 .map(|member| member.parent().unwrap().to_path_buf())
1003 .collect();
1004 match roots.len() {
1005 1 => Ok(()),
1006 0 => bail!(
1007 "`package.workspace` configuration points to a crate \
1008 which is not configured with [workspace]: \n\
1009 configuration at: {}\n\
1010 points to: {}",
1011 self.current_manifest.display(),
1012 self.root_manifest.as_ref().unwrap().display()
1013 ),
1014 _ => {
1015 bail!(
1016 "multiple workspace roots found in the same workspace:\n{}",
1017 roots
1018 .iter()
1019 .map(|r| format!(" {}", r.display()))
1020 .collect::<Vec<_>>()
1021 .join("\n")
1022 );
1023 }
1024 }
1025 }
1026
1027 #[tracing::instrument(skip_all)]
1028 fn validate_members(&mut self) -> CargoResult<()> {
1029 for member in self.members.clone() {
1030 let root = self.find_root(&member)?;
1031 if root == self.root_manifest {
1032 continue;
1033 }
1034
1035 match root {
1036 Some(root) => {
1037 bail!(
1038 "package `{}` is a member of the wrong workspace\n\
1039 expected: {}\n\
1040 actual: {}",
1041 member.display(),
1042 self.root_manifest.as_ref().unwrap().display(),
1043 root.display()
1044 );
1045 }
1046 None => {
1047 bail!(
1048 "workspace member `{}` is not hierarchically below \
1049 the workspace root `{}`",
1050 member.display(),
1051 self.root_manifest.as_ref().unwrap().display()
1052 );
1053 }
1054 }
1055 }
1056 Ok(())
1057 }
1058
1059 fn error_if_manifest_not_in_members(&mut self) -> CargoResult<()> {
1060 if self.members.contains(&self.current_manifest) {
1061 return Ok(());
1062 }
1063
1064 let root = self.root_manifest.as_ref().unwrap();
1065 let root_dir = root.parent().unwrap();
1066 let current_dir = self.current_manifest.parent().unwrap();
1067 let root_pkg = self.packages.get(root);
1068
1069 let members_msg = match current_dir.strip_prefix(root_dir) {
1071 Ok(rel) => format!(
1072 "this may be fixable by adding `{}` to the \
1073 `workspace.members` array of the manifest \
1074 located at: {}",
1075 rel.display(),
1076 root.display()
1077 ),
1078 Err(_) => format!(
1079 "this may be fixable by adding a member to \
1080 the `workspace.members` array of the \
1081 manifest located at: {}",
1082 root.display()
1083 ),
1084 };
1085 let extra = match *root_pkg {
1086 MaybePackage::Virtual(_) => members_msg,
1087 MaybePackage::Package(ref p) => {
1088 let has_members_list = match *p.manifest().workspace_config() {
1089 WorkspaceConfig::Root(ref root_config) => root_config.has_members_list(),
1090 WorkspaceConfig::Member { .. } => unreachable!(),
1091 };
1092 if !has_members_list {
1093 format!(
1094 "this may be fixable by ensuring that this \
1095 crate is depended on by the workspace \
1096 root: {}",
1097 root.display()
1098 )
1099 } else {
1100 members_msg
1101 }
1102 }
1103 };
1104 bail!(
1105 "current package believes it's in a workspace when it's not:\n\
1106 current: {}\n\
1107 workspace: {}\n\n{}\n\
1108 Alternatively, to keep it out of the workspace, add the package \
1109 to the `workspace.exclude` array, or add an empty `[workspace]` \
1110 table to the package's manifest.",
1111 self.current_manifest.display(),
1112 root.display(),
1113 extra
1114 );
1115 }
1116
1117 fn validate_manifest(&mut self) -> CargoResult<()> {
1118 if let Some(ref root_manifest) = self.root_manifest {
1119 for pkg in self
1120 .members()
1121 .filter(|p| p.manifest_path() != root_manifest)
1122 {
1123 let manifest = pkg.manifest();
1124 let emit_warning = |what| -> CargoResult<()> {
1125 let msg = format!(
1126 "{} for the non root package will be ignored, \
1127 specify {} at the workspace root:\n\
1128 package: {}\n\
1129 workspace: {}",
1130 what,
1131 what,
1132 pkg.manifest_path().display(),
1133 root_manifest.display(),
1134 );
1135 self.gctx.shell().warn(&msg)
1136 };
1137 if manifest.normalized_toml().has_profiles() {
1138 emit_warning("profiles")?;
1139 }
1140 if !manifest.replace().is_empty() {
1141 emit_warning("replace")?;
1142 }
1143 if !manifest.patch().is_empty() {
1144 emit_warning("patch")?;
1145 }
1146 if let Some(behavior) = manifest.resolve_behavior() {
1147 if behavior != self.resolve_behavior {
1148 emit_warning("resolver")?;
1150 }
1151 }
1152 }
1153 if let MaybePackage::Virtual(vm) = self.root_maybe() {
1154 if vm.resolve_behavior().is_none() {
1155 if let Some(edition) = self
1156 .members()
1157 .filter(|p| p.manifest_path() != root_manifest)
1158 .map(|p| p.manifest().edition())
1159 .filter(|&e| e >= Edition::Edition2021)
1160 .max()
1161 {
1162 let resolver = edition.default_resolve_behavior().to_manifest();
1163 let report = &[Level::WARNING
1164 .primary_title(format!(
1165 "virtual workspace defaulting to `resolver = \"1\"` despite one or more workspace members being on edition {edition} which implies `resolver = \"{resolver}\"`"
1166 ))
1167 .elements([
1168 Level::NOTE.message("to keep the current resolver, specify `workspace.resolver = \"1\"` in the workspace root's manifest"),
1169 Level::NOTE.message(
1170 format!("to use the edition {edition} resolver, specify `workspace.resolver = \"{resolver}\"` in the workspace root's manifest"),
1171 ),
1172 Level::NOTE.message("for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions"),
1173 ])];
1174 self.gctx.shell().print_report(report, false)?;
1175 }
1176 }
1177 }
1178 }
1179 Ok(())
1180 }
1181
1182 pub fn load(&self, manifest_path: &Path) -> CargoResult<Package> {
1183 match self.packages.maybe_get(manifest_path) {
1184 Some(MaybePackage::Package(p)) => return Ok(p.clone()),
1185 Some(&MaybePackage::Virtual(_)) => bail!("cannot load workspace root"),
1186 None => {}
1187 }
1188
1189 let mut loaded = self.loaded_packages.borrow_mut();
1190 if let Some(p) = loaded.get(manifest_path).cloned() {
1191 return Ok(p);
1192 }
1193 let source_id = SourceId::for_manifest_path(manifest_path)?;
1194 let package = ops::read_package(manifest_path, source_id, self.gctx)?;
1195 loaded.insert(manifest_path.to_path_buf(), package.clone());
1196 Ok(package)
1197 }
1198
1199 pub fn preload(&self, registry: &mut PackageRegistry<'gctx>) {
1206 if self.is_ephemeral {
1212 return;
1213 }
1214
1215 for pkg in self.packages.packages.values() {
1216 let pkg = match *pkg {
1217 MaybePackage::Package(ref p) => p.clone(),
1218 MaybePackage::Virtual(_) => continue,
1219 };
1220 let src = PathSource::preload_with(pkg, self.gctx);
1221 registry.add_preloaded(Box::new(src));
1222 }
1223 }
1224
1225 pub fn emit_warnings(&self) -> CargoResult<()> {
1226 let mut first_emitted_error = None;
1227
1228 if let Err(e) = self.emit_ws_lints() {
1229 first_emitted_error = Some(e);
1230 }
1231
1232 for (path, maybe_pkg) in &self.packages.packages {
1233 if let MaybePackage::Package(pkg) = maybe_pkg {
1234 if let Err(e) = self.emit_pkg_lints(pkg, &path)
1235 && first_emitted_error.is_none()
1236 {
1237 first_emitted_error = Some(e);
1238 }
1239 }
1240 let warnings = match maybe_pkg {
1241 MaybePackage::Package(pkg) => pkg.manifest().warnings().warnings(),
1242 MaybePackage::Virtual(vm) => vm.warnings().warnings(),
1243 };
1244 for warning in warnings {
1245 if warning.is_critical {
1246 let err = anyhow::format_err!("{}", warning.message);
1247 let cx =
1248 anyhow::format_err!("failed to parse manifest at `{}`", path.display());
1249 if first_emitted_error.is_none() {
1250 first_emitted_error = Some(err.context(cx));
1251 }
1252 } else {
1253 let msg = if self.root_manifest.is_none() {
1254 warning.message.to_string()
1255 } else {
1256 format!("{}: {}", path.display(), warning.message)
1259 };
1260 self.gctx.shell().warn(msg)?
1261 }
1262 }
1263 }
1264
1265 if let Some(error) = first_emitted_error {
1266 Err(error)
1267 } else {
1268 Ok(())
1269 }
1270 }
1271
1272 pub fn emit_pkg_lints(&self, pkg: &Package, path: &Path) -> CargoResult<()> {
1273 let toml_lints = pkg
1274 .manifest()
1275 .normalized_toml()
1276 .lints
1277 .clone()
1278 .map(|lints| lints.lints)
1279 .unwrap_or(manifest::TomlLints::default());
1280 let cargo_lints = toml_lints
1281 .get("cargo")
1282 .cloned()
1283 .unwrap_or(manifest::TomlToolLints::default());
1284
1285 if self.gctx.cli_unstable().cargo_lints {
1286 let mut verify_error_count = 0;
1287
1288 analyze_cargo_lints_table(
1289 pkg.into(),
1290 &path,
1291 &cargo_lints,
1292 &mut verify_error_count,
1293 self.gctx,
1294 )?;
1295
1296 if verify_error_count > 0 {
1297 let plural = if verify_error_count == 1 { "" } else { "s" };
1298 bail!("encountered {verify_error_count} error{plural} while verifying lints")
1299 }
1300
1301 let mut run_error_count = 0;
1302
1303 check_im_a_teapot(pkg, &path, &cargo_lints, &mut run_error_count, self.gctx)?;
1304 implicit_minimum_version_req(
1305 pkg.into(),
1306 &path,
1307 &cargo_lints,
1308 &mut run_error_count,
1309 self.gctx,
1310 )?;
1311
1312 if run_error_count > 0 {
1313 let plural = if run_error_count == 1 { "" } else { "s" };
1314 bail!("encountered {run_error_count} error{plural} while running lints")
1315 }
1316 }
1317
1318 Ok(())
1319 }
1320
1321 pub fn emit_ws_lints(&self) -> CargoResult<()> {
1322 let mut run_error_count = 0;
1323
1324 let cargo_lints = match self.root_maybe() {
1325 MaybePackage::Package(pkg) => {
1326 let toml = pkg.manifest().normalized_toml();
1327 if let Some(ws) = &toml.workspace {
1328 ws.lints.as_ref()
1329 } else {
1330 toml.lints.as_ref().map(|l| &l.lints)
1331 }
1332 }
1333 MaybePackage::Virtual(vm) => vm
1334 .normalized_toml()
1335 .workspace
1336 .as_ref()
1337 .unwrap()
1338 .lints
1339 .as_ref(),
1340 }
1341 .and_then(|t| t.get("cargo"))
1342 .cloned()
1343 .unwrap_or(manifest::TomlToolLints::default());
1344
1345 if self.gctx.cli_unstable().cargo_lints {
1346 let mut verify_error_count = 0;
1347
1348 analyze_cargo_lints_table(
1349 self.root_maybe().into(),
1350 self.root_manifest(),
1351 &cargo_lints,
1352 &mut verify_error_count,
1353 self.gctx,
1354 )?;
1355
1356 if verify_error_count > 0 {
1357 let plural = if verify_error_count == 1 { "" } else { "s" };
1358 bail!("encountered {verify_error_count} error{plural} while verifying lints")
1359 }
1360
1361 implicit_minimum_version_req(
1362 self.root_maybe().into(),
1363 self.root_manifest(),
1364 &cargo_lints,
1365 &mut run_error_count,
1366 self.gctx,
1367 )?;
1368 }
1369
1370 if self.gctx.cli_unstable().profile_hint_mostly_unused {
1374 blanket_hint_mostly_unused(
1375 self.root_maybe(),
1376 self.root_manifest(),
1377 &cargo_lints,
1378 &mut run_error_count,
1379 self.gctx,
1380 )?;
1381 }
1382
1383 if run_error_count > 0 {
1384 let plural = if run_error_count == 1 { "" } else { "s" };
1385 bail!("encountered {run_error_count} error{plural} while running lints")
1386 } else {
1387 Ok(())
1388 }
1389 }
1390
1391 pub fn set_target_dir(&mut self, target_dir: Filesystem) {
1392 self.target_dir = Some(target_dir);
1393 }
1394
1395 pub fn members_with_features(
1403 &self,
1404 specs: &[PackageIdSpec],
1405 cli_features: &CliFeatures,
1406 ) -> CargoResult<Vec<(&Package, CliFeatures)>> {
1407 assert!(
1408 !specs.is_empty() || cli_features.all_features,
1409 "no specs requires all_features"
1410 );
1411 if specs.is_empty() {
1412 return Ok(self
1415 .members()
1416 .map(|m| (m, CliFeatures::new_all(true)))
1417 .collect());
1418 }
1419 if self.allows_new_cli_feature_behavior() {
1420 self.members_with_features_new(specs, cli_features)
1421 } else {
1422 Ok(self.members_with_features_old(specs, cli_features))
1423 }
1424 }
1425
1426 fn collect_matching_features(
1429 member: &Package,
1430 cli_features: &CliFeatures,
1431 found_features: &mut BTreeSet<FeatureValue>,
1432 ) -> CliFeatures {
1433 if cli_features.features.is_empty() {
1434 return cli_features.clone();
1435 }
1436
1437 let summary = member.summary();
1439
1440 let summary_features = summary.features();
1442
1443 let dependencies: BTreeMap<InternedString, &Dependency> = summary
1445 .dependencies()
1446 .iter()
1447 .map(|dep| (dep.name_in_toml(), dep))
1448 .collect();
1449
1450 let optional_dependency_names: BTreeSet<_> = dependencies
1452 .iter()
1453 .filter(|(_, dep)| dep.is_optional())
1454 .map(|(name, _)| name)
1455 .copied()
1456 .collect();
1457
1458 let mut features = BTreeSet::new();
1459
1460 let summary_or_opt_dependency_feature = |feature: &InternedString| -> bool {
1462 summary_features.contains_key(feature) || optional_dependency_names.contains(feature)
1463 };
1464
1465 for feature in cli_features.features.iter() {
1466 match feature {
1467 FeatureValue::Feature(f) => {
1468 if summary_or_opt_dependency_feature(f) {
1469 features.insert(feature.clone());
1471 found_features.insert(feature.clone());
1472 }
1473 }
1474 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1476 FeatureValue::DepFeature {
1477 dep_name,
1478 dep_feature,
1479 weak: _,
1480 } => {
1481 if dependencies.contains_key(dep_name) {
1482 features.insert(feature.clone());
1485 found_features.insert(feature.clone());
1486 } else if *dep_name == member.name()
1487 && summary_or_opt_dependency_feature(dep_feature)
1488 {
1489 features.insert(FeatureValue::Feature(*dep_feature));
1494 found_features.insert(feature.clone());
1495 }
1496 }
1497 }
1498 }
1499 CliFeatures {
1500 features: Rc::new(features),
1501 all_features: cli_features.all_features,
1502 uses_default_features: cli_features.uses_default_features,
1503 }
1504 }
1505
1506 fn missing_feature_spelling_suggestions(
1507 &self,
1508 selected_members: &[&Package],
1509 cli_features: &CliFeatures,
1510 found_features: &BTreeSet<FeatureValue>,
1511 ) -> Vec<String> {
1512 let mut summary_features: Vec<InternedString> = Default::default();
1514
1515 let mut dependencies_features: BTreeMap<InternedString, &[InternedString]> =
1517 Default::default();
1518
1519 let mut optional_dependency_names: Vec<InternedString> = Default::default();
1521
1522 let mut summary_features_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
1524 Default::default();
1525
1526 let mut optional_dependency_names_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
1528 Default::default();
1529
1530 for &member in selected_members {
1531 let summary = member.summary();
1533
1534 summary_features.extend(summary.features().keys());
1536 summary_features_per_member
1537 .insert(member, summary.features().keys().copied().collect());
1538
1539 let dependencies: BTreeMap<InternedString, &Dependency> = summary
1541 .dependencies()
1542 .iter()
1543 .map(|dep| (dep.name_in_toml(), dep))
1544 .collect();
1545
1546 dependencies_features.extend(
1547 dependencies
1548 .iter()
1549 .map(|(name, dep)| (*name, dep.features())),
1550 );
1551
1552 let optional_dependency_names_raw: BTreeSet<_> = dependencies
1554 .iter()
1555 .filter(|(_, dep)| dep.is_optional())
1556 .map(|(name, _)| name)
1557 .copied()
1558 .collect();
1559
1560 optional_dependency_names.extend(optional_dependency_names_raw.iter());
1561 optional_dependency_names_per_member.insert(member, optional_dependency_names_raw);
1562 }
1563
1564 let edit_distance_test = |a: InternedString, b: InternedString| {
1565 edit_distance(a.as_str(), b.as_str(), 3).is_some()
1566 };
1567
1568 cli_features
1569 .features
1570 .difference(found_features)
1571 .map(|feature| match feature {
1572 FeatureValue::Feature(typo) => {
1574 let summary_features = summary_features
1576 .iter()
1577 .filter(move |feature| edit_distance_test(**feature, *typo));
1578
1579 let optional_dependency_features = optional_dependency_names
1581 .iter()
1582 .filter(move |feature| edit_distance_test(**feature, *typo));
1583
1584 summary_features
1585 .chain(optional_dependency_features)
1586 .map(|s| s.to_string())
1587 .collect::<Vec<_>>()
1588 }
1589 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1590 FeatureValue::DepFeature {
1591 dep_name,
1592 dep_feature,
1593 weak: _,
1594 } => {
1595 let pkg_feat_similar = dependencies_features
1597 .iter()
1598 .filter(|(name, _)| edit_distance_test(**name, *dep_name))
1599 .map(|(name, features)| {
1600 (
1601 name,
1602 features
1603 .iter()
1604 .filter(|feature| edit_distance_test(**feature, *dep_feature))
1605 .collect::<Vec<_>>(),
1606 )
1607 })
1608 .map(|(name, features)| {
1609 features
1610 .into_iter()
1611 .map(move |feature| format!("{}/{}", name, feature))
1612 })
1613 .flatten();
1614
1615 let optional_dependency_features = optional_dependency_names_per_member
1617 .iter()
1618 .filter(|(package, _)| edit_distance_test(package.name(), *dep_name))
1619 .map(|(package, optional_dependencies)| {
1620 optional_dependencies
1621 .into_iter()
1622 .filter(|optional_dependency| {
1623 edit_distance_test(**optional_dependency, *dep_name)
1624 })
1625 .map(move |optional_dependency| {
1626 format!("{}/{}", package.name(), optional_dependency)
1627 })
1628 })
1629 .flatten();
1630
1631 let summary_features = summary_features_per_member
1633 .iter()
1634 .filter(|(package, _)| edit_distance_test(package.name(), *dep_name))
1635 .map(|(package, summary_features)| {
1636 summary_features
1637 .into_iter()
1638 .filter(|summary_feature| {
1639 edit_distance_test(**summary_feature, *dep_feature)
1640 })
1641 .map(move |summary_feature| {
1642 format!("{}/{}", package.name(), summary_feature)
1643 })
1644 })
1645 .flatten();
1646
1647 pkg_feat_similar
1648 .chain(optional_dependency_features)
1649 .chain(summary_features)
1650 .collect::<Vec<_>>()
1651 }
1652 })
1653 .map(|v| v.into_iter())
1654 .flatten()
1655 .unique()
1656 .filter(|element| {
1657 let feature = FeatureValue::new(element.into());
1658 !cli_features.features.contains(&feature) && !found_features.contains(&feature)
1659 })
1660 .sorted()
1661 .take(5)
1662 .collect()
1663 }
1664
1665 fn report_unknown_features_error(
1666 &self,
1667 specs: &[PackageIdSpec],
1668 cli_features: &CliFeatures,
1669 found_features: &BTreeSet<FeatureValue>,
1670 ) -> CargoResult<()> {
1671 let unknown: Vec<_> = cli_features
1672 .features
1673 .difference(found_features)
1674 .map(|feature| feature.to_string())
1675 .sorted()
1676 .collect();
1677
1678 let (selected_members, unselected_members): (Vec<_>, Vec<_>) = self
1679 .members()
1680 .partition(|member| specs.iter().any(|spec| spec.matches(member.package_id())));
1681
1682 let missing_packages_with_the_features = unselected_members
1683 .into_iter()
1684 .filter(|member| {
1685 unknown
1686 .iter()
1687 .any(|feature| member.summary().features().contains_key(&**feature))
1688 })
1689 .map(|m| m.name())
1690 .collect_vec();
1691
1692 let these_features = if unknown.len() == 1 {
1693 "this feature"
1694 } else {
1695 "these features"
1696 };
1697 let mut msg = if let [singular] = &selected_members[..] {
1698 format!(
1699 "the package '{}' does not contain {these_features}: {}",
1700 singular.name(),
1701 unknown.join(", ")
1702 )
1703 } else {
1704 let names = selected_members.iter().map(|m| m.name()).join(", ");
1705 format!(
1706 "none of the selected packages contains {these_features}: {}\nselected packages: {names}",
1707 unknown.join(", ")
1708 )
1709 };
1710
1711 use std::fmt::Write;
1712 if !missing_packages_with_the_features.is_empty() {
1713 write!(
1714 &mut msg,
1715 "\nhelp: package{} with the missing feature{}: {}",
1716 if missing_packages_with_the_features.len() != 1 {
1717 "s"
1718 } else {
1719 ""
1720 },
1721 if unknown.len() != 1 { "s" } else { "" },
1722 missing_packages_with_the_features.join(", ")
1723 )?;
1724 } else {
1725 let suggestions = self.missing_feature_spelling_suggestions(
1726 &selected_members,
1727 cli_features,
1728 found_features,
1729 );
1730 if !suggestions.is_empty() {
1731 write!(
1732 &mut msg,
1733 "\nhelp: there {}: {}",
1734 if suggestions.len() == 1 {
1735 "is a similarly named feature"
1736 } else {
1737 "are similarly named features"
1738 },
1739 suggestions.join(", ")
1740 )?;
1741 }
1742 }
1743
1744 bail!("{msg}")
1745 }
1746
1747 fn members_with_features_new(
1750 &self,
1751 specs: &[PackageIdSpec],
1752 cli_features: &CliFeatures,
1753 ) -> CargoResult<Vec<(&Package, CliFeatures)>> {
1754 let mut found_features = Default::default();
1757
1758 let members: Vec<(&Package, CliFeatures)> = self
1759 .members()
1760 .filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
1761 .map(|m| {
1762 (
1763 m,
1764 Workspace::collect_matching_features(m, cli_features, &mut found_features),
1765 )
1766 })
1767 .collect();
1768
1769 if members.is_empty() {
1770 if !(cli_features.features.is_empty()
1773 && !cli_features.all_features
1774 && cli_features.uses_default_features)
1775 {
1776 bail!("cannot specify features for packages outside of workspace");
1777 }
1778 return Ok(self
1781 .members()
1782 .map(|m| (m, CliFeatures::new_all(false)))
1783 .collect());
1784 }
1785 if *cli_features.features != found_features {
1786 self.report_unknown_features_error(specs, cli_features, &found_features)?;
1787 }
1788 Ok(members)
1789 }
1790
1791 fn members_with_features_old(
1794 &self,
1795 specs: &[PackageIdSpec],
1796 cli_features: &CliFeatures,
1797 ) -> Vec<(&Package, CliFeatures)> {
1798 let mut member_specific_features: HashMap<InternedString, BTreeSet<FeatureValue>> =
1801 HashMap::new();
1802 let mut cwd_features = BTreeSet::new();
1804 for feature in cli_features.features.iter() {
1805 match feature {
1806 FeatureValue::Feature(_) => {
1807 cwd_features.insert(feature.clone());
1808 }
1809 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1811 FeatureValue::DepFeature {
1812 dep_name,
1813 dep_feature,
1814 weak: _,
1815 } => {
1816 let is_member = self.members().any(|member| {
1822 self.current_opt() != Some(member) && member.name() == *dep_name
1824 });
1825 if is_member && specs.iter().any(|spec| spec.name() == dep_name.as_str()) {
1826 member_specific_features
1827 .entry(*dep_name)
1828 .or_default()
1829 .insert(FeatureValue::Feature(*dep_feature));
1830 } else {
1831 cwd_features.insert(feature.clone());
1832 }
1833 }
1834 }
1835 }
1836
1837 let ms: Vec<_> = self
1838 .members()
1839 .filter_map(|member| {
1840 let member_id = member.package_id();
1841 match self.current_opt() {
1842 Some(current) if member_id == current.package_id() => {
1845 let feats = CliFeatures {
1846 features: Rc::new(cwd_features.clone()),
1847 all_features: cli_features.all_features,
1848 uses_default_features: cli_features.uses_default_features,
1849 };
1850 Some((member, feats))
1851 }
1852 _ => {
1853 if specs.iter().any(|spec| spec.matches(member_id)) {
1855 let feats = CliFeatures {
1865 features: Rc::new(
1866 member_specific_features
1867 .remove(member.name().as_str())
1868 .unwrap_or_default(),
1869 ),
1870 uses_default_features: true,
1871 all_features: cli_features.all_features,
1872 };
1873 Some((member, feats))
1874 } else {
1875 None
1877 }
1878 }
1879 }
1880 })
1881 .collect();
1882
1883 assert!(member_specific_features.is_empty());
1886
1887 ms
1888 }
1889
1890 pub fn unit_needs_doc_scrape(&self, unit: &Unit) -> bool {
1892 self.is_member(&unit.pkg) && !(unit.target.for_host() || unit.pkg.proc_macro())
1897 }
1898
1899 pub fn add_local_overlay(&mut self, id: SourceId, registry_path: PathBuf) {
1903 self.local_overlays.insert(id, registry_path);
1904 }
1905
1906 pub fn package_registry(&self) -> CargoResult<PackageRegistry<'gctx>> {
1908 let source_config =
1909 SourceConfigMap::new_with_overlays(self.gctx(), self.local_overlays()?)?;
1910 PackageRegistry::new_with_source_config(self.gctx(), source_config)
1911 }
1912
1913 fn local_overlays(&self) -> CargoResult<impl Iterator<Item = (SourceId, SourceId)>> {
1915 let mut ret = self
1916 .local_overlays
1917 .iter()
1918 .map(|(id, path)| Ok((*id, SourceId::for_local_registry(path)?)))
1919 .collect::<CargoResult<Vec<_>>>()?;
1920
1921 if let Ok(overlay) = self
1922 .gctx
1923 .get_env("__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS")
1924 {
1925 let (url, path) = overlay.split_once('=').ok_or(anyhow::anyhow!(
1926 "invalid overlay format. I won't tell you why; you shouldn't be using it anyway"
1927 ))?;
1928 ret.push((
1929 SourceId::from_url(url)?,
1930 SourceId::for_local_registry(path.as_ref())?,
1931 ));
1932 }
1933
1934 Ok(ret.into_iter())
1935 }
1936}
1937
1938impl<'gctx> Packages<'gctx> {
1939 fn get(&self, manifest_path: &Path) -> &MaybePackage {
1940 self.maybe_get(manifest_path).unwrap()
1941 }
1942
1943 fn get_mut(&mut self, manifest_path: &Path) -> &mut MaybePackage {
1944 self.maybe_get_mut(manifest_path).unwrap()
1945 }
1946
1947 fn maybe_get(&self, manifest_path: &Path) -> Option<&MaybePackage> {
1948 self.packages.get(manifest_path)
1949 }
1950
1951 fn maybe_get_mut(&mut self, manifest_path: &Path) -> Option<&mut MaybePackage> {
1952 self.packages.get_mut(manifest_path)
1953 }
1954
1955 fn load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage> {
1956 match self.packages.entry(manifest_path.to_path_buf()) {
1957 Entry::Occupied(e) => Ok(e.into_mut()),
1958 Entry::Vacant(v) => {
1959 let source_id = SourceId::for_manifest_path(manifest_path)?;
1960 let manifest = read_manifest(manifest_path, source_id, self.gctx)?;
1961 Ok(v.insert(match manifest {
1962 EitherManifest::Real(manifest) => {
1963 MaybePackage::Package(Package::new(manifest, manifest_path))
1964 }
1965 EitherManifest::Virtual(vm) => MaybePackage::Virtual(vm),
1966 }))
1967 }
1968 }
1969 }
1970}
1971
1972impl MaybePackage {
1973 fn workspace_config(&self) -> &WorkspaceConfig {
1974 match *self {
1975 MaybePackage::Package(ref p) => p.manifest().workspace_config(),
1976 MaybePackage::Virtual(ref vm) => vm.workspace_config(),
1977 }
1978 }
1979
1980 pub fn is_embedded(&self) -> bool {
1982 match self {
1983 MaybePackage::Package(p) => p.manifest().is_embedded(),
1984 MaybePackage::Virtual(_) => false,
1985 }
1986 }
1987
1988 pub fn contents(&self) -> &str {
1989 match self {
1990 MaybePackage::Package(p) => p.manifest().contents(),
1991 MaybePackage::Virtual(v) => v.contents(),
1992 }
1993 }
1994
1995 pub fn document(&self) -> &toml::Spanned<toml::de::DeTable<'static>> {
1996 match self {
1997 MaybePackage::Package(p) => p.manifest().document(),
1998 MaybePackage::Virtual(v) => v.document(),
1999 }
2000 }
2001
2002 pub fn edition(&self) -> Edition {
2003 match self {
2004 MaybePackage::Package(p) => p.manifest().edition(),
2005 MaybePackage::Virtual(_) => Edition::default(),
2006 }
2007 }
2008
2009 pub fn profiles(&self) -> Option<&TomlProfiles> {
2010 match self {
2011 MaybePackage::Package(p) => p.manifest().profiles(),
2012 MaybePackage::Virtual(v) => v.profiles(),
2013 }
2014 }
2015
2016 pub fn unstable_features(&self) -> &Features {
2017 match self {
2018 MaybePackage::Package(p) => p.manifest().unstable_features(),
2019 MaybePackage::Virtual(vm) => vm.unstable_features(),
2020 }
2021 }
2022}
2023
2024impl WorkspaceRootConfig {
2025 pub fn new(
2027 root_dir: &Path,
2028 members: &Option<Vec<String>>,
2029 default_members: &Option<Vec<String>>,
2030 exclude: &Option<Vec<String>>,
2031 inheritable: &Option<InheritableFields>,
2032 custom_metadata: &Option<toml::Value>,
2033 ) -> WorkspaceRootConfig {
2034 WorkspaceRootConfig {
2035 root_dir: root_dir.to_path_buf(),
2036 members: members.clone(),
2037 default_members: default_members.clone(),
2038 exclude: exclude.clone().unwrap_or_default(),
2039 inheritable_fields: inheritable.clone().unwrap_or_default(),
2040 custom_metadata: custom_metadata.clone(),
2041 }
2042 }
2043 fn is_excluded(&self, manifest_path: &Path) -> bool {
2047 let excluded = self
2048 .exclude
2049 .iter()
2050 .any(|ex| manifest_path.starts_with(self.root_dir.join(ex)));
2051
2052 let explicit_member = match self.members {
2053 Some(ref members) => members
2054 .iter()
2055 .any(|mem| manifest_path.starts_with(self.root_dir.join(mem))),
2056 None => false,
2057 };
2058
2059 !explicit_member && excluded
2060 }
2061
2062 fn has_members_list(&self) -> bool {
2063 self.members.is_some()
2064 }
2065
2066 #[tracing::instrument(skip_all)]
2069 fn members_paths<'g>(
2070 &self,
2071 globs: &'g [String],
2072 ) -> CargoResult<Vec<(PathBuf, Option<&'g str>)>> {
2073 let mut expanded_list = Vec::new();
2074
2075 for glob in globs {
2076 let pathbuf = self.root_dir.join(glob);
2077 let expanded_paths = Self::expand_member_path(&pathbuf)?;
2078
2079 if expanded_paths.is_empty() {
2082 expanded_list.push((pathbuf, None));
2083 } else {
2084 let used_glob_pattern = expanded_paths.len() > 1 || expanded_paths[0] != pathbuf;
2085 let glob = used_glob_pattern.then_some(glob.as_str());
2086
2087 for expanded_path in expanded_paths {
2093 if expanded_path.is_dir() {
2094 expanded_list.push((expanded_path, glob));
2095 }
2096 }
2097 }
2098 }
2099
2100 Ok(expanded_list)
2101 }
2102
2103 fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> {
2104 let Some(path) = path.to_str() else {
2105 return Ok(Vec::new());
2106 };
2107 let res = glob(path).with_context(|| format!("could not parse pattern `{}`", &path))?;
2108 let res = res
2109 .map(|p| p.with_context(|| format!("unable to match path to pattern `{}`", &path)))
2110 .collect::<Result<Vec<_>, _>>()?;
2111 Ok(res)
2112 }
2113
2114 pub fn inheritable(&self) -> &InheritableFields {
2115 &self.inheritable_fields
2116 }
2117}
2118
2119pub fn resolve_relative_path(
2120 label: &str,
2121 old_root: &Path,
2122 new_root: &Path,
2123 rel_path: &str,
2124) -> CargoResult<String> {
2125 let joined_path = normalize_path(&old_root.join(rel_path));
2126 match diff_paths(joined_path, new_root) {
2127 None => Err(anyhow!(
2128 "`{}` was defined in {} but could not be resolved with {}",
2129 label,
2130 old_root.display(),
2131 new_root.display()
2132 )),
2133 Some(path) => Ok(path
2134 .to_str()
2135 .ok_or_else(|| {
2136 anyhow!(
2137 "`{}` resolved to non-UTF value (`{}`)",
2138 label,
2139 path.display()
2140 )
2141 })?
2142 .to_owned()),
2143 }
2144}
2145
2146pub fn find_workspace_root(
2148 manifest_path: &Path,
2149 gctx: &GlobalContext,
2150) -> CargoResult<Option<PathBuf>> {
2151 find_workspace_root_with_loader(manifest_path, gctx, |self_path| {
2152 let source_id = SourceId::for_manifest_path(self_path)?;
2153 let manifest = read_manifest(self_path, source_id, gctx)?;
2154 Ok(manifest
2155 .workspace_config()
2156 .get_ws_root(self_path, manifest_path))
2157 })
2158}
2159
2160fn find_workspace_root_with_loader(
2165 manifest_path: &Path,
2166 gctx: &GlobalContext,
2167 mut loader: impl FnMut(&Path) -> CargoResult<Option<PathBuf>>,
2168) -> CargoResult<Option<PathBuf>> {
2169 {
2171 let roots = gctx.ws_roots();
2172 for current in manifest_path.ancestors().skip(1) {
2175 if let Some(ws_config) = roots.get(current) {
2176 if !ws_config.is_excluded(manifest_path) {
2177 return Ok(Some(current.join("Cargo.toml")));
2179 }
2180 }
2181 }
2182 }
2183
2184 for ances_manifest_path in find_root_iter(manifest_path, gctx) {
2185 debug!("find_root - trying {}", ances_manifest_path.display());
2186 if let Some(ws_root_path) = loader(&ances_manifest_path)? {
2187 return Ok(Some(ws_root_path));
2188 }
2189 }
2190 Ok(None)
2191}
2192
2193fn read_root_pointer(member_manifest: &Path, root_link: &str) -> PathBuf {
2194 let path = member_manifest
2195 .parent()
2196 .unwrap()
2197 .join(root_link)
2198 .join("Cargo.toml");
2199 debug!("find_root - pointer {}", path.display());
2200 paths::normalize_path(&path)
2201}
2202
2203fn find_root_iter<'a>(
2204 manifest_path: &'a Path,
2205 gctx: &'a GlobalContext,
2206) -> impl Iterator<Item = PathBuf> + 'a {
2207 LookBehind::new(paths::ancestors(manifest_path, None).skip(2))
2208 .take_while(|path| !path.curr.ends_with("target/package"))
2209 .take_while(|path| {
2215 if let Some(last) = path.last {
2216 gctx.home() != last
2217 } else {
2218 true
2219 }
2220 })
2221 .map(|path| path.curr.join("Cargo.toml"))
2222 .filter(|ances_manifest_path| ances_manifest_path.exists())
2223}
2224
2225struct LookBehindWindow<'a, T: ?Sized> {
2226 curr: &'a T,
2227 last: Option<&'a T>,
2228}
2229
2230struct LookBehind<'a, T: ?Sized, K: Iterator<Item = &'a T>> {
2231 iter: K,
2232 last: Option<&'a T>,
2233}
2234
2235impl<'a, T: ?Sized, K: Iterator<Item = &'a T>> LookBehind<'a, T, K> {
2236 fn new(items: K) -> Self {
2237 Self {
2238 iter: items,
2239 last: None,
2240 }
2241 }
2242}
2243
2244impl<'a, T: ?Sized, K: Iterator<Item = &'a T>> Iterator for LookBehind<'a, T, K> {
2245 type Item = LookBehindWindow<'a, T>;
2246
2247 fn next(&mut self) -> Option<Self::Item> {
2248 match self.iter.next() {
2249 None => None,
2250 Some(next) => {
2251 let last = self.last;
2252 self.last = Some(next);
2253 Some(LookBehindWindow { curr: next, last })
2254 }
2255 }
2256 }
2257}