Auto merge of #10517 - Muscraft:rfc2906-part2, r=epage

Part 2 of RFC2906 -- allow inheriting from a different `Cargo.toml`

Tracking issue: #8415
RFC: rust-lang/rfcs#2906

[Part 1](https://github.com/rust-lang/cargo/pull/10497)

This PR focuses on inheriting from a root workspace:
- Allow inheriting from a different `Cargo.toml`
- Add in searching for a workspace root in `to_real_manifest` as needed
- Fixed problem where a package would try to pull a dependency from a workspace and specify `{ workspace = true, optional = true }` and it would not respect the `optional`
- Added tests to verify everything is in working order

Remaining implementation work for the RFC
- 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`)

Problems:
- There is duplication of code that can't be removed without significant refactoring
- Potential to parse the same manifest many times when searching for a root
  - This should not happen when a `[package]` specifies its workspace
  - This should only happen if the workspace root is greater than one folder above
This commit is contained in:
bors 2022-04-05 17:04:53 +00:00
commit e2e2dddebe
5 changed files with 763 additions and 109 deletions

View file

@ -26,6 +26,15 @@ pub enum EitherManifest {
Virtual(VirtualManifest),
}
impl EitherManifest {
pub(crate) fn workspace_config(&self) -> &WorkspaceConfig {
match *self {
EitherManifest::Real(ref r) => r.workspace_config(),
EitherManifest::Virtual(ref v) => v.workspace_config(),
}
}
}
/// Contains all the information about a package, as loaded from a `Cargo.toml`.
///
/// This is deserialized using the [`TomlManifest`] type.

View file

@ -11,7 +11,8 @@ 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::{
InheritableFields, MaybePackage, Workspace, WorkspaceConfig, WorkspaceRootConfig,
find_workspace_root, InheritableFields, MaybePackage, Workspace, WorkspaceConfig,
WorkspaceRootConfig,
};
pub mod compiler;

View file

@ -591,16 +591,6 @@ impl<'cfg> Workspace<'cfg> {
/// 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<Option<PathBuf>> {
fn read_root_pointer(member_manifest: &Path, root_link: &str) -> PathBuf {
let path = member_manifest
.parent()
.unwrap()
.join(root_link)
.join("Cargo.toml");
debug!("find_root - pointer {}", path.display());
paths::normalize_path(&path)
}
{
let current = self.packages.load(manifest_path)?;
match *current.workspace_config() {
@ -615,42 +605,25 @@ impl<'cfg> Workspace<'cfg> {
}
}
for path in paths::ancestors(manifest_path, None).skip(2) {
if path.ends_with("target/package") {
break;
}
let ances_manifest_path = path.join("Cargo.toml");
for ances_manifest_path in find_root_iter(manifest_path, self.config) {
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));
}
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;
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 { .. } => {}
}
}
Ok(None)
}
@ -1653,6 +1626,10 @@ impl WorkspaceRootConfig {
.collect::<Result<Vec<_>, _>>()?;
Ok(res)
}
pub fn inheritable(&self) -> &InheritableFields {
&self.inheritable_fields
}
}
/// A group of fields that are inheritable by members of the workspace
@ -1841,3 +1818,99 @@ impl InheritableFields {
})
}
}
fn parse_manifest(manifest_path: &Path, config: &Config) -> CargoResult<EitherManifest> {
let key = manifest_path.parent().unwrap();
let source_id = SourceId::for_path(key)?;
let (manifest, _nested_paths) = read_manifest(manifest_path, source_id, config)?;
Ok(manifest)
}
pub fn find_workspace_root(manifest_path: &Path, config: &Config) -> CargoResult<Option<PathBuf>> {
for ances_manifest_path in find_root_iter(manifest_path, config) {
debug!("find_root - trying {}", ances_manifest_path.display());
match *parse_manifest(&ances_manifest_path, config)?.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 { .. } => {}
}
}
Ok(None)
}
fn read_root_pointer(member_manifest: &Path, root_link: &str) -> PathBuf {
let path = member_manifest
.parent()
.unwrap()
.join(root_link)
.join("Cargo.toml");
debug!("find_root - pointer {}", path.display());
paths::normalize_path(&path)
}
fn find_root_iter<'a>(
manifest_path: &'a Path,
config: &'a Config,
) -> impl Iterator<Item = PathBuf> + 'a {
LookBehind::new(paths::ancestors(manifest_path, None).skip(2))
.take_while(|path| !path.curr.ends_with("target/package"))
// 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.
.take_while(|path| {
if let Some(last) = path.last {
config.home() != last
} else {
true
}
})
.map(|path| path.curr.join("Cargo.toml"))
.filter(|ances_manifest_path| ances_manifest_path.exists())
}
struct LookBehindWindow<'a, T: ?Sized> {
curr: &'a T,
last: Option<&'a T>,
}
struct LookBehind<'a, T: ?Sized, K: Iterator<Item = &'a T>> {
iter: K,
last: Option<&'a T>,
}
impl<'a, T: ?Sized, K: Iterator<Item = &'a T>> LookBehind<'a, T, K> {
fn new(items: K) -> Self {
Self {
iter: items,
last: None,
}
}
}
impl<'a, T: ?Sized, K: Iterator<Item = &'a T>> Iterator for LookBehind<'a, T, K> {
type Item = LookBehindWindow<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next() {
None => None,
Some(next) => {
let last = self.last;
self.last = Some(next);
Some(LookBehindWindow { curr: next, last })
}
}
}
}

View file

@ -20,7 +20,7 @@ use crate::core::compiler::{CompileKind, CompileTarget};
use crate::core::dependency::{Artifact, ArtifactTarget, DepKind};
use crate::core::manifest::{ManifestMetadata, TargetSourcePath, Warnings};
use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, Manifest, PackageId, Summary, Target};
use crate::core::{find_workspace_root, Dependency, Manifest, PackageId, Summary, Target};
use crate::core::{
Edition, EitherManifest, Feature, Features, InheritableFields, VirtualManifest, Workspace,
};
@ -1283,13 +1283,30 @@ impl TomlManifest {
package_root: &Path,
config: &Config,
) -> CargoResult<(Manifest, Vec<PathBuf>)> {
// This is for later when we try to find the workspace root
fn get_ws(inheritable: Option<&InheritableFields>) -> CargoResult<&InheritableFields> {
match inheritable {
Some(inheritable) => Ok(inheritable),
None => Err(anyhow!(
"inheriting from a parent workspace is not implemented yet",
)),
fn get_ws(
config: &Config,
resolved_path: PathBuf,
workspace_config: WorkspaceConfig,
) -> CargoResult<InheritableFields> {
match workspace_config {
WorkspaceConfig::Root(root) => Ok(root.inheritable().clone()),
WorkspaceConfig::Member {
root: Some(ref path_to_root),
} => {
let path = resolved_path
.parent()
.unwrap()
.join(path_to_root)
.join("Cargo.toml");
let root_path = paths::normalize_path(&path);
inheritable_from_path(config, root_path)
}
WorkspaceConfig::Member { root: None } => {
match find_workspace_root(&resolved_path, config)? {
Some(path_to_root) => inheritable_from_path(config, path_to_root),
None => Err(anyhow!("failed to find a workspace root")),
}
}
}
}
@ -1343,8 +1360,6 @@ impl TomlManifest {
),
};
let inheritable = workspace_config.inheritable();
let package_name = project.name.trim();
if package_name.is_empty() {
bail!("package name cannot be an empty string")
@ -1352,10 +1367,11 @@ impl TomlManifest {
validate_package_name(package_name, "package name", "")?;
let version = project
.version
.clone()
.resolve(&features, "version", || get_ws(inheritable)?.version())?;
let resolved_path = package_root.join("Cargo.toml");
let version = project.version.clone().resolve(&features, "version", || {
get_ws(config, resolved_path.clone(), workspace_config.clone())?.version()
})?;
project.version = MaybeWorkspace::Defined(version.clone());
@ -1363,7 +1379,9 @@ impl TomlManifest {
let edition = if let Some(edition) = project.edition.clone() {
let edition: Edition = edition
.resolve(&features, "edition", || get_ws(inheritable)?.edition())?
.resolve(&features, "edition", || {
get_ws(config, resolved_path.clone(), workspace_config.clone())?.edition()
})?
.parse()
.with_context(|| "failed to parse the `edition` key")?;
project.edition = Some(MaybeWorkspace::Defined(edition.to_string()));
@ -1480,7 +1498,7 @@ impl TomlManifest {
cx: &mut Context<'_, '_>,
new_deps: Option<&BTreeMap<String, TomlDependency>>,
kind: Option<DepKind>,
inheritable: Option<&InheritableFields>,
workspace_config: &WorkspaceConfig,
) -> CargoResult<Option<BTreeMap<String, TomlDependency>>> {
let dependencies = match new_deps {
Some(dependencies) => dependencies,
@ -1488,9 +1506,14 @@ impl TomlManifest {
};
let mut deps: BTreeMap<String, TomlDependency> = BTreeMap::new();
for (n, v) in dependencies.iter() {
let resolved = v
.clone()
.resolve(features, n, || get_ws(inheritable)?.get_dependency(n))?;
let resolved = v.clone().resolve(features, n, || {
get_ws(
cx.config,
cx.root.join("Cargo.toml"),
workspace_config.clone(),
)?
.get_dependency(n)
})?;
let dep = resolved.to_dependency(n, cx, kind)?;
validate_package_name(dep.name_in_toml().as_str(), "dependency name", "")?;
cx.deps.push(dep);
@ -1505,7 +1528,7 @@ impl TomlManifest {
&mut cx,
me.dependencies.as_ref(),
None,
inheritable,
&workspace_config,
)?;
if me.dev_dependencies.is_some() && me.dev_dependencies2.is_some() {
warn_on_deprecated("dev-dependencies", package_name, "package", cx.warnings);
@ -1519,7 +1542,7 @@ impl TomlManifest {
&mut cx,
dev_deps,
Some(DepKind::Development),
inheritable,
&workspace_config,
)?;
if me.build_dependencies.is_some() && me.build_dependencies2.is_some() {
warn_on_deprecated("build-dependencies", package_name, "package", cx.warnings);
@ -1533,7 +1556,7 @@ impl TomlManifest {
&mut cx,
build_deps,
Some(DepKind::Build),
inheritable,
&workspace_config,
)?;
let mut target: BTreeMap<String, TomlPlatform> = BTreeMap::new();
@ -1548,7 +1571,7 @@ impl TomlManifest {
&mut cx,
platform.dependencies.as_ref(),
None,
inheritable,
&workspace_config,
)
.unwrap();
if platform.build_dependencies.is_some() && platform.build_dependencies2.is_some() {
@ -1563,7 +1586,7 @@ impl TomlManifest {
&mut cx,
build_deps,
Some(DepKind::Build),
inheritable,
&workspace_config,
)
.unwrap();
if platform.dev_dependencies.is_some() && platform.dev_dependencies2.is_some() {
@ -1578,7 +1601,7 @@ impl TomlManifest {
&mut cx,
dev_deps,
Some(DepKind::Development),
inheritable,
&workspace_config,
)
.unwrap();
target.insert(
@ -1635,21 +1658,27 @@ impl TomlManifest {
.clone()
.map(|mw| {
mw.resolve(&features, "description", || {
get_ws(inheritable)?.description()
get_ws(config, resolved_path.clone(), workspace_config.clone())?
.description()
})
})
.transpose()?,
homepage: project
.homepage
.clone()
.map(|mw| mw.resolve(&features, "homepage", || get_ws(inheritable)?.homepage()))
.map(|mw| {
mw.resolve(&features, "homepage", || {
get_ws(config, resolved_path.clone(), workspace_config.clone())?.homepage()
})
})
.transpose()?,
documentation: project
.documentation
.clone()
.map(|mw| {
mw.resolve(&features, "documentation", || {
get_ws(inheritable)?.documentation()
get_ws(config, resolved_path.clone(), workspace_config.clone())?
.documentation()
})
})
.transpose()?,
@ -1657,13 +1686,21 @@ impl TomlManifest {
authors: project
.authors
.clone()
.map(|mw| mw.resolve(&features, "authors", || get_ws(inheritable)?.authors()))
.map(|mw| {
mw.resolve(&features, "authors", || {
get_ws(config, resolved_path.clone(), workspace_config.clone())?.authors()
})
})
.transpose()?
.unwrap_or_default(),
license: project
.license
.clone()
.map(|mw| mw.resolve(&features, "license", || get_ws(inheritable)?.license()))
.map(|mw| {
mw.resolve(&features, "license", || {
get_ws(config, resolved_path.clone(), workspace_config.clone())?.license()
})
})
.transpose()?,
license_file: project.license_file.clone(),
repository: project
@ -1671,14 +1708,19 @@ impl TomlManifest {
.clone()
.map(|mw| {
mw.resolve(&features, "repository", || {
get_ws(inheritable)?.repository()
get_ws(config, resolved_path.clone(), workspace_config.clone())?
.repository()
})
})
.transpose()?,
keywords: project
.keywords
.clone()
.map(|mw| mw.resolve(&features, "keywords", || get_ws(inheritable)?.keywords()))
.map(|mw| {
mw.resolve(&features, "keywords", || {
get_ws(config, resolved_path.clone(), workspace_config.clone())?.keywords()
})
})
.transpose()?
.unwrap_or_default(),
categories: project
@ -1686,7 +1728,8 @@ impl TomlManifest {
.clone()
.map(|mw| {
mw.resolve(&features, "categories", || {
get_ws(inheritable)?.categories()
get_ws(config, resolved_path.clone(), workspace_config.clone())?
.categories()
})
})
.transpose()?
@ -1694,7 +1737,11 @@ impl TomlManifest {
badges: me
.badges
.clone()
.map(|mw| mw.resolve(&features, "badges", || get_ws(inheritable)?.badges()))
.map(|mw| {
mw.resolve(&features, "badges", || {
get_ws(config, resolved_path.clone(), workspace_config.clone())?.badges()
})
})
.transpose()?
.unwrap_or_default(),
links: project.links.clone(),
@ -1739,7 +1786,9 @@ impl TomlManifest {
let publish = project.publish.clone().map(|publish| {
publish
.resolve(&features, "publish", || get_ws(inheritable)?.publish())
.resolve(&features, "publish", || {
get_ws(config, resolved_path.clone(), workspace_config.clone())?.publish()
})
.unwrap()
});
@ -2073,6 +2122,22 @@ impl TomlManifest {
}
}
fn inheritable_from_path(
config: &Config,
resolved_path: PathBuf,
) -> CargoResult<InheritableFields> {
let key = resolved_path.parent().unwrap();
let source_id = SourceId::for_path(key)?;
let (man, _) = read_manifest(&resolved_path, source_id, config)?;
match man.workspace_config() {
WorkspaceConfig::Root(root) => Ok(root.inheritable().clone()),
_ => bail!(
"root of a workspace inferred but wasn't a root: {}",
resolved_path.display()
),
}
}
/// Returns the name of the README file for a `TomlProject`.
fn readme_for_project(package_root: &Path, project: &TomlProject) -> Option<String> {
match &project.readme {
@ -2196,7 +2261,18 @@ impl<P: ResolveToPath + Clone> TomlDependency<P> {
label, label
)).map(|dep| {
match dep {
TomlDependency::Simple(s) => TomlDependency::Simple(s),
TomlDependency::Simple(s) => {
if optional.is_some() || features.is_some() {
TomlDependency::Detailed(DetailedTomlDependency::<P> {
version: Some(s),
optional,
features,
..Default::default()
})
} else {
TomlDependency::Simple(s)
}
},
TomlDependency::Detailed(d) => {
let mut dep = d.clone();
dep.add_features(features);

View file

@ -1,6 +1,6 @@
//! Tests for inheriting Cargo.toml fields with { workspace = true }
use cargo_test_support::registry::{Dependency, Package};
use cargo_test_support::{basic_lib_manifest, git, paths, project, publish, registry};
use cargo_test_support::{basic_lib_manifest, git, path2url, paths, project, publish, registry};
#[cargo_test]
fn permit_additional_workspace_fields() {
@ -564,7 +564,7 @@ fn inherited_dependencies_union_features() {
}
#[cargo_test]
fn error_on_unimplemented_inheritance_fields() {
fn inherit_workspace_fields() {
registry::init();
let p = project().build();
@ -576,45 +576,403 @@ fn error_on_unimplemented_inheritance_fields() {
[workspace]
members = ["bar"]
version = "1.2.3"
authors = ["Rustaceans"]
description = "This is a crate"
documentation = "https://www.rust-lang.org/learn"
homepage = "https://www.rust-lang.org"
repository = "https://github.com/example/example"
keywords = ["cli"]
categories = ["development-tools"]
publish = true
edition = "2018"
[workspace.badges]
gitlab = { repository = "https://gitlab.com/rust-lang/rust", branch = "master" }
"#,
)
.file("src/main.rs", "fn main() {}")
.file(
"bar/Cargo.toml",
r#"
badges = { workspace = true }
cargo-features = ["workspace-inheritance"]
[package]
name = "bar"
workspace = ".."
version = { workspace = true }
authors = { workspace = true }
description = { workspace = true }
documentation = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
keywords = { workspace = true }
categories = { workspace = true }
publish = { workspace = true }
edition = { workspace = true }
"#,
)
.file("bar/src/main.rs", "fn main() {}")
.build();
p.cargo("publish --token sekrit")
.masquerade_as_nightly_cargo()
.cwd("bar")
.run();
publish::validate_upload_with_contents(
r#"
{
"authors": ["Rustaceans"],
"badges": {
"gitlab": { "branch": "master", "repository": "https://gitlab.com/rust-lang/rust" }
},
"categories": ["development-tools"],
"deps": [],
"description": "This is a crate",
"documentation": "https://www.rust-lang.org/learn",
"features": {},
"homepage": "https://www.rust-lang.org",
"keywords": ["cli"],
"license": null,
"license_file": null,
"links": null,
"name": "bar",
"readme": null,
"readme_file": null,
"repository": "https://github.com/example/example",
"vers": "1.2.3"
}
"#,
"bar-1.2.3.crate",
&[
"Cargo.lock",
"Cargo.toml",
"Cargo.toml.orig",
"src/main.rs",
".cargo_vcs_info.json",
],
&[(
"Cargo.toml",
&format!(
r#"{}
cargo-features = ["workspace-inheritance"]
[package]
edition = "2018"
name = "bar"
version = "1.2.3"
authors = ["Rustaceans"]
publish = true
description = "This is a crate"
homepage = "https://www.rust-lang.org"
documentation = "https://www.rust-lang.org/learn"
keywords = ["cli"]
categories = ["development-tools"]
repository = "https://github.com/example/example"
[badges.gitlab]
branch = "master"
repository = "https://gitlab.com/rust-lang/rust"
"#,
cargo::core::package::MANIFEST_PREAMBLE
),
)],
);
}
#[cargo_test]
fn inherit_dependencies() {
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["bar"]
[workspace.dependencies]
dep = "0.1"
dep-build = "0.8"
dep-dev = "0.5.2"
"#,
)
.file(
"bar/Cargo.toml",
r#"
cargo-features = ["workspace-inheritance"]
[project]
workspace = ".."
name = "bar"
version = "0.2.0"
authors = []
[dependencies]
dep = { workspace = true }
[build-dependencies]
dep-build = { workspace = true }
[dev-dependencies]
dep-dev = { workspace = true }
"#,
)
.file("bar/src/main.rs", "fn main() {}")
.build();
Package::new("dep", "0.1.2").publish();
Package::new("dep-build", "0.8.2").publish();
Package::new("dep-dev", "0.5.2").publish();
p.cargo("build")
.masquerade_as_nightly_cargo()
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] `[..]` index
[DOWNLOADING] crates ...
[DOWNLOADED] dep-build v0.8.2 ([..])
[DOWNLOADED] dep v0.1.2 ([..])
[COMPILING] dep v0.1.2
[COMPILING] bar v0.2.0 ([CWD]/bar)
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
p.cargo("check").masquerade_as_nightly_cargo().run();
let lockfile = p.read_lockfile();
assert!(lockfile.contains("dep"));
assert!(lockfile.contains("dep-dev"));
assert!(lockfile.contains("dep-build"));
p.cargo("publish --token sekrit")
.masquerade_as_nightly_cargo()
.cwd("bar")
.run();
publish::validate_upload_with_contents(
r#"
{
"authors": [],
"badges": {},
"categories": [],
"deps": [
{
"default_features": true,
"features": [],
"kind": "normal",
"name": "dep",
"optional": false,
"registry": "https://github.com/rust-lang/crates.io-index",
"target": null,
"version_req": "^0.1"
},
{
"default_features": true,
"features": [],
"kind": "dev",
"name": "dep-dev",
"optional": false,
"registry": "https://github.com/rust-lang/crates.io-index",
"target": null,
"version_req": "^0.5.2"
},
{
"default_features": true,
"features": [],
"kind": "build",
"name": "dep-build",
"optional": false,
"registry": "https://github.com/rust-lang/crates.io-index",
"target": null,
"version_req": "^0.8"
}
],
"description": null,
"documentation": null,
"features": {},
"homepage": null,
"keywords": [],
"license": null,
"license_file": null,
"links": null,
"name": "bar",
"readme": null,
"readme_file": null,
"repository": null,
"vers": "0.2.0"
}
"#,
"bar-0.2.0.crate",
&["Cargo.toml", "Cargo.toml.orig", "Cargo.lock", "src/main.rs"],
&[(
"Cargo.toml",
&format!(
r#"{}
cargo-features = ["workspace-inheritance"]
[package]
name = "bar"
version = "0.2.0"
authors = []
[dependencies.dep]
version = "0.1"
[dev-dependencies.dep-dev]
version = "0.5.2"
[build-dependencies.dep-build]
version = "0.8"
"#,
cargo::core::package::MANIFEST_PREAMBLE
),
)],
);
}
#[cargo_test]
fn inherit_target_dependencies() {
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["bar"]
[workspace.dependencies]
dep = "0.1"
"#,
)
.file(
"bar/Cargo.toml",
r#"
cargo-features = ["workspace-inheritance"]
[project]
workspace = ".."
name = "bar"
version = "0.2.0"
authors = []
[target.'cfg(unix)'.dependencies]
dep = { workspace = true }
[target.'cfg(windows)'.dependencies]
dep = { workspace = true }
"#,
)
.file("bar/src/main.rs", "fn main() {}")
.build();
Package::new("dep", "0.1.2").publish();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] `[..]` index
[DOWNLOADING] crates ...
[DOWNLOADED] dep v0.1.2 ([..])
[COMPILING] dep v0.1.2
[COMPILING] bar v0.2.0 ([CWD]/bar)
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
let lockfile = p.read_lockfile();
assert!(lockfile.contains("dep"));
}
#[cargo_test]
fn inherit_dependency_override_optional() {
Package::new("dep", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["bar"]
[workspace.dependencies]
dep = "0.1.0"
"#,
)
.file(
"bar/Cargo.toml",
r#"
cargo-features = ["workspace-inheritance"]
[project]
workspace = ".."
name = "bar"
version = "0.2.0"
authors = []
[dependencies]
dep = { workspace = true, optional = true }
"#,
)
.file("LICENSE", "license")
.file("README.md", "README.md")
.file("bar/src/main.rs", "fn main() {}")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.cwd("bar")
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
Caused by:
error inheriting `version` from workspace root manifest's `workspace.version`
Caused by:
inheriting from a parent workspace is not implemented yet
[UPDATING] `[..]` index
[COMPILING] bar v0.2.0 ([CWD]/bar)
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
}
#[cargo_test]
fn error_on_unimplemented_inheritance_dependencies() {
fn inherit_dependency_features() {
Package::new("dep", "0.1.0")
.feature("fancy", &["fancy_dep"])
.add_dep(Dependency::new("fancy_dep", "0.2").optional(true))
.file("src/lib.rs", "")
.publish();
Package::new("fancy_dep", "0.2.4").publish();
Package::new("dancy_dep", "0.6.8").publish();
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["workspace-inheritance"]
[project]
name = "bar"
version = "0.2.0"
authors = []
[dependencies]
dep = { workspace = true, features = ["fancy"] }
[workspace]
members = []
[workspace.dependencies]
dep = "0.1"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[UPDATING] `[..]` index
[DOWNLOADING] crates ...
[DOWNLOADED] fancy_dep v0.2.4 ([..])
[DOWNLOADED] dep v0.1.0 ([..])
[COMPILING] fancy_dep v0.2.4
[COMPILING] dep v0.1.0
[COMPILING] bar v0.2.0 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
let lockfile = p.read_lockfile();
assert!(lockfile.contains("dep"));
assert!(lockfile.contains("fancy_dep"));
}
#[cargo_test]
fn inherit_detailed_dependencies() {
let git_project = git::new("detailed", |project| {
project
.file("Cargo.toml", &basic_lib_manifest("detailed"))
@ -641,7 +999,6 @@ fn error_on_unimplemented_inheritance_dependencies() {
r#"
[workspace]
members = ["bar"]
[workspace.dependencies]
detailed = {{ git = '{}', branch = "branchy" }}
"#,
@ -658,7 +1015,6 @@ fn error_on_unimplemented_inheritance_dependencies() {
name = "bar"
version = "0.2.0"
authors = []
[dependencies]
detailed = { workspace = true }
"#,
@ -666,23 +1022,19 @@ fn error_on_unimplemented_inheritance_dependencies() {
.file("bar/src/main.rs", "fn main() {}")
.build();
let git_root = git_project.root();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
.with_stderr(&format!(
"\
[ERROR] failed to load manifest for workspace member `[CWD]/bar`
Caused by:
failed to parse manifest at `[CWD]/bar/Cargo.toml`
Caused by:
error reading `dependencies.detailed` from workspace root manifest's `workspace.dependencies.detailed`
Caused by:
inheriting from a parent workspace is not implemented yet
",
)
[UPDATING] git repository `{}`\n\
[COMPILING] detailed v0.5.0 ({}?branch=branchy#[..])\n\
[COMPILING] bar v0.2.0 ([CWD]/bar)\n\
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
path2url(&git_root),
path2url(&git_root),
))
.run();
}
@ -775,6 +1127,149 @@ Caused by:
.run();
}
#[cargo_test]
fn error_malformed_workspace_root() {
registry::init();
let p = project().build();
let _ = git::repo(&paths::root().join("foo"))
.file(
"Cargo.toml",
r#"
[workspace]
members = [invalid toml
"#,
)
.file("src/main.rs", "fn main() {}")
.file(
"bar/Cargo.toml",
r#"
cargo-features = ["workspace-inheritance"]
[package]
name = "bar"
workspace = ".."
version = "1.2.3"
authors = ["rustaceans"]
"#,
)
.file("bar/src/main.rs", "fn main() {}")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.cwd("bar")
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
[..]
Caused by:
[..]
|
3 | members = [invalid toml
| ^
Unexpected `i`
Expected newline or `#`
",
)
.run();
}
#[cargo_test]
fn error_no_root_workspace() {
registry::init();
let p = project().build();
let _ = git::repo(&paths::root().join("foo"))
.file(
"bar/Cargo.toml",
r#"
cargo-features = ["workspace-inheritance"]
[package]
name = "bar"
workspace = ".."
version = "1.2.3"
authors = ["rustaceans"]
description = { workspace = true }
"#,
)
.file("src/main.rs", "fn main() {}")
.file("bar/src/main.rs", "fn main() {}")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.cwd("bar")
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[..]/Cargo.toml`
Caused by:
error inheriting `description` from workspace root manifest's `workspace.description`
Caused by:
root of a workspace inferred but wasn't a root: [..]/Cargo.toml
",
)
.run();
}
#[cargo_test]
fn error_inherit_unspecified_dependency() {
let p = project().build();
let _ = git::repo(&paths::root().join("foo"))
.file(
"Cargo.toml",
r#"
[workspace]
members = ["bar"]
"#,
)
.file("src/main.rs", "fn main() {}")
.file(
"bar/Cargo.toml",
r#"
cargo-features = ["workspace-inheritance"]
[package]
name = "bar"
workspace = ".."
version = "1.2.3"
authors = ["rustaceans"]
[dependencies]
foo = { workspace = true }
"#,
)
.file("bar/src/main.rs", "fn main() {}")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.cwd("bar")
.with_status(101)
.with_stderr(
"\
[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
Caused by:
error reading `dependencies.foo` from workspace root manifest's `workspace.dependencies.foo`
Caused by:
`workspace.dependencies` was not defined
",
)
.run();
}
#[cargo_test]
fn workspace_inheritance_not_enabled() {
registry::init();