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, Patch,
21 PatchLocation,
22};
23use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
24use crate::lints::analyze_cargo_lints_table;
25use crate::lints::rules::blanket_hint_mostly_unused;
26use crate::lints::rules::check_im_a_teapot;
27use crate::lints::rules::implicit_minimum_version_req_pkg;
28use crate::lints::rules::implicit_minimum_version_req_ws;
29use crate::lints::rules::missing_lints_inheritance;
30use crate::lints::rules::non_kebab_case_bins;
31use crate::lints::rules::non_kebab_case_features;
32use crate::lints::rules::non_kebab_case_packages;
33use crate::lints::rules::non_snake_case_features;
34use crate::lints::rules::non_snake_case_packages;
35use crate::lints::rules::redundant_homepage;
36use crate::lints::rules::redundant_readme;
37use crate::lints::rules::unused_workspace_dependencies;
38use crate::lints::rules::unused_workspace_package_fields;
39use crate::ops;
40use crate::ops::lockfile::LOCKFILE_NAME;
41use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY, PathSource, SourceConfigMap};
42use crate::util::context;
43use crate::util::context::{FeatureUnification, Value};
44use crate::util::edit_distance;
45use crate::util::errors::{CargoResult, ManifestError};
46use crate::util::interning::InternedString;
47use crate::util::toml::{InheritableFields, read_manifest};
48use crate::util::{
49 Filesystem, GlobalContext, IntoUrl, context::CargoResolverConfig, context::ConfigRelativePath,
50 context::IncompatibleRustVersions,
51};
52
53use cargo_util::paths;
54use cargo_util::paths::normalize_path;
55use cargo_util_schemas::manifest;
56use cargo_util_schemas::manifest::RustVersion;
57use cargo_util_schemas::manifest::{TomlDependency, TomlManifest, TomlProfiles};
58use pathdiff::diff_paths;
59
60#[derive(Debug)]
66pub struct Workspace<'gctx> {
67 gctx: &'gctx GlobalContext,
69
70 current_manifest: PathBuf,
74
75 packages: Packages<'gctx>,
78
79 root_manifest: Option<PathBuf>,
84
85 target_dir: Option<Filesystem>,
88
89 build_dir: Option<Filesystem>,
92
93 members: Vec<PathBuf>,
97 member_ids: HashSet<PackageId>,
99
100 default_members: Vec<PathBuf>,
111
112 is_ephemeral: bool,
115
116 require_optional_deps: bool,
121
122 loaded_packages: RefCell<HashMap<PathBuf, Package>>,
125
126 ignore_lock: bool,
129
130 requested_lockfile_path: Option<PathBuf>,
132
133 resolve_behavior: ResolveBehavior,
135 resolve_honors_rust_version: bool,
139 resolve_feature_unification: FeatureUnification,
141 resolve_publish_time: Option<jiff::Timestamp>,
143 custom_metadata: Option<toml::Value>,
145
146 local_overlays: HashMap<SourceId, PathBuf>,
148}
149
150#[derive(Debug)]
153struct Packages<'gctx> {
154 gctx: &'gctx GlobalContext,
155 packages: HashMap<PathBuf, MaybePackage>,
156}
157
158#[derive(Debug)]
159pub enum MaybePackage {
160 Package(Package),
161 Virtual(VirtualManifest),
162}
163
164#[derive(Debug, Clone)]
166pub enum WorkspaceConfig {
167 Root(WorkspaceRootConfig),
170
171 Member { root: Option<String> },
174}
175
176impl WorkspaceConfig {
177 pub fn inheritable(&self) -> Option<&InheritableFields> {
178 match self {
179 WorkspaceConfig::Root(root) => Some(&root.inheritable_fields),
180 WorkspaceConfig::Member { .. } => None,
181 }
182 }
183
184 fn get_ws_root(&self, self_path: &Path, look_from: &Path) -> Option<PathBuf> {
192 match self {
193 WorkspaceConfig::Root(ances_root_config) => {
194 debug!("find_root - found a root checking exclusion");
195 if !ances_root_config.is_excluded(look_from) {
196 debug!("find_root - found!");
197 Some(self_path.to_owned())
198 } else {
199 None
200 }
201 }
202 WorkspaceConfig::Member {
203 root: Some(path_to_root),
204 } => {
205 debug!("find_root - found pointer");
206 Some(read_root_pointer(self_path, path_to_root))
207 }
208 WorkspaceConfig::Member { .. } => None,
209 }
210 }
211}
212
213#[derive(Debug, Clone)]
218pub struct WorkspaceRootConfig {
219 root_dir: PathBuf,
220 members: Option<Vec<String>>,
221 default_members: Option<Vec<String>>,
222 exclude: Vec<String>,
223 inheritable_fields: InheritableFields,
224 custom_metadata: Option<toml::Value>,
225}
226
227impl<'gctx> Workspace<'gctx> {
228 pub fn new(manifest_path: &Path, gctx: &'gctx GlobalContext) -> CargoResult<Workspace<'gctx>> {
235 let mut ws = Workspace::new_default(manifest_path.to_path_buf(), gctx);
236
237 if manifest_path.is_relative() {
238 bail!(
239 "manifest_path:{:?} is not an absolute path. Please provide an absolute path.",
240 manifest_path
241 )
242 } else {
243 ws.root_manifest = ws.find_root(manifest_path)?;
244 }
245
246 ws.target_dir = gctx.target_dir()?;
247 ws.build_dir = gctx.build_dir(ws.root_manifest())?;
248
249 ws.custom_metadata = ws
250 .load_workspace_config()?
251 .and_then(|cfg| cfg.custom_metadata);
252 ws.find_members()?;
253 ws.set_resolve_behavior()?;
254 ws.validate()?;
255 Ok(ws)
256 }
257
258 fn new_default(current_manifest: PathBuf, gctx: &'gctx GlobalContext) -> Workspace<'gctx> {
259 Workspace {
260 gctx,
261 current_manifest,
262 packages: Packages {
263 gctx,
264 packages: HashMap::new(),
265 },
266 root_manifest: None,
267 target_dir: None,
268 build_dir: None,
269 members: Vec::new(),
270 member_ids: HashSet::new(),
271 default_members: Vec::new(),
272 is_ephemeral: false,
273 require_optional_deps: true,
274 loaded_packages: RefCell::new(HashMap::new()),
275 ignore_lock: false,
276 requested_lockfile_path: None,
277 resolve_behavior: ResolveBehavior::V1,
278 resolve_honors_rust_version: false,
279 resolve_feature_unification: FeatureUnification::Selected,
280 resolve_publish_time: None,
281 custom_metadata: None,
282 local_overlays: HashMap::new(),
283 }
284 }
285
286 pub fn ephemeral(
296 package: Package,
297 gctx: &'gctx GlobalContext,
298 target_dir: Option<Filesystem>,
299 require_optional_deps: bool,
300 ) -> CargoResult<Workspace<'gctx>> {
301 let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), gctx);
302 ws.is_ephemeral = true;
303 ws.require_optional_deps = require_optional_deps;
304 let id = package.package_id();
305 let package = MaybePackage::Package(package);
306 ws.packages
307 .packages
308 .insert(ws.current_manifest.clone(), package);
309 ws.target_dir = if let Some(dir) = target_dir {
310 Some(dir)
311 } else {
312 ws.gctx.target_dir()?
313 };
314 ws.build_dir = ws.target_dir.clone();
315 ws.members.push(ws.current_manifest.clone());
316 ws.member_ids.insert(id);
317 ws.default_members.push(ws.current_manifest.clone());
318 ws.set_resolve_behavior()?;
319 Ok(ws)
320 }
321
322 pub fn reload(&self, gctx: &'gctx GlobalContext) -> CargoResult<Workspace<'gctx>> {
327 let mut ws = Workspace::new(&self.current_manifest, gctx)?;
328 ws.set_resolve_honors_rust_version(Some(self.resolve_honors_rust_version));
329 ws.set_resolve_feature_unification(self.resolve_feature_unification);
330 ws.set_requested_lockfile_path(self.requested_lockfile_path.clone());
331 Ok(ws)
332 }
333
334 fn set_resolve_behavior(&mut self) -> CargoResult<()> {
335 self.resolve_behavior = match self.root_maybe() {
340 MaybePackage::Package(p) => p
341 .manifest()
342 .resolve_behavior()
343 .unwrap_or_else(|| p.manifest().edition().default_resolve_behavior()),
344 MaybePackage::Virtual(vm) => vm.resolve_behavior().unwrap_or(ResolveBehavior::V1),
345 };
346
347 match self.resolve_behavior() {
348 ResolveBehavior::V1 | ResolveBehavior::V2 => {}
349 ResolveBehavior::V3 => {
350 if self.resolve_behavior == ResolveBehavior::V3 {
351 self.resolve_honors_rust_version = true;
352 }
353 }
354 }
355 let config = self.gctx().get::<CargoResolverConfig>("resolver")?;
356 if let Some(incompatible_rust_versions) = config.incompatible_rust_versions {
357 self.resolve_honors_rust_version =
358 incompatible_rust_versions == IncompatibleRustVersions::Fallback;
359 }
360 if self.gctx().cli_unstable().feature_unification {
361 self.resolve_feature_unification = config
362 .feature_unification
363 .unwrap_or(FeatureUnification::Selected);
364 } else if config.feature_unification.is_some() {
365 self.gctx()
366 .shell()
367 .warn("ignoring `resolver.feature-unification` without `-Zfeature-unification`")?;
368 };
369
370 if let Some(lockfile_path) = config.lockfile_path {
371 if self.gctx().cli_unstable().lockfile_path {
372 let replacements: [(&str, &str); 0] = [];
374 let path = lockfile_path
375 .resolve_templated_path(self.gctx(), replacements)
376 .map_err(|e| match e {
377 context::ResolveTemplateError::UnexpectedVariable {
378 variable,
379 raw_template,
380 } => {
381 anyhow!(
382 "unexpected variable `{variable}` in resolver.lockfile-path `{raw_template}`"
383 )
384 }
385 context::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
386 let (btype, literal) = match bracket_type {
387 context::BracketType::Opening => ("opening", "{"),
388 context::BracketType::Closing => ("closing", "}"),
389 };
390
391 anyhow!(
392 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
393 )
394 }
395 })?;
396 if !path.ends_with(LOCKFILE_NAME) {
397 bail!("the `resolver.lockfile-path` must be a path to a {LOCKFILE_NAME} file");
398 }
399 if path.is_dir() {
400 bail!(
401 "`resolver.lockfile-path` `{}` is a directory but expected a file",
402 path.display()
403 );
404 }
405 self.requested_lockfile_path = Some(path);
406 } else {
407 self.gctx().shell().warn(
408 "ignoring `resolver.lockfile-path`, pass `-Zlockfile-path` to enable it",
409 )?;
410 }
411 }
412
413 Ok(())
414 }
415
416 pub fn current(&self) -> CargoResult<&Package> {
422 let pkg = self.current_opt().ok_or_else(|| {
423 anyhow::format_err!(
424 "manifest path `{}` is a virtual manifest, but this \
425 command requires running against an actual package in \
426 this workspace",
427 self.current_manifest.display()
428 )
429 })?;
430 Ok(pkg)
431 }
432
433 pub fn current_mut(&mut self) -> CargoResult<&mut Package> {
434 let cm = self.current_manifest.clone();
435 let pkg = self.current_opt_mut().ok_or_else(|| {
436 anyhow::format_err!(
437 "manifest path `{}` is a virtual manifest, but this \
438 command requires running against an actual package in \
439 this workspace",
440 cm.display()
441 )
442 })?;
443 Ok(pkg)
444 }
445
446 pub fn current_opt(&self) -> Option<&Package> {
447 match *self.packages.get(&self.current_manifest) {
448 MaybePackage::Package(ref p) => Some(p),
449 MaybePackage::Virtual(..) => None,
450 }
451 }
452
453 pub fn current_opt_mut(&mut self) -> Option<&mut Package> {
454 match *self.packages.get_mut(&self.current_manifest) {
455 MaybePackage::Package(ref mut p) => Some(p),
456 MaybePackage::Virtual(..) => None,
457 }
458 }
459
460 pub fn is_virtual(&self) -> bool {
461 match *self.packages.get(&self.current_manifest) {
462 MaybePackage::Package(..) => false,
463 MaybePackage::Virtual(..) => true,
464 }
465 }
466
467 pub fn gctx(&self) -> &'gctx GlobalContext {
469 self.gctx
470 }
471
472 pub fn profiles(&self) -> Option<&TomlProfiles> {
473 self.root_maybe().profiles()
474 }
475
476 pub fn root(&self) -> &Path {
481 self.root_manifest().parent().unwrap()
482 }
483
484 pub fn root_manifest(&self) -> &Path {
487 self.root_manifest
488 .as_ref()
489 .unwrap_or(&self.current_manifest)
490 }
491
492 pub fn root_maybe(&self) -> &MaybePackage {
494 self.packages.get(self.root_manifest())
495 }
496
497 pub fn target_dir(&self) -> Filesystem {
498 self.target_dir
499 .clone()
500 .unwrap_or_else(|| self.default_target_dir())
501 }
502
503 pub fn build_dir(&self) -> Filesystem {
504 self.build_dir
505 .clone()
506 .or_else(|| self.target_dir.clone())
507 .unwrap_or_else(|| self.default_build_dir())
508 }
509
510 fn default_target_dir(&self) -> Filesystem {
511 if self.root_maybe().is_embedded() {
512 self.build_dir().join("target")
513 } else {
514 Filesystem::new(self.root().join("target"))
515 }
516 }
517
518 fn default_build_dir(&self) -> Filesystem {
519 if self.root_maybe().is_embedded() {
520 let default = ConfigRelativePath::new(
521 "{cargo-cache-home}/build/{workspace-path-hash}"
522 .to_owned()
523 .into(),
524 );
525 self.gctx()
526 .custom_build_dir(&default, self.root_manifest())
527 .expect("template is correct")
528 } else {
529 self.default_target_dir()
530 }
531 }
532
533 pub fn root_replace(&self) -> &[(PackageIdSpec, Dependency)] {
537 match self.root_maybe() {
538 MaybePackage::Package(p) => p.manifest().replace(),
539 MaybePackage::Virtual(vm) => vm.replace(),
540 }
541 }
542
543 fn config_patch(&self) -> CargoResult<HashMap<Url, Vec<Patch>>> {
544 let config_patch: Option<
545 BTreeMap<String, BTreeMap<String, Value<TomlDependency<ConfigRelativePath>>>>,
546 > = self.gctx.get("patch")?;
547
548 let source = SourceId::for_manifest_path(self.root_manifest())?;
549
550 let mut warnings = Vec::new();
551
552 let mut patch = HashMap::new();
553 for (url, deps) in config_patch.into_iter().flatten() {
554 let url = match &url[..] {
555 CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
556 url => self
557 .gctx
558 .get_registry_index(url)
559 .or_else(|_| url.into_url())
560 .with_context(|| {
561 format!("[patch] entry `{}` should be a URL or registry name", url)
562 })?,
563 };
564 patch.insert(
565 url,
566 deps.iter()
567 .map(|(name, dependency_cv)| {
568 crate::util::toml::config_patch_to_dependency(
569 &dependency_cv.val,
570 name,
571 source,
572 self.gctx,
573 &mut warnings,
574 )
575 .map(|dep| Patch {
576 dep,
577 loc: PatchLocation::Config(dependency_cv.definition.clone()),
578 })
579 })
580 .collect::<CargoResult<Vec<_>>>()?,
581 );
582 }
583
584 for message in warnings {
585 self.gctx
586 .shell()
587 .warn(format!("[patch] in cargo config: {}", message))?
588 }
589
590 Ok(patch)
591 }
592
593 pub fn root_patch(&self) -> CargoResult<HashMap<Url, Vec<Patch>>> {
597 let from_manifest = match self.root_maybe() {
598 MaybePackage::Package(p) => p.manifest().patch(),
599 MaybePackage::Virtual(vm) => vm.patch(),
600 };
601
602 let from_config = self.config_patch()?;
603 if from_config.is_empty() {
604 return Ok(from_manifest.clone());
605 }
606 if from_manifest.is_empty() {
607 return Ok(from_config);
608 }
609
610 let mut combined = from_config;
613 for (url, deps_from_manifest) in from_manifest {
614 if let Some(deps_from_config) = combined.get_mut(url) {
615 let mut from_manifest_pruned = deps_from_manifest.clone();
618 for dep_from_config in &mut *deps_from_config {
619 if let Some(i) = from_manifest_pruned.iter().position(|dep_from_manifest| {
620 dep_from_config.dep.name_in_toml() == dep_from_manifest.dep.name_in_toml()
622 }) {
623 from_manifest_pruned.swap_remove(i);
624 }
625 }
626 deps_from_config.extend(from_manifest_pruned);
628 } else {
629 combined.insert(url.clone(), deps_from_manifest.clone());
630 }
631 }
632 Ok(combined)
633 }
634
635 pub fn members(&self) -> impl Iterator<Item = &Package> {
637 let packages = &self.packages;
638 self.members
639 .iter()
640 .filter_map(move |path| match packages.get(path) {
641 MaybePackage::Package(p) => Some(p),
642 _ => None,
643 })
644 }
645
646 pub fn members_mut(&mut self) -> impl Iterator<Item = &mut Package> {
648 let packages = &mut self.packages.packages;
649 let members: HashSet<_> = self.members.iter().map(|path| path).collect();
650
651 packages.iter_mut().filter_map(move |(path, package)| {
652 if members.contains(path) {
653 if let MaybePackage::Package(p) = package {
654 return Some(p);
655 }
656 }
657
658 None
659 })
660 }
661
662 pub fn default_members<'a>(&'a self) -> impl Iterator<Item = &'a Package> {
664 let packages = &self.packages;
665 self.default_members
666 .iter()
667 .filter_map(move |path| match packages.get(path) {
668 MaybePackage::Package(p) => Some(p),
669 _ => None,
670 })
671 }
672
673 pub fn default_members_mut(&mut self) -> impl Iterator<Item = &mut Package> {
675 let packages = &mut self.packages.packages;
676 let members: HashSet<_> = self
677 .default_members
678 .iter()
679 .map(|path| path.parent().unwrap().to_owned())
680 .collect();
681
682 packages.iter_mut().filter_map(move |(path, package)| {
683 if members.contains(path) {
684 if let MaybePackage::Package(p) = package {
685 return Some(p);
686 }
687 }
688
689 None
690 })
691 }
692
693 pub fn is_member(&self, pkg: &Package) -> bool {
695 self.member_ids.contains(&pkg.package_id())
696 }
697
698 pub fn is_member_id(&self, package_id: PackageId) -> bool {
700 self.member_ids.contains(&package_id)
701 }
702
703 pub fn is_ephemeral(&self) -> bool {
704 self.is_ephemeral
705 }
706
707 pub fn require_optional_deps(&self) -> bool {
708 self.require_optional_deps
709 }
710
711 pub fn set_require_optional_deps(
712 &mut self,
713 require_optional_deps: bool,
714 ) -> &mut Workspace<'gctx> {
715 self.require_optional_deps = require_optional_deps;
716 self
717 }
718
719 pub fn ignore_lock(&self) -> bool {
720 self.ignore_lock
721 }
722
723 pub fn set_ignore_lock(&mut self, ignore_lock: bool) -> &mut Workspace<'gctx> {
724 self.ignore_lock = ignore_lock;
725 self
726 }
727
728 pub fn lock_root(&self) -> Filesystem {
730 if let Some(requested) = self.requested_lockfile_path.as_ref() {
731 return Filesystem::new(
732 requested
733 .parent()
734 .expect("Lockfile path can't be root")
735 .to_owned(),
736 );
737 }
738 self.default_lock_root()
739 }
740
741 fn default_lock_root(&self) -> Filesystem {
742 if self.root_maybe().is_embedded() {
743 let workspace_manifest_path = self.root_manifest();
746 let real_path = std::fs::canonicalize(workspace_manifest_path)
747 .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
748 let hash = crate::util::hex::short_hash(&real_path);
749 self.build_dir().join(hash)
750 } else {
751 Filesystem::new(self.root().to_owned())
752 }
753 }
754
755 pub fn set_requested_lockfile_path(&mut self, path: Option<PathBuf>) {
757 self.requested_lockfile_path = path;
758 }
759
760 pub fn requested_lockfile_path(&self) -> Option<&Path> {
761 self.requested_lockfile_path.as_deref()
762 }
763
764 pub fn lowest_rust_version(&self) -> Option<&RustVersion> {
767 self.members().filter_map(|pkg| pkg.rust_version()).min()
768 }
769
770 pub fn set_resolve_honors_rust_version(&mut self, honor_rust_version: Option<bool>) {
771 if let Some(honor_rust_version) = honor_rust_version {
772 self.resolve_honors_rust_version = honor_rust_version;
773 }
774 }
775
776 pub fn resolve_honors_rust_version(&self) -> bool {
777 self.resolve_honors_rust_version
778 }
779
780 pub fn set_resolve_feature_unification(&mut self, feature_unification: FeatureUnification) {
781 self.resolve_feature_unification = feature_unification;
782 }
783
784 pub fn resolve_feature_unification(&self) -> FeatureUnification {
785 self.resolve_feature_unification
786 }
787
788 pub fn set_resolve_publish_time(&mut self, publish_time: jiff::Timestamp) {
789 self.resolve_publish_time = Some(publish_time);
790 }
791
792 pub fn resolve_publish_time(&self) -> Option<jiff::Timestamp> {
793 self.resolve_publish_time
794 }
795
796 pub fn custom_metadata(&self) -> Option<&toml::Value> {
797 self.custom_metadata.as_ref()
798 }
799
800 pub fn load_workspace_config(&mut self) -> CargoResult<Option<WorkspaceRootConfig>> {
801 if let Some(root_path) = &self.root_manifest {
804 let root_package = self.packages.load(root_path)?;
805 match root_package.workspace_config() {
806 WorkspaceConfig::Root(root_config) => {
807 return Ok(Some(root_config.clone()));
808 }
809
810 _ => bail!(
811 "root of a workspace inferred but wasn't a root: {}",
812 root_path.display()
813 ),
814 }
815 }
816
817 Ok(None)
818 }
819
820 fn find_root(&mut self, manifest_path: &Path) -> CargoResult<Option<PathBuf>> {
830 let current = self.packages.load(manifest_path)?;
831 match current
832 .workspace_config()
833 .get_ws_root(manifest_path, manifest_path)
834 {
835 Some(root_path) => {
836 debug!("find_root - is root {}", manifest_path.display());
837 Ok(Some(root_path))
838 }
839 None => find_workspace_root_with_loader(manifest_path, self.gctx, |self_path| {
840 Ok(self
841 .packages
842 .load(self_path)?
843 .workspace_config()
844 .get_ws_root(self_path, manifest_path))
845 }),
846 }
847 }
848
849 #[tracing::instrument(skip_all)]
857 fn find_members(&mut self) -> CargoResult<()> {
858 let Some(workspace_config) = self.load_workspace_config()? else {
859 debug!("find_members - only me as a member");
860 self.members.push(self.current_manifest.clone());
861 self.default_members.push(self.current_manifest.clone());
862 if let Ok(pkg) = self.current() {
863 let id = pkg.package_id();
864 self.member_ids.insert(id);
865 }
866 return Ok(());
867 };
868
869 let root_manifest_path = self.root_manifest.clone().unwrap();
871
872 let members_paths = workspace_config
873 .members_paths(workspace_config.members.as_deref().unwrap_or_default())?;
874 let default_members_paths = if root_manifest_path == self.current_manifest {
875 if let Some(ref default) = workspace_config.default_members {
876 Some(workspace_config.members_paths(default)?)
877 } else {
878 None
879 }
880 } else {
881 None
882 };
883
884 for (path, glob) in &members_paths {
885 self.find_path_deps(&path.join("Cargo.toml"), &root_manifest_path, false)
886 .with_context(|| {
887 format!(
888 "failed to load manifest for workspace member `{}`\n\
889 referenced{} by workspace at `{}`",
890 path.display(),
891 glob.map(|g| format!(" via `{g}`")).unwrap_or_default(),
892 root_manifest_path.display(),
893 )
894 })?;
895 }
896
897 self.find_path_deps(&root_manifest_path, &root_manifest_path, false)?;
898
899 if let Some(default) = default_members_paths {
900 for (path, default_member_glob) in default {
901 let normalized_path = paths::normalize_path(&path);
902 let manifest_path = normalized_path.join("Cargo.toml");
903 if !self.members.contains(&manifest_path) {
904 let exclude = members_paths.iter().any(|(m, _)| *m == normalized_path)
911 && workspace_config.is_excluded(&normalized_path);
912 if exclude {
913 continue;
914 }
915 bail!(
916 "package `{}` is listed in default-members{} but is not a member\n\
917 for workspace at `{}`.",
918 path.display(),
919 default_member_glob
920 .map(|g| format!(" via `{g}`"))
921 .unwrap_or_default(),
922 root_manifest_path.display(),
923 )
924 }
925 self.default_members.push(manifest_path)
926 }
927 } else if self.is_virtual() {
928 self.default_members = self.members.clone()
929 } else {
930 self.default_members.push(self.current_manifest.clone())
931 }
932
933 Ok(())
934 }
935
936 fn find_path_deps(
937 &mut self,
938 manifest_path: &Path,
939 root_manifest: &Path,
940 is_path_dep: bool,
941 ) -> CargoResult<()> {
942 let manifest_path = paths::normalize_path(manifest_path);
943 if self.members.contains(&manifest_path) {
944 return Ok(());
945 }
946 if is_path_dep && self.root_maybe().is_embedded() {
947 return Ok(());
949 }
950 if is_path_dep
951 && !manifest_path.parent().unwrap().starts_with(self.root())
952 && self.find_root(&manifest_path)? != self.root_manifest
953 {
954 return Ok(());
957 }
958
959 if let WorkspaceConfig::Root(ref root_config) =
960 *self.packages.load(root_manifest)?.workspace_config()
961 {
962 if root_config.is_excluded(&manifest_path) {
963 return Ok(());
964 }
965 }
966
967 debug!("find_path_deps - {}", manifest_path.display());
968 self.members.push(manifest_path.clone());
969
970 let candidates = {
971 let pkg = match *self.packages.load(&manifest_path)? {
972 MaybePackage::Package(ref p) => p,
973 MaybePackage::Virtual(_) => return Ok(()),
974 };
975 self.member_ids.insert(pkg.package_id());
976 pkg.dependencies()
977 .iter()
978 .map(|d| (d.source_id(), d.package_name()))
979 .filter(|(s, _)| s.is_path())
980 .filter_map(|(s, n)| s.url().to_file_path().ok().map(|p| (p, n)))
981 .map(|(p, n)| (p.join("Cargo.toml"), n))
982 .collect::<Vec<_>>()
983 };
984 for (path, name) in candidates {
985 self.find_path_deps(&path, root_manifest, true)
986 .with_context(|| format!("failed to load manifest for dependency `{}`", name))
987 .map_err(|err| ManifestError::new(err, manifest_path.clone()))?;
988 }
989 Ok(())
990 }
991
992 pub fn unstable_features(&self) -> &Features {
994 self.root_maybe().unstable_features()
995 }
996
997 pub fn resolve_behavior(&self) -> ResolveBehavior {
998 self.resolve_behavior
999 }
1000
1001 pub fn allows_new_cli_feature_behavior(&self) -> bool {
1009 self.is_virtual()
1010 || match self.resolve_behavior() {
1011 ResolveBehavior::V1 => false,
1012 ResolveBehavior::V2 | ResolveBehavior::V3 => true,
1013 }
1014 }
1015
1016 #[tracing::instrument(skip_all)]
1022 fn validate(&mut self) -> CargoResult<()> {
1023 if self.root_manifest.is_none() {
1025 return Ok(());
1026 }
1027
1028 self.validate_unique_names()?;
1029 self.validate_workspace_roots()?;
1030 self.validate_members()?;
1031 self.error_if_manifest_not_in_members()?;
1032 self.validate_manifest()
1033 }
1034
1035 fn validate_unique_names(&self) -> CargoResult<()> {
1036 let mut names = BTreeMap::new();
1037 for member in self.members.iter() {
1038 let package = self.packages.get(member);
1039 let name = match *package {
1040 MaybePackage::Package(ref p) => p.name(),
1041 MaybePackage::Virtual(_) => continue,
1042 };
1043 if let Some(prev) = names.insert(name, member) {
1044 bail!(
1045 "two packages named `{}` in this workspace:\n\
1046 - {}\n\
1047 - {}",
1048 name,
1049 prev.display(),
1050 member.display()
1051 );
1052 }
1053 }
1054 Ok(())
1055 }
1056
1057 fn validate_workspace_roots(&self) -> CargoResult<()> {
1058 let roots: Vec<PathBuf> = self
1059 .members
1060 .iter()
1061 .filter(|&member| {
1062 let config = self.packages.get(member).workspace_config();
1063 matches!(config, WorkspaceConfig::Root(_))
1064 })
1065 .map(|member| member.parent().unwrap().to_path_buf())
1066 .collect();
1067 match roots.len() {
1068 1 => Ok(()),
1069 0 => bail!(
1070 "`package.workspace` configuration points to a crate \
1071 which is not configured with [workspace]: \n\
1072 configuration at: {}\n\
1073 points to: {}",
1074 self.current_manifest.display(),
1075 self.root_manifest.as_ref().unwrap().display()
1076 ),
1077 _ => {
1078 bail!(
1079 "multiple workspace roots found in the same workspace:\n{}",
1080 roots
1081 .iter()
1082 .map(|r| format!(" {}", r.display()))
1083 .collect::<Vec<_>>()
1084 .join("\n")
1085 );
1086 }
1087 }
1088 }
1089
1090 #[tracing::instrument(skip_all)]
1091 fn validate_members(&mut self) -> CargoResult<()> {
1092 for member in self.members.clone() {
1093 let root = self.find_root(&member)?;
1094 if root == self.root_manifest {
1095 continue;
1096 }
1097
1098 match root {
1099 Some(root) => {
1100 bail!(
1101 "package `{}` is a member of the wrong workspace\n\
1102 expected: {}\n\
1103 actual: {}",
1104 member.display(),
1105 self.root_manifest.as_ref().unwrap().display(),
1106 root.display()
1107 );
1108 }
1109 None => {
1110 bail!(
1111 "workspace member `{}` is not hierarchically below \
1112 the workspace root `{}`",
1113 member.display(),
1114 self.root_manifest.as_ref().unwrap().display()
1115 );
1116 }
1117 }
1118 }
1119 Ok(())
1120 }
1121
1122 fn error_if_manifest_not_in_members(&mut self) -> CargoResult<()> {
1123 if self.members.contains(&self.current_manifest) {
1124 return Ok(());
1125 }
1126
1127 let root = self.root_manifest.as_ref().unwrap();
1128 let root_dir = root.parent().unwrap();
1129 let current_dir = self.current_manifest.parent().unwrap();
1130 let root_pkg = self.packages.get(root);
1131
1132 let current_dir = paths::normalize_path(current_dir);
1138 let root_dir = paths::normalize_path(root_dir);
1139 let members_msg = match pathdiff::diff_paths(¤t_dir, &root_dir) {
1140 Some(rel) => format!(
1141 "this may be fixable by adding `{}` to the \
1142 `workspace.members` array of the manifest \
1143 located at: {}",
1144 rel.display(),
1145 root.display()
1146 ),
1147 None => format!(
1148 "this may be fixable by adding a member to \
1149 the `workspace.members` array of the \
1150 manifest located at: {}",
1151 root.display()
1152 ),
1153 };
1154 let extra = match *root_pkg {
1155 MaybePackage::Virtual(_) => members_msg,
1156 MaybePackage::Package(ref p) => {
1157 let has_members_list = match *p.manifest().workspace_config() {
1158 WorkspaceConfig::Root(ref root_config) => root_config.has_members_list(),
1159 WorkspaceConfig::Member { .. } => unreachable!(),
1160 };
1161 if !has_members_list {
1162 format!(
1163 "this may be fixable by ensuring that this \
1164 crate is depended on by the workspace \
1165 root: {}",
1166 root.display()
1167 )
1168 } else {
1169 members_msg
1170 }
1171 }
1172 };
1173 bail!(
1174 "current package believes it's in a workspace when it's not:\n\
1175 current: {}\n\
1176 workspace: {}\n\n{}\n\
1177 Alternatively, to keep it out of the workspace, add the package \
1178 to the `workspace.exclude` array, or add an empty `[workspace]` \
1179 table to the package's manifest.",
1180 self.current_manifest.display(),
1181 root.display(),
1182 extra
1183 );
1184 }
1185
1186 fn validate_manifest(&mut self) -> CargoResult<()> {
1187 if let Some(ref root_manifest) = self.root_manifest {
1188 for pkg in self
1189 .members()
1190 .filter(|p| p.manifest_path() != root_manifest)
1191 {
1192 let manifest = pkg.manifest();
1193 let emit_warning = |what| -> CargoResult<()> {
1194 let msg = format!(
1195 "{} for the non root package will be ignored, \
1196 specify {} at the workspace root:\n\
1197 package: {}\n\
1198 workspace: {}",
1199 what,
1200 what,
1201 pkg.manifest_path().display(),
1202 root_manifest.display(),
1203 );
1204 self.gctx.shell().warn(&msg)
1205 };
1206 if manifest.normalized_toml().has_profiles() {
1207 emit_warning("profiles")?;
1208 }
1209 if !manifest.replace().is_empty() {
1210 emit_warning("replace")?;
1211 }
1212 if !manifest.patch().is_empty() {
1213 emit_warning("patch")?;
1214 }
1215 if let Some(behavior) = manifest.resolve_behavior() {
1216 if behavior != self.resolve_behavior {
1217 emit_warning("resolver")?;
1219 }
1220 }
1221 }
1222 if let MaybePackage::Virtual(vm) = self.root_maybe() {
1223 if vm.resolve_behavior().is_none() {
1224 if let Some(edition) = self
1225 .members()
1226 .filter(|p| p.manifest_path() != root_manifest)
1227 .map(|p| p.manifest().edition())
1228 .filter(|&e| e >= Edition::Edition2021)
1229 .max()
1230 {
1231 let resolver = edition.default_resolve_behavior().to_manifest();
1232 let report = &[Level::WARNING
1233 .primary_title(format!(
1234 "virtual workspace defaulting to `resolver = \"1\"` despite one or more workspace members being on edition {edition} which implies `resolver = \"{resolver}\"`"
1235 ))
1236 .elements([
1237 Level::NOTE.message("to keep the current resolver, specify `workspace.resolver = \"1\"` in the workspace root's manifest"),
1238 Level::NOTE.message(
1239 format!("to use the edition {edition} resolver, specify `workspace.resolver = \"{resolver}\"` in the workspace root's manifest"),
1240 ),
1241 Level::NOTE.message("for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions"),
1242 ])];
1243 self.gctx.shell().print_report(report, false)?;
1244 }
1245 }
1246 }
1247 }
1248 Ok(())
1249 }
1250
1251 pub fn load(&self, manifest_path: &Path) -> CargoResult<Package> {
1252 match self.packages.maybe_get(manifest_path) {
1253 Some(MaybePackage::Package(p)) => return Ok(p.clone()),
1254 Some(&MaybePackage::Virtual(_)) => bail!("cannot load workspace root"),
1255 None => {}
1256 }
1257
1258 let mut loaded = self.loaded_packages.borrow_mut();
1259 if let Some(p) = loaded.get(manifest_path).cloned() {
1260 return Ok(p);
1261 }
1262 let source_id = SourceId::for_manifest_path(manifest_path)?;
1263 let package = ops::read_package(manifest_path, source_id, self.gctx)?;
1264 loaded.insert(manifest_path.to_path_buf(), package.clone());
1265 Ok(package)
1266 }
1267
1268 pub fn preload(&self, registry: &mut PackageRegistry<'gctx>) {
1275 if self.is_ephemeral {
1281 return;
1282 }
1283
1284 for pkg in self.packages.packages.values() {
1285 let pkg = match *pkg {
1286 MaybePackage::Package(ref p) => p.clone(),
1287 MaybePackage::Virtual(_) => continue,
1288 };
1289 let src = PathSource::preload_with(pkg, self.gctx);
1290 registry.add_preloaded(Box::new(src));
1291 }
1292 }
1293
1294 pub fn emit_warnings(&self) -> CargoResult<()> {
1295 let mut first_emitted_error = None;
1296
1297 if let Err(e) = self.emit_ws_lints() {
1298 first_emitted_error = Some(e);
1299 }
1300
1301 for (path, maybe_pkg) in &self.packages.packages {
1302 if let MaybePackage::Package(pkg) = maybe_pkg {
1303 if let Err(e) = self.emit_pkg_lints(pkg, &path)
1304 && first_emitted_error.is_none()
1305 {
1306 first_emitted_error = Some(e);
1307 }
1308 }
1309 let warnings = match maybe_pkg {
1310 MaybePackage::Package(pkg) => pkg.manifest().warnings().warnings(),
1311 MaybePackage::Virtual(vm) => vm.warnings().warnings(),
1312 };
1313 for warning in warnings {
1314 if warning.is_critical {
1315 let err = anyhow::format_err!("{}", warning.message);
1316 let cx =
1317 anyhow::format_err!("failed to parse manifest at `{}`", path.display());
1318 if first_emitted_error.is_none() {
1319 first_emitted_error = Some(err.context(cx));
1320 }
1321 } else {
1322 let msg = if self.root_manifest.is_none() {
1323 warning.message.to_string()
1324 } else {
1325 format!("{}: {}", path.display(), warning.message)
1328 };
1329 self.gctx.shell().warn(msg)?
1330 }
1331 }
1332 }
1333
1334 if let Some(error) = first_emitted_error {
1335 Err(error)
1336 } else {
1337 Ok(())
1338 }
1339 }
1340
1341 pub fn emit_pkg_lints(&self, pkg: &Package, path: &Path) -> CargoResult<()> {
1342 let toml_lints = pkg
1343 .manifest()
1344 .normalized_toml()
1345 .lints
1346 .clone()
1347 .map(|lints| lints.lints)
1348 .unwrap_or(manifest::TomlLints::default());
1349 let cargo_lints = toml_lints
1350 .get("cargo")
1351 .cloned()
1352 .unwrap_or(manifest::TomlToolLints::default());
1353
1354 if self.gctx.cli_unstable().cargo_lints {
1355 let mut verify_error_count = 0;
1356
1357 analyze_cargo_lints_table(
1358 pkg.into(),
1359 &path,
1360 &cargo_lints,
1361 &mut verify_error_count,
1362 self.gctx,
1363 )?;
1364
1365 if verify_error_count > 0 {
1366 let plural = if verify_error_count == 1 { "" } else { "s" };
1367 bail!("encountered {verify_error_count} error{plural} while verifying lints")
1368 }
1369
1370 let mut run_error_count = 0;
1371
1372 check_im_a_teapot(pkg, &path, &cargo_lints, &mut run_error_count, self.gctx)?;
1373 implicit_minimum_version_req_pkg(
1374 pkg,
1375 &path,
1376 &cargo_lints,
1377 &mut run_error_count,
1378 self.gctx,
1379 )?;
1380 non_kebab_case_packages(pkg, &path, &cargo_lints, &mut run_error_count, self.gctx)?;
1381 non_snake_case_packages(pkg, &path, &cargo_lints, &mut run_error_count, self.gctx)?;
1382 non_kebab_case_bins(
1383 self,
1384 pkg,
1385 &path,
1386 &cargo_lints,
1387 &mut run_error_count,
1388 self.gctx,
1389 )?;
1390 non_kebab_case_features(pkg, &path, &cargo_lints, &mut run_error_count, self.gctx)?;
1391 non_snake_case_features(pkg, &path, &cargo_lints, &mut run_error_count, self.gctx)?;
1392 redundant_readme(pkg, &path, &cargo_lints, &mut run_error_count, self.gctx)?;
1393 redundant_homepage(pkg, &path, &cargo_lints, &mut run_error_count, self.gctx)?;
1394 missing_lints_inheritance(
1395 self,
1396 pkg,
1397 &path,
1398 &cargo_lints,
1399 &mut run_error_count,
1400 self.gctx,
1401 )?;
1402
1403 if run_error_count > 0 {
1404 let plural = if run_error_count == 1 { "" } else { "s" };
1405 bail!("encountered {run_error_count} error{plural} while running lints")
1406 }
1407 }
1408
1409 Ok(())
1410 }
1411
1412 pub fn emit_ws_lints(&self) -> CargoResult<()> {
1413 let mut run_error_count = 0;
1414
1415 let cargo_lints = match self.root_maybe() {
1416 MaybePackage::Package(pkg) => {
1417 let toml = pkg.manifest().normalized_toml();
1418 if let Some(ws) = &toml.workspace {
1419 ws.lints.as_ref()
1420 } else {
1421 toml.lints.as_ref().map(|l| &l.lints)
1422 }
1423 }
1424 MaybePackage::Virtual(vm) => vm
1425 .normalized_toml()
1426 .workspace
1427 .as_ref()
1428 .unwrap()
1429 .lints
1430 .as_ref(),
1431 }
1432 .and_then(|t| t.get("cargo"))
1433 .cloned()
1434 .unwrap_or(manifest::TomlToolLints::default());
1435
1436 if self.gctx.cli_unstable().cargo_lints {
1437 let mut verify_error_count = 0;
1438
1439 analyze_cargo_lints_table(
1440 (self, self.root_maybe()).into(),
1441 self.root_manifest(),
1442 &cargo_lints,
1443 &mut verify_error_count,
1444 self.gctx,
1445 )?;
1446
1447 if verify_error_count > 0 {
1448 let plural = if verify_error_count == 1 { "" } else { "s" };
1449 bail!("encountered {verify_error_count} error{plural} while verifying lints")
1450 }
1451
1452 unused_workspace_package_fields(
1453 self,
1454 self.root_maybe(),
1455 self.root_manifest(),
1456 &cargo_lints,
1457 &mut run_error_count,
1458 self.gctx,
1459 )?;
1460 unused_workspace_dependencies(
1461 self,
1462 self.root_maybe(),
1463 self.root_manifest(),
1464 &cargo_lints,
1465 &mut run_error_count,
1466 self.gctx,
1467 )?;
1468 implicit_minimum_version_req_ws(
1469 self,
1470 self.root_maybe(),
1471 self.root_manifest(),
1472 &cargo_lints,
1473 &mut run_error_count,
1474 self.gctx,
1475 )?;
1476 }
1477
1478 if self.gctx.cli_unstable().profile_hint_mostly_unused {
1482 blanket_hint_mostly_unused(
1483 self,
1484 self.root_maybe(),
1485 self.root_manifest(),
1486 &cargo_lints,
1487 &mut run_error_count,
1488 self.gctx,
1489 )?;
1490 }
1491
1492 if run_error_count > 0 {
1493 let plural = if run_error_count == 1 { "" } else { "s" };
1494 bail!("encountered {run_error_count} error{plural} while running lints")
1495 } else {
1496 Ok(())
1497 }
1498 }
1499
1500 pub fn set_target_dir(&mut self, target_dir: Filesystem) {
1501 self.target_dir = Some(target_dir);
1502 }
1503
1504 pub fn members_with_features(
1512 &self,
1513 specs: &[PackageIdSpec],
1514 cli_features: &CliFeatures,
1515 ) -> CargoResult<Vec<(&Package, CliFeatures)>> {
1516 assert!(
1517 !specs.is_empty() || cli_features.all_features,
1518 "no specs requires all_features"
1519 );
1520 if specs.is_empty() {
1521 return Ok(self
1524 .members()
1525 .map(|m| (m, CliFeatures::new_all(true)))
1526 .collect());
1527 }
1528 if self.allows_new_cli_feature_behavior() {
1529 self.members_with_features_new(specs, cli_features)
1530 } else {
1531 Ok(self.members_with_features_old(specs, cli_features))
1532 }
1533 }
1534
1535 fn collect_matching_features(
1538 member: &Package,
1539 cli_features: &CliFeatures,
1540 found_features: &mut BTreeSet<FeatureValue>,
1541 ) -> CliFeatures {
1542 if cli_features.features.is_empty() {
1543 return cli_features.clone();
1544 }
1545
1546 let summary = member.summary();
1548
1549 let summary_features = summary.features();
1551
1552 let dependencies: BTreeMap<InternedString, &Dependency> = summary
1554 .dependencies()
1555 .iter()
1556 .map(|dep| (dep.name_in_toml(), dep))
1557 .collect();
1558
1559 let optional_dependency_names: BTreeSet<_> = dependencies
1561 .iter()
1562 .filter(|(_, dep)| dep.is_optional())
1563 .map(|(name, _)| name)
1564 .copied()
1565 .collect();
1566
1567 let mut features = BTreeSet::new();
1568
1569 let summary_or_opt_dependency_feature = |feature: &InternedString| -> bool {
1571 summary_features.contains_key(feature) || optional_dependency_names.contains(feature)
1572 };
1573
1574 for feature in cli_features.features.iter() {
1575 match feature {
1576 FeatureValue::Feature(f) => {
1577 if summary_or_opt_dependency_feature(f) {
1578 features.insert(feature.clone());
1580 found_features.insert(feature.clone());
1581 }
1582 }
1583 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1585 FeatureValue::DepFeature {
1586 dep_name,
1587 dep_feature,
1588 weak: _,
1589 } => {
1590 if dependencies.contains_key(dep_name) {
1591 features.insert(feature.clone());
1594 found_features.insert(feature.clone());
1595 } else if *dep_name == member.name()
1596 && summary_or_opt_dependency_feature(dep_feature)
1597 {
1598 features.insert(FeatureValue::Feature(*dep_feature));
1603 found_features.insert(feature.clone());
1604 }
1605 }
1606 }
1607 }
1608 CliFeatures {
1609 features: Rc::new(features),
1610 all_features: cli_features.all_features,
1611 uses_default_features: cli_features.uses_default_features,
1612 }
1613 }
1614
1615 fn missing_feature_spelling_suggestions(
1616 &self,
1617 selected_members: &[&Package],
1618 cli_features: &CliFeatures,
1619 found_features: &BTreeSet<FeatureValue>,
1620 ) -> Vec<String> {
1621 let mut summary_features: Vec<InternedString> = Default::default();
1623
1624 let mut dependencies_features: BTreeMap<InternedString, &[InternedString]> =
1626 Default::default();
1627
1628 let mut optional_dependency_names: Vec<InternedString> = Default::default();
1630
1631 let mut summary_features_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
1633 Default::default();
1634
1635 let mut optional_dependency_names_per_member: BTreeMap<&Package, BTreeSet<InternedString>> =
1637 Default::default();
1638
1639 for &member in selected_members {
1640 let summary = member.summary();
1642
1643 summary_features.extend(summary.features().keys());
1645 summary_features_per_member
1646 .insert(member, summary.features().keys().copied().collect());
1647
1648 let dependencies: BTreeMap<InternedString, &Dependency> = summary
1650 .dependencies()
1651 .iter()
1652 .map(|dep| (dep.name_in_toml(), dep))
1653 .collect();
1654
1655 dependencies_features.extend(
1656 dependencies
1657 .iter()
1658 .map(|(name, dep)| (*name, dep.features())),
1659 );
1660
1661 let optional_dependency_names_raw: BTreeSet<_> = dependencies
1663 .iter()
1664 .filter(|(_, dep)| dep.is_optional())
1665 .map(|(name, _)| name)
1666 .copied()
1667 .collect();
1668
1669 optional_dependency_names.extend(optional_dependency_names_raw.iter());
1670 optional_dependency_names_per_member.insert(member, optional_dependency_names_raw);
1671 }
1672
1673 let edit_distance_test = |a: InternedString, b: InternedString| {
1674 edit_distance(a.as_str(), b.as_str(), 3).is_some()
1675 };
1676
1677 cli_features
1678 .features
1679 .difference(found_features)
1680 .map(|feature| match feature {
1681 FeatureValue::Feature(typo) => {
1683 let summary_features = summary_features
1685 .iter()
1686 .filter(move |feature| edit_distance_test(**feature, *typo));
1687
1688 let optional_dependency_features = optional_dependency_names
1690 .iter()
1691 .filter(move |feature| edit_distance_test(**feature, *typo));
1692
1693 summary_features
1694 .chain(optional_dependency_features)
1695 .map(|s| s.to_string())
1696 .collect::<Vec<_>>()
1697 }
1698 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1699 FeatureValue::DepFeature {
1700 dep_name,
1701 dep_feature,
1702 weak: _,
1703 } => {
1704 let pkg_feat_similar = dependencies_features
1706 .iter()
1707 .filter(|(name, _)| edit_distance_test(**name, *dep_name))
1708 .map(|(name, features)| {
1709 (
1710 name,
1711 features
1712 .iter()
1713 .filter(|feature| edit_distance_test(**feature, *dep_feature))
1714 .collect::<Vec<_>>(),
1715 )
1716 })
1717 .map(|(name, features)| {
1718 features
1719 .into_iter()
1720 .map(move |feature| format!("{}/{}", name, feature))
1721 })
1722 .flatten();
1723
1724 let optional_dependency_features = optional_dependency_names_per_member
1726 .iter()
1727 .filter(|(package, _)| edit_distance_test(package.name(), *dep_name))
1728 .map(|(package, optional_dependencies)| {
1729 optional_dependencies
1730 .into_iter()
1731 .filter(|optional_dependency| {
1732 edit_distance_test(**optional_dependency, *dep_name)
1733 })
1734 .map(move |optional_dependency| {
1735 format!("{}/{}", package.name(), optional_dependency)
1736 })
1737 })
1738 .flatten();
1739
1740 let summary_features = summary_features_per_member
1742 .iter()
1743 .filter(|(package, _)| edit_distance_test(package.name(), *dep_name))
1744 .map(|(package, summary_features)| {
1745 summary_features
1746 .into_iter()
1747 .filter(|summary_feature| {
1748 edit_distance_test(**summary_feature, *dep_feature)
1749 })
1750 .map(move |summary_feature| {
1751 format!("{}/{}", package.name(), summary_feature)
1752 })
1753 })
1754 .flatten();
1755
1756 pkg_feat_similar
1757 .chain(optional_dependency_features)
1758 .chain(summary_features)
1759 .collect::<Vec<_>>()
1760 }
1761 })
1762 .map(|v| v.into_iter())
1763 .flatten()
1764 .unique()
1765 .filter(|element| {
1766 let feature = FeatureValue::new(element.into());
1767 !cli_features.features.contains(&feature) && !found_features.contains(&feature)
1768 })
1769 .sorted()
1770 .take(5)
1771 .collect()
1772 }
1773
1774 fn report_unknown_features_error(
1775 &self,
1776 specs: &[PackageIdSpec],
1777 cli_features: &CliFeatures,
1778 found_features: &BTreeSet<FeatureValue>,
1779 ) -> CargoResult<()> {
1780 let unknown: Vec<_> = cli_features
1781 .features
1782 .difference(found_features)
1783 .map(|feature| feature.to_string())
1784 .sorted()
1785 .collect();
1786
1787 let (selected_members, unselected_members): (Vec<_>, Vec<_>) = self
1788 .members()
1789 .partition(|member| specs.iter().any(|spec| spec.matches(member.package_id())));
1790
1791 let missing_packages_with_the_features = unselected_members
1792 .into_iter()
1793 .filter(|member| {
1794 unknown
1795 .iter()
1796 .any(|feature| member.summary().features().contains_key(&**feature))
1797 })
1798 .map(|m| m.name())
1799 .collect_vec();
1800
1801 let these_features = if unknown.len() == 1 {
1802 "this feature"
1803 } else {
1804 "these features"
1805 };
1806 let mut msg = if let [singular] = &selected_members[..] {
1807 format!(
1808 "the package '{}' does not contain {these_features}: {}",
1809 singular.name(),
1810 unknown.join(", ")
1811 )
1812 } else {
1813 let names = selected_members.iter().map(|m| m.name()).join(", ");
1814 format!(
1815 "none of the selected packages contains {these_features}: {}\nselected packages: {names}",
1816 unknown.join(", ")
1817 )
1818 };
1819
1820 use std::fmt::Write;
1821 if !missing_packages_with_the_features.is_empty() {
1822 write!(
1823 &mut msg,
1824 "\nhelp: package{} with the missing feature{}: {}",
1825 if missing_packages_with_the_features.len() != 1 {
1826 "s"
1827 } else {
1828 ""
1829 },
1830 if unknown.len() != 1 { "s" } else { "" },
1831 missing_packages_with_the_features.join(", ")
1832 )?;
1833 } else {
1834 let suggestions = self.missing_feature_spelling_suggestions(
1835 &selected_members,
1836 cli_features,
1837 found_features,
1838 );
1839 if !suggestions.is_empty() {
1840 write!(
1841 &mut msg,
1842 "\nhelp: there {}: {}",
1843 if suggestions.len() == 1 {
1844 "is a similarly named feature"
1845 } else {
1846 "are similarly named features"
1847 },
1848 suggestions.join(", ")
1849 )?;
1850 }
1851 }
1852
1853 bail!("{msg}")
1854 }
1855
1856 fn members_with_features_new(
1859 &self,
1860 specs: &[PackageIdSpec],
1861 cli_features: &CliFeatures,
1862 ) -> CargoResult<Vec<(&Package, CliFeatures)>> {
1863 let mut found_features = Default::default();
1866
1867 let members: Vec<(&Package, CliFeatures)> = self
1868 .members()
1869 .filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
1870 .map(|m| {
1871 (
1872 m,
1873 Workspace::collect_matching_features(m, cli_features, &mut found_features),
1874 )
1875 })
1876 .collect();
1877
1878 if members.is_empty() {
1879 if !(cli_features.features.is_empty()
1882 && !cli_features.all_features
1883 && cli_features.uses_default_features)
1884 {
1885 bail!("cannot specify features for packages outside of workspace");
1886 }
1887 return Ok(self
1890 .members()
1891 .map(|m| (m, CliFeatures::new_all(false)))
1892 .collect());
1893 }
1894 if *cli_features.features != found_features {
1895 self.report_unknown_features_error(specs, cli_features, &found_features)?;
1896 }
1897 Ok(members)
1898 }
1899
1900 fn members_with_features_old(
1903 &self,
1904 specs: &[PackageIdSpec],
1905 cli_features: &CliFeatures,
1906 ) -> Vec<(&Package, CliFeatures)> {
1907 let mut member_specific_features: HashMap<InternedString, BTreeSet<FeatureValue>> =
1910 HashMap::new();
1911 let mut cwd_features = BTreeSet::new();
1913 for feature in cli_features.features.iter() {
1914 match feature {
1915 FeatureValue::Feature(_) => {
1916 cwd_features.insert(feature.clone());
1917 }
1918 FeatureValue::Dep { .. } => panic!("unexpected dep: syntax {}", feature),
1920 FeatureValue::DepFeature {
1921 dep_name,
1922 dep_feature,
1923 weak: _,
1924 } => {
1925 let is_member = self.members().any(|member| {
1931 self.current_opt() != Some(member) && member.name() == *dep_name
1933 });
1934 if is_member && specs.iter().any(|spec| spec.name() == dep_name.as_str()) {
1935 member_specific_features
1936 .entry(*dep_name)
1937 .or_default()
1938 .insert(FeatureValue::Feature(*dep_feature));
1939 } else {
1940 cwd_features.insert(feature.clone());
1941 }
1942 }
1943 }
1944 }
1945
1946 let ms: Vec<_> = self
1947 .members()
1948 .filter_map(|member| {
1949 let member_id = member.package_id();
1950 match self.current_opt() {
1951 Some(current) if member_id == current.package_id() => {
1954 let feats = CliFeatures {
1955 features: Rc::new(cwd_features.clone()),
1956 all_features: cli_features.all_features,
1957 uses_default_features: cli_features.uses_default_features,
1958 };
1959 Some((member, feats))
1960 }
1961 _ => {
1962 if specs.iter().any(|spec| spec.matches(member_id)) {
1964 let feats = CliFeatures {
1974 features: Rc::new(
1975 member_specific_features
1976 .remove(member.name().as_str())
1977 .unwrap_or_default(),
1978 ),
1979 uses_default_features: true,
1980 all_features: cli_features.all_features,
1981 };
1982 Some((member, feats))
1983 } else {
1984 None
1986 }
1987 }
1988 }
1989 })
1990 .collect();
1991
1992 assert!(member_specific_features.is_empty());
1995
1996 ms
1997 }
1998
1999 pub fn unit_needs_doc_scrape(&self, unit: &Unit) -> bool {
2001 self.is_member(&unit.pkg) && !(unit.target.for_host() || unit.pkg.proc_macro())
2006 }
2007
2008 pub fn add_local_overlay(&mut self, id: SourceId, registry_path: PathBuf) {
2012 self.local_overlays.insert(id, registry_path);
2013 }
2014
2015 pub fn package_registry(&self) -> CargoResult<PackageRegistry<'gctx>> {
2017 let source_config =
2018 SourceConfigMap::new_with_overlays(self.gctx(), self.local_overlays()?)?;
2019 PackageRegistry::new_with_source_config(self.gctx(), source_config)
2020 }
2021
2022 fn local_overlays(&self) -> CargoResult<impl Iterator<Item = (SourceId, SourceId)>> {
2024 let mut ret = self
2025 .local_overlays
2026 .iter()
2027 .map(|(id, path)| Ok((*id, SourceId::for_local_registry(path)?)))
2028 .collect::<CargoResult<Vec<_>>>()?;
2029
2030 if let Ok(overlay) = self
2031 .gctx
2032 .get_env("__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS")
2033 {
2034 let (url, path) = overlay.split_once('=').ok_or(anyhow::anyhow!(
2035 "invalid overlay format. I won't tell you why; you shouldn't be using it anyway"
2036 ))?;
2037 ret.push((
2038 SourceId::from_url(url)?,
2039 SourceId::for_local_registry(path.as_ref())?,
2040 ));
2041 }
2042
2043 Ok(ret.into_iter())
2044 }
2045}
2046
2047impl<'gctx> Packages<'gctx> {
2048 fn get(&self, manifest_path: &Path) -> &MaybePackage {
2049 self.maybe_get(manifest_path).unwrap()
2050 }
2051
2052 fn get_mut(&mut self, manifest_path: &Path) -> &mut MaybePackage {
2053 self.maybe_get_mut(manifest_path).unwrap()
2054 }
2055
2056 fn maybe_get(&self, manifest_path: &Path) -> Option<&MaybePackage> {
2057 self.packages.get(manifest_path)
2058 }
2059
2060 fn maybe_get_mut(&mut self, manifest_path: &Path) -> Option<&mut MaybePackage> {
2061 self.packages.get_mut(manifest_path)
2062 }
2063
2064 fn load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage> {
2065 match self.packages.entry(manifest_path.to_path_buf()) {
2066 Entry::Occupied(e) => Ok(e.into_mut()),
2067 Entry::Vacant(v) => {
2068 let source_id = SourceId::for_manifest_path(manifest_path)?;
2069 let manifest = read_manifest(manifest_path, source_id, self.gctx)?;
2070 Ok(v.insert(match manifest {
2071 EitherManifest::Real(manifest) => {
2072 MaybePackage::Package(Package::new(manifest, manifest_path))
2073 }
2074 EitherManifest::Virtual(vm) => MaybePackage::Virtual(vm),
2075 }))
2076 }
2077 }
2078 }
2079}
2080
2081impl MaybePackage {
2082 fn workspace_config(&self) -> &WorkspaceConfig {
2083 match *self {
2084 MaybePackage::Package(ref p) => p.manifest().workspace_config(),
2085 MaybePackage::Virtual(ref vm) => vm.workspace_config(),
2086 }
2087 }
2088
2089 pub fn is_embedded(&self) -> bool {
2091 match self {
2092 MaybePackage::Package(p) => p.manifest().is_embedded(),
2093 MaybePackage::Virtual(_) => false,
2094 }
2095 }
2096
2097 pub fn contents(&self) -> Option<&str> {
2098 match self {
2099 MaybePackage::Package(p) => p.manifest().contents(),
2100 MaybePackage::Virtual(v) => v.contents(),
2101 }
2102 }
2103
2104 pub fn document(&self) -> Option<&toml::Spanned<toml::de::DeTable<'static>>> {
2105 match self {
2106 MaybePackage::Package(p) => p.manifest().document(),
2107 MaybePackage::Virtual(v) => v.document(),
2108 }
2109 }
2110
2111 pub fn original_toml(&self) -> Option<&TomlManifest> {
2112 match self {
2113 MaybePackage::Package(p) => p.manifest().original_toml(),
2114 MaybePackage::Virtual(v) => v.original_toml(),
2115 }
2116 }
2117
2118 pub fn normalized_toml(&self) -> &TomlManifest {
2119 match self {
2120 MaybePackage::Package(p) => p.manifest().normalized_toml(),
2121 MaybePackage::Virtual(v) => v.normalized_toml(),
2122 }
2123 }
2124
2125 pub fn edition(&self) -> Edition {
2126 match self {
2127 MaybePackage::Package(p) => p.manifest().edition(),
2128 MaybePackage::Virtual(_) => Edition::default(),
2129 }
2130 }
2131
2132 pub fn profiles(&self) -> Option<&TomlProfiles> {
2133 match self {
2134 MaybePackage::Package(p) => p.manifest().profiles(),
2135 MaybePackage::Virtual(v) => v.profiles(),
2136 }
2137 }
2138
2139 pub fn unstable_features(&self) -> &Features {
2140 match self {
2141 MaybePackage::Package(p) => p.manifest().unstable_features(),
2142 MaybePackage::Virtual(vm) => vm.unstable_features(),
2143 }
2144 }
2145}
2146
2147impl WorkspaceRootConfig {
2148 pub fn new(
2150 root_dir: &Path,
2151 members: &Option<Vec<String>>,
2152 default_members: &Option<Vec<String>>,
2153 exclude: &Option<Vec<String>>,
2154 inheritable: &Option<InheritableFields>,
2155 custom_metadata: &Option<toml::Value>,
2156 ) -> WorkspaceRootConfig {
2157 WorkspaceRootConfig {
2158 root_dir: root_dir.to_path_buf(),
2159 members: members.clone(),
2160 default_members: default_members.clone(),
2161 exclude: exclude.clone().unwrap_or_default(),
2162 inheritable_fields: inheritable.clone().unwrap_or_default(),
2163 custom_metadata: custom_metadata.clone(),
2164 }
2165 }
2166 fn is_excluded(&self, manifest_path: &Path) -> bool {
2170 let excluded = self
2171 .exclude
2172 .iter()
2173 .any(|ex| manifest_path.starts_with(self.root_dir.join(ex)));
2174
2175 let explicit_member = match self.members {
2176 Some(ref members) => members
2177 .iter()
2178 .any(|mem| manifest_path.starts_with(self.root_dir.join(mem))),
2179 None => false,
2180 };
2181
2182 !explicit_member && excluded
2183 }
2184
2185 fn is_explicitly_listed_member(&self, manifest_path: &Path) -> bool {
2196 let root_manifest = self.root_dir.join("Cargo.toml");
2197 if manifest_path == root_manifest {
2198 return true;
2199 }
2200 match self.members {
2201 Some(ref members) => {
2202 let Ok(expanded_members) = self.members_paths(members) else {
2204 return false;
2205 };
2206 let normalized_manifest = paths::normalize_path(manifest_path);
2208 expanded_members.iter().any(|(member_path, _)| {
2209 let normalized_member = paths::normalize_path(member_path);
2211 normalized_manifest.parent() == Some(normalized_member.as_path())
2214 })
2215 }
2216 None => false,
2217 }
2218 }
2219
2220 fn has_members_list(&self) -> bool {
2221 self.members.is_some()
2222 }
2223
2224 fn has_default_members(&self) -> bool {
2226 self.default_members.is_some()
2227 }
2228
2229 #[tracing::instrument(skip_all)]
2232 fn members_paths<'g>(
2233 &self,
2234 globs: &'g [String],
2235 ) -> CargoResult<Vec<(PathBuf, Option<&'g str>)>> {
2236 let mut expanded_list = Vec::new();
2237
2238 for glob in globs {
2239 let pathbuf = self.root_dir.join(glob);
2240 let expanded_paths = Self::expand_member_path(&pathbuf)?;
2241
2242 if expanded_paths.is_empty() {
2245 expanded_list.push((pathbuf, None));
2246 } else {
2247 let used_glob_pattern = expanded_paths.len() > 1 || expanded_paths[0] != pathbuf;
2248 let glob = used_glob_pattern.then_some(glob.as_str());
2249
2250 for expanded_path in expanded_paths {
2256 if expanded_path.is_dir() {
2257 expanded_list.push((expanded_path, glob));
2258 }
2259 }
2260 }
2261 }
2262
2263 Ok(expanded_list)
2264 }
2265
2266 fn expand_member_path(path: &Path) -> CargoResult<Vec<PathBuf>> {
2267 let Some(path) = path.to_str() else {
2268 return Ok(Vec::new());
2269 };
2270 let res = glob(path).with_context(|| format!("could not parse pattern `{}`", &path))?;
2271 let res = res
2272 .map(|p| p.with_context(|| format!("unable to match path to pattern `{}`", &path)))
2273 .collect::<Result<Vec<_>, _>>()?;
2274 Ok(res)
2275 }
2276
2277 pub fn inheritable(&self) -> &InheritableFields {
2278 &self.inheritable_fields
2279 }
2280}
2281
2282pub fn resolve_relative_path(
2283 label: &str,
2284 old_root: &Path,
2285 new_root: &Path,
2286 rel_path: &str,
2287) -> CargoResult<String> {
2288 let joined_path = normalize_path(&old_root.join(rel_path));
2289 match diff_paths(joined_path, new_root) {
2290 None => Err(anyhow!(
2291 "`{}` was defined in {} but could not be resolved with {}",
2292 label,
2293 old_root.display(),
2294 new_root.display()
2295 )),
2296 Some(path) => Ok(path
2297 .to_str()
2298 .ok_or_else(|| {
2299 anyhow!(
2300 "`{}` resolved to non-UTF value (`{}`)",
2301 label,
2302 path.display()
2303 )
2304 })?
2305 .to_owned()),
2306 }
2307}
2308
2309pub fn find_workspace_root(
2311 manifest_path: &Path,
2312 gctx: &GlobalContext,
2313) -> CargoResult<Option<PathBuf>> {
2314 find_workspace_root_with_loader(manifest_path, gctx, |self_path| {
2315 let source_id = SourceId::for_manifest_path(self_path)?;
2316 let manifest = read_manifest(self_path, source_id, gctx)?;
2317 Ok(manifest
2318 .workspace_config()
2319 .get_ws_root(self_path, manifest_path))
2320 })
2321}
2322
2323pub fn find_workspace_root_with_membership_check(
2330 manifest_path: &Path,
2331 gctx: &GlobalContext,
2332) -> CargoResult<Option<PathBuf>> {
2333 let source_id = SourceId::for_manifest_path(manifest_path)?;
2334 let current_manifest = read_manifest(manifest_path, source_id, gctx)?;
2335
2336 match current_manifest.workspace_config() {
2337 WorkspaceConfig::Root(root_config) => {
2338 if root_config.has_default_members() {
2341 Ok(None)
2342 } else {
2343 Ok(Some(manifest_path.to_path_buf()))
2344 }
2345 }
2346 WorkspaceConfig::Member {
2347 root: Some(path_to_root),
2348 } => {
2349 let ws_manifest_path = read_root_pointer(manifest_path, path_to_root);
2351 let ws_source_id = SourceId::for_manifest_path(&ws_manifest_path)?;
2352 let ws_manifest = read_manifest(&ws_manifest_path, ws_source_id, gctx)?;
2353
2354 if let WorkspaceConfig::Root(ref root_config) = *ws_manifest.workspace_config() {
2356 if root_config.is_explicitly_listed_member(manifest_path)
2357 && !root_config.is_excluded(manifest_path)
2358 {
2359 return Ok(Some(ws_manifest_path));
2360 }
2361 }
2362 Ok(None)
2364 }
2365 WorkspaceConfig::Member { root: None } => {
2366 find_workspace_root_with_loader(manifest_path, gctx, |candidate_manifest_path| {
2368 let source_id = SourceId::for_manifest_path(candidate_manifest_path)?;
2369 let manifest = read_manifest(candidate_manifest_path, source_id, gctx)?;
2370 if let WorkspaceConfig::Root(ref root_config) = *manifest.workspace_config() {
2371 if root_config.is_explicitly_listed_member(manifest_path)
2372 && !root_config.is_excluded(manifest_path)
2373 {
2374 return Ok(Some(candidate_manifest_path.to_path_buf()));
2375 }
2376 }
2377 Ok(None)
2378 })
2379 }
2380 }
2381}
2382
2383fn find_workspace_root_with_loader(
2388 manifest_path: &Path,
2389 gctx: &GlobalContext,
2390 mut loader: impl FnMut(&Path) -> CargoResult<Option<PathBuf>>,
2391) -> CargoResult<Option<PathBuf>> {
2392 {
2394 let roots = gctx.ws_roots();
2395 for current in manifest_path.ancestors().skip(1) {
2398 if let Some(ws_config) = roots.get(current) {
2399 if !ws_config.is_excluded(manifest_path) {
2400 return Ok(Some(current.join("Cargo.toml")));
2402 }
2403 }
2404 }
2405 }
2406
2407 for ances_manifest_path in find_root_iter(manifest_path, gctx) {
2408 debug!("find_root - trying {}", ances_manifest_path.display());
2409 if let Some(ws_root_path) = loader(&ances_manifest_path)? {
2410 return Ok(Some(ws_root_path));
2411 }
2412 }
2413 Ok(None)
2414}
2415
2416fn read_root_pointer(member_manifest: &Path, root_link: &str) -> PathBuf {
2417 let path = member_manifest
2418 .parent()
2419 .unwrap()
2420 .join(root_link)
2421 .join("Cargo.toml");
2422 debug!("find_root - pointer {}", path.display());
2423 paths::normalize_path(&path)
2424}
2425
2426fn find_root_iter<'a>(
2427 manifest_path: &'a Path,
2428 gctx: &'a GlobalContext,
2429) -> impl Iterator<Item = PathBuf> + 'a {
2430 LookBehind::new(paths::ancestors(manifest_path, None).skip(2))
2431 .take_while(|path| !path.curr.ends_with("target/package"))
2432 .take_while(|path| {
2438 if let Some(last) = path.last {
2439 gctx.home() != last
2440 } else {
2441 true
2442 }
2443 })
2444 .map(|path| path.curr.join("Cargo.toml"))
2445 .filter(|ances_manifest_path| ances_manifest_path.exists())
2446}
2447
2448struct LookBehindWindow<'a, T: ?Sized> {
2449 curr: &'a T,
2450 last: Option<&'a T>,
2451}
2452
2453struct LookBehind<'a, T: ?Sized, K: Iterator<Item = &'a T>> {
2454 iter: K,
2455 last: Option<&'a T>,
2456}
2457
2458impl<'a, T: ?Sized, K: Iterator<Item = &'a T>> LookBehind<'a, T, K> {
2459 fn new(items: K) -> Self {
2460 Self {
2461 iter: items,
2462 last: None,
2463 }
2464 }
2465}
2466
2467impl<'a, T: ?Sized, K: Iterator<Item = &'a T>> Iterator for LookBehind<'a, T, K> {
2468 type Item = LookBehindWindow<'a, T>;
2469
2470 fn next(&mut self) -> Option<Self::Item> {
2471 match self.iter.next() {
2472 None => None,
2473 Some(next) => {
2474 let last = self.last;
2475 self.last = Some(next);
2476 Some(LookBehindWindow { curr: next, last })
2477 }
2478 }
2479 }
2480}