]> git.proxmox.com Git - cargo.git/blame - src/cargo/core/workspace.rs
Add comment on relationship of RunCustomBuild and UnitFor::host.
[cargo.git] / src / cargo / core / workspace.rs
CommitLineData
9659f560 1use std::cell::RefCell;
9659f560 2use std::collections::hash_map::{Entry, HashMap};
f660b3e4 3use std::collections::{BTreeMap, HashSet};
58ddb28a
AC
4use std::path::{Path, PathBuf};
5use std::slice;
6
b3a747cf 7use glob::glob;
9ed82b57 8use log::debug;
61a3c68b 9use url::Url;
b3a747cf 10
87f1a4b2 11use crate::core::features::Features;
04ddd4d0 12use crate::core::registry::PackageRegistry;
76f0d68d 13use crate::core::resolver::features::RequestedFeatures;
63a9c7aa 14use crate::core::{Dependency, PackageId, PackageIdSpec};
04ddd4d0
DW
15use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
16use crate::ops;
17use crate::sources::PathSource;
18use crate::util::errors::{CargoResult, CargoResultExt, ManifestError};
19use crate::util::paths;
77ee608d 20use crate::util::toml::{read_manifest, TomlProfiles};
04ddd4d0 21use crate::util::{Config, Filesystem};
58ddb28a
AC
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.
c946944b 28#[derive(Debug)]
58ddb28a
AC
29pub struct Workspace<'cfg> {
30 config: &'cfg Config,
31
32 // This path is a path to where the current cargo subcommand was invoked
4f0e1361 33 // from. That is the `--manifest-path` argument to Cargo, and
58ddb28a
AC
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
a2cc38dc
AK
47 // Shared target directory for all the packages of this workspace.
48 // `None` if the default path of `root/target` should be used.
3c9b362b
AK
49 target_dir: Option<Filesystem>,
50
58ddb28a
AC
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>,
f660b3e4 55 member_ids: HashSet<PackageId>,
68c6f2b0 56
499a6788
SS
57 // The subset of `members` that are used by the
58 // `build`, `check`, `test`, and `bench` subcommands
ecf824f7 59 // when no package is selected with `--package` / `-p` and `--workspace`
499a6788
SS
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
ecf824f7 65 // (`--workspace` is implied)
499a6788 66 // or only the root package for non-virtual workspaces.
ba7911dd
SS
67 default_members: Vec<PathBuf>,
68
f7c91ba6
AR
69 // `true` if this is a temporary workspace created for the purposes of the
70 // `cargo install` or `cargo package` commands.
68c6f2b0 71 is_ephemeral: bool,
db71d878 72
f7c91ba6 73 // `true` if this workspace should enforce optional dependencies even when
db71d878 74 // not needed; false if this workspace should only enforce dependencies
df5f7d68
XL
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.
db71d878 77 require_optional_deps: bool,
9659f560 78
57a7e126 79 // A cache of loaded packages for particular paths which is disjoint from
51d23560 80 // `packages` up above, used in the `load` method down below.
9659f560 81 loaded_packages: RefCell<HashMap<PathBuf, Package>>,
3d893793
EH
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,
58ddb28a
AC
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.
c946944b 90#[derive(Debug)]
58ddb28a
AC
91struct Packages<'cfg> {
92 config: &'cfg Config,
93 packages: HashMap<PathBuf, MaybePackage>,
94}
95
c946944b 96#[derive(Debug)]
58ddb28a
AC
97enum MaybePackage {
98 Package(Package),
99 Virtual(VirtualManifest),
100}
101
102/// Configuration of a workspace in a manifest.
103#[derive(Debug, Clone)]
104pub enum WorkspaceConfig {
105 /// Indicates that `[workspace]` was present and the members were
106 /// optionally specified as well.
f320997f 107 Root(WorkspaceRootConfig),
58ddb28a
AC
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
fd07cfd0
BE
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.
f320997f
BE
118#[derive(Debug, Clone)]
119pub struct WorkspaceRootConfig {
120 root_dir: PathBuf,
121 members: Option<Vec<String>>,
ba7911dd 122 default_members: Option<Vec<String>>,
f320997f
BE
123 exclude: Vec<String>,
124}
125
58ddb28a
AC
126/// An iterator over the member packages of a workspace, returned by
127/// `Workspace::members`
46615d29 128pub struct Members<'a, 'cfg> {
58ddb28a
AC
129 ws: &'a Workspace<'cfg>,
130 iter: slice::Iter<'a, PathBuf>,
131}
132
133impl<'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.
1e682848 140 pub fn new(manifest_path: &Path, config: &'cfg Config) -> CargoResult<Workspace<'cfg>> {
1f14fa31
EH
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 }
a2cc38dc 148
1f14fa31
EH
149 fn new_default(current_manifest: PathBuf, config: &'cfg Config) -> Workspace<'cfg> {
150 Workspace {
0247dc42 151 config,
1f14fa31 152 current_manifest,
58ddb28a 153 packages: Packages {
0247dc42 154 config,
58ddb28a
AC
155 packages: HashMap::new(),
156 },
157 root_manifest: None,
1f14fa31 158 target_dir: None,
58ddb28a 159 members: Vec::new(),
f660b3e4 160 member_ids: HashSet::new(),
ba7911dd 161 default_members: Vec::new(),
68c6f2b0 162 is_ephemeral: false,
db71d878 163 require_optional_deps: true,
9659f560 164 loaded_packages: RefCell::new(HashMap::new()),
3d893793 165 ignore_lock: false,
1f14fa31
EH
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));
82655b46 181 ws.find_members()?;
1f14fa31
EH
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.
58ddb28a
AC
184 Ok(ws)
185 }
186
015e7972 187 /// Creates a "temporary workspace" from one package which only contains
58ddb28a
AC
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`.
1e682848
AC
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>> {
e137ee69 202 let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), config);
1f14fa31
EH
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()?
58ddb28a 213 };
1f14fa31
EH
214 ws.members.push(ws.current_manifest.clone());
215 ws.member_ids.insert(id);
216 ws.default_members.push(ws.current_manifest.clone());
23591fe5 217 Ok(ws)
58ddb28a
AC
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> {
37cffbe0 226 let pkg = self.current_opt().ok_or_else(|| {
3a18c89a 227 anyhow::format_err!(
1e682848
AC
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 )
37cffbe0
AC
233 })?;
234 Ok(pkg)
a2f687a5
AK
235 }
236
d5eeab84
LB
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
a2f687a5 250 pub fn current_opt(&self) -> Option<&Package> {
58ddb28a 251 match *self.packages.get(&self.current_manifest) {
a2f687a5 252 MaybePackage::Package(ref p) => Some(p),
1e682848 253 MaybePackage::Virtual(..) => None,
58ddb28a
AC
254 }
255 }
256
d5eeab84
LB
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
193a95a2
RS
264 pub fn is_virtual(&self) -> bool {
265 match *self.packages.get(&self.current_manifest) {
266 MaybePackage::Package(..) => false,
1e682848 267 MaybePackage::Virtual(..) => true,
193a95a2
RS
268 }
269 }
270
58ddb28a
AC
271 /// Returns the `Config` this workspace is associated with.
272 pub fn config(&self) -> &'cfg Config {
273 self.config
274 }
275
77ee608d 276 pub fn profiles(&self) -> Option<&TomlProfiles> {
2b6fd6f0
EH
277 match self.root_maybe() {
278 MaybePackage::Package(p) => p.manifest().profiles(),
279 MaybePackage::Virtual(vm) => vm.profiles(),
151f12fc
AK
280 }
281 }
282
58ddb28a
AC
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,
1e682848 290 None => &self.current_manifest,
e5a11190
E
291 }
292 .parent()
293 .unwrap()
58ddb28a
AC
294 }
295
2b6fd6f0
EH
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
3c9b362b 305 pub fn target_dir(&self) -> Filesystem {
1e682848
AC
306 self.target_dir
307 .clone()
308 .unwrap_or_else(|| Filesystem::new(self.root().join("target")))
3c9b362b
AK
309 }
310
e26ef017 311 /// Returns the root `[replace]` section of this workspace.
58ddb28a
AC
312 ///
313 /// This may be from a virtual crate or an actual crate.
314 pub fn root_replace(&self) -> &[(PackageIdSpec, Dependency)] {
2b6fd6f0
EH
315 match self.root_maybe() {
316 MaybePackage::Package(p) => p.manifest().replace(),
317 MaybePackage::Virtual(vm) => vm.replace(),
58ddb28a
AC
318 }
319 }
320
e26ef017 321 /// Returns the root `[patch]` section of this workspace.
61a3c68b
AC
322 ///
323 /// This may be from a virtual crate or an actual crate.
324 pub fn root_patch(&self) -> &HashMap<Url, Vec<Dependency>> {
2b6fd6f0
EH
325 match self.root_maybe() {
326 MaybePackage::Package(p) => p.manifest().patch(),
327 MaybePackage::Virtual(vm) => vm.patch(),
61a3c68b
AC
328 }
329 }
330
58ddb28a
AC
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
ba7911dd
SS
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
575d6e81
EH
347 /// Returns true if the package is a member of the workspace.
348 pub fn is_member(&self, pkg: &Package) -> bool {
f660b3e4 349 self.member_ids.contains(&pkg.package_id())
575d6e81
EH
350 }
351
68c6f2b0
AK
352 pub fn is_ephemeral(&self) -> bool {
353 self.is_ephemeral
354 }
355
db71d878
JT
356 pub fn require_optional_deps(&self) -> bool {
357 self.require_optional_deps
358 }
359
3d893793
EH
360 pub fn set_require_optional_deps(
361 &mut self,
1e682848
AC
362 require_optional_deps: bool,
363 ) -> &mut Workspace<'cfg> {
ee78456a
XL
364 self.require_optional_deps = require_optional_deps;
365 self
366 }
367
3d893793
EH
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
58ddb28a
AC
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.
1e682848 386 fn find_root(&mut self, manifest_path: &Path) -> CargoResult<Option<PathBuf>> {
3435414e 387 fn read_root_pointer(member_manifest: &Path, root_link: &str) -> CargoResult<PathBuf> {
1e682848
AC
388 let path = member_manifest
389 .parent()
390 .unwrap()
3435414e
AK
391 .join(root_link)
392 .join("Cargo.toml");
393 debug!("find_root - pointer {}", path.display());
23591fe5 394 Ok(paths::normalize_path(&path))
3435414e
AK
395 };
396
58ddb28a 397 {
23591fe5 398 let current = self.packages.load(manifest_path)?;
58ddb28a 399 match *current.workspace_config() {
f320997f 400 WorkspaceConfig::Root(_) => {
58ddb28a 401 debug!("find_root - is root {}", manifest_path.display());
1e682848 402 return Ok(Some(manifest_path.to_path_buf()));
58ddb28a 403 }
1e682848
AC
404 WorkspaceConfig::Member {
405 root: Some(ref path_to_root),
406 } => return Ok(Some(read_root_pointer(manifest_path, path_to_root)?)),
58ddb28a
AC
407 WorkspaceConfig::Member { root: None } => {}
408 }
409 }
410
290f6ab7 411 for path in paths::ancestors(manifest_path).skip(2) {
4f0e1361 412 if path.ends_with("target/package") {
413 break;
414 }
415
fd07cfd0
BE
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) => {
67364baa 421 debug!("find_root - found a root checking exclusion");
676edacf 422 if !ances_root_config.is_excluded(manifest_path) {
67364baa 423 debug!("find_root - found!");
1e682848 424 return Ok(Some(ances_manifest_path));
67364baa 425 }
58ddb28a 426 }
1e682848
AC
427 WorkspaceConfig::Member {
428 root: Some(ref path_to_root),
429 } => {
67364baa 430 debug!("find_root - found pointer");
1e682848 431 return Ok(Some(read_root_pointer(&ances_manifest_path, path_to_root)?));
3435414e 432 }
58ddb28a
AC
433 WorkspaceConfig::Member { .. } => {}
434 }
435 }
4f28bc1a
AC
436
437 // Don't walk across `CARGO_HOME` when we're looking for the
3492a390 438 // workspace root. Sometimes a package will be organized with
4f28bc1a 439 // `CARGO_HOME` pointing inside of the workspace root or in the
3492a390 440 // current package, but we don't want to mistakenly try to put
4f28bc1a
AC
441 // crates.io crates into the workspace by accident.
442 if self.config.home() == path {
1e682848 443 break;
4f28bc1a 444 }
58ddb28a
AC
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<()> {
f320997f 458 let root_manifest_path = match self.root_manifest {
58ddb28a
AC
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());
ba7911dd 463 self.default_members.push(self.current_manifest.clone());
f660b3e4
AC
464 if let Ok(pkg) = self.current() {
465 let id = pkg.package_id();
466 self.member_ids.insert(id);
467 }
1e682848 468 return Ok(());
58ddb28a
AC
469 }
470 };
f320997f 471
ba7911dd
SS
472 let members_paths;
473 let default_members_paths;
474 {
f320997f
BE
475 let root_package = self.packages.load(&root_manifest_path)?;
476 match *root_package.workspace_config() {
ba7911dd 477 WorkspaceConfig::Root(ref root_config) => {
e5a11190
E
478 members_paths = root_config
479 .members_paths(root_config.members.as_ref().unwrap_or(&vec![]))?;
53e84c57
WL
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 }
ba7911dd
SS
486 } else {
487 None
53e84c57 488 };
ba7911dd 489 }
3a18c89a 490 _ => anyhow::bail!(
1e682848
AC
491 "root of a workspace inferred but wasn't a root: {}",
492 root_manifest_path.display()
493 ),
58ddb28a 494 }
ba7911dd 495 }
58ddb28a 496
f320997f
BE
497 for path in members_paths {
498 self.find_path_deps(&path.join("Cargo.toml"), &root_manifest_path, false)?;
58ddb28a
AC
499 }
500
ba7911dd
SS
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) {
3a18c89a 505 anyhow::bail!(
1e682848
AC
506 "package `{}` is listed in workspace’s default-members \
507 but is not a member.",
508 path.display()
509 )
ba7911dd
SS
510 }
511 self.default_members.push(manifest_path)
512 }
8d5e73c6 513 } else if self.is_virtual() {
ba7911dd 514 self.default_members = self.members.clone()
8d5e73c6
SS
515 } else {
516 self.default_members.push(self.current_manifest.clone())
ba7911dd
SS
517 }
518
f320997f 519 self.find_path_deps(&root_manifest_path, &root_manifest_path, false)
58ddb28a
AC
520 }
521
1e682848
AC
522 fn find_path_deps(
523 &mut self,
524 manifest_path: &Path,
525 root_manifest: &Path,
526 is_path_dep: bool,
527 ) -> CargoResult<()> {
083da141 528 let manifest_path = paths::normalize_path(manifest_path);
ba7911dd 529 if self.members.contains(&manifest_path) {
1e682848 530 return Ok(());
58ddb28a 531 }
e5a11190
E
532 if is_path_dep
533 && !manifest_path.parent().unwrap().starts_with(self.root())
1e682848
AC
534 && self.find_root(&manifest_path)? != self.root_manifest
535 {
07c667fb
AK
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.
1e682848 538 return Ok(());
07c667fb 539 }
58ddb28a 540
1e682848
AC
541 if let WorkspaceConfig::Root(ref root_config) =
542 *self.packages.load(root_manifest)?.workspace_config()
543 {
676edacf 544 if root_config.is_excluded(&manifest_path) {
1e682848 545 return Ok(());
67364baa 546 }
67364baa
AC
547 }
548
58ddb28a 549 debug!("find_members - {}", manifest_path.display());
083da141 550 self.members.push(manifest_path.clone());
58ddb28a
AC
551
552 let candidates = {
25b7ad26 553 let pkg = match *self.packages.load(&manifest_path)? {
10b3ebf0
AB
554 MaybePackage::Package(ref p) => p,
555 MaybePackage::Virtual(_) => return Ok(()),
58ddb28a 556 };
f660b3e4 557 self.member_ids.insert(pkg.package_id());
58ddb28a 558 pkg.dependencies()
1e682848
AC
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<_>>()
58ddb28a
AC
565 };
566 for candidate in candidates {
49ab03e3
AB
567 self.find_path_deps(&candidate, root_manifest, true)
568 .map_err(|err| ManifestError::new(err, manifest_path.clone()))?;
58ddb28a
AC
569 }
570 Ok(())
571 }
572
87f1a4b2
AH
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
58ddb28a
AC
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<()> {
2b6fd6f0 586 // The rest of the checks require a VirtualManifest or multiple members.
58ddb28a 587 if self.root_manifest.is_none() {
1e682848 588 return Ok(());
58ddb28a
AC
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() {
f320997f 597 WorkspaceConfig::Root(_) => {
58ddb28a
AC
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) {
3a18c89a 607 anyhow::bail!(
1e682848
AC
608 "two packages named `{}` in this workspace:\n\
609 - {}\n\
610 - {}",
611 name,
612 prev.display(),
613 member.display()
614 );
58ddb28a
AC
615 }
616 }
617 }
618
619 match roots.len() {
3a18c89a 620 0 => anyhow::bail!(
1e682848
AC
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 ),
58ddb28a
AC
628 1 => {}
629 _ => {
3a18c89a 630 anyhow::bail!(
1e682848
AC
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 );
58ddb28a
AC
638 }
639 }
640
641 for member in self.members.clone() {
82655b46 642 let root = self.find_root(&member)?;
58ddb28a 643 if root == self.root_manifest {
1e682848 644 continue;
58ddb28a
AC
645 }
646
647 match root {
648 Some(root) => {
3a18c89a 649 anyhow::bail!(
1e682848
AC
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 );
58ddb28a
AC
657 }
658 None => {
3a18c89a 659 anyhow::bail!(
1e682848
AC
660 "workspace member `{}` is not hierarchically below \
661 the workspace root `{}`",
662 member.display(),
663 self.root_manifest.as_ref().unwrap().display()
664 );
58ddb28a
AC
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
fd07cfd0
BE
675 // FIXME: Make this more generic by using a relative path resolver between member and
676 // root.
58ddb28a 677 let members_msg = match current_dir.strip_prefix(root_dir) {
1e682848
AC
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 ),
58ddb28a
AC
691 };
692 let extra = match *root_pkg {
693 MaybePackage::Virtual(_) => members_msg,
694 MaybePackage::Package(ref p) => {
f320997f
BE
695 let has_members_list = match *p.manifest().workspace_config() {
696 WorkspaceConfig::Root(ref root_config) => root_config.has_members_list(),
58ddb28a
AC
697 WorkspaceConfig::Member { .. } => unreachable!(),
698 };
f320997f 699 if !has_members_list {
1e682848
AC
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 )
58ddb28a
AC
706 } else {
707 members_msg
708 }
709 }
710 };
3a18c89a 711 anyhow::bail!(
1e682848
AC
712 "current package believes it's in a workspace when it's not:\n\
713 current: {}\n\
d7a92124
EH
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.",
1e682848
AC
718 self.current_manifest.display(),
719 root.display(),
720 extra
721 );
58ddb28a
AC
722 }
723
151f12fc 724 if let Some(ref root_manifest) = self.root_manifest {
e5a11190
E
725 for pkg in self
726 .members()
1e682848
AC
727 .filter(|p| p.manifest_path() != root_manifest)
728 {
5b2b5f95
EH
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\
1e682848
AC
734 package: {}\n\
735 workspace: {}",
5b2b5f95
EH
736 what,
737 what,
1e682848 738 pkg.manifest_path().display(),
5b2b5f95 739 root_manifest.display(),
1e682848 740 );
5b2b5f95
EH
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")?;
151f12fc
AK
751 }
752 }
753 }
754
58ddb28a
AC
755 Ok(())
756 }
9659f560
AC
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()),
3a18c89a 761 Some(&MaybePackage::Virtual(_)) => anyhow::bail!("cannot load workspace root"),
9659f560
AC
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())?;
e5a11190 770 let (package, _nested_paths) = ops::read_package(manifest_path, source_id, self.config)?;
9659f560
AC
771 loaded.insert(manifest_path.to_path_buf(), package.clone());
772 Ok(package)
773 }
51d23560
AC
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 }
688a4fa6
EH
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 };
6ad77940 812 let path = path.join("Cargo.toml");
688a4fa6
EH
813 for warning in warnings {
814 if warning.is_critical {
3a18c89a 815 let err = anyhow::format_err!("{}", warning.message);
54c42142 816 let cx =
3a18c89a 817 anyhow::format_err!("failed to parse manifest at `{}`", path.display());
688a4fa6
EH
818 return Err(err.context(cx).into());
819 } else {
6ad77940
EH
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)?
688a4fa6
EH
828 }
829 }
830 }
831 Ok(())
832 }
e137ee69
CD
833
834 pub fn set_target_dir(&mut self, target_dir: Filesystem) {
835 self.target_dir = Some(target_dir);
836 }
76f0d68d
EH
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 }
58ddb28a
AC
917}
918
919impl<'cfg> Packages<'cfg> {
920 fn get(&self, manifest_path: &Path) -> &MaybePackage {
9659f560
AC
921 self.maybe_get(manifest_path).unwrap()
922 }
923
d5eeab84
LB
924 fn get_mut(&mut self, manifest_path: &Path) -> &mut MaybePackage {
925 self.maybe_get_mut(manifest_path).unwrap()
926 }
927
9659f560
AC
928 fn maybe_get(&self, manifest_path: &Path) -> Option<&MaybePackage> {
929 self.packages.get(manifest_path.parent().unwrap())
58ddb28a
AC
930 }
931
d5eeab84
LB
932 fn maybe_get_mut(&mut self, manifest_path: &Path) -> Option<&mut MaybePackage> {
933 self.packages.get_mut(manifest_path.parent().unwrap())
934 }
935
58ddb28a
AC
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) => {
82655b46 941 let source_id = SourceId::for_path(key)?;
9671c682 942 let (manifest, _nested_paths) =
e5a11190 943 read_manifest(manifest_path, source_id, self.config)?;
58ddb28a
AC
944 Ok(v.insert(match manifest {
945 EitherManifest::Real(manifest) => {
fd07cfd0 946 MaybePackage::Package(Package::new(manifest, manifest_path))
58ddb28a 947 }
1e682848 948 EitherManifest::Virtual(vm) => MaybePackage::Virtual(vm),
58ddb28a
AC
949 }))
950 }
951 }
952 }
953}
954
955impl<'a, 'cfg> Iterator for Members<'a, 'cfg> {
956 type Item = &'a Package;
957
958 fn next(&mut self) -> Option<&'a Package> {
959 loop {
1e682848 960 let next = self.iter.next().map(|path| self.ws.packages.get(path));
58ddb28a
AC
961 match next {
962 Some(&MaybePackage::Package(ref p)) => return Some(p),
963 Some(&MaybePackage::Virtual(_)) => {}
964 None => return None,
965 }
966 }
967 }
3d1b950e
P
968
969 fn size_hint(&self) -> (usize, Option<usize>) {
970 let (_, upper) = self.iter.size_hint();
971 (0, upper)
972 }
58ddb28a
AC
973}
974
975impl MaybePackage {
976 fn workspace_config(&self) -> &WorkspaceConfig {
977 match *self {
fd07cfd0
BE
978 MaybePackage::Package(ref p) => p.manifest().workspace_config(),
979 MaybePackage::Virtual(ref vm) => vm.workspace_config(),
58ddb28a
AC
980 }
981 }
982}
f320997f
BE
983
984impl WorkspaceRootConfig {
f7c91ba6 985 /// Creates a new Intermediate Workspace Root configuration.
f320997f
BE
986 pub fn new(
987 root_dir: &Path,
988 members: &Option<Vec<String>>,
ba7911dd 989 default_members: &Option<Vec<String>>,
f320997f
BE
990 exclude: &Option<Vec<String>>,
991 ) -> WorkspaceRootConfig {
992 WorkspaceRootConfig {
993 root_dir: root_dir.to_path_buf(),
994 members: members.clone(),
ba7911dd 995 default_members: default_members.clone(),
f320997f
BE
996 exclude: exclude.clone().unwrap_or_default(),
997 }
998 }
999
fd07cfd0
BE
1000 /// Checks the path against the `excluded` list.
1001 ///
f7c91ba6 1002 /// This method does **not** consider the `members` list.
f320997f 1003 fn is_excluded(&self, manifest_path: &Path) -> bool {
e5a11190
E
1004 let excluded = self
1005 .exclude
1e682848
AC
1006 .iter()
1007 .any(|ex| manifest_path.starts_with(self.root_dir.join(ex)));
f320997f
BE
1008
1009 let explicit_member = match self.members {
1e682848
AC
1010 Some(ref members) => members
1011 .iter()
1012 .any(|mem| manifest_path.starts_with(self.root_dir.join(mem))),
f320997f
BE
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
ba7911dd 1023 fn members_paths(&self, globs: &[String]) -> CargoResult<Vec<PathBuf>> {
f320997f
BE
1024 let mut expanded_list = Vec::new();
1025
ba7911dd
SS
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);
f320997f
BE
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 };
54c42142 1047 let res =
3a18c89a 1048 glob(path).chain_err(|| anyhow::format_err!("could not parse pattern `{}`", &path))?;
e5a11190 1049 let res = res
54c42142 1050 .map(|p| {
3a18c89a 1051 p.chain_err(|| anyhow::format_err!("unable to match path to pattern `{}`", &path))
54c42142 1052 })
e5a11190 1053 .collect::<Result<Vec<_>, _>>()?;
37cffbe0 1054 Ok(res)
f320997f
BE
1055 }
1056}