Skip to content

Commit 23ae8a0

Browse files
committed
Auto merge of #10650 - epage:install, r=ehuss
feat(install): Support `foo@version` like cargo-add ### What does this PR try to resolve? This aims to make `cargo install` consistent with - `cargo add foo@version` from #10472 - pkgid changes in #10582 - `cargo yank foo@version` from #10597 It also offers a shorthand for people installing a specific version. ### How should we test and review this PR? #10582 acted as the FCP for this, see #10597 Documentation updates are split into their own commit to not clog up browsing the code. Examine the tests to see if they make sense ### Additional information While the `foo@vewrsion` syntax is the same, each's semantics are different. We had decided it was better to have the same syntax with different semantics than having the user worry about what syntax they use where. In `cargo install`s case, it has an implicit-but-required `=` operand while `cargo-add` allows any operand. This doesn't use the full `pkgid` syntax because that allows syntax that is unsupported here. This doesn't use `cargo-add`s parser because that is for version reqs. I held off on reusing the parser from `cargo-yank` because they had different type system needs and the level of duplication didn't seem worth it (see Rule of Three).
2 parents 08bef9d + f063c65 commit 23ae8a0

File tree

7 files changed

+81
-22
lines changed

7 files changed

+81
-22
lines changed

src/bin/cargo/commands/install.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,12 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
9797
// but not `Config::reload_rooted_at` which is always cwd)
9898
let path = path.map(|p| paths::normalize_path(&p));
9999

100+
let version = args.value_of("version");
100101
let krates = args
101102
.values_of("crate")
102103
.unwrap_or_default()
103-
.collect::<Vec<_>>();
104+
.map(|k| resolve_crate(k, version))
105+
.collect::<crate::CargoResult<Vec<_>>>()?;
104106

105107
let mut from_cwd = false;
106108

@@ -129,7 +131,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
129131
SourceId::crates_io(config)?
130132
};
131133

132-
let version = args.value_of("version");
133134
let root = args.value_of("root");
134135

135136
// We only provide workspace information for local crate installation from
@@ -166,11 +167,28 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
166167
krates,
167168
source,
168169
from_cwd,
169-
version,
170170
&compile_opts,
171171
args.is_present("force"),
172172
args.is_present("no-track"),
173173
)?;
174174
}
175175
Ok(())
176176
}
177+
178+
fn resolve_crate<'k>(
179+
mut krate: &'k str,
180+
mut version: Option<&'k str>,
181+
) -> crate::CargoResult<(&'k str, Option<&'k str>)> {
182+
if let Some((k, v)) = krate.split_once('@') {
183+
if version.is_some() {
184+
anyhow::bail!("cannot specify both `@{v}` and `--version`");
185+
}
186+
if k.is_empty() {
187+
// by convention, arguments starting with `@` are response files
188+
anyhow::bail!("missing crate name for `@{v}`");
189+
}
190+
krate = k;
191+
version = Some(v);
192+
}
193+
Ok((krate, version))
194+
}

