Skip to content

Commit 1206e5e

Browse files
committed
Auto merge of #2026 - alexcrichton:cargo-install, r=brson
This commit is an implementation of [RFC 1200][rfc] which brings two new subcommands: `cargo install` and `cargo uninstall`. Most of this is a straight implementation of the RFC, but a few tweaks were made: * The `-p` or `--package` arguments are no longer needed as you just pass `crate` as a bare argument to the command, this means `cargo install foo` works and downloads from crates.io by default. * Some logic around selecting which crate in a multi-crate repo is installed has been tweaked slightly, but mostly in the realm of "let's do the thing that makes sense" rather than the literal "let's do what's in the RFC". Specifically, we don't pick a crate with examples if there are multiple crates with binaries (instead an error is generated saying there are multiple binary crates). [rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1200-cargo-install.md
2 parents 1a6d6c4 + bc60f64 commit 1206e5e

21 files changed

+1137
-70
lines changed

src/bin/cargo.rs

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ macro_rules! each_subcommand{ ($mac:ident) => ({
6969
$mac!(generate_lockfile);
7070
$mac!(git_checkout);
7171
$mac!(help);
72+
$mac!(install);
7273
$mac!(locate_project);
7374
$mac!(login);
7475
$mac!(new);
@@ -81,6 +82,7 @@ macro_rules! each_subcommand{ ($mac:ident) => ({
8182
$mac!(rustc);
8283
$mac!(search);
8384
$mac!(test);
85+
$mac!(uninstall);
8486
$mac!(update);
8587
$mac!(verify_project);
8688
$mac!(version);

src/bin/install.rs

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use std::path::Path;
2+
3+
use cargo::ops;
4+
use cargo::core::{SourceId, GitReference};
5+
use cargo::util::{CliResult, Config, ToUrl, human};
6+
7+
#[derive(RustcDecodable)]
8+
struct Options {
9+
flag_jobs: Option<u32>,
10+
flag_features: Vec<String>,
11+
flag_no_default_features: bool,
12+
flag_debug: bool,
13+
flag_bin: Vec<String>,
14+
flag_example: Vec<String>,
15+
flag_verbose: bool,
16+
flag_quiet: bool,
17+
flag_color: Option<String>,
18+
flag_root: Option<String>,
19+
flag_list: bool,
20+
21+
arg_crate: Option<String>,
22+
flag_vers: Option<String>,
23+
24+
flag_git: Option<String>,
25+
flag_branch: Option<String>,
26+
flag_tag: Option<String>,
27+
flag_rev: Option<String>,
28+
29+
flag_path: Option<String>,
30+
}
31+
32+
pub const USAGE: &'static str = "
33+
Install a Rust binary
34+
35+
Usage:
36+
cargo install [options] [<crate>]
37+
cargo install [options] --list
38+
39+
Specifying what crate to install:
40+
--vers VERS Specify a version to install from crates.io
41+
--git URL Git URL to install the specified crate from
42+
--branch BRANCH Branch to use when installing from git
43+
--tag TAG Tag to use when installing from git
44+
--rev SHA Specific commit to use when installing from git
45+
--path PATH Filesystem path to local crate to install
46+
47+
Build and install options:
48+
-h, --help Print this message
49+
-j N, --jobs N The number of jobs to run in parallel
50+
--features FEATURES Space-separated list of features to activate
51+
--no-default-features Do not build the `default` feature
52+
--debug Build in debug mode instead of release mode
53+
--bin NAME Only install the binary NAME
54+
--example EXAMPLE Install the example EXAMPLE instead of binaries
55+
--root DIR Directory to install packages into
56+
-v, --verbose Use verbose output
57+
-q, --quiet Less output printed to stdout
58+
--color WHEN Coloring: auto, always, never
59+
60+
This command manages Cargo's local set of install binary crates. Only packages
61+
which have [[bin]] targets can be installed, and all binaries are installed into
62+
the installation root's `bin` folder. The installation root is determined, in
63+
order of precedence, by `--root`, `$CARGO_INSTALL_ROOT`, the `install.root`
64+
configuration key, and finally the home directory (which is either
65+
`$CARGO_HOME` if set or `$HOME/.cargo` by default).
66+
67+
There are multiple sources from which a crate can be installed. The default
68+
location is crates.io but the `--git` and `--path` flags can change this source.
69+
If the source contains more than one package (such as crates.io or a git
70+
repository with multiple crates) the `<crate>` argument is required to indicate
71+
which crate should be installed.
72+
73+
Crates from crates.io can optionally specify the version they wish to install
74+
via the `--vers` flags, and similarly packages from git repositories can
75+
optionally specify the branch, tag, or revision that should be installed. If a
76+
crate has multiple binaries, the `--bin` argument can selectively install only
77+
one of them, and if you'd rather install examples the `--example` argument can
78+
be used as well.
79+
80+
The `--list` option will list all installed packages (and their versions).
81+
";
82+
83+
pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
84+
try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
85+
try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));
86+
87+
let compile_opts = ops::CompileOptions {
88+
config: config,
89+
jobs: options.flag_jobs,
90+
target: None,
91+
features: &options.flag_features,
92+
no_default_features: options.flag_no_default_features,
93+
spec: &[],
94+
exec_engine: None,
95+
mode: ops::CompileMode::Build,
96+
release: !options.flag_debug,
97+
filter: ops::CompileFilter::new(false, &options.flag_bin, &[],
98+
&options.flag_example, &[]),
99+
target_rustc_args: None,
100+
};
101+
102+
let source = if let Some(url) = options.flag_git {
103+
let url = try!(url.to_url().map_err(human));
104+
let gitref = if let Some(branch) = options.flag_branch {
105+
GitReference::Branch(branch)
106+
} else if let Some(tag) = options.flag_tag {
107+
GitReference::Tag(tag)
108+
} else if let Some(rev) = options.flag_rev {
109+
GitReference::Rev(rev)
110+
} else {
111+
GitReference::Branch("master".to_string())
112+
};
113+
SourceId::for_git(&url, gitref)
114+
} else if let Some(path) = options.flag_path {
115+
try!(SourceId::for_path(Path::new(&path)))
116+
} else {
117+
try!(SourceId::for_central(config))
118+
};
119+
120+
let krate = options.arg_crate.as_ref().map(|s| &s[..]);
121+
let vers = options.flag_vers.as_ref().map(|s| &s[..]);
122+
let root = options.flag_root.as_ref().map(|s| &s[..]);
123+
124+
if options.flag_list {
125+
try!(ops::install_list(root, config));
126+
} else {
127+
try!(ops::install(root, krate, &source, vers, &compile_opts));
128+
}
129+
Ok(None)
130+
}

src/bin/uninstall.rs

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use cargo::ops;
2+
use cargo::util::{CliResult, Config};
3+
4+
#[derive(RustcDecodable)]
5+
struct Options {
6+
flag_bin: Vec<String>,
7+
flag_root: Option<String>,
8+
flag_verbose: bool,
9+
flag_quiet: bool,
10+
flag_color: Option<String>,
11+
12+
arg_spec: String,
13+
}
14+
15+
pub const USAGE: &'static str = "
16+
Remove a Rust binary
17+
18+
Usage:
19+
cargo uninstall [options] <spec>
20+
21+
Options:
22+
-h, --help Print this message
23+
--root DIR Directory to uninstall packages from
24+
--bin NAME Only uninstall the binary NAME
25+
-v, --verbose Use verbose output
26+
-q, --quiet Less output printed to stdout
27+
--color WHEN Coloring: auto, always, never
28+
29+
The argument SPEC is a package id specification (see `cargo help pkgid`) to
30+
specify which crate should be uninstalled. By default all binaries are
31+
uninstalled for a crate but the `--bin` and `--example` flags can be used to
32+
only uninstall particular binaries.
33+
";
34+
35+
pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
36+
try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
37+
try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));
38+
39+
let root = options.flag_root.as_ref().map(|s| &s[..]);
40+
try!(ops::uninstall(root, &options.arg_spec, &options.flag_bin, config));
41+
Ok(None)
42+
}
43+

