diff --git a/src/cargo/core/mod.rs b/src/cargo/core/mod.rs index 9667be09e68..df91808ccaf 100644 --- a/src/cargo/core/mod.rs +++ b/src/cargo/core/mod.rs @@ -11,9 +11,10 @@ pub use self::shell::{Shell, Verbosity}; pub use self::source::{GitReference, Source, SourceId, SourceMap}; pub use self::summary::{FeatureMap, FeatureValue, Summary}; pub use self::workspace::{ - find_workspace_root, resolve_relative_path, InheritableFields, MaybePackage, Workspace, - WorkspaceConfig, WorkspaceRootConfig, + find_workspace_root, resolve_relative_path, MaybePackage, Workspace, WorkspaceConfig, + WorkspaceRootConfig, }; +pub use crate::util::toml::InheritableFields; pub mod compiler; pub mod dependency; diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 7b57b749e34..cf5aeb5161e 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -23,9 +23,7 @@ use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY}; use crate::util::errors::{CargoResult, ManifestError}; use crate::util::interning::InternedString; use crate::util::lev_distance; -use crate::util::toml::{ - read_manifest, readme_for_project, StringOrBool, TomlDependency, TomlProfiles, VecStringOrBool, -}; +use crate::util::toml::{read_manifest, InheritableFields, TomlDependency, TomlProfiles}; use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl}; use cargo_util::paths; use cargo_util::paths::normalize_path; @@ -1644,213 +1642,6 @@ impl WorkspaceRootConfig { } } -/// A group of fields that are inheritable by members of the workspace -#[derive(Clone, Debug, Default)] -pub struct InheritableFields { - dependencies: Option>, - version: Option, - authors: Option>, - description: Option, - homepage: Option, - documentation: Option, - readme: Option, - keywords: Option>, - categories: Option>, - license: Option, - license_file: Option, - repository: Option, - publish: Option, - edition: Option, - badges: Option>>, - rust_version: Option, - ws_root: PathBuf, -} - -impl InheritableFields { - pub fn new( - dependencies: Option>, - version: Option, - authors: Option>, - description: Option, - homepage: Option, - documentation: Option, - readme: Option, - keywords: Option>, - categories: Option>, - license: Option, - license_file: Option, - repository: Option, - publish: Option, - edition: Option, - badges: Option>>, - rust_version: Option, - ws_root: PathBuf, - ) -> InheritableFields { - Self { - dependencies, - version, - authors, - description, - homepage, - documentation, - readme, - keywords, - categories, - license, - license_file, - repository, - publish, - edition, - badges, - rust_version, - ws_root, - } - } - - pub fn dependencies(&self) -> CargoResult> { - self.dependencies.clone().map_or( - Err(anyhow!("`workspace.dependencies` was not defined")), - |d| Ok(d), - ) - } - - pub fn get_dependency(&self, name: &str) -> CargoResult { - self.dependencies.clone().map_or( - Err(anyhow!("`workspace.dependencies` was not defined")), - |deps| { - deps.get(name).map_or( - Err(anyhow!( - "`dependency.{}` was not found in `workspace.dependencies`", - name - )), - |dep| Ok(dep.clone()), - ) - }, - ) - } - - pub fn version(&self) -> CargoResult { - self.version - .clone() - .map_or(Err(anyhow!("`workspace.version` was not defined")), |d| { - Ok(d) - }) - } - - pub fn authors(&self) -> CargoResult> { - self.authors - .clone() - .map_or(Err(anyhow!("`workspace.authors` was not defined")), |d| { - Ok(d) - }) - } - - pub fn description(&self) -> CargoResult { - self.description.clone().map_or( - Err(anyhow!("`workspace.description` was not defined")), - |d| Ok(d), - ) - } - - pub fn homepage(&self) -> CargoResult { - self.homepage - .clone() - .map_or(Err(anyhow!("`workspace.homepage` was not defined")), |d| { - Ok(d) - }) - } - - pub fn documentation(&self) -> CargoResult { - self.documentation.clone().map_or( - Err(anyhow!("`workspace.documentation` was not defined")), - |d| Ok(d), - ) - } - - pub fn readme(&self, package_root: &Path) -> CargoResult { - readme_for_project(self.ws_root.as_path(), self.readme.clone()).map_or( - Err(anyhow!("`workspace.readme` was not defined")), - |readme| { - let rel_path = - resolve_relative_path("readme", &self.ws_root, package_root, &readme)?; - Ok(StringOrBool::String(rel_path)) - }, - ) - } - - pub fn keywords(&self) -> CargoResult> { - self.keywords - .clone() - .map_or(Err(anyhow!("`workspace.keywords` was not defined")), |d| { - Ok(d) - }) - } - - pub fn categories(&self) -> CargoResult> { - self.categories.clone().map_or( - Err(anyhow!("`workspace.categories` was not defined")), - |d| Ok(d), - ) - } - - pub fn license(&self) -> CargoResult { - self.license - .clone() - .map_or(Err(anyhow!("`workspace.license` was not defined")), |d| { - Ok(d) - }) - } - - pub fn license_file(&self, package_root: &Path) -> CargoResult { - self.license_file.clone().map_or( - Err(anyhow!("`workspace.license_file` was not defined")), - |d| resolve_relative_path("license-file", &self.ws_root, package_root, &d), - ) - } - - pub fn repository(&self) -> CargoResult { - self.repository.clone().map_or( - Err(anyhow!("`workspace.repository` was not defined")), - |d| Ok(d), - ) - } - - pub fn publish(&self) -> CargoResult { - self.publish - .clone() - .map_or(Err(anyhow!("`workspace.publish` was not defined")), |d| { - Ok(d) - }) - } - - pub fn edition(&self) -> CargoResult { - self.edition - .clone() - .map_or(Err(anyhow!("`workspace.edition` was not defined")), |d| { - Ok(d) - }) - } - - pub fn rust_version(&self) -> CargoResult { - self.rust_version.clone().map_or( - Err(anyhow!("`workspace.rust-version` was not defined")), - |d| Ok(d), - ) - } - - pub fn badges(&self) -> CargoResult>> { - self.badges - .clone() - .map_or(Err(anyhow!("`workspace.badges` was not defined")), |d| { - Ok(d) - }) - } - - pub fn ws_root(&self) -> &PathBuf { - &self.ws_root - } -} - pub fn resolve_relative_path( label: &str, old_root: &Path, diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 7778878bead..16d67ee8559 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -23,9 +23,7 @@ use crate::core::resolver::ResolveBehavior; use crate::core::{ find_workspace_root, resolve_relative_path, Dependency, Manifest, PackageId, Summary, Target, }; -use crate::core::{ - Edition, EitherManifest, Feature, Features, InheritableFields, VirtualManifest, Workspace, -}; +use crate::core::{Edition, EitherManifest, Feature, Features, VirtualManifest, Workspace}; use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRootConfig}; use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY}; use crate::util::errors::{CargoResult, ManifestError}; @@ -1013,7 +1011,7 @@ impl MaybeWorkspace { MaybeWorkspace::Workspace(TomlWorkspaceField { workspace: true }) => { cargo_features.require(Feature::workspace_inheritance())?; get_ws_field().context(format!( - "error inheriting `{}` from workspace root manifest's `workspace.{}`", + "error inheriting `{}` from workspace root manifest's `workspace.package.{}`", label, label )) } @@ -1095,28 +1093,187 @@ pub struct TomlWorkspace { resolver: Option, // Properties that can be inherited by members. + package: Option, + dependencies: Option>, + + // Note that this field must come last due to the way toml serialization + // works which requires tables to be emitted after all values. + metadata: Option, +} + +/// A group of fields that are inheritable by members of the workspace +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct InheritableFields { + // We use skip here since it will never be present when deserializing + // and we don't want it present when serializing + #[serde(skip)] dependencies: Option>, version: Option, authors: Option>, description: Option, + homepage: Option, documentation: Option, readme: Option, - homepage: Option, - repository: Option, + keywords: Option>, + categories: Option>, license: Option, #[serde(rename = "license-file")] license_file: Option, - keywords: Option>, - categories: Option>, + repository: Option, publish: Option, edition: Option, badges: Option>>, #[serde(rename = "rust-version")] rust_version: Option, + // We use skip here since it will never be present when deserializing + // and we don't want it present when serializing + #[serde(skip)] + ws_root: PathBuf, +} - // Note that this field must come last due to the way toml serialization - // works which requires tables to be emitted after all values. - metadata: Option, +impl InheritableFields { + pub fn update_deps(&mut self, deps: Option>) { + self.dependencies = deps; + } + + pub fn update_ws_path(&mut self, ws_root: PathBuf) { + self.ws_root = ws_root; + } + + pub fn dependencies(&self) -> CargoResult> { + self.dependencies.clone().map_or( + Err(anyhow!("`workspace.dependencies` was not defined")), + |d| Ok(d), + ) + } + + pub fn get_dependency(&self, name: &str) -> CargoResult { + self.dependencies.clone().map_or( + Err(anyhow!("`workspace.dependencies` was not defined")), + |deps| { + deps.get(name).map_or( + Err(anyhow!( + "`dependency.{}` was not found in `workspace.dependencies`", + name + )), + |dep| Ok(dep.clone()), + ) + }, + ) + } + + pub fn version(&self) -> CargoResult { + self.version.clone().map_or( + Err(anyhow!("`workspace.package.version` was not defined")), + |d| Ok(d), + ) + } + + pub fn authors(&self) -> CargoResult> { + self.authors.clone().map_or( + Err(anyhow!("`workspace.package.authors` was not defined")), + |d| Ok(d), + ) + } + + pub fn description(&self) -> CargoResult { + self.description.clone().map_or( + Err(anyhow!("`workspace.package.description` was not defined")), + |d| Ok(d), + ) + } + + pub fn homepage(&self) -> CargoResult { + self.homepage.clone().map_or( + Err(anyhow!("`workspace.package.homepage` was not defined")), + |d| Ok(d), + ) + } + + pub fn documentation(&self) -> CargoResult { + self.documentation.clone().map_or( + Err(anyhow!("`workspace.package.documentation` was not defined")), + |d| Ok(d), + ) + } + + pub fn readme(&self, package_root: &Path) -> CargoResult { + readme_for_project(self.ws_root.as_path(), self.readme.clone()).map_or( + Err(anyhow!("`workspace.package.readme` was not defined")), + |readme| { + let rel_path = + resolve_relative_path("readme", &self.ws_root, package_root, &readme)?; + Ok(StringOrBool::String(rel_path)) + }, + ) + } + + pub fn keywords(&self) -> CargoResult> { + self.keywords.clone().map_or( + Err(anyhow!("`workspace.package.keywords` was not defined")), + |d| Ok(d), + ) + } + + pub fn categories(&self) -> CargoResult> { + self.categories.clone().map_or( + Err(anyhow!("`workspace.package.categories` was not defined")), + |d| Ok(d), + ) + } + + pub fn license(&self) -> CargoResult { + self.license.clone().map_or( + Err(anyhow!("`workspace.package.license` was not defined")), + |d| Ok(d), + ) + } + + pub fn license_file(&self, package_root: &Path) -> CargoResult { + self.license_file.clone().map_or( + Err(anyhow!("`workspace.package.license_file` was not defined")), + |d| resolve_relative_path("license-file", &self.ws_root, package_root, &d), + ) + } + + pub fn repository(&self) -> CargoResult { + self.repository.clone().map_or( + Err(anyhow!("`workspace.package.repository` was not defined")), + |d| Ok(d), + ) + } + + pub fn publish(&self) -> CargoResult { + self.publish.clone().map_or( + Err(anyhow!("`workspace.package.publish` was not defined")), + |d| Ok(d), + ) + } + + pub fn edition(&self) -> CargoResult { + self.edition.clone().map_or( + Err(anyhow!("`workspace.package.edition` was not defined")), + |d| Ok(d), + ) + } + + pub fn rust_version(&self) -> CargoResult { + self.rust_version.clone().map_or( + Err(anyhow!("`workspace.package.rust-version` was not defined")), + |d| Ok(d), + ) + } + + pub fn badges(&self) -> CargoResult>> { + self.badges.clone().map_or( + Err(anyhow!("`workspace.package.badges` was not defined")), + |d| Ok(d), + ) + } + + pub fn ws_root(&self) -> &PathBuf { + &self.ws_root + } } impl TomlProject { @@ -1362,32 +1519,15 @@ impl TomlManifest { let workspace_config = match (me.workspace.as_ref(), project.workspace.as_ref()) { (Some(config), None) => { - let inheritable = InheritableFields::new( - config.dependencies.clone(), - config.version.clone(), - config.authors.clone(), - config.description.clone(), - config.homepage.clone(), - config.documentation.clone(), - config.readme.clone(), - config.keywords.clone(), - config.categories.clone(), - config.license.clone(), - config.license_file.clone(), - config.repository.clone(), - config.publish.clone(), - config.edition.clone(), - config.badges.clone(), - config.rust_version.clone(), - package_root.to_path_buf(), - ); - + let mut inheritable = config.package.clone().unwrap_or_default(); + inheritable.update_ws_path(package_root.to_path_buf()); + inheritable.update_deps(config.dependencies.clone()); WorkspaceConfig::Root(WorkspaceRootConfig::new( package_root, &config.members, &config.default_members, &config.exclude, - &Some(inheritable.clone()), + &Some(inheritable), &config.metadata, )) } @@ -2057,25 +2197,9 @@ impl TomlManifest { .transpose()?; let workspace_config = match me.workspace { Some(ref config) => { - let inheritable = InheritableFields::new( - config.dependencies.clone(), - config.version.clone(), - config.authors.clone(), - config.description.clone(), - config.homepage.clone(), - config.documentation.clone(), - config.readme.clone(), - config.keywords.clone(), - config.categories.clone(), - config.license.clone(), - config.license_file.clone(), - config.repository.clone(), - config.publish.clone(), - config.edition.clone(), - config.badges.clone(), - config.rust_version.clone(), - root.to_path_buf(), - ); + let mut inheritable = config.package.clone().unwrap_or_default(); + inheritable.update_ws_path(root.to_path_buf()); + inheritable.update_deps(config.dependencies.clone()); WorkspaceConfig::Root(WorkspaceRootConfig::new( root, &config.members, diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 5c151200f57..d3f7e99740d 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1439,7 +1439,7 @@ log2 = { workspace = true } Example 2: ```toml # in workspace's Cargo.toml -[workspace] +[workspace.package] version = "1.2.3" authors = ["Nice Folks"] description = "..." diff --git a/tests/testsuite/inheritable_workspace_fields.rs b/tests/testsuite/inheritable_workspace_fields.rs index dc192942f14..3f88045e1a6 100644 --- a/tests/testsuite/inheritable_workspace_fields.rs +++ b/tests/testsuite/inheritable_workspace_fields.rs @@ -12,6 +12,7 @@ fn permit_additional_workspace_fields() { r#" [workspace] members = ["bar"] + [workspace.package] version = "1.2.3" authors = ["Rustaceans"] description = "This is a crate" @@ -27,7 +28,7 @@ fn permit_additional_workspace_fields() { edition = "2018" rust-version = "1.60" - [workspace.badges] + [workspace.package.badges] gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" } [workspace.dependencies] @@ -135,6 +136,7 @@ fn inherit_own_workspace_fields() { [workspace] members = [] + [workspace.package] version = "1.2.3" authors = ["Rustaceans"] description = "This is a crate" @@ -147,7 +149,7 @@ fn inherit_own_workspace_fields() { publish = true edition = "2018" rust-version = "1.60" - [workspace.badges] + [workspace.package.badges] gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" } "#, ) @@ -501,10 +503,10 @@ fn inherit_from_own_undefined_field() { [ERROR] failed to parse manifest at `[CWD]/Cargo.toml` Caused by: - error inheriting `description` from workspace root manifest's `workspace.description` + error inheriting `description` from workspace root manifest's `workspace.package.description` Caused by: - `workspace.description` was not defined + `workspace.package.description` was not defined ", ) .run(); @@ -581,6 +583,7 @@ fn inherit_workspace_fields() { r#" [workspace] members = ["bar"] + [workspace.package] version = "1.2.3" authors = ["Rustaceans"] description = "This is a crate" @@ -595,7 +598,7 @@ fn inherit_workspace_fields() { publish = true edition = "2018" rust-version = "1.60" - [workspace.badges] + [workspace.package.badges] gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" } "#, ) @@ -1281,7 +1284,7 @@ fn error_no_root_workspace() { [ERROR] failed to parse manifest at `[..]/Cargo.toml` Caused by: - error inheriting `description` from workspace root manifest's `workspace.description` + error inheriting `description` from workspace root manifest's `workspace.package.description` Caused by: root of a workspace inferred but wasn't a root: [..]/Cargo.toml