From 8de55540f04dc074cb09073d5c0c354135ba4c36 Mon Sep 17 00:00:00 2001
From: Daniel Paoliello
Date: Wed, 3 Jan 2024 14:24:11 -0800
Subject: [PATCH] Implement path-bases (RFC 3529) 2/n: `cargo add` support
RFC: https://github.com/rust-lang/rfcs/pull/3529
Tracking Issue: https://github.com/rust-lang/cargo/issues/14355
This PR adds the `--base` option to `cargo add` to allow adding a path dependency with a path base.
---
src/bin/cargo/commands/add.rs | 8 +
src/bin/cargo/commands/remove.rs | 57 +++--
src/cargo/ops/cargo_add/mod.rs | 85 ++++++-
src/cargo/ops/cargo_update.rs | 23 +-
src/cargo/util/toml_mut/dependency.rs | 212 ++++++++++++++----
src/cargo/util/toml_mut/manifest.rs | 30 ++-
src/doc/man/cargo-add.md | 6 +
src/doc/man/generated_txt/cargo-add.txt | 8 +
src/doc/src/commands/cargo-add.md | 5 +
src/etc/man/cargo-add.1 | 7 +
.../in/.cargo/config.toml | 2 +
.../in/Cargo.toml | 7 +
.../in/deps/dependency/Cargo.toml | 4 +
.../in/deps/dependency/src/lib.rs | 0
.../in/primary/Cargo.toml | 6 +
.../in/primary/src/lib.rs | 0
.../detect_workspace_inherit_path_base/mod.rs | 27 +++
.../out/Cargo.toml | 7 +
.../out/deps/dependency/Cargo.toml | 4 +
.../out/primary/Cargo.toml | 9 +
.../stderr.term.svg | 27 +++
.../in/.cargo/config.toml | 2 +
.../in/dependency/Cargo.toml | 6 +
.../in/dependency/src/lib.rs | 0
.../in/primary/Cargo.toml | 11 +
.../in/primary/src/lib.rs | 0
.../cargo_add/dev_existing_path_base/mod.rs | 25 +++
.../out/dependency/Cargo.toml | 6 +
.../out/primary/Cargo.toml | 14 ++
.../dev_existing_path_base/stderr.term.svg | 29 +++
.../testsuite/cargo_add/help/stdout.term.svg | 74 +++---
tests/testsuite/cargo_add/mod.rs | 7 +
.../in/.cargo/config.toml | 2 +
.../in/dependency/Cargo.toml | 6 +
.../in/dependency/src/lib.rs | 0
.../in/primary/Cargo.toml | 11 +
.../in/primary/src/lib.rs | 0
.../overwrite_path_base_with_version/mod.rs | 29 +++
.../out/dependency/Cargo.toml | 6 +
.../out/primary/Cargo.toml | 14 ++
.../stderr.term.svg | 35 +++
.../cargo_add/path_base/in/.cargo/config.toml | 2 +
.../path_base/in/dependency/Cargo.toml | 6 +
.../path_base/in/dependency/src/lib.rs | 0
.../cargo_add/path_base/in/primary/Cargo.toml | 8 +
.../cargo_add/path_base/in/primary/src/lib.rs | 0
tests/testsuite/cargo_add/path_base/mod.rs | 25 +++
.../path_base/out/dependency/Cargo.toml | 6 +
.../path_base/out/primary/Cargo.toml | 11 +
.../cargo_add/path_base/stderr.term.svg | 29 +++
.../in/.cargo/config.toml | 2 +
.../in/dependency/Cargo.toml | 6 +
.../in/dependency/src/lib.rs | 0
.../in/primary/Cargo.toml | 8 +
.../in/primary/src/lib.rs | 0
.../cargo_add/path_base_inferred_name/mod.rs | 25 +++
.../out/dependency/Cargo.toml | 6 +
.../out/primary/Cargo.toml | 11 +
.../path_base_inferred_name/stderr.term.svg | 29 +++
.../in/primary/Cargo.toml | 6 +
.../in/primary/src/lib.rs | 0
.../path_base_missing_base_path/mod.rs | 25 +++
.../out/primary/Cargo.toml | 6 +
.../stderr.term.svg | 27 +++
.../path_base_unstable/in/primary/Cargo.toml | 4 +
.../path_base_unstable/in/primary/src/lib.rs | 0
.../cargo_add/path_base_unstable/mod.rs | 24 ++
.../path_base_unstable/out/primary/Cargo.toml | 4 +
.../path_base_unstable/stderr.term.svg | 37 +++
69 files changed, 1008 insertions(+), 110 deletions(-)
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/.cargo/config.toml
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/deps/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/deps/dependency/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/primary/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/mod.rs
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/deps/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/detect_workspace_inherit_path_base/stderr.term.svg
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/in/.cargo/config.toml
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/in/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/in/dependency/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/in/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/in/primary/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/mod.rs
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/out/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/out/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/dev_existing_path_base/stderr.term.svg
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/in/.cargo/config.toml
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/in/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/in/dependency/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/in/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/in/primary/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/mod.rs
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/out/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/out/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/overwrite_path_base_with_version/stderr.term.svg
create mode 100644 tests/testsuite/cargo_add/path_base/in/.cargo/config.toml
create mode 100644 tests/testsuite/cargo_add/path_base/in/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base/in/dependency/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/path_base/in/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base/in/primary/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/path_base/mod.rs
create mode 100644 tests/testsuite/cargo_add/path_base/out/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base/out/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base/stderr.term.svg
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/in/.cargo/config.toml
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/in/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/in/dependency/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/in/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/in/primary/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/mod.rs
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/out/dependency/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/out/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base_inferred_name/stderr.term.svg
create mode 100644 tests/testsuite/cargo_add/path_base_missing_base_path/in/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base_missing_base_path/in/primary/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/path_base_missing_base_path/mod.rs
create mode 100644 tests/testsuite/cargo_add/path_base_missing_base_path/out/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base_missing_base_path/stderr.term.svg
create mode 100644 tests/testsuite/cargo_add/path_base_unstable/in/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base_unstable/in/primary/src/lib.rs
create mode 100644 tests/testsuite/cargo_add/path_base_unstable/mod.rs
create mode 100644 tests/testsuite/cargo_add/path_base_unstable/out/primary/Cargo.toml
create mode 100644 tests/testsuite/cargo_add/path_base_unstable/stderr.term.svg
diff --git a/src/bin/cargo/commands/add.rs b/src/bin/cargo/commands/add.rs
index da821f328bd..4c9436df696 100644
--- a/src/bin/cargo/commands/add.rs
+++ b/src/bin/cargo/commands/add.rs
@@ -101,6 +101,12 @@ Example uses:
.help("Filesystem path to local crate to add")
.group("selected")
.conflicts_with("git"),
+ clap::Arg::new("base")
+ .long("base")
+ .action(ArgAction::Set)
+ .value_name("BASE")
+ .help("The path base to use when adding from a local crate (unstable).")
+ .requires("path"),
clap::Arg::new("git")
.long("git")
.action(ArgAction::Set)
@@ -224,6 +230,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
fn parse_dependencies(gctx: &GlobalContext, matches: &ArgMatches) -> CargoResult> {
let path = matches.get_one::("path");
+ let base = matches.get_one::("base");
let git = matches.get_one::("git");
let branch = matches.get_one::("branch");
let rev = matches.get_one::("rev");
@@ -329,6 +336,7 @@ fn parse_dependencies(gctx: &GlobalContext, matches: &ArgMatches) -> CargoResult
public,
registry: registry.clone(),
path: path.map(String::from),
+ base: base.map(String::from),
git: git.map(String::from),
branch: branch.map(String::from),
rev: rev.map(String::from),
diff --git a/src/bin/cargo/commands/remove.rs b/src/bin/cargo/commands/remove.rs
index e74f53d6442..833fd00c549 100644
--- a/src/bin/cargo/commands/remove.rs
+++ b/src/bin/cargo/commands/remove.rs
@@ -167,20 +167,37 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> {
let members = workspace
.members()
- .map(|p| LocalManifest::try_new(p.manifest_path()))
+ .map(|p| {
+ Ok((
+ LocalManifest::try_new(p.manifest_path())?,
+ p.manifest().unstable_features(),
+ ))
+ })
.collect::>>()?;
let mut dependencies = members
- .iter()
- .flat_map(|manifest| {
- manifest.get_sections().into_iter().flat_map(|(_, table)| {
- table
- .as_table_like()
- .unwrap()
- .iter()
- .map(|(key, item)| Dependency::from_toml(&manifest.path, key, item))
- .collect::>()
- })
+ .into_iter()
+ .flat_map(|(manifest, unstable_features)| {
+ manifest
+ .get_sections()
+ .into_iter()
+ .flat_map(move |(_, table)| {
+ table
+ .as_table_like()
+ .unwrap()
+ .iter()
+ .map(|(key, item)| {
+ Dependency::from_toml(
+ workspace.gctx(),
+ workspace.root(),
+ &manifest.path,
+ &unstable_features,
+ key,
+ item,
+ )
+ })
+ .collect::>()
+ })
})
.collect::>>()?;
@@ -192,7 +209,14 @@ fn gc_workspace(workspace: &Workspace<'_>) -> CargoResult<()> {
{
deps_table.set_implicit(true);
for (key, item) in deps_table.iter_mut() {
- let ws_dep = Dependency::from_toml(&workspace.root(), key.get(), item)?;
+ let ws_dep = Dependency::from_toml(
+ workspace.gctx(),
+ workspace.root(),
+ &workspace.root(),
+ workspace.unstable_features(),
+ key.get(),
+ item,
+ )?;
// search for uses of this workspace dependency
let mut is_used = false;
@@ -329,7 +353,14 @@ fn gc_unused_patches(workspace: &Workspace<'_>, resolve: &Resolve) -> CargoResul
patch_table.set_implicit(true);
for (key, item) in patch_table.iter_mut() {
- let dep = Dependency::from_toml(&workspace.root_manifest(), key.get(), item)?;
+ let dep = Dependency::from_toml(
+ workspace.gctx(),
+ workspace.root(),
+ &workspace.root_manifest(),
+ workspace.unstable_features(),
+ key.get(),
+ item,
+ )?;
// Generate a PackageIdSpec url for querying
let url = if let MaybeWorkspace::Other(source_id) =
diff --git a/src/cargo/ops/cargo_add/mod.rs b/src/cargo/ops/cargo_add/mod.rs
index ad5d6f1230d..ac49f58e1d3 100644
--- a/src/cargo/ops/cargo_add/mod.rs
+++ b/src/cargo/ops/cargo_add/mod.rs
@@ -12,6 +12,7 @@ use std::str::FromStr;
use anyhow::Context as _;
use cargo_util::paths;
use cargo_util_schemas::core::PartialVersion;
+use cargo_util_schemas::manifest::PathBaseName;
use cargo_util_schemas::manifest::RustVersion;
use indexmap::IndexSet;
use itertools::Itertools;
@@ -20,6 +21,7 @@ use toml_edit::Item as TomlItem;
use crate::core::dependency::DepKind;
use crate::core::registry::PackageRegistry;
use crate::core::FeatureValue;
+use crate::core::Features;
use crate::core::Package;
use crate::core::Registry;
use crate::core::Shell;
@@ -28,6 +30,7 @@ use crate::core::Workspace;
use crate::sources::source::QueryKind;
use crate::util::cache_lock::CacheLockMode;
use crate::util::style;
+use crate::util::toml::lookup_path_base;
use crate::util::toml_mut::dependency::Dependency;
use crate::util::toml_mut::dependency::GitSource;
use crate::util::toml_mut::dependency::MaybeWorkspace;
@@ -197,7 +200,13 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
print_dep_table_msg(&mut options.gctx.shell(), &dep)?;
- manifest.insert_into_table(&dep_table, &dep)?;
+ manifest.insert_into_table(
+ &dep_table,
+ &dep,
+ workspace.gctx(),
+ workspace.root(),
+ options.spec.manifest().unstable_features(),
+ )?;
if dep.optional == Some(true) {
let is_namespaced_features_supported =
check_rust_version_for_optional_dependency(options.spec.rust_version())?;
@@ -270,8 +279,11 @@ pub struct DepOp {
/// Registry for looking up dependency version
pub registry: Option,
- /// Git repo for dependency
+ /// File system path for dependency
pub path: Option,
+ /// Specify a named base for a path dependency
+ pub base: Option,
+
/// Git repo for dependency
pub git: Option,
/// Specify an alternative git branch
@@ -332,7 +344,19 @@ fn resolve_dependency(
selected
} else if let Some(raw_path) = &arg.path {
let path = paths::normalize_path(&std::env::current_dir()?.join(raw_path));
- let src = PathSource::new(&path);
+ let mut src = PathSource::new(path);
+ src.base = arg.base.clone();
+
+ if let Some(base) = &arg.base {
+ // Validate that the base is valid.
+ let workspace_root = || Ok(ws.root_manifest().parent().unwrap());
+ lookup_path_base(
+ &PathBaseName::new(base.clone())?,
+ &gctx,
+ &workspace_root,
+ spec.manifest().unstable_features(),
+ )?;
+ }
let selected = if let Some(crate_spec) = &crate_spec {
if let Some(v) = crate_spec.version_req() {
@@ -349,9 +373,13 @@ fn resolve_dependency(
}
selected
} else {
- let mut source = crate::sources::PathSource::new(&path, src.source_id()?, gctx);
+ let mut source = crate::sources::PathSource::new(&src.path, src.source_id()?, gctx);
let package = source.root_package()?;
- Dependency::from(package.summary())
+ let mut selected = Dependency::from(package.summary());
+ if let Some(Source::Path(selected_src)) = &mut selected.source {
+ selected_src.base = src.base;
+ }
+ selected
};
selected
} else if let Some(crate_spec) = &crate_spec {
@@ -361,9 +389,16 @@ fn resolve_dependency(
};
selected_dep = populate_dependency(selected_dep, arg);
- let lookup = |dep_key: &_| get_existing_dependency(manifest, dep_key, section);
+ let lookup = |dep_key: &_| {
+ get_existing_dependency(
+ ws,
+ spec.manifest().unstable_features(),
+ manifest,
+ dep_key,
+ section,
+ )
+ };
let old_dep = fuzzy_lookup(&mut selected_dep, lookup, gctx)?;
-
let mut dependency = if let Some(mut old_dep) = old_dep.clone() {
if old_dep.name != selected_dep.name {
// Assuming most existing keys are not relevant when the package changes
@@ -385,7 +420,9 @@ fn resolve_dependency(
if dependency.source().is_none() {
// Checking for a workspace dependency happens first since a member could be specified
// in the workspace dependencies table as a dependency
- let lookup = |toml_key: &_| Ok(find_workspace_dep(toml_key, ws.root_manifest()).ok());
+ let lookup = |toml_key: &_| {
+ Ok(find_workspace_dep(toml_key, ws, ws.root_manifest(), ws.unstable_features()).ok())
+ };
if let Some(_dep) = fuzzy_lookup(&mut dependency, lookup, gctx)? {
dependency = dependency.set_source(WorkspaceSource::new());
} else if let Some(package) = ws.members().find(|p| p.name().as_str() == dependency.name) {
@@ -432,7 +469,12 @@ fn resolve_dependency(
let query = dependency.query(gctx)?;
let query = match query {
MaybeWorkspace::Workspace(_workspace) => {
- let dep = find_workspace_dep(dependency.toml_key(), ws.root_manifest())?;
+ let dep = find_workspace_dep(
+ dependency.toml_key(),
+ ws,
+ ws.root_manifest(),
+ ws.unstable_features(),
+ )?;
if let Some(features) = dep.features.clone() {
dependency = dependency.set_inherited_features(features);
}
@@ -547,6 +589,8 @@ fn check_rust_version_for_optional_dependency(
/// If it doesn't exist but exists in another table, let's use that as most likely users
/// want to use the same version across all tables unless they are renaming.
fn get_existing_dependency(
+ ws: &Workspace<'_>,
+ unstable_features: &Features,
manifest: &LocalManifest,
dep_key: &str,
section: &DepTable,
@@ -561,7 +605,7 @@ fn get_existing_dependency(
}
let mut possible: Vec<_> = manifest
- .get_dependency_versions(dep_key)
+ .get_dependency_versions(dep_key, ws, unstable_features)
.map(|(path, dep)| {
let key = if path == *section {
(Key::Existing, true)
@@ -776,6 +820,11 @@ fn select_package(
if let Some(reg_name) = dependency.registry.as_deref() {
dep = dep.set_registry(reg_name);
}
+ if let Some(Source::Path(PathSource { base, .. })) = dependency.source() {
+ if let Some(Source::Path(dep_src)) = &mut dep.source {
+ dep_src.base = base.clone();
+ }
+ }
Ok(dep)
}
_ => {
@@ -1107,7 +1156,12 @@ fn format_features_version_suffix(dep: &DependencyUI) -> String {
}
}
-fn find_workspace_dep(toml_key: &str, root_manifest: &Path) -> CargoResult {
+fn find_workspace_dep(
+ toml_key: &str,
+ ws: &Workspace<'_>,
+ root_manifest: &Path,
+ unstable_features: &Features,
+) -> CargoResult {
let manifest = LocalManifest::try_new(root_manifest)?;
let manifest = manifest
.data
@@ -1127,7 +1181,14 @@ fn find_workspace_dep(toml_key: &str, root_manifest: &Path) -> CargoResult>();
- for manifest_path in manifest_paths {
+ for (manifest_path, unstable_features) in items {
trace!("updating TOML manifest at `{manifest_path:?}` with upgraded dependencies");
let crate_root = manifest_path
@@ -417,7 +422,10 @@ pub fn write_manifest_upgrades(
for (mut dep_key, dep_item) in dep_table.iter_mut() {
let dep_key_str = dep_key.get();
let dependency = crate::util::toml_mut::dependency::Dependency::from_toml(
+ ws.gctx(),
+ ws.root(),
&manifest_path,
+ unstable_features,
dep_key_str,
dep_item,
)?;
@@ -472,7 +480,14 @@ pub fn write_manifest_upgrades(
dep.source = Some(Source::Registry(source));
trace!("upgrading dependency {name}");
- dep.update_toml(&crate_root, &mut dep_key, dep_item);
+ dep.update_toml(
+ ws.gctx(),
+ ws.root(),
+ &crate_root,
+ unstable_features,
+ &mut dep_key,
+ dep_item,
+ )?;
manifest_has_changed = true;
any_file_has_changed = true;
}
diff --git a/src/cargo/util/toml_mut/dependency.rs b/src/cargo/util/toml_mut/dependency.rs
index ce7e6229021..b66fc56afbc 100644
--- a/src/cargo/util/toml_mut/dependency.rs
+++ b/src/cargo/util/toml_mut/dependency.rs
@@ -1,16 +1,19 @@
//! Information about dependencies in a manifest.
+use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::path::{Path, PathBuf};
+use cargo_util_schemas::manifest::PathBaseName;
use indexmap::IndexSet;
use itertools::Itertools;
use toml_edit::KeyMut;
use super::manifest::str_or_1_len_table;
-use crate::core::GitReference;
use crate::core::SourceId;
use crate::core::Summary;
+use crate::core::{Features, GitReference};
+use crate::util::toml::lookup_path_base;
use crate::util::toml_mut::is_sorted;
use crate::CargoResult;
use crate::GlobalContext;
@@ -219,7 +222,14 @@ pub enum MaybeWorkspace {
impl Dependency {
/// Create a dependency from a TOML table entry.
- pub fn from_toml(crate_root: &Path, key: &str, item: &toml_edit::Item) -> CargoResult {
+ pub fn from_toml(
+ gctx: &GlobalContext,
+ workspace_root: &Path,
+ crate_root: &Path,
+ unstable_features: &Features,
+ key: &str,
+ item: &toml_edit::Item,
+ ) -> CargoResult {
if let Some(version) = item.as_str() {
let dep = Self::new(key).set_source(RegistrySource::new(version));
Ok(dep)
@@ -266,12 +276,31 @@ impl Dependency {
}
src.into()
} else if let Some(path) = table.get("path") {
+ let base = table
+ .get("base")
+ .map(|base| {
+ base.as_str()
+ .ok_or_else(|| invalid_type(key, "base", base.type_name(), "string"))
+ .map(|s| s.to_owned())
+ })
+ .transpose()?;
+ let relative_to = if let Some(base) = &base {
+ Cow::Owned(lookup_path_base(
+ &PathBaseName::new(base.clone())?,
+ gctx,
+ &|| Ok(workspace_root),
+ unstable_features,
+ )?)
+ } else {
+ Cow::Borrowed(crate_root)
+ };
let path =
- crate_root
+ relative_to
.join(path.as_str().ok_or_else(|| {
invalid_type(key, "path", path.type_name(), "string")
})?);
let mut src = PathSource::new(path);
+ src.base = base;
if let Some(value) = table.get("version") {
src = src.set_version(value.as_str().ok_or_else(|| {
invalid_type(key, "version", value.type_name(), "string")
@@ -372,7 +401,13 @@ impl Dependency {
/// # Panic
///
/// Panics if the path is relative
- pub fn to_toml(&self, crate_root: &Path) -> toml_edit::Item {
+ pub fn to_toml<'a>(
+ &self,
+ gctx: &GlobalContext,
+ workspace_root: &Path,
+ crate_root: &Path,
+ unstable_features: &Features,
+ ) -> CargoResult {
assert!(
crate_root.is_absolute(),
"Absolute path needed, got: {}",
@@ -412,10 +447,14 @@ impl Dependency {
table.insert("version", src.version.as_str().into());
}
Some(Source::Path(src)) => {
- let relpath = path_field(crate_root, &src.path);
+ let relpath =
+ path_field(&src, gctx, workspace_root, crate_root, unstable_features)?;
if let Some(r) = src.version.as_deref() {
table.insert("version", r.into());
}
+ if let Some(base) = &src.base {
+ table.insert("base", base.into());
+ }
table.insert("path", relpath.into());
}
Some(Source::Git(src)) => {
@@ -465,19 +504,22 @@ impl Dependency {
}
};
- table
+ Ok(table)
}
/// Modify existing entry to match this dependency.
- pub fn update_toml<'k>(
+ pub fn update_toml<'k, 'a>(
&self,
+ gctx: &GlobalContext,
+ workspace_root: &Path,
crate_root: &Path,
+ unstable_features: &Features,
key: &mut KeyMut<'k>,
item: &mut toml_edit::Item,
- ) {
+ ) -> CargoResult<()> {
if str_or_1_len_table(item) {
// Little to preserve
- let mut new_item = self.to_toml(crate_root);
+ let mut new_item = self.to_toml(gctx, workspace_root, crate_root, unstable_features)?;
match (&item, &mut new_item) {
(toml_edit::Item::Value(old), toml_edit::Item::Value(new)) => {
*new.decor_mut() = old.decor().clone();
@@ -493,12 +535,18 @@ impl Dependency {
Some(Source::Registry(src)) => {
overwrite_value(table, "version", src.version.as_str());
- for key in ["path", "git", "branch", "tag", "rev", "workspace"] {
+ for key in ["path", "git", "branch", "tag", "rev", "workspace", "base"] {
table.remove(key);
}
}
Some(Source::Path(src)) => {
- let relpath = path_field(crate_root, &src.path);
+ if let Some(base) = &src.base {
+ overwrite_value(table, "base", base);
+ } else {
+ table.remove("base");
+ }
+ let relpath =
+ path_field(&src, gctx, workspace_root, crate_root, unstable_features)?;
overwrite_value(table, "path", relpath);
if let Some(r) = src.version.as_deref() {
overwrite_value(table, "version", r);
@@ -533,7 +581,7 @@ impl Dependency {
table.remove("version");
}
- for key in ["path", "workspace"] {
+ for key in ["path", "workspace", "base"] {
table.remove(key);
}
}
@@ -552,6 +600,7 @@ impl Dependency {
"rev",
"package",
"default-features",
+ "base",
] {
table.remove(key);
}
@@ -623,6 +672,7 @@ impl Dependency {
} else {
unreachable!("Invalid dependency type: {}", item.type_name());
}
+ Ok(())
}
}
@@ -682,10 +732,26 @@ impl From for Dependency {
}
}
-fn path_field(crate_root: &Path, abs_path: &Path) -> String {
- let relpath = pathdiff::diff_paths(abs_path, crate_root).expect("both paths are absolute");
+fn path_field<'a>(
+ source: &PathSource,
+ gctx: &GlobalContext,
+ workspace_root: &Path,
+ crate_root: &Path,
+ unstable_features: &Features,
+) -> CargoResult {
+ let relative_to = if let Some(base) = &source.base {
+ Cow::Owned(lookup_path_base(
+ &PathBaseName::new(base.clone())?,
+ gctx,
+ &|| Ok(workspace_root),
+ unstable_features,
+ )?)
+ } else {
+ Cow::Borrowed(crate_root)
+ };
+ let relpath = pathdiff::diff_paths(&source.path, relative_to).expect("both paths are absolute");
let relpath = relpath.to_str().unwrap().replace('\\', "/");
- relpath
+ Ok(relpath)
}
/// Primary location of a dependency.
@@ -812,6 +878,8 @@ impl std::fmt::Display for RegistrySource {
pub struct PathSource {
/// Local, absolute path.
pub path: PathBuf,
+ /// The path base, if using one.
+ pub base: Option,
/// Version requirement for when published.
pub version: Option,
}
@@ -821,6 +889,7 @@ impl PathSource {
pub fn new(path: impl Into) -> Self {
Self {
path: path.into(),
+ base: None,
version: None,
}
}
@@ -975,11 +1044,14 @@ mod tests {
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "dep".to_owned());
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -988,12 +1060,15 @@ mod tests {
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep").set_source(RegistrySource::new("1.0"));
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "dep".to_owned());
assert_eq!(item.as_str(), Some("1.0"));
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1004,7 +1079,10 @@ mod tests {
.set_source(RegistrySource::new("1.0"))
.set_optional(true);
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
@@ -1012,7 +1090,7 @@ mod tests {
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("optional").unwrap().as_bool(), Some(true));
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1023,7 +1101,10 @@ mod tests {
.set_source(RegistrySource::new("1.0"))
.set_default_features(false);
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
@@ -1031,7 +1112,7 @@ mod tests {
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1040,7 +1121,10 @@ mod tests {
let crate_root = root.join("foo");
let dep = Dependency::new("dep").set_source(PathSource::new(root.join("bar")));
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
@@ -1048,7 +1132,7 @@ mod tests {
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("path").unwrap().as_str(), Some("../bar"));
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1057,7 +1141,10 @@ mod tests {
paths::normalize_path(&std::env::current_dir().unwrap().join(Path::new("/")));
let dep = Dependency::new("dep").set_source(GitSource::new("https://foor/bar.git"));
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
@@ -1068,7 +1155,7 @@ mod tests {
Some("https://foor/bar.git")
);
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1079,7 +1166,10 @@ mod tests {
.set_source(RegistrySource::new("1.0"))
.set_rename("d");
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "d".to_owned());
assert!(item.is_inline_table());
@@ -1087,7 +1177,7 @@ mod tests {
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("package").unwrap().as_str(), Some("dep"));
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1098,7 +1188,10 @@ mod tests {
.set_source(RegistrySource::new("1.0"))
.set_registry("alternative");
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "dep".to_owned());
assert!(item.is_inline_table());
@@ -1106,7 +1199,7 @@ mod tests {
let dep = item.as_inline_table().unwrap();
assert_eq!(dep.get("registry").unwrap().as_str(), Some("alternative"));
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1118,7 +1211,10 @@ mod tests {
.set_default_features(false)
.set_rename("d");
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, "d".to_owned());
assert!(item.is_inline_table());
@@ -1128,7 +1224,7 @@ mod tests {
assert_eq!(dep.get("version").unwrap().as_str(), Some("1.0"));
assert_eq!(dep.get("default-features").unwrap().as_bool(), Some(false));
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1139,13 +1235,16 @@ mod tests {
let relpath = "sibling/crate";
let dep = Dependency::new("dep").set_source(PathSource::new(path));
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
let table = item.as_inline_table().unwrap();
let got = table.get("path").unwrap().as_str().unwrap();
assert_eq!(got, relpath);
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[test]
@@ -1159,10 +1258,21 @@ mod tests {
manifest,
};
assert_eq!(local.manifest.to_string(), toml);
+ let gctx = GlobalContext::default().unwrap();
for (key, item) in local.data.clone().iter() {
- let dep = Dependency::from_toml(&crate_root, key, item).unwrap();
+ let dep = Dependency::from_toml(
+ &gctx,
+ &crate_root,
+ &crate_root,
+ &Features::default(),
+ key,
+ item,
+ )
+ .unwrap();
let dep = dep.set_source(WorkspaceSource::new());
- local.insert_into_table(&vec![], &dep).unwrap();
+ local
+ .insert_into_table(&vec![], &dep, &gctx, &crate_root, &Features::default())
+ .unwrap();
assert_eq!(local.data.to_string(), "dep.workspace = true\n");
}
}
@@ -1176,20 +1286,38 @@ mod tests {
let should_be = "sibling/crate";
let dep = Dependency::new("dep").set_source(PathSource::new(original));
let key = dep.toml_key();
- let item = dep.to_toml(&crate_root);
+ let gctx = GlobalContext::default().unwrap();
+ let item = dep
+ .to_toml(&gctx, &crate_root, &crate_root, &Features::default())
+ .unwrap();
let table = item.as_inline_table().unwrap();
let got = table.get("path").unwrap().as_str().unwrap();
assert_eq!(got, should_be);
- verify_roundtrip(&crate_root, key, &item);
+ verify_roundtrip(&crate_root, &gctx, key, &item);
}
#[track_caller]
- fn verify_roundtrip(crate_root: &Path, key: &str, item: &toml_edit::Item) {
- let roundtrip = Dependency::from_toml(crate_root, key, item).unwrap();
+ fn verify_roundtrip(
+ crate_root: &Path,
+ gctx: &GlobalContext,
+ key: &str,
+ item: &toml_edit::Item,
+ ) {
+ let roundtrip = Dependency::from_toml(
+ gctx,
+ crate_root,
+ crate_root,
+ &Features::default(),
+ key,
+ item,
+ )
+ .unwrap();
let round_key = roundtrip.toml_key();
- let round_item = roundtrip.to_toml(crate_root);
+ let round_item = roundtrip
+ .to_toml(gctx, crate_root, crate_root, &Features::default())
+ .unwrap();
assert_eq!(key, round_key);
assert_eq!(item.to_string(), round_item.to_string());
}
diff --git a/src/cargo/util/toml_mut/manifest.rs b/src/cargo/util/toml_mut/manifest.rs
index d7ed18440d2..e166a24f2be 100644
--- a/src/cargo/util/toml_mut/manifest.rs
+++ b/src/cargo/util/toml_mut/manifest.rs
@@ -8,9 +8,9 @@ use anyhow::Context as _;
use super::dependency::Dependency;
use crate::core::dependency::DepKind;
-use crate::core::FeatureValue;
+use crate::core::{FeatureValue, Features, Workspace};
use crate::util::interning::InternedString;
-use crate::CargoResult;
+use crate::{CargoResult, GlobalContext};
/// Dependency table to add deps to.
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -286,6 +286,8 @@ impl LocalManifest {
pub fn get_dependency_versions<'s>(
&'s self,
dep_key: &'s str,
+ ws: &'s Workspace<'_>,
+ unstable_features: &'s Features,
) -> impl Iterator- )> + 's {
let crate_root = self.path.parent().expect("manifest path is absolute");
self.get_sections()
@@ -307,7 +309,14 @@ impl LocalManifest {
})
.flatten()
.map(move |(table_path, dep_key, dep_item)| {
- let dep = Dependency::from_toml(crate_root, &dep_key, &dep_item);
+ let dep = Dependency::from_toml(
+ ws.gctx(),
+ ws.root(),
+ crate_root,
+ unstable_features,
+ &dep_key,
+ &dep_item,
+ );
(table_path, dep)
})
}
@@ -317,6 +326,9 @@ impl LocalManifest {
&mut self,
table_path: &[String],
dep: &Dependency,
+ gctx: &GlobalContext,
+ workspace_root: &Path,
+ unstable_features: &Features,
) -> CargoResult<()> {
let crate_root = self
.path
@@ -331,7 +343,14 @@ impl LocalManifest {
.unwrap()
.get_key_value_mut(dep_key)
{
- dep.update_toml(&crate_root, &mut dep_key, dep_item);
+ dep.update_toml(
+ gctx,
+ workspace_root,
+ &crate_root,
+ unstable_features,
+ &mut dep_key,
+ dep_item,
+ )?;
if let Some(table) = dep_item.as_inline_table_mut() {
// So long as we don't have `Cargo.toml` auto-formatting and inline-tables can only
// be on one line, there isn't really much in the way of interesting formatting to
@@ -339,7 +358,8 @@ impl LocalManifest {
table.fmt();
}
} else {
- let new_dependency = dep.to_toml(&crate_root);
+ let new_dependency =
+ dep.to_toml(gctx, workspace_root, &crate_root, unstable_features)?;
table[dep_key] = new_dependency;
}
diff --git a/src/doc/man/cargo-add.md b/src/doc/man/cargo-add.md
index 1c0b736af90..555c96b064e 100644
--- a/src/doc/man/cargo-add.md
+++ b/src/doc/man/cargo-add.md
@@ -63,6 +63,12 @@ Specific commit to use when adding from git.
[Filesystem path](../reference/specifying-dependencies.html#specifying-path-dependencies) to local crate to add.
{{/option}}
+{{#option "`--base` _base_" }}
+The [path base](../reference/unstable.html#path-bases) to use when adding a local crate.
+
+[Unstable (nightly-only)](../reference/unstable.html#path-bases)
+{{/option}}
+
{{> options-registry }}
{{/options}}
diff --git a/src/doc/man/generated_txt/cargo-add.txt b/src/doc/man/generated_txt/cargo-add.txt
index 33c536f8321..70757246ec2 100644
--- a/src/doc/man/generated_txt/cargo-add.txt
+++ b/src/doc/man/generated_txt/cargo-add.txt
@@ -56,6 +56,14 @@ OPTIONS
to local crate to add.
+ --base base
+ The path base
+
+ to use when adding a local crate.
+
+ Unstable (nightly-only)
+
+
--registry registry
Name of the registry to use. Registry names are defined in Cargo
config files
diff --git a/src/doc/src/commands/cargo-add.md b/src/doc/src/commands/cargo-add.md
index 774ca41edfa..e61f8408eb8 100644
--- a/src/doc/src/commands/cargo-add.md
+++ b/src/doc/src/commands/cargo-add.md
@@ -59,6 +59,11 @@ dependency will be listed in the command's output.
Filesystem path to local crate to add.
+
--base
base
+The path base to use when adding a local crate.
+Unstable (nightly-only)
+
+
--registry
registry
Name of the registry to use. Registry names are defined in Cargo config
files. If not specified, the default registry is used,
diff --git a/src/etc/man/cargo-add.1 b/src/etc/man/cargo-add.1
index b89508b3f09..a7b2d1a110d 100644
--- a/src/etc/man/cargo-add.1
+++ b/src/etc/man/cargo-add.1
@@ -74,6 +74,13 @@ Specific commit to use when adding from git.
\fIFilesystem path\fR to local crate to add.
.RE
.sp
+\fB\-\-base\fR \fIbase\fR
+.RS 4
+The \fIpath base\fR to use when adding a local crate.
+.sp
+\fIUnstable (nightly\-only)\fR
+.RE
+.sp
\fB\-\-registry\fR \fIregistry\fR
.RS 4
Name of the registry to use. Registry names are defined in \fICargo config
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/.cargo/config.toml b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/.cargo/config.toml
new file mode 100644
index 00000000000..fa0f7bb4d2b
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/.cargo/config.toml
@@ -0,0 +1,2 @@
+[path-bases]
+my_base = "deps"
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/Cargo.toml b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/Cargo.toml
new file mode 100644
index 00000000000..32f9a4ec1cd
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/Cargo.toml
@@ -0,0 +1,7 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+members = ["primary", "deps/dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", base = "my_base", path = "./dependency"}
\ No newline at end of file
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/deps/dependency/Cargo.toml b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/deps/dependency/Cargo.toml
new file mode 100644
index 00000000000..0de6f9b15a8
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/deps/dependency/Cargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "foo"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/deps/dependency/src/lib.rs b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/deps/dependency/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/primary/Cargo.toml b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/primary/Cargo.toml
new file mode 100644
index 00000000000..737232cece6
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+cargo-features = ["path-bases"]
+
+[package]
+name = "bar"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/primary/src/lib.rs b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/in/primary/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/mod.rs b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/mod.rs
new file mode 100644
index 00000000000..95ecd0d6500
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/mod.rs
@@ -0,0 +1,27 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::str;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ cargo_test_support::registry::init();
+
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = &project_root;
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .args(["foo", "-p", "bar"])
+ .current_dir(cwd)
+ .masquerade_as_nightly_cargo(&["path-base"])
+ .assert()
+ .success()
+ .stdout_eq(str![""])
+ .stderr_eq(file!["stderr.term.svg"]);
+
+ assert_ui().subset_matches(current_dir!().join("out"), &project_root);
+}
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/Cargo.toml b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/Cargo.toml
new file mode 100644
index 00000000000..32f9a4ec1cd
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/Cargo.toml
@@ -0,0 +1,7 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+members = ["primary", "deps/dependency"]
+
+[workspace.dependencies]
+foo = { version = "0.0.0", base = "my_base", path = "./dependency"}
\ No newline at end of file
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/deps/dependency/Cargo.toml b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/deps/dependency/Cargo.toml
new file mode 100644
index 00000000000..0de6f9b15a8
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/deps/dependency/Cargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "foo"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/primary/Cargo.toml b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/primary/Cargo.toml
new file mode 100644
index 00000000000..67ed3895785
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/out/primary/Cargo.toml
@@ -0,0 +1,9 @@
+cargo-features = ["path-bases"]
+
+[package]
+name = "bar"
+version = "0.0.0"
+edition = "2015"
+
+[dependencies]
+foo.workspace = true
diff --git a/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/stderr.term.svg b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/stderr.term.svg
new file mode 100644
index 00000000000..890a67bc986
--- /dev/null
+++ b/tests/testsuite/cargo_add/detect_workspace_inherit_path_base/stderr.term.svg
@@ -0,0 +1,27 @@
+
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/in/.cargo/config.toml b/tests/testsuite/cargo_add/dev_existing_path_base/in/.cargo/config.toml
new file mode 100644
index 00000000000..4539a438423
--- /dev/null
+++ b/tests/testsuite/cargo_add/dev_existing_path_base/in/.cargo/config.toml
@@ -0,0 +1,2 @@
+[path-bases]
+my_base = "."
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/in/dependency/Cargo.toml b/tests/testsuite/cargo_add/dev_existing_path_base/in/dependency/Cargo.toml
new file mode 100644
index 00000000000..c645a3371c0
--- /dev/null
+++ b/tests/testsuite/cargo_add/dev_existing_path_base/in/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/in/dependency/src/lib.rs b/tests/testsuite/cargo_add/dev_existing_path_base/in/dependency/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/in/primary/Cargo.toml b/tests/testsuite/cargo_add/dev_existing_path_base/in/primary/Cargo.toml
new file mode 100644
index 00000000000..ecba72b5df6
--- /dev/null
+++ b/tests/testsuite/cargo_add/dev_existing_path_base/in/primary/Cargo.toml
@@ -0,0 +1,11 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", base = "my_base", path = "dependency" }
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/in/primary/src/lib.rs b/tests/testsuite/cargo_add/dev_existing_path_base/in/primary/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/mod.rs b/tests/testsuite/cargo_add/dev_existing_path_base/mod.rs
new file mode 100644
index 00000000000..be1a8550392
--- /dev/null
+++ b/tests/testsuite/cargo_add/dev_existing_path_base/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::str;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --dev")
+ .current_dir(&cwd)
+ .masquerade_as_nightly_cargo(&["path-base"])
+ .assert()
+ .success()
+ .stdout_eq(str![""])
+ .stderr_eq(file!["stderr.term.svg"]);
+
+ assert_ui().subset_matches(current_dir!().join("out"), &project_root);
+}
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/out/dependency/Cargo.toml b/tests/testsuite/cargo_add/dev_existing_path_base/out/dependency/Cargo.toml
new file mode 100644
index 00000000000..c645a3371c0
--- /dev/null
+++ b/tests/testsuite/cargo_add/dev_existing_path_base/out/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/out/primary/Cargo.toml b/tests/testsuite/cargo_add/dev_existing_path_base/out/primary/Cargo.toml
new file mode 100644
index 00000000000..6f036f2f6b9
--- /dev/null
+++ b/tests/testsuite/cargo_add/dev_existing_path_base/out/primary/Cargo.toml
@@ -0,0 +1,14 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", base = "my_base", path = "dependency" }
+
+[dev-dependencies]
+cargo-list-test-fixture-dependency = { base = "my_base", path = "dependency" }
diff --git a/tests/testsuite/cargo_add/dev_existing_path_base/stderr.term.svg b/tests/testsuite/cargo_add/dev_existing_path_base/stderr.term.svg
new file mode 100644
index 00000000000..c4a8bc01449
--- /dev/null
+++ b/tests/testsuite/cargo_add/dev_existing_path_base/stderr.term.svg
@@ -0,0 +1,29 @@
+
diff --git a/tests/testsuite/cargo_add/help/stdout.term.svg b/tests/testsuite/cargo_add/help/stdout.term.svg
index 9335d606113..cee4d65c49f 100644
--- a/tests/testsuite/cargo_add/help/stdout.term.svg
+++ b/tests/testsuite/cargo_add/help/stdout.term.svg
@@ -219,83 +219,89 @@
- --git <URI>
+ --base <BASE>
- Git repository location
+ The path base to use when adding from a local crate (unstable).
-
+
- Without any other information, cargo will use latest commit on the main branch.
+ --git <URI>
-
+ Git repository location
- --branch <BRANCH>
+
- Git branch to download the crate from
+ Without any other information, cargo will use latest commit on the main branch.
- --tag <TAG>
+ --branch <BRANCH>
- Git tag to download the crate from
+ Git branch to download the crate from
- --rev <REV>
+ --tag <TAG>
- Git reference to download the crate from
+ Git tag to download the crate from
-
+
- This is the catch all, handling hashes to named references in remote repositories.
+ --rev <REV>
-
+ Git reference to download the crate from
- --registry <NAME>
+
- Package registry for this dependency
+ This is the catch all, handling hashes to named references in remote repositories.
- Section:
+ --registry <NAME>
- --dev
+ Package registry for this dependency
- Add as development dependency
+
-
+ Section:
- Dev-dependencies are not used when compiling a package for building, but are used for
+ --dev
- compiling tests, examples, and benchmarks.
+ Add as development dependency
- These dependencies are not propagated to other packages which depend on this package.
+ Dev-dependencies are not used when compiling a package for building, but are used for
-
+ compiling tests, examples, and benchmarks.
- --build
+
- Add as build dependency
+ These dependencies are not propagated to other packages which depend on this package.
-
+
- Build-dependencies are the only dependencies available for use by build scripts
+ --build
- (`build.rs` files).
+ Add as build dependency
-
+
- --target <TARGET>
+ Build-dependencies are the only dependencies available for use by build scripts
- Add as dependency to the given target platform
+ (`build.rs` files).
- Run `cargo help add` for more detailed information.
+ --target <TARGET>
-
+ Add as dependency to the given target platform
+
+
+
+ Run `cargo help add` for more detailed information.
+
+
diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs
index 8207d5fd387..ff910628ee8 100644
--- a/tests/testsuite/cargo_add/mod.rs
+++ b/tests/testsuite/cargo_add/mod.rs
@@ -16,9 +16,11 @@ mod detect_workspace_inherit;
mod detect_workspace_inherit_features;
mod detect_workspace_inherit_fuzzy;
mod detect_workspace_inherit_optional;
+mod detect_workspace_inherit_path_base;
mod detect_workspace_inherit_public;
mod dev;
mod dev_build_conflict;
+mod dev_existing_path_base;
mod dev_prefer_existing_version;
mod dry_run;
mod empty_dep_name;
@@ -94,6 +96,7 @@ mod overwrite_no_public_with_public;
mod overwrite_optional;
mod overwrite_optional_with_no_optional;
mod overwrite_optional_with_optional;
+mod overwrite_path_base_with_version;
mod overwrite_path_noop;
mod overwrite_path_with_version;
mod overwrite_preserves_inline_table;
@@ -108,6 +111,10 @@ mod overwrite_with_rename;
mod overwrite_workspace_dep;
mod overwrite_workspace_dep_features;
mod path;
+mod path_base;
+mod path_base_inferred_name;
+mod path_base_missing_base_path;
+mod path_base_unstable;
mod path_dev;
mod path_inferred_name;
mod path_inferred_name_conflicts_full_feature;
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/.cargo/config.toml b/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/.cargo/config.toml
new file mode 100644
index 00000000000..4539a438423
--- /dev/null
+++ b/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/.cargo/config.toml
@@ -0,0 +1,2 @@
+[path-bases]
+my_base = "."
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/dependency/Cargo.toml b/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/dependency/Cargo.toml
new file mode 100644
index 00000000000..c645a3371c0
--- /dev/null
+++ b/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/dependency/src/lib.rs b/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/dependency/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/primary/Cargo.toml b/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/primary/Cargo.toml
new file mode 100644
index 00000000000..9d01c094369
--- /dev/null
+++ b/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/primary/Cargo.toml
@@ -0,0 +1,11 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { optional = true, path = "dependency", base = "my_base" }
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/primary/src/lib.rs b/tests/testsuite/cargo_add/overwrite_path_base_with_version/in/primary/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/mod.rs b/tests/testsuite/cargo_add/overwrite_path_base_with_version/mod.rs
new file mode 100644
index 00000000000..ade44c07b48
--- /dev/null
+++ b/tests/testsuite/cargo_add/overwrite_path_base_with_version/mod.rs
@@ -0,0 +1,29 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::str;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ cargo_test_support::registry::init();
+ cargo_test_support::registry::Package::new("cargo-list-test-fixture-dependency", "20.0.0")
+ .publish();
+
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency@20.0")
+ .current_dir(&cwd)
+ .masquerade_as_nightly_cargo(&["path-base"])
+ .assert()
+ .success()
+ .stdout_eq(str![""])
+ .stderr_eq(file!["stderr.term.svg"]);
+
+ assert_ui().subset_matches(current_dir!().join("out"), &project_root);
+}
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/out/dependency/Cargo.toml b/tests/testsuite/cargo_add/overwrite_path_base_with_version/out/dependency/Cargo.toml
new file mode 100644
index 00000000000..c645a3371c0
--- /dev/null
+++ b/tests/testsuite/cargo_add/overwrite_path_base_with_version/out/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/out/primary/Cargo.toml b/tests/testsuite/cargo_add/overwrite_path_base_with_version/out/primary/Cargo.toml
new file mode 100644
index 00000000000..ead3ec39232
--- /dev/null
+++ b/tests/testsuite/cargo_add/overwrite_path_base_with_version/out/primary/Cargo.toml
@@ -0,0 +1,14 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { optional = true, version = "20.0" }
+
+[features]
+cargo-list-test-fixture-dependency = ["dep:cargo-list-test-fixture-dependency"]
diff --git a/tests/testsuite/cargo_add/overwrite_path_base_with_version/stderr.term.svg b/tests/testsuite/cargo_add/overwrite_path_base_with_version/stderr.term.svg
new file mode 100644
index 00000000000..ea8afda68de
--- /dev/null
+++ b/tests/testsuite/cargo_add/overwrite_path_base_with_version/stderr.term.svg
@@ -0,0 +1,35 @@
+
diff --git a/tests/testsuite/cargo_add/path_base/in/.cargo/config.toml b/tests/testsuite/cargo_add/path_base/in/.cargo/config.toml
new file mode 100644
index 00000000000..4539a438423
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base/in/.cargo/config.toml
@@ -0,0 +1,2 @@
+[path-bases]
+my_base = "."
diff --git a/tests/testsuite/cargo_add/path_base/in/dependency/Cargo.toml b/tests/testsuite/cargo_add/path_base/in/dependency/Cargo.toml
new file mode 100644
index 00000000000..c645a3371c0
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base/in/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base/in/dependency/src/lib.rs b/tests/testsuite/cargo_add/path_base/in/dependency/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/path_base/in/primary/Cargo.toml b/tests/testsuite/cargo_add/path_base/in/primary/Cargo.toml
new file mode 100644
index 00000000000..420ef1eb8f7
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base/in/primary/Cargo.toml
@@ -0,0 +1,8 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base/in/primary/src/lib.rs b/tests/testsuite/cargo_add/path_base/in/primary/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/path_base/mod.rs b/tests/testsuite/cargo_add/path_base/mod.rs
new file mode 100644
index 00000000000..326a76283bc
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::str;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("cargo-list-test-fixture-dependency --path ../dependency --base my_base")
+ .current_dir(&cwd)
+ .masquerade_as_nightly_cargo(&["path-base"])
+ .assert()
+ .success()
+ .stdout_eq(str![""])
+ .stderr_eq(file!["stderr.term.svg"]);
+
+ assert_ui().subset_matches(current_dir!().join("out"), &project_root);
+}
diff --git a/tests/testsuite/cargo_add/path_base/out/dependency/Cargo.toml b/tests/testsuite/cargo_add/path_base/out/dependency/Cargo.toml
new file mode 100644
index 00000000000..c645a3371c0
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base/out/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base/out/primary/Cargo.toml b/tests/testsuite/cargo_add/path_base/out/primary/Cargo.toml
new file mode 100644
index 00000000000..ecba72b5df6
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base/out/primary/Cargo.toml
@@ -0,0 +1,11 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", base = "my_base", path = "dependency" }
diff --git a/tests/testsuite/cargo_add/path_base/stderr.term.svg b/tests/testsuite/cargo_add/path_base/stderr.term.svg
new file mode 100644
index 00000000000..6781757da4a
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base/stderr.term.svg
@@ -0,0 +1,29 @@
+
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/in/.cargo/config.toml b/tests/testsuite/cargo_add/path_base_inferred_name/in/.cargo/config.toml
new file mode 100644
index 00000000000..4539a438423
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_inferred_name/in/.cargo/config.toml
@@ -0,0 +1,2 @@
+[path-bases]
+my_base = "."
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/in/dependency/Cargo.toml b/tests/testsuite/cargo_add/path_base_inferred_name/in/dependency/Cargo.toml
new file mode 100644
index 00000000000..c645a3371c0
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_inferred_name/in/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/in/dependency/src/lib.rs b/tests/testsuite/cargo_add/path_base_inferred_name/in/dependency/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/in/primary/Cargo.toml b/tests/testsuite/cargo_add/path_base_inferred_name/in/primary/Cargo.toml
new file mode 100644
index 00000000000..420ef1eb8f7
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_inferred_name/in/primary/Cargo.toml
@@ -0,0 +1,8 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/in/primary/src/lib.rs b/tests/testsuite/cargo_add/path_base_inferred_name/in/primary/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/mod.rs b/tests/testsuite/cargo_add/path_base_inferred_name/mod.rs
new file mode 100644
index 00000000000..005a9d75330
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_inferred_name/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::str;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("--path ../dependency --base my_base")
+ .current_dir(&cwd)
+ .masquerade_as_nightly_cargo(&["path-base"])
+ .assert()
+ .success()
+ .stdout_eq(str![""])
+ .stderr_eq(file!["stderr.term.svg"]);
+
+ assert_ui().subset_matches(current_dir!().join("out"), &project_root);
+}
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/out/dependency/Cargo.toml b/tests/testsuite/cargo_add/path_base_inferred_name/out/dependency/Cargo.toml
new file mode 100644
index 00000000000..c645a3371c0
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_inferred_name/out/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture-dependency"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/out/primary/Cargo.toml b/tests/testsuite/cargo_add/path_base_inferred_name/out/primary/Cargo.toml
new file mode 100644
index 00000000000..ecba72b5df6
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_inferred_name/out/primary/Cargo.toml
@@ -0,0 +1,11 @@
+cargo-features = ["path-bases"]
+
+[workspace]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
+
+[dependencies]
+cargo-list-test-fixture-dependency = { version = "0.0.0", base = "my_base", path = "dependency" }
diff --git a/tests/testsuite/cargo_add/path_base_inferred_name/stderr.term.svg b/tests/testsuite/cargo_add/path_base_inferred_name/stderr.term.svg
new file mode 100644
index 00000000000..6781757da4a
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_inferred_name/stderr.term.svg
@@ -0,0 +1,29 @@
+
diff --git a/tests/testsuite/cargo_add/path_base_missing_base_path/in/primary/Cargo.toml b/tests/testsuite/cargo_add/path_base_missing_base_path/in/primary/Cargo.toml
new file mode 100644
index 00000000000..1622ce604fd
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_missing_base_path/in/primary/Cargo.toml
@@ -0,0 +1,6 @@
+cargo-features = ["path-bases"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base_missing_base_path/in/primary/src/lib.rs b/tests/testsuite/cargo_add/path_base_missing_base_path/in/primary/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/path_base_missing_base_path/mod.rs b/tests/testsuite/cargo_add/path_base_missing_base_path/mod.rs
new file mode 100644
index 00000000000..dbae05ad0a4
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_missing_base_path/mod.rs
@@ -0,0 +1,25 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::str;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("--path dependency --base bad_base")
+ .current_dir(&cwd)
+ .masquerade_as_nightly_cargo(&["path-base"])
+ .assert()
+ .code(101)
+ .stdout_eq(str![""])
+ .stderr_eq(file!["stderr.term.svg"]);
+
+ assert_ui().subset_matches(current_dir!().join("out"), &project_root);
+}
diff --git a/tests/testsuite/cargo_add/path_base_missing_base_path/out/primary/Cargo.toml b/tests/testsuite/cargo_add/path_base_missing_base_path/out/primary/Cargo.toml
new file mode 100644
index 00000000000..1622ce604fd
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_missing_base_path/out/primary/Cargo.toml
@@ -0,0 +1,6 @@
+cargo-features = ["path-bases"]
+
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base_missing_base_path/stderr.term.svg b/tests/testsuite/cargo_add/path_base_missing_base_path/stderr.term.svg
new file mode 100644
index 00000000000..ebe52195f6c
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_missing_base_path/stderr.term.svg
@@ -0,0 +1,27 @@
+
diff --git a/tests/testsuite/cargo_add/path_base_unstable/in/primary/Cargo.toml b/tests/testsuite/cargo_add/path_base_unstable/in/primary/Cargo.toml
new file mode 100644
index 00000000000..8194d1deb0e
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_unstable/in/primary/Cargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base_unstable/in/primary/src/lib.rs b/tests/testsuite/cargo_add/path_base_unstable/in/primary/src/lib.rs
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/testsuite/cargo_add/path_base_unstable/mod.rs b/tests/testsuite/cargo_add/path_base_unstable/mod.rs
new file mode 100644
index 00000000000..c55fa83a123
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_unstable/mod.rs
@@ -0,0 +1,24 @@
+use cargo_test_support::compare::assert_ui;
+use cargo_test_support::current_dir;
+use cargo_test_support::file;
+use cargo_test_support::prelude::*;
+use cargo_test_support::str;
+use cargo_test_support::Project;
+
+#[cargo_test]
+fn case() {
+ let project = Project::from_template(current_dir!().join("in"));
+ let project_root = project.root();
+ let cwd = project_root.join("primary");
+
+ snapbox::cmd::Command::cargo_ui()
+ .arg("add")
+ .arg_line("--path dependency --base mybase")
+ .current_dir(&cwd)
+ .assert()
+ .code(101)
+ .stdout_eq(str![""])
+ .stderr_eq(file!["stderr.term.svg"]);
+
+ assert_ui().subset_matches(current_dir!().join("out"), &project_root);
+}
diff --git a/tests/testsuite/cargo_add/path_base_unstable/out/primary/Cargo.toml b/tests/testsuite/cargo_add/path_base_unstable/out/primary/Cargo.toml
new file mode 100644
index 00000000000..8194d1deb0e
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_unstable/out/primary/Cargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "cargo-list-test-fixture"
+version = "0.0.0"
+edition = "2015"
diff --git a/tests/testsuite/cargo_add/path_base_unstable/stderr.term.svg b/tests/testsuite/cargo_add/path_base_unstable/stderr.term.svg
new file mode 100644
index 00000000000..ca60ce2b30e
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_unstable/stderr.term.svg
@@ -0,0 +1,37 @@
+