Skip to content

Commit 9953db9

Browse files
committed
Implement path-bases (RFC 3529) 2/n: cargo add support
RFC: rust-lang/rfcs#3529 Tracking Issue: #14355 This PR adds the `--base` option to `cargo add` to allow adding a path dependency with a path base.
1 parent c956e9f commit 9953db9

File tree

51 files changed

+634
-57
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+634
-57
lines changed

src/bin/cargo/commands/add.rs

+8
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ Example uses:
101101
.help("Filesystem path to local crate to add")
102102
.group("selected")
103103
.conflicts_with("git"),
104+
clap::Arg::new("base")
105+
.long("base")
106+
.action(ArgAction::Set)
107+
.value_name("BASE")
108+
.help("The path base to use when adding from a local crate.")
109+
.requires("path"),
104110
clap::Arg::new("git")
105111
.long("git")
106112
.action(ArgAction::Set)
@@ -224,6 +230,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
224230

225231
fn parse_dependencies(gctx: &GlobalContext, matches: &ArgMatches) -> CargoResult<Vec<DepOp>> {
226232
let path = matches.get_one::<String>("path");
233+
let base = matches.get_one::<String>("base");
227234
let git = matches.get_one::<String>("git");
228235
let branch = matches.get_one::<String>("branch");
229236
let rev = matches.get_one::<String>("rev");
@@ -329,6 +336,7 @@ fn parse_dependencies(gctx: &GlobalContext, matches: &ArgMatches) -> CargoResult
329336
public,
330337
registry: registry.clone(),
331338
path: path.map(String::from),
339+
base: base.map(String::from),
332340
git: git.map(String::from),
333341
branch: branch.map(String::from),
334342
rev: rev.map(String::from),

src/cargo/ops/cargo_add/mod.rs

+30-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::str::FromStr;
1212
use anyhow::Context as _;
1313
use cargo_util::paths;
1414
use cargo_util_schemas::core::PartialVersion;
15+
use cargo_util_schemas::manifest::PathBaseName;
1516
use cargo_util_schemas::manifest::RustVersion;
1617
use indexmap::IndexSet;
1718
use itertools::Itertools;
@@ -28,6 +29,7 @@ use crate::core::Workspace;
2829
use crate::sources::source::QueryKind;
2930
use crate::util::cache_lock::CacheLockMode;
3031
use crate::util::style;
32+
use crate::util::toml::lookup_path_base;
3133
use crate::util::toml_mut::dependency::Dependency;
3234
use crate::util::toml_mut::dependency::GitSource;
3335
use crate::util::toml_mut::dependency::MaybeWorkspace;
@@ -270,8 +272,11 @@ pub struct DepOp {
270272
/// Registry for looking up dependency version
271273
pub registry: Option<String>,
272274

273-
/// Git repo for dependency
275+
/// File system path for dependency
274276
pub path: Option<String>,
277+
/// Specify a named base for a path dependency
278+
pub base: Option<String>,
279+
275280
/// Git repo for dependency
276281
pub git: Option<String>,
277282
/// Specify an alternative git branch
@@ -331,10 +336,25 @@ fn resolve_dependency(
331336
};
332337
selected
333338
} else if let Some(raw_path) = &arg.path {
334-
let path = paths::normalize_path(&std::env::current_dir()?.join(raw_path));
335-
let src = PathSource::new(&path);
339+
let (path, base_name_and_value) = if let Some(base_name) = &arg.base {
340+
let workspace_root = || Ok(ws.root_manifest().parent().unwrap());
341+
let base_value = lookup_path_base(
342+
&PathBaseName::new(base_name.clone())?,
343+
gctx,
344+
&workspace_root,
345+
None,
346+
)?;
347+
(
348+
base_value.join(raw_path),
349+
Some((base_name.clone(), base_value)),
350+
)
351+
} else {
352+
(std::env::current_dir()?.join(raw_path), None)
353+
};
354+
let path = paths::normalize_path(&path);
355+
let src = PathSource::new(path);
336356

337-
let selected = if let Some(crate_spec) = &crate_spec {
357+
let mut selected = if let Some(crate_spec) = &crate_spec {
338358
if let Some(v) = crate_spec.version_req() {
339359
// crate specifier includes a version (e.g. `[email protected]`)
340360
anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`).");
@@ -349,10 +369,15 @@ fn resolve_dependency(
349369
}
350370
selected
351371
} else {
352-
let mut source = crate::sources::PathSource::new(&path, src.source_id()?, gctx);
372+
let mut source = crate::sources::PathSource::new(&src.path, src.source_id()?, gctx);
353373
let package = source.root_package()?;
354374
Dependency::from(package.summary())
355375
};
376+
if let Some(selected_source) = selected.source.as_mut() {
377+
if let Source::Path(selected_source) = selected_source {
378+
selected_source.base_name_and_value = base_name_and_value;
379+
}
380+
}
356381
selected
357382
} else if let Some(crate_spec) = &crate_spec {
358383
crate_spec.to_dependency()?

src/cargo/util/toml/mod.rs

+12-9
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ fn normalize_toml(
312312
inherit_cell
313313
.try_borrow_with(|| load_inheritable_fields(gctx, manifest_file, &workspace_config))
314314
};
315-
let workspace_root = || inherit().map(|fields| fields.ws_root());
315+
let workspace_root = || inherit().map(|fields| fields.ws_root().as_path());
316316

317317
if let Some(original_package) = original_toml.package() {
318318
let package_name = &original_package.name;
@@ -538,7 +538,7 @@ fn normalize_toml(
538538
fn normalize_patch<'a>(
539539
gctx: &GlobalContext,
540540
original_patch: Option<&BTreeMap<String, BTreeMap<PackageName, TomlDependency>>>,
541-
workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>,
541+
workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
542542
features: &Features,
543543
) -> CargoResult<Option<BTreeMap<String, BTreeMap<PackageName, TomlDependency>>>> {
544544
if let Some(patch) = original_patch {
@@ -757,7 +757,7 @@ fn normalize_dependencies<'a>(
757757
activated_opt_deps: &HashSet<&str>,
758758
kind: Option<DepKind>,
759759
inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
760-
workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>,
760+
workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
761761
package_root: &Path,
762762
warnings: &mut Vec<String>,
763763
) -> CargoResult<Option<BTreeMap<manifest::PackageName, manifest::InheritableDependency>>> {
@@ -839,12 +839,13 @@ fn normalize_dependencies<'a>(
839839
fn normalize_path_dependency<'a>(
840840
gctx: &GlobalContext,
841841
detailed_dep: &mut TomlDetailedDependency,
842-
workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>,
842+
workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
843843
features: &Features,
844844
) -> CargoResult<()> {
845845
if let Some(base) = detailed_dep.base.take() {
846846
if let Some(path) = detailed_dep.path.as_mut() {
847-
let new_path = lookup_path_base(&base, gctx, workspace_root, features)?.join(&path);
847+
let new_path =
848+
lookup_path_base(&base, gctx, workspace_root, Some(features))?.join(&path);
848849
*path = new_path.to_str().unwrap().to_string();
849850
} else {
850851
bail!("`base` can only be used with path dependencies");
@@ -2225,10 +2226,12 @@ fn to_dependency_source_id<P: ResolveToPath + Clone>(
22252226
pub(crate) fn lookup_path_base<'a>(
22262227
base: &PathBaseName,
22272228
gctx: &GlobalContext,
2228-
workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>,
2229-
features: &Features,
2229+
workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
2230+
features: Option<&Features>,
22302231
) -> CargoResult<PathBuf> {
2231-
features.require(Feature::path_bases())?;
2232+
if let Some(features) = features {
2233+
features.require(Feature::path_bases())?;
2234+
}
22322235

22332236
// HACK: The `base` string is user controlled, but building the path is safe from injection
22342237
// attacks since the `PathBaseName` type restricts the characters that can be used to exclude `.`
@@ -2240,7 +2243,7 @@ pub(crate) fn lookup_path_base<'a>(
22402243
} else {
22412244
// Otherwise, check the built-in bases.
22422245
match base.as_str() {
2243-
"workspace" => Ok(workspace_root()?.clone()),
2246+
"workspace" => Ok(workspace_root()?.to_path_buf()),
22442247
_ => bail!(
22452248
"path base `{base}` is undefined. \
22462249
You must add an entry for `{base}` in the Cargo configuration [path-bases] table."

src/cargo/util/toml_mut/dependency.rs

+29-5
Original file line numberDiff line numberDiff line change
@@ -412,10 +412,18 @@ impl Dependency {
412412
table.insert("version", src.version.as_str().into());
413413
}
414414
Some(Source::Path(src)) => {
415-
let relpath = path_field(crate_root, &src.path);
416415
if let Some(r) = src.version.as_deref() {
417416
table.insert("version", r.into());
418417
}
418+
let relative_to = if let Some((base_name, base_value)) =
419+
src.base_name_and_value.as_ref()
420+
{
421+
table.insert("base", base_name.into());
422+
base_value
423+
} else {
424+
crate_root
425+
};
426+
let relpath = path_field(relative_to, &src.path);
419427
table.insert("path", relpath.into());
420428
}
421429
Some(Source::Git(src)) => {
@@ -493,12 +501,20 @@ impl Dependency {
493501
Some(Source::Registry(src)) => {
494502
overwrite_value(table, "version", src.version.as_str());
495503

496-
for key in ["path", "git", "branch", "tag", "rev", "workspace"] {
504+
for key in ["path", "git", "branch", "tag", "rev", "workspace", "base"] {
497505
table.remove(key);
498506
}
499507
}
500508
Some(Source::Path(src)) => {
501-
let relpath = path_field(crate_root, &src.path);
509+
let relative_to =
510+
if let Some((base_name, base_value)) = src.base_name_and_value.as_ref() {
511+
overwrite_value(table, "base", base_name);
512+
base_value
513+
} else {
514+
table.remove("base");
515+
crate_root
516+
};
517+
let relpath = path_field(relative_to, &src.path);
502518
overwrite_value(table, "path", relpath);
503519
if let Some(r) = src.version.as_deref() {
504520
overwrite_value(table, "version", r);
@@ -533,7 +549,7 @@ impl Dependency {
533549
table.remove("version");
534550
}
535551

536-
for key in ["path", "workspace"] {
552+
for key in ["path", "workspace", "base"] {
537553
table.remove(key);
538554
}
539555
}
@@ -552,6 +568,7 @@ impl Dependency {
552568
"rev",
553569
"package",
554570
"default-features",
571+
"base",
555572
] {
556573
table.remove(key);
557574
}
@@ -812,6 +829,8 @@ impl std::fmt::Display for RegistrySource {
812829
pub struct PathSource {
813830
/// Local, absolute path.
814831
pub path: PathBuf,
832+
/// If using a named base, the base and relative path.
833+
pub base_name_and_value: Option<(String, PathBuf)>,
815834
/// Version requirement for when published.
816835
pub version: Option<String>,
817836
}
@@ -821,6 +840,7 @@ impl PathSource {
821840
pub fn new(path: impl Into<PathBuf>) -> Self {
822841
Self {
823842
path: path.into(),
843+
base_name_and_value: None,
824844
version: None,
825845
}
826846
}
@@ -843,7 +863,11 @@ impl PathSource {
843863

844864
impl std::fmt::Display for PathSource {
845865
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
846-
self.path.display().fmt(f)
866+
if let Some((base_name, _)) = &self.base_name_and_value {
867+
write!(f, "[{base_name}]/{}", self.path.display())
868+
} else {
869+
self.path.display().fmt(f)
870+
}
847871
}
848872
}
849873

src/doc/man/cargo-add.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ cargo-add --- Add dependencies to a Cargo.toml manifest file
1010
## SYNOPSIS
1111

1212
`cargo add` [_options_] _crate_...\
13-
`cargo add` [_options_] `--path` _path_\
13+
`cargo add` [_options_] `--path` _path_ [`--base` _base_]\
1414
`cargo add` [_options_] `--git` _url_ [_crate_...]
1515

1616

@@ -63,6 +63,10 @@ Specific commit to use when adding from git.
6363
[Filesystem path](../reference/specifying-dependencies.html#specifying-path-dependencies) to local crate to add.
6464
{{/option}}
6565

66+
{{#option "`--base` _base_" }}
67+
The [path base](../reference/unstable.html#path-bases) to use when adding a local crate.
68+
{{/option}}
69+
6670
{{> options-registry }}
6771

6872
{{/options}}

src/doc/man/generated_txt/cargo-add.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ NAME
55

66
SYNOPSIS
77
cargo add [options] crate…
8-
cargo add [options] --path path
8+
cargo add [options] --path path [--base base]
99
cargo add [options] --git url [crate…]
1010

1111
DESCRIPTION
@@ -56,6 +56,11 @@ OPTIONS
5656
<https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies>
5757
to local crate to add.
5858

59+
--base base
60+
The path base
61+
<https://doc.rust-lang.org/cargo/reference/unstable.html#path-bases>
62+
to use when adding a local crate.
63+
5964
--registry registry
6065
Name of the registry to use. Registry names are defined in Cargo
6166
config files

src/doc/src/commands/cargo-add.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ cargo-add --- Add dependencies to a Cargo.toml manifest file
66
## SYNOPSIS
77

88
`cargo add` [_options_] _crate_...\
9-
`cargo add` [_options_] `--path` _path_\
9+
`cargo add` [_options_] `--path` _path_ [`--base` _base_]\
1010
`cargo add` [_options_] `--git` _url_ [_crate_...]
1111

1212

@@ -59,6 +59,10 @@ dependency will be listed in the command's output.
5959
<dd class="option-desc"><a href="../reference/specifying-dependencies.html#specifying-path-dependencies">Filesystem path</a> to local crate to add.</dd>
6060

6161

62+
<dt class="option-term" id="option-cargo-add---base"><a class="option-anchor" href="#option-cargo-add---base"></a><code>--base</code> <em>base</em></dt>
63+
<dd class="option-desc">The <a href="../reference/unstable.html#path-bases">path base</a> to use when adding a local crate.</dd>
64+
65+
6266
<dt class="option-term" id="option-cargo-add---registry"><a class="option-anchor" href="#option-cargo-add---registry"></a><code>--registry</code> <em>registry</em></dt>
6367
<dd class="option-desc">Name of the registry to use. Registry names are defined in <a href="../reference/config.html">Cargo config
6468
files</a>. If not specified, the default registry is used,

src/etc/man/cargo-add.1

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ cargo\-add \[em] Add dependencies to a Cargo.toml manifest file
88
.SH "SYNOPSIS"
99
\fBcargo add\fR [\fIoptions\fR] \fIcrate\fR\[u2026]
1010
.br
11-
\fBcargo add\fR [\fIoptions\fR] \fB\-\-path\fR \fIpath\fR
11+
\fBcargo add\fR [\fIoptions\fR] \fB\-\-path\fR \fIpath\fR [\fB\-\-base\fR \fIbase\fR]
1212
.br
1313
\fBcargo add\fR [\fIoptions\fR] \fB\-\-git\fR \fIurl\fR [\fIcrate\fR\[u2026]]
1414
.SH "DESCRIPTION"
@@ -74,6 +74,11 @@ Specific commit to use when adding from git.
7474
\fIFilesystem path\fR <https://doc.rust\-lang.org/cargo/reference/specifying\-dependencies.html#specifying\-path\-dependencies> to local crate to add.
7575
.RE
7676
.sp
77+
\fB\-\-base\fR \fIbase\fR
78+
.RS 4
79+
The \fIpath base\fR <https://doc.rust\-lang.org/cargo/reference/unstable.html#path\-bases> to use when adding a local crate.
80+
.RE
81+
.sp
7782
\fB\-\-registry\fR \fIregistry\fR
7883
.RS 4
7984
Name of the registry to use. Registry names are defined in \fICargo config

0 commit comments

Comments
 (0)