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