From f6c1ae379734f5725d5a157353c4e6208e75526b Mon Sep 17 00:00:00 2001 From: Daniel Paoliello Date: Tue, 6 Aug 2024 14:18:34 -0700 Subject: [PATCH] Implement base paths (RFC 3529) 1/n: path dep and patch support --- CHANGELOG.md | 4 + crates/cargo-util-schemas/src/manifest/mod.rs | 12 + .../src/restricted_names.rs | 4 + src/cargo/core/features.rs | 3 + src/cargo/util/toml/mod.rs | 117 +++- src/doc/src/reference/unstable.md | 53 ++ tests/testsuite/patch.rs | 46 ++ tests/testsuite/path.rs | 612 ++++++++++++++++++ 8 files changed, 842 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e92832629f5..086756610367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ - `-Ztarget-applies-to-host`: Fixed passing of links-overrides with target-applies-to-host and an implicit target [#14205](https://github.com/rust-lang/cargo/pull/14205) +- Added the `path-bases` feature to support paths that resolve relatively to a + base specified in the config. + [docs](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#path-bases) + [#14360](https://github.com/rust-lang/cargo/pull/14360) ### Documentation diff --git a/crates/cargo-util-schemas/src/manifest/mod.rs b/crates/cargo-util-schemas/src/manifest/mod.rs index df0bb51dcfde..fbf645015786 100644 --- a/crates/cargo-util-schemas/src/manifest/mod.rs +++ b/crates/cargo-util-schemas/src/manifest/mod.rs @@ -776,6 +776,7 @@ pub struct TomlDetailedDependency { // `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to // that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file. pub path: Option

, + pub base: Option, pub git: Option, pub branch: Option, pub tag: Option, @@ -815,6 +816,7 @@ impl Default for TomlDetailedDependency

{ registry: Default::default(), registry_index: Default::default(), path: Default::default(), + base: Default::default(), git: Default::default(), branch: Default::default(), tag: Default::default(), @@ -1413,6 +1415,16 @@ impl> FeatureName { } } +str_newtype!(PathBaseName); + +impl> PathBaseName { + /// Validated path base name + pub fn new(name: T) -> Result { + restricted_names::validate_path_base_name(name.as_ref())?; + Ok(Self(name)) + } +} + /// Corresponds to a `target` entry, but `TomlTarget` is already used. #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] diff --git a/crates/cargo-util-schemas/src/restricted_names.rs b/crates/cargo-util-schemas/src/restricted_names.rs index 40f22197a5be..18be53bda66b 100644 --- a/crates/cargo-util-schemas/src/restricted_names.rs +++ b/crates/cargo-util-schemas/src/restricted_names.rs @@ -238,6 +238,10 @@ pub(crate) fn validate_feature_name(name: &str) -> Result<()> { Ok(()) } +pub(crate) fn validate_path_base_name(name: &str) -> Result<()> { + validate_name(name, "path base name") +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index c44b8d51eb4e..3dc2a1d8e9c8 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -513,6 +513,9 @@ features! { /// Allow multiple packages to participate in the same API namespace (unstable, open_namespaces, "", "reference/unstable.html#open-namespaces"), + + /// Allow paths that resolve relatively to a base specified in the config. + (unstable, path_bases, "", "reference/unstable.html#path-bases"), } /// Status and metadata for a single unstable feature. diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 353189917892..8bdcc21e57a0 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -10,7 +10,9 @@ use crate::AlreadyPrintedError; use anyhow::{anyhow, bail, Context as _}; use cargo_platform::Platform; use cargo_util::paths::{self, normalize_path}; -use cargo_util_schemas::manifest::{self, TomlManifest}; +use cargo_util_schemas::manifest::{ + self, PackageName, PathBaseName, TomlDependency, TomlDetailedDependency, TomlManifest, +}; use cargo_util_schemas::manifest::{RustVersion, StringOrBool}; use itertools::Itertools; use lazycell::LazyCell; @@ -296,7 +298,7 @@ fn normalize_toml( features: None, target: None, replace: original_toml.replace.clone(), - patch: original_toml.patch.clone(), + patch: None, workspace: original_toml.workspace.clone(), badges: None, lints: None, @@ -310,6 +312,7 @@ fn normalize_toml( inherit_cell .try_borrow_with(|| load_inheritable_fields(gctx, manifest_file, &workspace_config)) }; + let workspace_root = || inherit().map(|fields| fields.ws_root()); if let Some(original_package) = original_toml.package() { let package_name = &original_package.name; @@ -390,6 +393,7 @@ fn normalize_toml( &activated_opt_deps, None, &inherit, + &workspace_root, package_root, warnings, )?; @@ -410,6 +414,7 @@ fn normalize_toml( &activated_opt_deps, Some(DepKind::Development), &inherit, + &workspace_root, package_root, warnings, )?; @@ -430,6 +435,7 @@ fn normalize_toml( &activated_opt_deps, Some(DepKind::Build), &inherit, + &workspace_root, package_root, warnings, )?; @@ -443,6 +449,7 @@ fn normalize_toml( &activated_opt_deps, None, &inherit, + &workspace_root, package_root, warnings, )?; @@ -463,6 +470,7 @@ fn normalize_toml( &activated_opt_deps, Some(DepKind::Development), &inherit, + &workspace_root, package_root, warnings, )?; @@ -483,6 +491,7 @@ fn normalize_toml( &activated_opt_deps, Some(DepKind::Build), &inherit, + &workspace_root, package_root, warnings, )?; @@ -499,6 +508,13 @@ fn normalize_toml( } normalized_toml.target = (!normalized_target.is_empty()).then_some(normalized_target); + normalized_toml.patch = normalize_patch( + gctx, + original_toml.patch.as_ref(), + &workspace_root, + features, + )?; + let normalized_lints = original_toml .lints .clone() @@ -519,6 +535,37 @@ fn normalize_toml( Ok(normalized_toml) } +fn normalize_patch<'a>( + gctx: &GlobalContext, + original_patch: Option<&BTreeMap>>, + workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>, + features: &Features, +) -> CargoResult>>> { + if let Some(patch) = original_patch { + let mut normalized_patch = BTreeMap::new(); + for (name, packages) in patch { + let mut normalized_packages = BTreeMap::new(); + for (pkg, dep) in packages { + let dep = if let TomlDependency::Detailed(dep) = dep { + let mut dep = dep.clone(); + normalize_path_dependency(gctx, &mut dep, workspace_root, features) + .with_context(|| { + format!("resolving path for patch of ({pkg}) for source ({name})") + })?; + TomlDependency::Detailed(dep) + } else { + dep.clone() + }; + normalized_packages.insert(pkg.clone(), dep); + } + normalized_patch.insert(name.clone(), normalized_packages); + } + Ok(Some(normalized_patch)) + } else { + Ok(None) + } +} + #[tracing::instrument(skip_all)] fn normalize_package_toml<'a>( original_package: &manifest::TomlPackage, @@ -710,6 +757,7 @@ fn normalize_dependencies<'a>( activated_opt_deps: &HashSet<&str>, kind: Option, inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>, + workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>, package_root: &Path, warnings: &mut Vec, ) -> CargoResult>> { @@ -768,6 +816,8 @@ fn normalize_dependencies<'a>( } } } + normalize_path_dependency(gctx, d, workspace_root, features) + .with_context(|| format!("resolving path dependency {name_in_toml}"))?; } // if the dependency is not optional, it is always used @@ -786,6 +836,23 @@ fn normalize_dependencies<'a>( Ok(Some(deps)) } +fn normalize_path_dependency<'a>( + gctx: &GlobalContext, + detailed_dep: &mut TomlDetailedDependency, + workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>, + features: &Features, +) -> CargoResult<()> { + if let Some(base) = detailed_dep.base.take() { + if let Some(path) = detailed_dep.path.as_mut() { + let new_path = lookup_path_base(&base, gctx, workspace_root, features)?.join(&path); + *path = new_path.to_str().unwrap().to_string(); + } else { + bail!("`base` can only be used with path dependencies"); + } + } + Ok(()) +} + fn load_inheritable_fields( gctx: &GlobalContext, normalized_path: &Path, @@ -901,13 +968,17 @@ impl InheritableFields { }; let mut dep = dep.clone(); if let manifest::TomlDependency::Detailed(detailed) = &mut dep { - if let Some(rel_path) = &detailed.path { - detailed.path = Some(resolve_relative_path( - name, - self.ws_root(), - package_root, - rel_path, - )?); + if detailed.base.is_none() { + // If this is a path dependency without a base, then update the path to be relative + // to the workspace root instead. + if let Some(rel_path) = &detailed.path { + detailed.path = Some(resolve_relative_path( + name, + self.ws_root(), + package_root, + rel_path, + )?); + } } } Ok(dep) @@ -2151,6 +2222,33 @@ fn to_dependency_source_id( } } +pub(crate) fn lookup_path_base<'a>( + base: &PathBaseName, + gctx: &GlobalContext, + workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>, + features: &Features, +) -> CargoResult { + features.require(Feature::path_bases())?; + + // HACK: The `base` string is user controlled, but building the path is safe from injection + // attacks since the `PathBaseName` type restricts the characters that can be used to exclude `.` + let base_key = format!("path-bases.{base}"); + + // Look up the relevant base in the Config and use that as the root. + if let Some(path_bases) = gctx.get::>(&base_key)? { + Ok(path_bases.resolve_path(gctx)) + } else { + // Otherwise, check the built-in bases. + match base.as_str() { + "workspace" => Ok(workspace_root()?.clone()), + _ => bail!( + "path base `{base}` is undefined. \ + You must add an entry for `{base}` in the Cargo configuration [path-bases] table." + ), + } + } +} + pub trait ResolveToPath { fn resolve(&self, gctx: &GlobalContext) -> PathBuf; } @@ -2865,6 +2963,7 @@ fn prepare_toml_for_publish( let mut d = d.clone(); // Path dependencies become crates.io deps. d.path.take(); + d.base.take(); // Same with git dependencies. d.git.take(); d.branch.take(); diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 18be45fc2b03..cf92d1bd1c59 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -101,6 +101,7 @@ Each new feature described below should explain how to use it. * [Edition 2024](#edition-2024) — Adds support for the 2024 Edition. * [Profile `trim-paths` option](#profile-trim-paths-option) --- Control the sanitization of file paths in build outputs. * [`[lints.cargo]`](#lintscargo) --- Allows configuring lints for Cargo. + * [path bases](#path-bases) --- Named base directories for path dependencies. * Information and metadata * [Build-plan](#build-plan) --- Emits JSON information on which commands will be run. * [unit-graph](#unit-graph) --- Emits JSON for Cargo's internal graph structure. @@ -1570,6 +1571,58 @@ implicit-features = "warn" workspace = true ``` +## Path Bases + +* Tracking Issue: [#14355](https://github.com/rust-lang/cargo/issues/14355) + +A `path` dependency may optionally specify a base by setting the `base` key to +the name of a path base from the `[path-bases]` table in either the +[configuration](config.md) or one of the [built-in path bases](#built-in-path-bases). +The value of that path base is prepended to the `path` value (along with a path +separator if necessary) to produce the actual location where Cargo will look for +the dependency. + +For example, if the `Cargo.toml` contains: + +```toml +[dependencies] +foo = { base = "dev", path = "foo" } +``` + +Given a `[path-bases]` table in the configuration that contains: + +```toml +[path-bases] +dev = "/home/user/dev/rust/libraries/" +``` + +This will produce a `path` dependency `foo` located at +`/home/user/dev/rust/libraries/foo`. + +Path bases can be either absolute or relative. Relative path bases are relative +to the parent directory of the configuration file that declared that path base. + +The name of a path base must use only [alphanumeric](https://doc.rust-lang.org/std/primitive.char.html#method.is_alphanumeric) +characters or `-` or `_`, must start with an [alphabetic](https://doc.rust-lang.org/std/primitive.char.html#method.is_alphabetic) +character, and must not be empty. + +If the name of path base used in a dependency is neither in the configuration +nor one of the built-in path base, then Cargo will raise an error. + +#### Built-in path bases + +Cargo provides implicit path bases that can be used without the need to specify +them in a `[path-bases]` table. + +* `workspace` - If a project is [a workspace or workspace member](workspaces.md) +then this path base is defined as the parent directory of the root `Cargo.toml` +of the workspace. + +If a built-in path base name is also declared in the configuration, then Cargo +will prefer the value in the configuration. The allows Cargo to add new built-in +path bases without compatibility issues (as existing uses will shadow the +built-in name). + # Stabilized and removed features ## Compile progress diff --git a/tests/testsuite/patch.rs b/tests/testsuite/patch.rs index bc9bd535bf4b..a6cfd357aea1 100644 --- a/tests/testsuite/patch.rs +++ b/tests/testsuite/patch.rs @@ -3028,3 +3028,49 @@ foo v0.0.0 ([ROOT]/foo) assert_eq!(p.read_file("Cargo.lock"), p.read_file("Cargo.lock.orig")); } + +#[cargo_test] +fn patch_with_base() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "pub fn hello() {}") + .build(); + Package::new("bar", "0.5.0").publish(); + + let p = project() + .file( + ".cargo/config.toml", + &format!( + r#" + [path-bases] + test = '{}' + "#, + bar.root().parent().unwrap().display() + ), + ) + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + edition = "2018" + + [dependencies] + bar = "0.5.0" + + [patch.crates-io] + bar = { base = 'test', path = 'bar' } + "#, + ) + .file("src/lib.rs", "use bar::hello as _;") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["path-bases"]) + .run(); +} diff --git a/tests/testsuite/path.rs b/tests/testsuite/path.rs index 17179a520cc8..f8da0140a27c 100644 --- a/tests/testsuite/path.rs +++ b/tests/testsuite/path.rs @@ -589,6 +589,618 @@ Caused by: .run(); } +#[cargo_test] +fn path_bases_not_stable() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + let p = project() + .file( + ".cargo/config.toml", + &format!( + r#" + [path-bases] + test = '{}' + "#, + bar.root().parent().unwrap().display() + ), + ) + .file( + "Cargo.toml", + r#" + [package] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + bar = { base = 'test', path = 'bar' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build") + .with_status(101) + .with_stderr_data( + "\ +[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml` + +Caused by: + resolving path dependency bar + +Caused by: + feature `path-bases` is required + + The package requires the Cargo feature called `path-bases`, but that feature is not stabilized in this version of Cargo ([..]). + Consider trying a newer version of Cargo (this may require the nightly release). + See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#path-bases for more information about the status of this feature. +", + ) + .run(); +} + +#[cargo_test] +fn path_with_base() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + let p = project() + .file( + ".cargo/config.toml", + &format!( + r#" + [path-bases] + test = '{}' + "#, + bar.root().parent().unwrap().display() + ), + ) + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + bar = { base = 'test', path = 'bar' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["path-bases"]) + .run(); +} + +#[cargo_test] +fn current_dir_with_base() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + let p = project() + .file( + ".cargo/config.toml", + &format!( + r#" + [path-bases] + test = '{}' + "#, + bar.root().display() + ), + ) + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + bar = { base = 'test', path = '.' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["path-bases"]) + .run(); +} + +#[cargo_test] +fn parent_dir_with_base() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + let p = project() + .file( + ".cargo/config.toml", + &format!( + r#" + [path-bases] + test = '{}/subdir' + "#, + bar.root().display() + ), + ) + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + bar = { base = 'test', path = '..' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["path-bases"]) + .run(); +} + +#[cargo_test] +fn inherit_dependency_using_base() { + let bar = project() + .at("dep_with_base") + .file("Cargo.toml", &basic_manifest("dep_with_base", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + let p = project() + .file( + ".cargo/config.toml", + &format!( + r#" + [path-bases] + test = '{}' + "#, + bar.root().parent().unwrap().display() + ), + ) + .file( + "Cargo.toml", + r#" + [package] + name = "parent" + version = "0.1.0" + authors = [] + + [workspace] + members = ["child"] + + [workspace.dependencies] + dep_with_base = { base = 'test', path = 'dep_with_base' } + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "child/Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "child" + version = "0.1.0" + authors = [] + + [dependencies] + dep_with_base = { workspace = true } + "#, + ) + .file("child/src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["path-bases"]) + .run(); +} + +#[cargo_test] +fn path_with_relative_base() { + project() + .at("shared_proj/bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + let p = project() + .file( + "../.cargo/config.toml", + r#" + [path-bases] + test = 'shared_proj' + "#, + ) + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + bar = { base = 'test', path = 'bar' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["path-bases"]) + .run(); +} + +#[cargo_test] +fn workspace_builtin_base() { + project() + .at("dep_with_base") + .file("Cargo.toml", &basic_manifest("dep_with_base", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "parent" + version = "0.1.0" + authors = [] + + [workspace] + members = ["child"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "child/Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "child" + version = "0.1.0" + authors = [] + + [dependencies] + dep_with_base = { base = 'workspace', path = '../dep_with_base' } + "#, + ) + .file("child/src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["path-bases"]) + .run(); +} + +#[cargo_test] +fn workspace_builtin_base_not_a_workspace() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [dependencies] + bar = { base = 'workspace', path = '../dep_with_base' } + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["path-bases"]) + .with_status(101) + .with_stderr_data( + "\ +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + resolving path dependency bar + +Caused by: + failed to find a workspace root +", + ) + .run(); +} + +#[cargo_test] +fn shadow_workspace_builtin_base() { + let bar = project() + .at("dep_with_base") + .file("Cargo.toml", &basic_manifest("dep_with_base", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + let p = project() + .file( + ".cargo/config.toml", + &format!( + r#" + [path-bases] + workspace = '{}/subdir/anotherdir' + "#, + bar.root().parent().unwrap().display() + ), + ) + .file( + "Cargo.toml", + r#" + [package] + name = "parent" + version = "0.1.0" + authors = [] + + [workspace] + members = ["child"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "child/Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "child" + version = "0.1.0" + authors = [] + + [dependencies] + dep_with_base = { base = 'workspace', path = '../../dep_with_base' } + "#, + ) + .file("child/src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v") + .masquerade_as_nightly_cargo(&["path-bases"]) + .run(); +} + +#[cargo_test] +fn unknown_base() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + bar = { base = 'test', path = 'bar' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["path-bases"]) + .with_status(101) + .with_stderr_data( + "\ +[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml` + +Caused by: + resolving path dependency bar + +Caused by: + path base `test` is undefined. You must add an entry for `test` in the Cargo configuration [path-bases] table. +", + ) + .run(); +} + +#[cargo_test] +fn base_without_path() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + bar = { version = '1.0.0', base = 'test' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["path-bases"]) + .with_status(101) + .with_stderr_data( + "\ +[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml` + +Caused by: + resolving path dependency bar + +Caused by: + `base` can only be used with path dependencies +", + ) + .run(); +} + +#[cargo_test] +fn invalid_base() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies] + bar = { base = '^^not-valid^^', path = 'bar' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["path-bases"]) + .with_status(101) + .with_stderr_data( + "\ +[ERROR] invalid character `^` in path base name: `^^not-valid^^`, the first character must be a Unicode XID start character (most letters or `_`) + + + --> Cargo.toml:10:23 + | +10 | bar = { base = '^^not-valid^^', path = 'bar' } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +", + ) + .run(); +} + +#[cargo_test] +fn invalid_path_with_base() { + let p = project() + .file( + ".cargo/config.toml", + r#" + [path-bases] + test = 'shared_proj' + "#, + ) + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + edition = "2015" + + [dependencies] + bar = { base = 'test', path = '"' } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["path-bases"]) + .with_status(101) + .with_stderr_data( + "\ +[ERROR] failed to get `bar` as a dependency of package `foo v0.5.0 ([ROOT]/foo)` + +Caused by: + failed to load source for dependency `bar` + +Caused by: + Unable to update [ROOT]/foo/shared_proj/\" + +Caused by: + failed to read `[ROOT]/foo/shared_proj/\"/Cargo.toml` + +Caused by: + [NOT_FOUND] +", + ) + .run(); +} + +#[cargo_test] +fn self_dependency_using_base() { + let p = project() + .file( + ".cargo/config.toml", + r#" + [path-bases] + test = '.' + "#, + ) + .file( + "Cargo.toml", + r#" + cargo-features = ["path-bases"] + + [package] + name = "foo" + version = "0.1.0" + authors = [] + edition = "2015" + + [dependencies] + foo = { base = 'test', path = '.' } + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo(&["path-bases"]) + .with_status(101) + .with_stderr_data( + "\ +[ERROR] cyclic package dependency: package `foo v0.1.0 ([ROOT]/foo)` depends on itself. Cycle: +package `foo v0.1.0 ([ROOT]/foo)` + ... which satisfies path dependency `foo` of package `foo v0.1.0 ([ROOT]/foo)` +", + ) + .run(); +} + #[cargo_test] fn override_relative() { let bar = project()