Skip to content

Commit b1636fc

Browse files
committed
Auto merge of #10497 - Muscraft:rfc2906-part1, r=epage
Part 1 of RFC2906 - Packages can inherit fields from their root workspace Tracking issue: #8415 RFC: rust-lang/rfcs#2906 All changes were heavily inspired by #8664 and #9684 A big thanks to both `@yaymukund` and `@ParkMyCar.` I pulled a lot of inspiration from their branches. I would also like to thank `@alexcrichton` for all the feedback on the first attempt. It has brought this into a much better state. All changes have been made according to the RFC as well as `@alexcrichton's` [comment](#8664 (comment)). This is part 1 of many, as described by [this comment](#9684 (comment)), [this comment](#9684 (review)) and redefined [by this one](#10497 (comment)). This PR focuses on inheriting in root package, including: - Add `MaybeWorkspace<T>` to allow for `{ workspace = true }` - Add a new variant to `TomlDependency` to allow inheriting dependencies from a workspace - Add `InheritableFields` so that we have somewhere to get a value from when we `resolve` a `MaybeWorkspace<T>` - `license_file` and `readme` are in `InheritableFields` in this part but are not `MaybeWorkspace` for reasons [described here](#10497 (comment)) - Add a method to `resolve` a `MaybeWorkspace<T>` into a `T` that can fail if we have nowhere to pull from or the workspace does not define that field - Disallow inheriting from a different `Cargo.toml` - This means that you can only inherit from inside the same `Cargo.toml`, i.e. it has both a `[workspace]` and a `[package]` - Forcing this requirement allows us to test the implementations of `MaybeWorkspace<T>` and the new `TomlDependency` variant without having to add searching for a workspace root and the complexity with it Remaining implementation work for the RFC - Support inheriting in any workspace member - Correctly inherit fields that are relative paths - Including adding support for inheriting `license_file`, `readme`, and path-dependencies - Path dependencies infer version directive - Lock workspace dependencies and warn when unused - Optimizations, as needed - Evaluate any new fields for being inheritable (e.g. `rust-version`) - Evaluate making users of `{ workspace = true }` define the workspace they pull from in `[package]` Areas of concern: - `TomlDependency` Deserialization is a mess, and I could not figure out how to do it in a cleaner way without significant headaches. I ended up having to do the same thing as last time [which was an issue](#9684 (comment)). - Resolving on a `MaybeWorkspace` feels extremely verbose currently: ```rust project.homepage.clone().map(|mw| mw.resolve(&features, "homepage", || get_ws(inheritable)?.homepage())).transpose()?, ``` This way allows for lazy resolution of inheritable fields and finding a workspace (in part 2) so you do not pay a penalty for not using this feature. The other bit of good news is `&features` will go away as it is just feature checking currently. - This feature requires some level of duplication of code as well as remaking the original `TomlManifest` which adds extra length to the changes. - This feature also takes a linear process and makes it potentially non-linear which adds lots of complexity (which will get worse in Part 2) Please let me know if you have any feedback or suggestions!
2 parents af8ddf5 + e6992d4 commit b1636fc

File tree

8 files changed

+1759
-145
lines changed

8 files changed

+1759
-145
lines changed

