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