src/cargo/ops/cargo_install.rs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -556,10 +556,9 @@ impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
556556
pub fn install(
557557
config: &Config,
558558
root: Option<&str>,
559-
krates: Vec<&str>,
559+
krates: Vec<(&str, Option<&str>)>,
560560
source_id: SourceId,
561561
from_cwd: bool,
562-
vers: Option<&str>,
563562
opts: &ops::CompileOptions,
564563
force: bool,
565564
no_track: bool,
@@ -569,18 +568,13 @@ pub fn install(
569568
let map = SourceConfigMap::new(config)?;
570569

571570
let (installed_anything, scheduled_error) = if krates.len() <= 1 {
571+
let (krate, vers) = krates
572+
.into_iter()
573+
.next()
574+
.map(|(k, v)| (Some(k), v))
575+
.unwrap_or((None, None));
572576
let installable_pkg = InstallablePackage::new(
573-
config,
574-
root,
575-
map,
576-
krates.into_iter().next(),
577-
source_id,
578-
from_cwd,
579-
vers,
580-
opts,
581-
force,
582-
no_track,
583-
true,
577+
config, root, map, krate, source_id, from_cwd, vers, opts, force, no_track, true,
584578
)?;
585579
let mut installed_anything = true;
586580
if let Some(installable_pkg) = installable_pkg {
@@ -596,7 +590,7 @@ pub fn install(
596590

597591
let pkgs_to_install: Vec<_> = krates
598592
.into_iter()
599-
.filter_map(|krate| {
593+
.filter_map(|(krate, vers)| {
600594
let root = root.clone();
601595
let map = map.clone();
602596
match InstallablePackage::new(

src/doc/man/cargo-install.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ cargo-install - Build and install a Rust binary
88

99
## SYNOPSIS
1010

11-
`cargo install` [_options_] _crate_...\
11+
`cargo install` [_options_] _crate_[@_version_]...\
1212
`cargo install` [_options_] `--path` _path_\
1313
`cargo install` [_options_] `--git` _url_ [_crate_...]\
1414
`cargo install` [_options_] `--list`

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ NAME
44
cargo-install - Build and install a Rust binary
55

66
SYNOPSIS
7-
cargo install [options] crate...
7+
cargo install [options] crate[@version]...
88
cargo install [options] --path path
99
cargo install [options] --git url [crate...]
1010
cargo install [options] --list

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ cargo-install - Build and install a Rust binary
88

99
## SYNOPSIS
1010

11-
`cargo install` [_options_] _crate_...\
11+
`cargo install` [_options_] _crate_[@_version_]...\
1212
`cargo install` [_options_] `--path` _path_\
1313
`cargo install` [_options_] `--git` _url_ [_crate_...]\
1414
`cargo install` [_options_] `--list`

src/etc/man/cargo-install.1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
.SH "NAME"
77
cargo\-install \- Build and install a Rust binary
88
.SH "SYNOPSIS"
9-
\fBcargo install\fR [\fIoptions\fR] \fIcrate\fR\&...
9+
\fBcargo install\fR [\fIoptions\fR] \fIcrate\fR[@\fIversion\fR]\&...
1010
.br
1111
\fBcargo install\fR [\fIoptions\fR] \fB\-\-path\fR \fIpath\fR
1212
.br

tests/testsuite/install.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1382,7 +1382,7 @@ fn vers_precise() {
13821382
}
13831383

13841384
#[cargo_test]
1385-
fn version_too() {
1385+
fn version_precise() {
13861386
pkg("foo", "0.1.1");
13871387
pkg("foo", "0.1.2");
13881388

@@ -1391,6 +1391,53 @@ fn version_too() {
13911391
.run();
13921392
}
13931393

1394+
#[cargo_test]
1395+
fn inline_version_precise() {
1396+
pkg("foo", "0.1.1");
1397+
pkg("foo", "0.1.2");
1398+
1399+
cargo_process("install [email protected]")
1400+
.with_stderr_contains("[DOWNLOADED] foo v0.1.1 (registry [..])")
1401+
.run();
1402+
}
1403+
1404+
#[cargo_test]
1405+
fn inline_version_multiple() {
1406+
pkg("foo", "0.1.0");
1407+
pkg("foo", "0.1.1");
1408+
pkg("foo", "0.1.2");
1409+
pkg("bar", "0.2.0");
1410+
pkg("bar", "0.2.1");
1411+
pkg("bar", "0.2.2");
1412+
1413+
cargo_process("install [email protected] [email protected]")
1414+
.with_stderr_contains("[DOWNLOADED] foo v0.1.1 (registry [..])")
1415+
.with_stderr_contains("[DOWNLOADED] bar v0.2.1 (registry [..])")
1416+
.run();
1417+
}
1418+
1419+
#[cargo_test]
1420+
fn inline_version_without_name() {
1421+
pkg("foo", "0.1.1");
1422+
pkg("foo", "0.1.2");
1423+
1424+
cargo_process("install @0.1.1")
1425+
.with_status(101)
1426+
.with_stderr("error: missing crate name for `@0.1.1`")
1427+
.run();
1428+
}
1429+
1430+
#[cargo_test]
1431+
fn inline_and_explicit_version() {
1432+
pkg("foo", "0.1.1");
1433+
pkg("foo", "0.1.2");
1434+
1435+
cargo_process("install [email protected] --version 0.1.1")
1436+
.with_status(101)
1437+
.with_stderr("error: cannot specify both `@0.1.1` and `--version`")
1438+
.run();
1439+
}
1440+
13941441
#[cargo_test]
13951442
fn not_both_vers_and_version() {
13961443
pkg("foo", "0.1.1");

0 commit comments

Comments
 (0)