diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index 57da10dd54b..99e94a4bdce 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -1609,6 +1609,19 @@ pub fn basic_lib_manifest(name: &str) -> String { ) } +pub fn basic_workspace_manifest(name: &str, workspace: &str) -> String { + format!( + r#" + [package] + name = "{}" + version = "0.1.0" + authors = [] + workspace = "{}" + "#, + name, workspace + ) +} + pub fn path2url>(p: P) -> Url { Url::from_file_path(p).ok().unwrap() } diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index ed3dd77336c..b4b4108c980 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -11,6 +11,7 @@ use crate::util::errors::CargoResult; use std::collections::{HashMap, HashSet}; use std::env; use std::path::PathBuf; +use std::rc::Rc; /// Parse the `-Zbuild-std` flag. pub fn parse_unstable_flag(value: Option<&str>) -> Vec { @@ -60,17 +61,13 @@ pub fn resolve_std<'cfg>( String::from("library/alloc"), String::from("library/test"), ]; - let ws_config = crate::core::WorkspaceConfig::Root(crate::core::WorkspaceRootConfig::new( - &src_path, - &Some(members), - /*default_members*/ &None, - /*exclude*/ &None, - /*custom_metadata*/ &None, - )); + let ws_config = crate::core::WorkspaceConfig::Root( + crate::core::WorkspaceRootConfig::from_members(&src_path, members), + ); let virtual_manifest = crate::core::VirtualManifest::new( /*replace*/ Vec::new(), patch, - ws_config, + Rc::new(ws_config), /*profiles*/ None, crate::core::Features::default(), None, diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index dc69fbc08e3..d1bc9cf24b5 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -16,7 +16,7 @@ use crate::core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary}; use crate::core::{Edition, Feature, Features, WorkspaceConfig}; use crate::util::errors::*; use crate::util::interning::InternedString; -use crate::util::toml::{TomlManifest, TomlProfiles}; +use crate::util::toml::{DefinedTomlManifest, TomlProfiles}; use crate::util::{short_hash, Config, Filesystem}; pub enum EitherManifest { @@ -24,6 +24,15 @@ pub enum EitherManifest { Virtual(VirtualManifest), } +impl EitherManifest { + pub fn workspace(&self) -> Rc { + match self { + Self::Real(manifest) => Rc::clone(&manifest.workspace), + Self::Virtual(virtual_manifest) => Rc::clone(&virtual_manifest.workspace), + } + } +} + /// Contains all the information about a package, as loaded from a `Cargo.toml`. #[derive(Clone, Debug)] pub struct Manifest { @@ -40,8 +49,8 @@ pub struct Manifest { publish_lockfile: bool, replace: Vec<(PackageIdSpec, Dependency)>, patch: HashMap>, - workspace: WorkspaceConfig, - original: Rc, + workspace: Rc, + original: Rc, features: Features, edition: Edition, im_a_teapot: Option, @@ -66,7 +75,7 @@ pub struct Warnings(Vec); pub struct VirtualManifest { replace: Vec<(PackageIdSpec, Dependency)>, patch: HashMap>, - workspace: WorkspaceConfig, + workspace: Rc, profiles: Option, warnings: Warnings, features: Features, @@ -370,12 +379,12 @@ impl Manifest { publish_lockfile: bool, replace: Vec<(PackageIdSpec, Dependency)>, patch: HashMap>, - workspace: WorkspaceConfig, + workspace: Rc, features: Features, edition: Edition, im_a_teapot: Option, default_run: Option, - original: Rc, + original: Rc, metabuild: Option>, resolve_behavior: Option, ) -> Manifest { @@ -453,7 +462,7 @@ impl Manifest { pub fn replace(&self) -> &[(PackageIdSpec, Dependency)] { &self.replace } - pub fn original(&self) -> &TomlManifest { + pub fn original(&self) -> &DefinedTomlManifest { &self.original } pub fn patch(&self) -> &HashMap> { @@ -463,8 +472,8 @@ impl Manifest { self.links.as_deref() } - pub fn workspace_config(&self) -> &WorkspaceConfig { - &self.workspace + pub fn workspace_config(&self) -> Rc { + Rc::clone(&self.workspace) } pub fn features(&self) -> &Features { @@ -538,7 +547,7 @@ impl VirtualManifest { pub fn new( replace: Vec<(PackageIdSpec, Dependency)>, patch: HashMap>, - workspace: WorkspaceConfig, + workspace: Rc, profiles: Option, features: Features, resolve_behavior: Option, @@ -562,8 +571,8 @@ impl VirtualManifest { &self.patch } - pub fn workspace_config(&self) -> &WorkspaceConfig { - &self.workspace + pub fn workspace_config(&self) -> Rc { + Rc::clone(&self.workspace) } pub fn profiles(&self) -> Option<&TomlProfiles> { diff --git a/src/cargo/core/mod.rs b/src/cargo/core/mod.rs index 5abab1b9cd4..3a9eeebbb1f 100644 --- a/src/cargo/core/mod.rs +++ b/src/cargo/core/mod.rs @@ -28,4 +28,4 @@ pub mod resolver; pub mod shell; pub mod source; pub mod summary; -mod workspace; +pub mod workspace; diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 1f327d75409..43f1d3f38b8 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -240,7 +240,9 @@ impl Package { let manifest = self .manifest() .original() - .prepare_for_publish(ws, self.root())?; + .prepare_for_publish(ws, self.manifest_path())? + .into_toml_manifest(); + let toml = toml::to_string(&manifest)?; Ok(format!("{}\n{}", MANIFEST_PREAMBLE, toml)) } diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index a8f95fcddeb..b76e0485de9 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -20,7 +20,10 @@ use crate::sources::PathSource; use crate::util::errors::{CargoResult, CargoResultExt, ManifestError}; use crate::util::interning::InternedString; use crate::util::paths; -use crate::util::toml::{read_manifest, TomlProfiles}; +use crate::util::toml::{ + parse_manifest, prepare_deps, read_manifest, DefinedTomlDependency, StringOrBool, TomlProfiles, + TomlWorkspace, VecStringOrBool, +}; use crate::util::{Config, Filesystem}; /// The core abstraction in Cargo for working with a workspace of crates. @@ -120,6 +123,15 @@ pub enum WorkspaceConfig { Member { root: Option }, } +impl WorkspaceConfig { + pub fn is_root(&self) -> bool { + match self { + Self::Root(_) => true, + _ => false, + } + } +} + /// Intermediate configuration of a workspace root in a manifest. /// /// Knows the Workspace Root path, as well as `members` and `exclude` lists of path patterns, which @@ -131,6 +143,23 @@ pub struct WorkspaceRootConfig { default_members: Option>, exclude: Vec, custom_metadata: Option, + + // Properties that can be inherited by members. + dependencies: Option>, + version: Option, + authors: Option>, + description: Option, + documentation: Option, + readme: Option, + homepage: Option, + repository: Option, + license: Option, + license_file: Option, + keywords: Option>, + categories: Option>, + publish: Option, + edition: Option, + badges: Option>>, } /// An iterator over the member packages of a workspace, returned by @@ -157,9 +186,10 @@ impl<'cfg> Workspace<'cfg> { manifest_path ) } else { - ws.root_manifest = ws.find_root(manifest_path)?; + ws.root_manifest = find_workspace_root(manifest_path, config)?; } + ws.load_current()?; ws.custom_metadata = ws .load_workspace_config()? .and_then(|cfg| cfg.custom_metadata); @@ -247,6 +277,11 @@ impl<'cfg> Workspace<'cfg> { Ok(ws) } + fn load_current(&mut self) -> CargoResult<()> { + self.packages.load(&self.current_manifest)?; + Ok(()) + } + /// Returns the current package of this workspace. /// /// Note that this can return an error if it the current manifest is @@ -413,7 +448,7 @@ impl<'cfg> Workspace<'cfg> { // metadata. if let Some(root_path) = &self.root_manifest { let root_package = self.packages.load(root_path)?; - match root_package.workspace_config() { + match root_package.workspace_config().as_ref() { WorkspaceConfig::Root(ref root_config) => { return Ok(Some(root_config.clone())); } @@ -428,79 +463,6 @@ impl<'cfg> Workspace<'cfg> { Ok(None) } - /// Finds the root of a workspace for the crate whose manifest is located - /// at `manifest_path`. - /// - /// This will parse the `Cargo.toml` at `manifest_path` and then interpret - /// the workspace configuration, optionally walking up the filesystem - /// looking for other workspace roots. - /// - /// Returns an error if `manifest_path` isn't actually a valid manifest or - /// if some other transient error happens. - fn find_root(&mut self, manifest_path: &Path) -> CargoResult> { - fn read_root_pointer(member_manifest: &Path, root_link: &str) -> CargoResult { - let path = member_manifest - .parent() - .unwrap() - .join(root_link) - .join("Cargo.toml"); - debug!("find_root - pointer {}", path.display()); - Ok(paths::normalize_path(&path)) - }; - - { - let current = self.packages.load(manifest_path)?; - match *current.workspace_config() { - WorkspaceConfig::Root(_) => { - debug!("find_root - is root {}", manifest_path.display()); - return Ok(Some(manifest_path.to_path_buf())); - } - WorkspaceConfig::Member { - root: Some(ref path_to_root), - } => return Ok(Some(read_root_pointer(manifest_path, path_to_root)?)), - WorkspaceConfig::Member { root: None } => {} - } - } - - for path in paths::ancestors(manifest_path).skip(2) { - if path.ends_with("target/package") { - break; - } - - let ances_manifest_path = path.join("Cargo.toml"); - debug!("find_root - trying {}", ances_manifest_path.display()); - if ances_manifest_path.exists() { - match *self.packages.load(&ances_manifest_path)?.workspace_config() { - WorkspaceConfig::Root(ref ances_root_config) => { - debug!("find_root - found a root checking exclusion"); - if !ances_root_config.is_excluded(manifest_path) { - debug!("find_root - found!"); - return Ok(Some(ances_manifest_path)); - } - } - WorkspaceConfig::Member { - root: Some(ref path_to_root), - } => { - debug!("find_root - found pointer"); - return Ok(Some(read_root_pointer(&ances_manifest_path, path_to_root)?)); - } - WorkspaceConfig::Member { .. } => {} - } - } - - // Don't walk across `CARGO_HOME` when we're looking for the - // workspace root. Sometimes a package will be organized with - // `CARGO_HOME` pointing inside of the workspace root or in the - // current package, but we don't want to mistakenly try to put - // crates.io crates into the workspace by accident. - if self.config.home() == path { - break; - } - } - - Ok(None) - } - /// After the root of a workspace has been located, probes for all members /// of a workspace. /// @@ -587,7 +549,7 @@ impl<'cfg> Workspace<'cfg> { } if is_path_dep && !manifest_path.parent().unwrap().starts_with(self.root()) - && self.find_root(&manifest_path)? != self.root_manifest + && find_workspace_root(&manifest_path, self.config)? != self.root_manifest { // If `manifest_path` is a path dependency outside of the workspace, // don't add it, or any of its dependencies, as a members. @@ -691,7 +653,7 @@ impl<'cfg> Workspace<'cfg> { .iter() .filter(|&member| { let config = self.packages.get(member).workspace_config(); - matches!(config, WorkspaceConfig::Root(_)) + matches!(config.as_ref(), WorkspaceConfig::Root(_)) }) .map(|member| member.parent().unwrap().to_path_buf()) .collect(); @@ -720,7 +682,7 @@ impl<'cfg> Workspace<'cfg> { fn validate_members(&mut self) -> CargoResult<()> { for member in self.members.clone() { - let root = self.find_root(&member)?; + let root = find_workspace_root(&member, self.config)?; if root == self.root_manifest { continue; } @@ -1106,11 +1068,11 @@ impl<'cfg> Packages<'cfg> { } fn load(&mut self, manifest_path: &Path) -> CargoResult<&MaybePackage> { - let key = manifest_path.parent().unwrap(); - match self.packages.entry(key.to_path_buf()) { + let key = manifest_path.parent().unwrap().to_path_buf(); + match self.packages.entry(key.clone()) { Entry::Occupied(e) => Ok(e.into_mut()), Entry::Vacant(v) => { - let source_id = SourceId::for_path(key)?; + let source_id = SourceId::for_path(&key)?; let (manifest, _nested_paths) = read_manifest(manifest_path, source_id, self.config)?; Ok(v.insert(match manifest { @@ -1145,7 +1107,7 @@ impl<'a, 'cfg> Iterator for Members<'a, 'cfg> { } impl MaybePackage { - fn workspace_config(&self) -> &WorkspaceConfig { + fn workspace_config(&self) -> Rc { match *self { MaybePackage::Package(ref p) => p.manifest().workspace_config(), MaybePackage::Virtual(ref vm) => vm.workspace_config(), @@ -1154,22 +1116,65 @@ impl MaybePackage { } impl WorkspaceRootConfig { - /// Creates a new Intermediate Workspace Root configuration. - pub fn new( - root_dir: &Path, - members: &Option>, - default_members: &Option>, - exclude: &Option>, - custom_metadata: &Option, - ) -> WorkspaceRootConfig { - WorkspaceRootConfig { + pub fn from_members(root_dir: &Path, members: Vec) -> WorkspaceRootConfig { + Self { root_dir: root_dir.to_path_buf(), - members: members.clone(), - default_members: default_members.clone(), - exclude: exclude.clone().unwrap_or_default(), - custom_metadata: custom_metadata.clone(), + members: Some(members), + default_members: None, + exclude: Vec::new(), + custom_metadata: None, + dependencies: None, + version: None, + authors: None, + description: None, + documentation: None, + readme: None, + homepage: None, + repository: None, + license: None, + license_file: None, + keywords: None, + categories: None, + publish: None, + edition: None, + badges: None, } } + /// Creates a new Intermediate Workspace Root configuration from a toml workspace. + pub fn from_toml_workspace( + root_dir: &Path, + config: &Config, + toml_workspace: &TomlWorkspace, + ) -> CargoResult { + let dependencies = prepare_deps( + config, + toml_workspace.dependencies.as_ref(), + |_d: &DefinedTomlDependency| true, + )?; + + Ok(Self { + root_dir: root_dir.to_path_buf(), + members: toml_workspace.members.clone(), + default_members: toml_workspace.default_members.clone(), + exclude: toml_workspace.exclude.clone().unwrap_or_default(), + custom_metadata: toml_workspace.metadata.clone(), + dependencies, + version: toml_workspace.version.clone(), + authors: toml_workspace.authors.clone(), + description: toml_workspace.description.clone(), + documentation: toml_workspace.documentation.clone(), + readme: toml_workspace.readme.clone(), + homepage: toml_workspace.homepage.clone(), + repository: toml_workspace.repository.clone(), + license: toml_workspace.license.clone(), + license_file: toml_workspace.license_file.clone(), + keywords: toml_workspace.keywords.clone(), + categories: toml_workspace.categories.clone(), + publish: toml_workspace.publish.clone(), + edition: toml_workspace.edition.clone(), + badges: toml_workspace.badges.clone(), + }) + } /// Checks the path against the `excluded` list. /// @@ -1237,3 +1242,85 @@ impl WorkspaceRootConfig { Ok(res) } } + +/// Finds the root of a workspace for the crate whose manifest is located +/// at `manifest_path`. +/// +/// This will parse the `Cargo.toml` at `manifest_path` and then interpret +/// the workspace configuration, optionally walking up the filesystem +/// looking for other workspace roots. +/// +/// Returns an error if `manifest_path` isn't actually a valid manifest or +/// if some other transient error happens. +pub fn find_workspace_root(manifest_path: &Path, config: &Config) -> CargoResult> { + fn read_root_pointer(member_manifest: &Path, root_link: &str) -> CargoResult { + let path = member_manifest + .parent() + .unwrap() + .join(root_link) + .join("Cargo.toml"); + debug!("find_workspace_root - pointer {}", path.display()); + Ok(paths::normalize_path(&path)) + }; + + fn workspace_config(manifest_path: &Path, config: &Config) -> CargoResult { + let output = parse_manifest(manifest_path, config)?; + output + .manifest + .workspace_config(manifest_path.parent().unwrap(), config) + } + + { + match workspace_config(manifest_path, config)? { + WorkspaceConfig::Root(_) => { + debug!("find_workspace_root - is root {}", manifest_path.display()); + return Ok(Some(manifest_path.to_path_buf())); + } + WorkspaceConfig::Member { + root: Some(ref path_to_root), + } => return Ok(Some(read_root_pointer(manifest_path, path_to_root)?)), + WorkspaceConfig::Member { root: None } => {} + } + } + + for path in paths::ancestors(manifest_path).skip(2) { + if path.ends_with("target/package") { + break; + } + + let ances_manifest_path = path.join("Cargo.toml"); + debug!( + "find_workspace_root - trying {}", + ances_manifest_path.display() + ); + if ances_manifest_path.exists() { + match workspace_config(&ances_manifest_path, config)? { + WorkspaceConfig::Root(ref ances_root_config) => { + debug!("find_workspace_root - found a root checking exclusion"); + if !ances_root_config.is_excluded(manifest_path) { + debug!("find_workspace_root - found!"); + return Ok(Some(ances_manifest_path)); + } + } + WorkspaceConfig::Member { + root: Some(ref path_to_root), + } => { + debug!("find_workspace_root - found pointer"); + return Ok(Some(read_root_pointer(&ances_manifest_path, path_to_root)?)); + } + WorkspaceConfig::Member { .. } => {} + } + } + + // Don't walk across `CARGO_HOME` when we're looking for the + // workspace root. Sometimes a package will be organized with + // `CARGO_HOME` pointing inside of the workspace root or in the + // current package, but we don't want to mistakenly try to put + // crates.io crates into the workspace by accident. + if config.home() == path { + break; + } + } + + Ok(None) +} diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 5dc077c4b4d..356d652176c 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -3,7 +3,6 @@ use std::fs::{self, File}; use std::io::prelude::*; use std::io::SeekFrom; use std::path::{Path, PathBuf}; -use std::rc::Rc; use std::sync::Arc; use std::time::SystemTime; @@ -18,7 +17,6 @@ use crate::core::{Package, PackageId, PackageSet, Resolve, Source, SourceId}; use crate::sources::PathSource; use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::paths; -use crate::util::toml::TomlManifest; use crate::util::{self, restricted_names, Config, FileLock}; use crate::{drop_println, ops}; @@ -107,8 +105,6 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult