Skip to content

Commit

Permalink
Implement path-bases (RFC 3529) 2/n: cargo add support
Browse files Browse the repository at this point in the history
RFC: rust-lang/rfcs#3529
Tracking Issue: rust-lang#14355

This PR adds the `--base` option to `cargo add` to allow adding a path dependency with a path base.
  • Loading branch information
dpaoliello committed Aug 19, 2024
1 parent c956e9f commit 9953db9
Show file tree
Hide file tree
Showing 51 changed files with 634 additions and 57 deletions.
8 changes: 8 additions & 0 deletions src/bin/cargo/commands/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
.requires("path"),
clap::Arg::new("git")
.long("git")
.action(ArgAction::Set)
Expand Down Expand Up @@ -224,6 +230,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {

fn parse_dependencies(gctx: &GlobalContext, matches: &ArgMatches) -> CargoResult<Vec<DepOp>> {
let path = matches.get_one::<String>("path");
let base = matches.get_one::<String>("base");
let git = matches.get_one::<String>("git");
let branch = matches.get_one::<String>("branch");
let rev = matches.get_one::<String>("rev");
Expand Down Expand Up @@ -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),
Expand Down
35 changes: 30 additions & 5 deletions src/cargo/ops/cargo_add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +29,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;
Expand Down Expand Up @@ -270,8 +272,11 @@ pub struct DepOp {
/// Registry for looking up dependency version
pub registry: Option<String>,

/// Git repo for dependency
/// File system path for dependency
pub path: Option<String>,
/// Specify a named base for a path dependency
pub base: Option<String>,

/// Git repo for dependency
pub git: Option<String>,
/// Specify an alternative git branch
Expand Down Expand Up @@ -331,10 +336,25 @@ 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 (path, base_name_and_value) = if let Some(base_name) = &arg.base {
let workspace_root = || Ok(ws.root_manifest().parent().unwrap());
let base_value = lookup_path_base(
&PathBaseName::new(base_name.clone())?,
gctx,
&workspace_root,
None,
)?;
(
base_value.join(raw_path),
Some((base_name.clone(), base_value)),
)
} else {
(std::env::current_dir()?.join(raw_path), None)
};
let path = paths::normalize_path(&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. `[email protected]`)
anyhow::bail!("cannot specify a path (`{raw_path}`) with a version (`{v}`).");
Expand All @@ -349,10 +369,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()?
Expand Down
21 changes: 12 additions & 9 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ fn normalize_toml(
inherit_cell
.try_borrow_with(|| load_inheritable_fields(gctx, manifest_file, &workspace_config))
};
let workspace_root = || inherit().map(|fields| fields.ws_root());
let workspace_root = || inherit().map(|fields| fields.ws_root().as_path());

if let Some(original_package) = original_toml.package() {
let package_name = &original_package.name;
Expand Down Expand Up @@ -538,7 +538,7 @@ fn normalize_toml(
fn normalize_patch<'a>(
gctx: &GlobalContext,
original_patch: Option<&BTreeMap<String, BTreeMap<PackageName, TomlDependency>>>,
workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>,
workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
features: &Features,
) -> CargoResult<Option<BTreeMap<String, BTreeMap<PackageName, TomlDependency>>>> {
if let Some(patch) = original_patch {
Expand Down Expand Up @@ -757,7 +757,7 @@ fn normalize_dependencies<'a>(
activated_opt_deps: &HashSet<&str>,
kind: Option<DepKind>,
inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>,
workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
package_root: &Path,
warnings: &mut Vec<String>,
) -> CargoResult<Option<BTreeMap<manifest::PackageName, manifest::InheritableDependency>>> {
Expand Down Expand Up @@ -839,12 +839,13 @@ fn normalize_dependencies<'a>(
fn normalize_path_dependency<'a>(
gctx: &GlobalContext,
detailed_dep: &mut TomlDetailedDependency,
workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>,
workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
features: &Features,
) -> CargoResult<()> {
if let Some(base) = detailed_dep.base.take() {
if let Some(path) = detailed_dep.path.as_mut() {
let new_path = lookup_path_base(&base, gctx, workspace_root, features)?.join(&path);
let new_path =
lookup_path_base(&base, gctx, workspace_root, Some(features))?.join(&path);
*path = new_path.to_str().unwrap().to_string();
} else {
bail!("`base` can only be used with path dependencies");
Expand Down Expand Up @@ -2225,10 +2226,12 @@ fn to_dependency_source_id<P: ResolveToPath + Clone>(
pub(crate) fn lookup_path_base<'a>(
base: &PathBaseName,
gctx: &GlobalContext,
workspace_root: &dyn Fn() -> CargoResult<&'a PathBuf>,
features: &Features,
workspace_root: &dyn Fn() -> CargoResult<&'a Path>,
features: Option<&Features>,
) -> CargoResult<PathBuf> {
features.require(Feature::path_bases())?;
if let Some(features) = features {
features.require(Feature::path_bases())?;
}

// HACK: The `base` string is user controlled, but building the path is safe from injection
// attacks since the `PathBaseName` type restricts the characters that can be used to exclude `.`
Expand All @@ -2240,7 +2243,7 @@ pub(crate) fn lookup_path_base<'a>(
} else {
// Otherwise, check the built-in bases.
match base.as_str() {
"workspace" => Ok(workspace_root()?.clone()),
"workspace" => Ok(workspace_root()?.to_path_buf()),
_ => bail!(
"path base `{base}` is undefined. \
You must add an entry for `{base}` in the Cargo configuration [path-bases] table."
Expand Down
34 changes: 29 additions & 5 deletions src/cargo/util/toml_mut/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,18 @@ 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());
}
let relative_to = if let Some((base_name, base_value)) =
src.base_name_and_value.as_ref()
{
table.insert("base", base_name.into());
base_value
} else {
crate_root
};
let relpath = path_field(relative_to, &src.path);
table.insert("path", relpath.into());
}
Some(Source::Git(src)) => {
Expand Down Expand Up @@ -493,12 +501,20 @@ 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);
let relative_to =
if let Some((base_name, base_value)) = src.base_name_and_value.as_ref() {
overwrite_value(table, "base", base_name);
base_value
} else {
table.remove("base");
crate_root
};
let relpath = path_field(relative_to, &src.path);
overwrite_value(table, "path", relpath);
if let Some(r) = src.version.as_deref() {
overwrite_value(table, "version", r);
Expand Down Expand Up @@ -533,7 +549,7 @@ impl Dependency {
table.remove("version");
}

for key in ["path", "workspace"] {
for key in ["path", "workspace", "base"] {
table.remove(key);
}
}
Expand All @@ -552,6 +568,7 @@ impl Dependency {
"rev",
"package",
"default-features",
"base",
] {
table.remove(key);
}
Expand Down Expand Up @@ -812,6 +829,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<String>,
}
Expand All @@ -821,6 +840,7 @@ impl PathSource {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
base_name_and_value: None,
version: None,
}
}
Expand All @@ -843,7 +863,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)
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/doc/man/cargo-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ cargo-add --- Add dependencies to a Cargo.toml manifest file
## SYNOPSIS

`cargo add` [_options_] _crate_...\
`cargo add` [_options_] `--path` _path_\
`cargo add` [_options_] `--path` _path_ [`--base` _base_]\
`cargo add` [_options_] `--git` _url_ [_crate_...]


Expand Down Expand Up @@ -63,6 +63,10 @@ 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.
{{/option}}

{{> options-registry }}

{{/options}}
Expand Down
7 changes: 6 additions & 1 deletion src/doc/man/generated_txt/cargo-add.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ NAME

SYNOPSIS
cargo add [options] crate…
cargo add [options] --path path
cargo add [options] --path path [--base base]
cargo add [options] --git url [crate…]

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

--base base
The path base
<https://doc.rust-lang.org/cargo/reference/unstable.html#path-bases>
to use when adding a local crate.

--registry registry
Name of the registry to use. Registry names are defined in Cargo
config files
Expand Down
6 changes: 5 additions & 1 deletion src/doc/src/commands/cargo-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cargo-add --- Add dependencies to a Cargo.toml manifest file
## SYNOPSIS

`cargo add` [_options_] _crate_...\
`cargo add` [_options_] `--path` _path_\
`cargo add` [_options_] `--path` _path_ [`--base` _base_]\
`cargo add` [_options_] `--git` _url_ [_crate_...]


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


<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>
<dd class="option-desc">The <a href="../reference/unstable.html#path-bases">path base</a> to use when adding a local crate.</dd>


<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>
<dd class="option-desc">Name of the registry to use. Registry names are defined in <a href="../reference/config.html">Cargo config
files</a>. If not specified, the default registry is used,
Expand Down
7 changes: 6 additions & 1 deletion src/etc/man/cargo-add.1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ cargo\-add \[em] Add dependencies to a Cargo.toml manifest file
.SH "SYNOPSIS"
\fBcargo add\fR [\fIoptions\fR] \fIcrate\fR\[u2026]
.br
\fBcargo add\fR [\fIoptions\fR] \fB\-\-path\fR \fIpath\fR
\fBcargo add\fR [\fIoptions\fR] \fB\-\-path\fR \fIpath\fR [\fB\-\-base\fR \fIbase\fR]
.br
\fBcargo add\fR [\fIoptions\fR] \fB\-\-git\fR \fIurl\fR [\fIcrate\fR\[u2026]]
.SH "DESCRIPTION"
Expand Down Expand Up @@ -74,6 +74,11 @@ Specific commit to use when adding from git.
\fIFilesystem path\fR <https://doc.rust\-lang.org/cargo/reference/specifying\-dependencies.html#specifying\-path\-dependencies> to local crate to add.
.RE
.sp
\fB\-\-base\fR \fIbase\fR
.RS 4
The \fIpath base\fR <https://doc.rust\-lang.org/cargo/reference/unstable.html#path\-bases> to use when adding a local crate.
.RE
.sp
\fB\-\-registry\fR \fIregistry\fR
.RS 4
Name of the registry to use. Registry names are defined in \fICargo config
Expand Down
Loading

0 comments on commit 9953db9

Please sign in to comment.