From ffd37c48d1046c33726bfb631d469f4344cf2763 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/cargo/ops/cargo_add/mod.rs | 50 ++++++++++++-
src/cargo/util/toml_mut/dependency.rs | 31 ++++++--
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 ++
.../testsuite/cargo_add/help/stdout.term.svg | 74 ++++++++++---------
tests/testsuite/cargo_add/mod.rs | 5 ++
.../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 ++++++++++
46 files changed, 559 insertions(+), 45 deletions(-)
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 da821f328bd2..4c9436df6966 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/cargo/ops/cargo_add/mod.rs b/src/cargo/ops/cargo_add/mod.rs
index c6f71847df38..1c864e27108c 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;
@@ -270,8 +273,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
@@ -331,10 +337,41 @@ fn resolve_dependency(
};
selected
} else if let Some(raw_path) = &arg.path {
+ let base_name_and_value = if let Some(base_name) = &arg.base {
+ let workspace_root = ws.root_manifest().parent().unwrap().to_path_buf();
+ let workspace_root = || Ok(&workspace_root);
+
+ let features = {
+ let empty = Vec::new();
+ let mut warnings = Vec::new();
+ let cargo_features = spec
+ .manifest()
+ .original_toml()
+ .cargo_features
+ .as_ref()
+ .unwrap_or(&empty);
+ let features = Features::new(cargo_features, gctx, &mut warnings, true)?;
+ if !warnings.is_empty() {
+ for warning in warnings {
+ gctx.shell().warn(warning)?;
+ }
+ }
+ features
+ };
+ let base_value = lookup_path_base(
+ &PathBaseName::new(base_name.clone())?,
+ gctx,
+ &workspace_root,
+ &features,
+ )?;
+ Some((base_name.clone(), base_value))
+ } else {
+ None
+ };
let path = paths::normalize_path(&std::env::current_dir()?.join(raw_path));
- let src = PathSource::new(&path);
+ let src = PathSource::new(path);
- let selected = if let Some(crate_spec) = &crate_spec {
+ let mut selected = if let Some(crate_spec) = &crate_spec {
if let Some(v) = crate_spec.version_req() {
// crate specifier includes a version (e.g. `docopt@0.8`)
anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`).");
@@ -349,10 +386,15 @@ 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())
};
+ if let Some(selected_source) = selected.source.as_mut() {
+ if let Source::Path(selected_source) = selected_source {
+ selected_source.base_name_and_value = base_name_and_value;
+ }
+ }
selected
} else if let Some(crate_spec) = &crate_spec {
crate_spec.to_dependency()?
diff --git a/src/cargo/util/toml_mut/dependency.rs b/src/cargo/util/toml_mut/dependency.rs
index ce7e6229021e..cca078b0c0e8 100644
--- a/src/cargo/util/toml_mut/dependency.rs
+++ b/src/cargo/util/toml_mut/dependency.rs
@@ -412,11 +412,15 @@ impl Dependency {
table.insert("version", src.version.as_str().into());
}
Some(Source::Path(src)) => {
- let relpath = path_field(crate_root, &src.path);
if let Some(r) = src.version.as_deref() {
table.insert("version", r.into());
}
- table.insert("path", relpath.into());
+ if let Some((base_name, base_value)) = src.base_name_and_value.as_ref() {
+ table.insert("base", base_name.into());
+ table.insert("path", path_field(base_value, &src.path).into());
+ } else {
+ table.insert("path", path_field(crate_root, &src.path).into());
+ }
}
Some(Source::Git(src)) => {
table.insert("git", src.git.as_str().into());
@@ -493,13 +497,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);
- overwrite_value(table, "path", relpath);
+ if let Some((base_name, base_value)) = src.base_name_and_value.as_ref() {
+ overwrite_value(table, "base", base_name);
+ overwrite_value(table, "path", path_field(base_value, &src.path));
+ } else {
+ table.remove("base");
+ overwrite_value(table, "path", path_field(crate_root, &src.path));
+ }
if let Some(r) = src.version.as_deref() {
overwrite_value(table, "version", r);
} else {
@@ -533,7 +542,7 @@ impl Dependency {
table.remove("version");
}
- for key in ["path", "workspace"] {
+ for key in ["path", "workspace", "base"] {
table.remove(key);
}
}
@@ -552,6 +561,7 @@ impl Dependency {
"rev",
"package",
"default-features",
+ "base",
] {
table.remove(key);
}
@@ -812,6 +822,8 @@ impl std::fmt::Display for RegistrySource {
pub struct PathSource {
/// Local, absolute path.
pub path: PathBuf,
+ /// If using a named base, the base and relative path.
+ pub base_name_and_value: Option<(String, PathBuf)>,
/// Version requirement for when published.
pub version: Option,
}
@@ -821,6 +833,7 @@ impl PathSource {
pub fn new(path: impl Into) -> Self {
Self {
path: path.into(),
+ base_name_and_value: None,
version: None,
}
}
@@ -843,7 +856,11 @@ impl PathSource {
impl std::fmt::Display for PathSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- self.path.display().fmt(f)
+ if let Some((base_name, _)) = &self.base_name_and_value {
+ write!(f, "{{{base_name}}}/{}", self.path.display())
+ } else {
+ self.path.display().fmt(f)
+ }
}
}
diff --git a/src/doc/man/cargo-add.md b/src/doc/man/cargo-add.md
index 1c0b736af907..555c96b064eb 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 33c536f8321c..70757246ec21 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 774ca41edfa4..e61f8408eb85 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 b89508b3f091..a7b2d1a110d0 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/help/stdout.term.svg b/tests/testsuite/cargo_add/help/stdout.term.svg
index 9335d6061138..cee4d65c49f4 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 62feb8422c85..5319fbec4a12 100644
--- a/tests/testsuite/cargo_add/mod.rs
+++ b/tests/testsuite/cargo_add/mod.rs
@@ -91,6 +91,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;
@@ -105,6 +106,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 000000000000..4539a4384236
--- /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 000000000000..c645a3371c0b
--- /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 000000000000..e69de29bb2d1
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 000000000000..9d01c0943691
--- /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 000000000000..e69de29bb2d1
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 000000000000..ade44c07b48c
--- /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 000000000000..c645a3371c0b
--- /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 000000000000..ead3ec392329
--- /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 000000000000..965f9a26dead
--- /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 000000000000..4539a4384236
--- /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 000000000000..c645a3371c0b
--- /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 000000000000..e69de29bb2d1
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 000000000000..420ef1eb8f7f
--- /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 000000000000..e69de29bb2d1
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 000000000000..326a76283bc3
--- /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 000000000000..c645a3371c0b
--- /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 000000000000..ecba72b5df69
--- /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 000000000000..602e208606ed
--- /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 000000000000..4539a4384236
--- /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 000000000000..c645a3371c0b
--- /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 000000000000..e69de29bb2d1
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 000000000000..420ef1eb8f7f
--- /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 000000000000..e69de29bb2d1
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 000000000000..005a9d753306
--- /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 000000000000..c645a3371c0b
--- /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 000000000000..ecba72b5df69
--- /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 000000000000..602e208606ed
--- /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 000000000000..1622ce604fd4
--- /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 000000000000..e69de29bb2d1
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 000000000000..dbae05ad0a41
--- /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 000000000000..1622ce604fd4
--- /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 000000000000..ebe52195f6c0
--- /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 000000000000..8194d1deb0e0
--- /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 000000000000..e69de29bb2d1
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 000000000000..c55fa83a1236
--- /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 000000000000..8194d1deb0e0
--- /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 000000000000..ca60ce2b30ec
--- /dev/null
+++ b/tests/testsuite/cargo_add/path_base_unstable/stderr.term.svg
@@ -0,0 +1,37 @@
+