Skip to content

Commit 863c00a

Browse files
epageHezuikn
authored andcommitted
feat(install): Support foo@version like cargo-add
In rust-lang#10472, cargo-add was merged with support for an inline version syntax of `foo@version`. That also served as the change proposal for extending that syntax to `cargo install` for convinience and consistency. While both commands are specifying a version-req, `cargo-install` 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).
1 parent 18bbe02 commit 863c00a

File tree

2 files changed

+68
-3
lines changed

2 files changed

+68
-3
lines changed

src/bin/cargo/commands/install.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
101101
let krates = args
102102
.values_of("crate")
103103
.unwrap_or_default()
104-
.map(|k| (k, version))
105-
.collect::<Vec<_>>();
104+
.map(|k| resolve_crate(k, version))
105+
.collect::<crate::CargoResult<Vec<_>>>()?;
106106

107107
let mut from_cwd = false;
108108

@@ -174,3 +174,21 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
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+
}

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 foo@0.1.1")
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 foo@0.1.1 bar@0.2.1")
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 foo@0.1.1 --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)