src/cargo/core/compiler/standard_lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pub fn resolve_std<'cfg>(
6565
&Some(members),
6666
/*default_members*/ &None,
6767
/*exclude*/ &None,
68+
/*inheritable*/ &None,
6869
/*custom_metadata*/ &None,
6970
));
7071
let virtual_manifest = crate::core::VirtualManifest::new(

src/cargo/core/features.rs

+3
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ features! {
412412

413413
// Allow specifying rustflags directly in a profile
414414
(unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"),
415+
416+
// Allow specifying rustflags directly in a profile
417+
(unstable, workspace_inheritance, "", "reference/unstable.html#workspace-inheritance"),
415418
}
416419

417420
pub struct Feature {

src/cargo/core/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ pub use self::resolver::{Resolve, ResolveVersion};
1010
pub use self::shell::{Shell, Verbosity};
1111
pub use self::source::{GitReference, Source, SourceId, SourceMap};
1212
pub use self::summary::{FeatureMap, FeatureValue, Summary};
13-
pub use self::workspace::{MaybePackage, Workspace, WorkspaceConfig, WorkspaceRootConfig};
13+
pub use self::workspace::{
14+
InheritableFields, MaybePackage, Workspace, WorkspaceConfig, WorkspaceRootConfig,
15+
};
1416

1517
pub mod compiler;
1618
pub mod dependency;

src/cargo/core/workspace.rs

+203-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::collections::{BTreeMap, BTreeSet, HashSet};
44
use std::path::{Path, PathBuf};
55
use std::rc::Rc;
66

7-
use anyhow::{bail, Context as _};
7+
use anyhow::{anyhow, bail, Context as _};
88
use glob::glob;
99
use itertools::Itertools;
1010
use log::debug;
@@ -22,7 +22,9 @@ use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
2222
use crate::util::errors::{CargoResult, ManifestError};
2323
use crate::util::interning::InternedString;
2424
use crate::util::lev_distance;
25-
use crate::util::toml::{read_manifest, TomlDependency, TomlProfiles};
25+
use crate::util::toml::{
26+
read_manifest, StringOrBool, TomlDependency, TomlProfiles, VecStringOrBool,
27+
};
2628
use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl};
2729
use cargo_util::paths;
2830

@@ -123,6 +125,15 @@ pub enum WorkspaceConfig {
123125
Member { root: Option<String> },
124126
}
125127

128+
impl WorkspaceConfig {
129+
pub fn inheritable(&self) -> Option<&InheritableFields> {
130+
match self {
131+
WorkspaceConfig::Root(root) => Some(&root.inheritable_fields),
132+
WorkspaceConfig::Member { .. } => None,
133+
}
134+
}
135+
}
136+
126137
/// Intermediate configuration of a workspace root in a manifest.
127138
///
128139
/// Knows the Workspace Root path, as well as `members` and `exclude` lists of path patterns, which
@@ -133,6 +144,7 @@ pub struct WorkspaceRootConfig {
133144
members: Option<Vec<String>>,
134145
default_members: Option<Vec<String>>,
135146
exclude: Vec<String>,
147+
inheritable_fields: InheritableFields,
136148
custom_metadata: Option<toml::Value>,
137149
}
138150

@@ -1567,17 +1579,18 @@ impl WorkspaceRootConfig {
15671579
members: &Option<Vec<String>>,
15681580
default_members: &Option<Vec<String>>,
15691581
exclude: &Option<Vec<String>>,
1582+
inheritable: &Option<InheritableFields>,
15701583
custom_metadata: &Option<toml::Value>,
15711584
) -> WorkspaceRootConfig {
15721585
WorkspaceRootConfig {
15731586
root_dir: root_dir.to_path_buf(),
15741587
members: members.clone(),
15751588
default_members: default_members.clone(),
15761589
exclude: exclude.clone().unwrap_or_default(),
1590+
inheritable_fields: inheritable.clone().unwrap_or_default(),
15771591
custom_metadata: custom_metadata.clone(),
15781592
}
15791593
}
1580-
15811594
/// Checks the path against the `excluded` list.
15821595
///
15831596
/// This method does **not** consider the `members` list.
@@ -1641,3 +1654,190 @@ impl WorkspaceRootConfig {
16411654
Ok(res)
16421655
}
16431656
}
1657+
1658+
/// A group of fields that are inheritable by members of the workspace
1659+
#[derive(Clone, Debug, Default)]
1660+
pub struct InheritableFields {
1661+
dependencies: Option<BTreeMap<String, TomlDependency>>,
1662+
version: Option<semver::Version>,
1663+
authors: Option<Vec<String>>,
1664+
description: Option<String>,
1665+
homepage: Option<String>,
1666+
documentation: Option<String>,
1667+
readme: Option<StringOrBool>,
1668+
keywords: Option<Vec<String>>,
1669+
categories: Option<Vec<String>>,
1670+
license: Option<String>,
1671+
license_file: Option<String>,
1672+
repository: Option<String>,
1673+
publish: Option<VecStringOrBool>,
1674+
edition: Option<String>,
1675+
badges: Option<BTreeMap<String, BTreeMap<String, String>>>,
1676+
}
1677+
1678+
impl InheritableFields {
1679+
pub fn new(
1680+
dependencies: Option<BTreeMap<String, TomlDependency>>,
1681+
version: Option<semver::Version>,
1682+
authors: Option<Vec<String>>,
1683+
description: Option<String>,
1684+
homepage: Option<String>,
1685+
documentation: Option<String>,
1686+
readme: Option<StringOrBool>,
1687+
keywords: Option<Vec<String>>,
1688+
categories: Option<Vec<String>>,
1689+
license: Option<String>,
1690+
license_file: Option<String>,
1691+
repository: Option<String>,
1692+
publish: Option<VecStringOrBool>,
1693+
edition: Option<String>,
1694+
badges: Option<BTreeMap<String, BTreeMap<String, String>>>,
1695+
) -> InheritableFields {
1696+
Self {
1697+
dependencies,
1698+
version,
1699+
authors,
1700+
description,
1701+
homepage,
1702+
documentation,
1703+
readme,
1704+
keywords,
1705+
categories,
1706+
license,
1707+
license_file,
1708+
repository,
1709+
publish,
1710+
edition,
1711+
badges,
1712+
}
1713+
}
1714+
1715+
pub fn dependencies(&self) -> CargoResult<BTreeMap<String, TomlDependency>> {
1716+
self.dependencies.clone().map_or(
1717+
Err(anyhow!("`workspace.dependencies` was not defined")),
1718+
|d| Ok(d),
1719+
)
1720+
}
1721+
1722+
pub fn get_dependency(&self, name: &str) -> CargoResult<TomlDependency> {
1723+
self.dependencies.clone().map_or(
1724+
Err(anyhow!("`workspace.dependencies` was not defined")),
1725+
|deps| {
1726+
deps.get(name).map_or(
1727+
Err(anyhow!(
1728+
"`dependency.{}` was not found in `workspace.dependencies`",
1729+
name
1730+
)),
1731+
|dep| Ok(dep.clone()),
1732+
)
1733+
},
1734+
)
1735+
}
1736+
1737+
pub fn version(&self) -> CargoResult<semver::Version> {
1738+
self.version
1739+
.clone()
1740+
.map_or(Err(anyhow!("`workspace.version` was not defined")), |d| {
1741+
Ok(d)
1742+
})
1743+
}
1744+
1745+
pub fn authors(&self) -> CargoResult<Vec<String>> {
1746+
self.authors
1747+
.clone()
1748+
.map_or(Err(anyhow!("`workspace.authors` was not defined")), |d| {
1749+
Ok(d)
1750+
})
1751+
}
1752+
1753+
pub fn description(&self) -> CargoResult<String> {
1754+
self.description.clone().map_or(
1755+
Err(anyhow!("`workspace.description` was not defined")),
1756+
|d| Ok(d),
1757+
)
1758+
}
1759+
1760+
pub fn homepage(&self) -> CargoResult<String> {
1761+
self.homepage
1762+
.clone()
1763+
.map_or(Err(anyhow!("`workspace.homepage` was not defined")), |d| {
1764+
Ok(d)
1765+
})
1766+
}
1767+
1768+
pub fn documentation(&self) -> CargoResult<String> {
1769+
self.documentation.clone().map_or(
1770+
Err(anyhow!("`workspace.documentation` was not defined")),
1771+
|d| Ok(d),
1772+
)
1773+
}
1774+
1775+
pub fn readme(&self) -> CargoResult<StringOrBool> {
1776+
self.readme
1777+
.clone()
1778+
.map_or(Err(anyhow!("`workspace.readme` was not defined")), |d| {
1779+
Ok(d)
1780+
})
1781+
}
1782+
1783+
pub fn keywords(&self) -> CargoResult<Vec<String>> {
1784+
self.keywords
1785+
.clone()
1786+
.map_or(Err(anyhow!("`workspace.keywords` was not defined")), |d| {
1787+
Ok(d)
1788+
})
1789+
}
1790+
1791+
pub fn categories(&self) -> CargoResult<Vec<String>> {
1792+
self.categories.clone().map_or(
1793+
Err(anyhow!("`workspace.categories` was not defined")),
1794+
|d| Ok(d),
1795+
)
1796+
}
1797+
1798+
pub fn license(&self) -> CargoResult<String> {
1799+
self.license
1800+
.clone()
1801+
.map_or(Err(anyhow!("`workspace.license` was not defined")), |d| {
1802+
Ok(d)
1803+
})
1804+
}
1805+
1806+
pub fn license_file(&self) -> CargoResult<String> {
1807+
self.license_file.clone().map_or(
1808+
Err(anyhow!("`workspace.license_file` was not defined")),
1809+
|d| Ok(d),
1810+
)
1811+
}
1812+
1813+
pub fn repository(&self) -> CargoResult<String> {
1814+
self.repository.clone().map_or(
1815+
Err(anyhow!("`workspace.repository` was not defined")),
1816+
|d| Ok(d),
1817+
)
1818+
}
1819+
1820+
pub fn publish(&self) -> CargoResult<VecStringOrBool> {
1821+
self.publish
1822+
.clone()
1823+
.map_or(Err(anyhow!("`workspace.publish` was not defined")), |d| {
1824+
Ok(d)
1825+
})
1826+
}
1827+
1828+
pub fn edition(&self) -> CargoResult<String> {
1829+
self.edition
1830+
.clone()
1831+
.map_or(Err(anyhow!("`workspace.edition` was not defined")), |d| {
1832+
Ok(d)
1833+
})
1834+
}
1835+
1836+
pub fn badges(&self) -> CargoResult<BTreeMap<String, BTreeMap<String, String>>> {
1837+
self.badges
1838+
.clone()
1839+
.map_or(Err(anyhow!("`workspace.badges` was not defined")), |d| {
1840+
Ok(d)
1841+
})
1842+
}
1843+
}

0 commit comments

Comments
 (0)