src/cargo/core/package_id_spec.rs

+56
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use std::collections::HashMap;
12
use std::fmt;
3+
24
use semver::Version;
35
use url::{self, Url, UrlParser};
46

@@ -45,6 +47,15 @@ impl PackageIdSpec {
4547
})
4648
}
4749

50+
pub fn query_str<'a, I>(spec: &str, i: I) -> CargoResult<&'a PackageId>
51+
where I: IntoIterator<Item=&'a PackageId>
52+
{
53+
let spec = try!(PackageIdSpec::parse(spec).chain_error(|| {
54+
human(format!("invalid package id specification: `{}`", spec))
55+
}));
56+
spec.query(i)
57+
}
58+
4859
pub fn from_package_id(package_id: &PackageId) -> PackageIdSpec {
4960
PackageIdSpec {
5061
name: package_id.name().to_string(),
@@ -115,6 +126,51 @@ impl PackageIdSpec {
115126
None => true
116127
}
117128
}
129+
130+
pub fn query<'a, I>(&self, i: I) -> CargoResult<&'a PackageId>
131+
where I: IntoIterator<Item=&'a PackageId>
132+
{
133+
let mut ids = i.into_iter().filter(|p| self.matches(*p));
134+
let ret = match ids.next() {
135+
Some(id) => id,
136+
None => return Err(human(format!("package id specification `{}` \
137+
matched no packages", self))),
138+
};
139+
return match ids.next() {
140+
Some(other) => {
141+
let mut msg = format!("There are multiple `{}` packages in \
142+
your project, and the specification \
143+
`{}` is ambiguous.\n\
144+
Please re-run this command \
145+
with `-p <spec>` where `<spec>` is one \
146+
of the following:",
147+
self.name(), self);
148+
let mut vec = vec![ret, other];
149+
vec.extend(ids);
150+
minimize(&mut msg, vec, self);
151+
Err(human(msg))
152+
}
153+
None => Ok(ret)
154+
};
155+
156+
fn minimize(msg: &mut String,
157+
ids: Vec<&PackageId>,
158+
spec: &PackageIdSpec) {
159+
let mut version_cnt = HashMap::new();
160+
for id in ids.iter() {
161+
*version_cnt.entry(id.version()).or_insert(0) += 1;
162+
}
163+
for id in ids.iter() {
164+
if version_cnt[id.version()] == 1 {
165+
msg.push_str(&format!("\n {}:{}", spec.name(),
166+
id.version()));
167+
} else {
168+
msg.push_str(&format!("\n {}",
169+
PackageIdSpec::from_package_id(*id)));
170+
}
171+
}
172+
}
173+
}
118174
}
119175

120176
fn url(s: &str) -> url::ParseResult<Url> {

src/cargo/core/registry.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ impl<'cfg> PackageRegistry<'cfg> {
154154
Ok(())
155155
}
156156

157+
pub fn add_preloaded(&mut self, id: &SourceId, source: Box<Source + 'cfg>) {
158+
self.add_source(id, source, Kind::Locked);
159+
}
160+
161+
fn add_source(&mut self, id: &SourceId, source: Box<Source + 'cfg>,
162+
kind: Kind) {
163+
self.sources.insert(id, source);
164+
self.source_ids.insert(id.clone(), (id.clone(), kind));
165+
}
166+
157167
pub fn add_overrides(&mut self, ids: Vec<SourceId>) -> CargoResult<()> {
158168
for id in ids.iter() {
159169
try!(self.load(id, Kind::Override));
@@ -183,8 +193,7 @@ impl<'cfg> PackageRegistry<'cfg> {
183193
}
184194

185195
// Save off the source
186-
self.sources.insert(source_id, source);
187-
self.source_ids.insert(source_id.clone(), (source_id.clone(), kind));
196+
self.add_source(source_id, source, kind);
188197

189198
Ok(())
190199
}).chain_error(|| human(format!("Unable to update {}", source_id)))

src/cargo/core/resolver/mod.rs

+5-47
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ use semver;
5555

5656
use core::{PackageId, Registry, SourceId, Summary, Dependency};
5757
use core::PackageIdSpec;
58-
use util::{CargoResult, Graph, human, ChainError, CargoError};
58+
use util::{CargoResult, Graph, human, CargoError};
5959
use util::profile;
6060
use util::graph::{Nodes, Edges};
6161

@@ -118,55 +118,13 @@ impl Resolve {
118118
self.graph.edges(pkg)
119119
}
120120

121-
pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
122-
let spec = try!(PackageIdSpec::parse(spec).chain_error(|| {
123-
human(format!("invalid package id specification: `{}`", spec))
124-
}));
125-
let mut ids = self.iter().filter(|p| spec.matches(*p));
126-
let ret = match ids.next() {
127-
Some(id) => id,
128-
None => return Err(human(format!("package id specification `{}` \
129-
matched no packages", spec))),
130-
};
131-
return match ids.next() {
132-
Some(other) => {
133-
let mut msg = format!("There are multiple `{}` packages in \
134-
your project, and the specification \
135-
`{}` is ambiguous.\n\
136-
Please re-run this command \
137-
with `-p <spec>` where `<spec>` is one \
138-
of the following:",
139-
spec.name(), spec);
140-
let mut vec = vec![ret, other];
141-
vec.extend(ids);
142-
minimize(&mut msg, vec, &spec);
143-
Err(human(msg))
144-
}
145-
None => Ok(ret)
146-
};
147-
148-
fn minimize(msg: &mut String,
149-
ids: Vec<&PackageId>,
150-
spec: &PackageIdSpec) {
151-
let mut version_cnt = HashMap::new();
152-
for id in ids.iter() {
153-
*version_cnt.entry(id.version()).or_insert(0) += 1;
154-
}
155-
for id in ids.iter() {
156-
if version_cnt[id.version()] == 1 {
157-
msg.push_str(&format!("\n {}:{}", spec.name(),
158-
id.version()));
159-
} else {
160-
msg.push_str(&format!("\n {}",
161-
PackageIdSpec::from_package_id(*id)));
162-
}
163-
}
164-
}
165-
}
166-
167121
pub fn features(&self, pkg: &PackageId) -> Option<&HashSet<String>> {
168122
self.features.get(pkg)
169123
}
124+
125+
pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
126+
PackageIdSpec::query_str(spec, self.iter())
127+
}
170128
}
171129

172130
impl fmt::Debug for Resolve {

src/cargo/core/source.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,19 @@ impl SourceId {
129129
SourceId::new(Kind::Registry, url)
130130
.with_precise(Some("locked".to_string()))
131131
}
132-
"path" => SourceId::for_path(Path::new(&url[5..])).unwrap(),
132+
"path" => {
133+
let url = url.to_url().unwrap();
134+
SourceId::new(Kind::Path, url)
135+
}
133136
_ => panic!("Unsupported serialized SourceId")
134137
}
135138
}
136139

137140
pub fn to_url(&self) -> String {
138141
match *self.inner {
139-
SourceIdInner { kind: Kind::Path, .. } => {
140-
panic!("Path sources are not included in the lockfile, \
141-
so this is unimplemented")
142-
},
142+
SourceIdInner { kind: Kind::Path, ref url, .. } => {
143+
format!("path+{}", url)
144+
}
143145
SourceIdInner {
144146
kind: Kind::Git(ref reference), ref url, ref precise, ..
145147
} => {

0 commit comments

Comments
 (0)