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}
;
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}
;
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}
;
25 /// The core abstraction in Cargo for working with a workspace of crates.
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.
31 pub struct Workspace
<'cfg
> {
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
,
39 // A list of packages found in this workspace. Always includes at least the
40 // package mentioned by `current_manifest`.
41 packages
: Packages
<'cfg
>,
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
>,
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
>,
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`
56 members
: Vec
<PathBuf
>,
57 member_ids
: HashSet
<PackageId
>,
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`
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
>,
71 // `true` if this is a temporary workspace created for the purposes of the
72 // `cargo install` or `cargo package` commands.
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
,
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
>>,
85 // If `true`, then the resolver will ignore any existing `Cargo.lock`
86 // file. This is set for `cargo install` without `--locked`.
89 /// The resolver behavior specified with the `resolver` field.
90 resolve_behavior
: Option
<ResolveBehavior
>,
93 // Separate structure for tracking loaded packages (to avoid loading anything
94 // twice), and this is separate to help appease the borrow checker.
96 struct Packages
<'cfg
> {
98 packages
: HashMap
<PathBuf
, MaybePackage
>,
104 Virtual(VirtualManifest
),
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
),
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> }
,
119 /// Intermediate configuration of a workspace root in a manifest.
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
{
126 members
: Option
<Vec
<String
>>,
127 default_members
: Option
<Vec
<String
>>,
128 exclude
: Vec
<String
>,
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
>,
138 impl<'cfg
> Workspace
<'cfg
> {
139 /// Creates a new workspace given the target manifest pointed to by
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
)?
;
150 ws
.resolve_behavior
= match ws
.root_maybe() {
151 MaybePackage
::Package(p
) => p
.manifest().resolve_behavior(),
152 MaybePackage
::Virtual(vm
) => vm
.resolve_behavior(),
158 fn new_default(current_manifest
: PathBuf
, config
: &'cfg Config
) -> Workspace
<'cfg
> {
164 packages
: HashMap
::new(),
169 member_ids
: HashSet
::new(),
170 default_members
: Vec
::new(),
172 require_optional_deps
: true,
173 loaded_packages
: RefCell
::new(HashMap
::new()),
175 resolve_behavior
: None
,
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();
191 .insert(root_path
, MaybePackage
::Virtual(manifest
));
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.
198 /// Creates a "temporary workspace" from one package which only contains
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.
205 /// This is currently only used in niche situations like `cargo install` or
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
{
224 ws
.config
.target_dir()?
226 ws
.members
.push(ws
.current_manifest
.clone());
227 ws
.member_ids
.insert(id
);
228 ws
.default_members
.push(ws
.current_manifest
.clone());
232 /// Returns the current package of this workspace.
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(|| {
240 "manifest path `{}` is a virtual manifest, but this \
241 command requires running against an actual package in \
243 self.current_manifest
.display()
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(|| {
253 "manifest path `{}` is a virtual manifest, but this \
254 command requires running against an actual package in \
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
,
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
,
276 pub fn is_virtual(&self) -> bool
{
277 match *self.packages
.get(&self.current_manifest
) {
278 MaybePackage
::Package(..) => false,
279 MaybePackage
::Virtual(..) => true,
283 /// Returns the `Config` this workspace is associated with.
284 pub fn config(&self) -> &'cfg Config
{
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(),
295 /// Returns the root path of this workspace.
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
{
302 None
=> &self.current_manifest
,
308 /// Returns the root Package or VirtualManifest.
309 fn root_maybe(&self) -> &MaybePackage
{
313 .unwrap_or(&self.current_manifest
);
314 self.packages
.get(root
)
317 pub fn target_dir(&self) -> Filesystem
{
320 .unwrap_or_else(|| Filesystem
::new(self.root().join("target")))
323 /// Returns the root `[replace]` section of this workspace.
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(),
333 /// Returns the root `[patch]` section of this workspace.
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(),
343 /// Returns an iterator over all packages in this workspace
344 pub fn members
<'a
>(&'a
self) -> Members
<'a
, 'cfg
> {
347 iter
: self.members
.iter(),
351 /// Returns an iterator over default packages in this workspace
352 pub fn default_members
<'a
>(&'a
self) -> Members
<'a
, 'cfg
> {
355 iter
: self.default_members
.iter(),
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())
364 pub fn is_ephemeral(&self) -> bool
{
368 pub fn require_optional_deps(&self) -> bool
{
369 self.require_optional_deps
372 pub fn set_require_optional_deps(
374 require_optional_deps
: bool
,
375 ) -> &mut Workspace
<'cfg
> {
376 self.require_optional_deps
= require_optional_deps
;
380 pub fn ignore_lock(&self) -> bool
{
384 pub fn set_ignore_lock(&mut self, ignore_lock
: bool
) -> &mut Workspace
<'cfg
> {
385 self.ignore_lock
= ignore_lock
;
389 /// Finds the root of a workspace for the crate whose manifest is located
390 /// at `manifest_path`.
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.
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
405 debug
!("find_root - pointer {}", path
.display());
406 Ok(paths
::normalize_path(&path
))
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()));
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 }
=> {}
423 for path
in paths
::ancestors(manifest_path
).skip(2) {
424 if path
.ends_with("target/package") {
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
));
439 WorkspaceConfig
::Member
{
440 root
: Some(ref path_to_root
),
442 debug
!("find_root - found pointer");
443 return Ok(Some(read_root_pointer(&ances_manifest_path
, path_to_root
)?
));
445 WorkspaceConfig
::Member { .. }
=> {}
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
{
462 /// After the root of a workspace has been located, probes for all members
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
469 fn find_members(&mut self) -> CargoResult
<()> {
470 let root_manifest_path
= match self.root_manifest
{
471 Some(ref path
) => path
.clone(),
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
);
485 let default_members_paths
;
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)?
)
503 "root of a workspace inferred but wasn't a root: {}",
504 root_manifest_path
.display()
509 for path
in members_paths
{
510 self.find_path_deps(&path
.join("Cargo.toml"), &root_manifest_path
, false)?
;
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
) {
518 "package `{}` is listed in workspace’s default-members \
519 but is not a member.",
523 self.default_members
.push(manifest_path
)
525 } else if self.is_virtual() {
526 self.default_members
= self.members
.clone()
528 self.default_members
.push(self.current_manifest
.clone())
531 self.find_path_deps(&root_manifest_path
, &root_manifest_path
, false)
536 manifest_path
: &Path
,
537 root_manifest
: &Path
,
539 ) -> CargoResult
<()> {
540 let manifest_path
= paths
::normalize_path(manifest_path
);
541 if self.members
.contains(&manifest_path
) {
545 && !manifest_path
.parent().unwrap().starts_with(self.root())
546 && self.find_root(&manifest_path
)?
!= self.root_manifest
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.
553 if let WorkspaceConfig
::Root(ref root_config
) =
554 *self.packages
.load(root_manifest
)?
.workspace_config()
556 if root_config
.is_excluded(&manifest_path
) {
561 debug
!("find_members - {}", manifest_path
.display());
562 self.members
.push(manifest_path
.clone());
565 let pkg
= match *self.packages
.load(&manifest_path
)?
{
566 MaybePackage
::Package(ref p
) => p
,
567 MaybePackage
::Virtual(_
) => return Ok(()),
569 self.member_ids
.insert(pkg
.package_id());
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"))
578 for candidate
in candidates
{
579 self.find_path_deps(&candidate
, root_manifest
, true)
580 .map_err(|err
| ManifestError
::new(err
, manifest_path
.clone()))?
;
585 pub fn features(&self) -> &Features
{
586 match self.root_maybe() {
587 MaybePackage
::Package(p
) => p
.manifest().features(),
588 MaybePackage
::Virtual(vm
) => vm
.features(),
592 pub fn resolve_behavior(&self) -> ResolveBehavior
{
593 self.resolve_behavior
.unwrap_or(ResolveBehavior
::V1
)
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,
604 /// Validates a workspace, ensuring that a number of invariants are upheld:
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() {
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()
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,
630 if let Some(prev
) = names
.insert(name
, member
) {
632 "two packages named `{}` in this workspace:\n\
644 fn validate_workspace_roots(&self) -> CargoResult
<()> {
645 let roots
: Vec
<PathBuf
> = self
649 let config
= self.packages
.get(member
).workspace_config();
650 matches
!(config
, WorkspaceConfig
::Root(_
))
652 .map(|member
| member
.parent().unwrap().to_path_buf())
657 "`package.workspace` configuration points to a crate \
658 which is not configured with [workspace]: \n\
659 configuration at: {}\n\
661 self.current_manifest
.display(),
662 self.root_manifest
.as_ref().unwrap().display()
666 "multiple workspace roots found in the same workspace:\n{}",
669 .map(|r
| format
!(" {}", r
.display()))
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
{
687 "package `{}` is a member of the wrong workspace\n\
691 self.root_manifest
.as_ref().unwrap().display(),
697 "workspace member `{}` is not hierarchically below \
698 the workspace root `{}`",
700 self.root_manifest
.as_ref().unwrap().display()
708 fn error_if_manifest_not_in_members(&mut self) -> CargoResult
<()> {
709 if self.members
.contains(&self.current_manifest
) {
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
);
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
) {
721 "this may be fixable by adding `{}` to the \
722 `workspace.members` array of the manifest \
728 "this may be fixable by adding a member to \
729 the `workspace.members` array of the \
730 manifest located at: {}",
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
!(),
741 if !has_members_list
{
743 "this may be fixable by ensuring that this \
744 crate is depended on by the workspace \
754 "current package believes it's in a workspace when it's not:\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(),
766 fn validate_manifest(&mut self) -> CargoResult
<()> {
767 if let Some(ref root_manifest
) = self.root_manifest
{
770 .filter(|p
| p
.manifest_path() != root_manifest
)
772 let manifest
= pkg
.manifest();
773 let emit_warning
= |what
| -> CargoResult
<()> {
775 "{} for the non root package will be ignored, \
776 specify {} at the workspace root:\n\
781 pkg
.manifest_path().display(),
782 root_manifest
.display(),
784 self.config
.shell().warn(&msg
)
786 if manifest
.original().has_profiles() {
787 emit_warning("profiles")?
;
789 if !manifest
.replace().is_empty() {
790 emit_warning("replace")?
;
792 if !manifest
.patch().is_empty() {
793 emit_warning("patch")?
;
795 if manifest
.resolve_behavior().is_some()
796 && manifest
.resolve_behavior() != self.resolve_behavior
798 // Only warn if they don't match.
799 emit_warning("resolver")?
;
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"),
813 let mut loaded
= self.loaded_packages
.borrow_mut();
814 if let Some(p
) = loaded
.get(manifest_path
).cloned() {
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());
823 /// Preload the provided registry with already loaded packages.
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
{
839 for pkg
in self.packages
.packages
.values() {
840 let pkg
= match *pkg
{
841 MaybePackage
::Package(ref p
) => p
.clone(),
842 MaybePackage
::Virtual(_
) => continue,
844 let mut src
= PathSource
::new(
846 pkg
.package_id().source_id(),
849 src
.preload_with(pkg
);
850 registry
.add_preloaded(Box
::new(src
));
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(),
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
);
865 anyhow
::format_err
!("failed to parse manifest at `{}`", path
.display());
866 return Err(err
.context(cx
).into());
868 let msg
= if self.root_manifest
.is_none() {
869 warning
.message
.to_string()
871 // In a workspace, it can be confusing where a warning
872 // originated, so include the path.
873 format
!("{}: {}", path
.display(), warning
.message
)
875 self.config
.shell().warn(msg
)?
882 pub fn set_target_dir(&mut self, target_dir
: Filesystem
) {
883 self.target_dir
= Some(target_dir
);
886 /// Returns a Vec of `(&Package, RequestedFeatures)` tuples that
887 /// represent the workspace members that were requested on the command-line.
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(
895 specs
: &[PackageIdSpec
],
896 requested_features
: &RequestedFeatures
,
897 ) -> CargoResult
<Vec
<(&Package
, RequestedFeatures
)>> {
899 !specs
.is_empty() || requested_features
.all_features
,
900 "no specs requires all_features"
902 if specs
.is_empty() {
903 // When resolving the entire workspace, resolve each member with
904 // all features enabled.
907 .map(|m
| (m
, RequestedFeatures
::new_all(true)))
910 if self.allows_unstable_package_features() {
911 self.members_with_features_pf(specs
, requested_features
)
913 self.members_with_features_stable(specs
, requested_features
)
917 /// New command-line feature selection with -Zpackage-features.
918 fn members_with_features_pf(
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();
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();
933 // Only include features this member defines.
934 let summary
= member
.summary();
935 let member_features
= summary
.features();
936 let mut features
= BTreeSet
::new();
938 // Checks if a member contains the given feature.
939 let contains
= |feature
: InternedString
| -> bool
{
940 member_features
.contains_key(&feature
)
944 .any(|dep
| dep
.is_optional() && dep
.name_in_toml() == feature
)
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
);
956 .any(|dep
| dep
.name_in_toml() == pkg
)
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
);
967 } else if contains(*feature
) {
968 // feature exists in this member.
969 features
.insert(*feature
);
970 found
.insert(*feature
);
974 features
: Rc
::new(features
),
976 uses_default_features
: requested_features
.uses_default_features
,
980 let members
: Vec
<(&Package
, RequestedFeatures
)> = self
982 .filter(|m
| specs
.iter().any(|spec
| spec
.matches(m
.package_id())))
983 .map(|m
| (m
, matching_features(m
)))
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
)
992 anyhow
::bail
!("cannot specify features for packages outside of workspace");
994 // Add all members from the workspace so we can ensure `-p nonmember`
995 // is in the resolve graph.
998 .map(|m
| (m
, RequestedFeatures
::new_all(false)))
1001 if *requested_features
.features
!= found
{
1002 let missing
: Vec
<_
> = requested_features
1007 // TODO: typo suggestions would be good here.
1009 "none of the selected packages contains these features: {}",
1016 /// This is the current "stable" behavior for command-line feature selection.
1017 fn members_with_features_stable(
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()))
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`.
1038 RequestedFeatures
::new_all(requested_features
.all_features
),
1041 // This member was not requested on the command-line, skip.
1051 impl<'cfg
> Packages
<'cfg
> {
1052 fn get(&self, manifest_path
: &Path
) -> &MaybePackage
{
1053 self.maybe_get(manifest_path
).unwrap()
1056 fn get_mut(&mut self, manifest_path
: &Path
) -> &mut MaybePackage
{
1057 self.maybe_get_mut(manifest_path
).unwrap()
1060 fn maybe_get(&self, manifest_path
: &Path
) -> Option
<&MaybePackage
> {
1061 self.packages
.get(manifest_path
.parent().unwrap())
1064 fn maybe_get_mut(&mut self, manifest_path
: &Path
) -> Option
<&mut MaybePackage
> {
1065 self.packages
.get_mut(manifest_path
.parent().unwrap())
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
))
1080 EitherManifest
::Virtual(vm
) => MaybePackage
::Virtual(vm
),
1087 impl<'a
, 'cfg
> Iterator
for Members
<'a
, 'cfg
> {
1088 type Item
= &'a Package
;
1090 fn next(&mut self) -> Option
<&'a Package
> {
1092 let next
= self.iter
.next().map(|path
| self.ws
.packages
.get(path
));
1094 Some(&MaybePackage
::Package(ref p
)) => return Some(p
),
1095 Some(&MaybePackage
::Virtual(_
)) => {}
1096 None
=> return None
,
1101 fn size_hint(&self) -> (usize, Option
<usize>) {
1102 let (_
, upper
) = self.iter
.size_hint();
1108 fn workspace_config(&self) -> &WorkspaceConfig
{
1110 MaybePackage
::Package(ref p
) => p
.manifest().workspace_config(),
1111 MaybePackage
::Virtual(ref vm
) => vm
.workspace_config(),
1116 impl WorkspaceRootConfig
{
1117 /// Creates a new Intermediate Workspace Root configuration.
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(),
1132 /// Checks the path against the `excluded` list.
1134 /// This method does **not** consider the `members` list.
1135 fn is_excluded(&self, manifest_path
: &Path
) -> bool
{
1139 .any(|ex
| manifest_path
.starts_with(self.root_dir
.join(ex
)));
1141 let explicit_member
= match self.members
{
1142 Some(ref members
) => members
1144 .any(|mem
| manifest_path
.starts_with(self.root_dir
.join(mem
))),
1148 !explicit_member
&& excluded
1151 fn has_members_list(&self) -> bool
{
1152 self.members
.is_some()
1155 fn members_paths(&self, globs
: &[String
]) -> CargoResult
<Vec
<PathBuf
>> {
1156 let mut expanded_list
= Vec
::new();
1159 let pathbuf
= self.root_dir
.join(glob
);
1160 let expanded_paths
= Self::expand_member_path(&pathbuf
)?
;
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
);
1167 expanded_list
.extend(expanded_paths
);
1174 fn expand_member_path(path
: &Path
) -> CargoResult
<Vec
<PathBuf
>> {
1175 let path
= match path
.to_str() {
1177 None
=> return Ok(Vec
::new()),
1180 glob(path
).chain_err(|| anyhow
::format_err
!("could not parse pattern `{}`", &path
))?
;
1183 p
.chain_err(|| anyhow
::format_err
!("unable to match path to pattern `{}`", &path
))
1185 .collect
::<Result
<Vec
<_
>, _
>>()?
;