Skip to content

Commit bc60f64

Browse files
committed
Finish implementing cargo install
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
1 parent f8308bc commit bc60f64

21 files changed

+1080
-123
lines changed

src/bin/cargo.rs

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ macro_rules! each_subcommand{ ($mac:ident) => ({
8282
$mac!(rustc);
8383
$mac!(search);
8484
$mac!(test);
85+
$mac!(uninstall);
8586
$mac!(update);
8687
$mac!(verify_project);
8788
$mac!(version);

src/bin/install.rs

+82-41
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,74 @@
1-
use cargo::ops;
2-
use cargo::util::{CliResult, CliError, Config};
31
use std::path::Path;
42

5-
#[allow(dead_code)] // for now until all options are implemented
3+
use cargo::ops;
4+
use cargo::core::{SourceId, GitReference};
5+
use cargo::util::{CliResult, Config, ToUrl, human};
66

77
#[derive(RustcDecodable)]
88
struct Options {
99
flag_jobs: Option<u32>,
1010
flag_features: Vec<String>,
1111
flag_no_default_features: bool,
1212
flag_debug: bool,
13-
flag_bin: Option<String>,
13+
flag_bin: Vec<String>,
1414
flag_example: Vec<String>,
15-
flag_package: Vec<String>,
1615
flag_verbose: bool,
16+
flag_quiet: bool,
17+
flag_color: Option<String>,
1718
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>,
1830
}
1931

2032
pub const USAGE: &'static str = "
21-
Install a crate onto the local system
33+
Install a Rust binary
2234
23-
Installing new crates:
24-
cargo install [options]
25-
cargo install [options] [-p CRATE | --package CRATE] [--vers VERS]
26-
cargo install [options] --git URL [--branch BRANCH | --tag TAG | --rev SHA]
27-
cargo install [options] --path PATH
28-
29-
Managing installed crates:
35+
Usage:
36+
cargo install [options] [<crate>]
3037
cargo install [options] --list
3138
32-
Options:
33-
-h, --help Print this message
34-
-j N, --jobs N The number of jobs to run in parallel
35-
--features FEATURES Space-separated list of features to activate
36-
--no-default-features Do not build the `default` feature
37-
--debug Build in debug mode instead of release mode
38-
--bin NAME Only install the binary NAME
39-
--example EXAMPLE Install the example EXAMPLE instead of binaries
40-
-p, --package CRATE Install this crate from crates.io or select the
41-
package in a repository/path to install.
42-
-v, --verbose Use verbose output
43-
--root DIR Directory to install packages into
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
4459
4560
This command manages Cargo's local set of install binary crates. Only packages
4661
which have [[bin]] targets can be installed, and all binaries are installed into
47-
`$HOME/.cargo/bin` by default (or `$CARGO_HOME/bin` if you change the home
48-
directory).
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).
4966
50-
There are multiple methods of installing a new crate onto the system. The
51-
`cargo install` command with no arguments will install the current crate (as
52-
specifed by the current directory). Otherwise the `-p`, `--package`, `--git`,
53-
and `--path` options all specify the source from which a crate is being
54-
installed. The `-p` and `--package` options will download crates from crates.io.
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.
5572
5673
Crates from crates.io can optionally specify the version they wish to install
5774
via the `--vers` flags, and similarly packages from git repositories can
@@ -64,26 +81,50 @@ The `--list` option will list all installed packages (and their versions).
6481
";
6582

6683
pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
67-
config.shell().set_verbose(options.flag_verbose);
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[..])));
6886

6987
let compile_opts = ops::CompileOptions {
7088
config: config,
7189
jobs: options.flag_jobs,
7290
target: None,
7391
features: &options.flag_features,
7492
no_default_features: options.flag_no_default_features,
75-
spec: None,
93+
spec: &[],
7694
exec_engine: None,
7795
mode: ops::CompileMode::Build,
78-
release: true,
79-
filter: ops::CompileFilter::Everything,
96+
release: !options.flag_debug,
97+
filter: ops::CompileFilter::new(false, &options.flag_bin, &[],
98+
&options.flag_example, &[]),
8099
target_rustc_args: None,
81100
};
82101

83-
let root = &Path::new("$HOME/.cargo/bin");
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[..]);
84123

85-
ops::install(&root,
86-
&compile_opts).map_err(|err| {
87-
CliError::from_boxed(err, 101)
88-
}).map(|_| None)
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)
89130
}

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 {

0 commit comments

Comments
 (0)