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