Skip to content

Commit

Permalink
Auto merge of #2026 - alexcrichton:cargo-install, r=brson
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bors committed Oct 19, 2015
2 parents 1a6d6c4 + bc60f64 commit 1206e5e
Show file tree
Hide file tree
Showing 21 changed files with 1,137 additions and 70 deletions.
2 changes: 2 additions & 0 deletions src/bin/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ macro_rules! each_subcommand{ ($mac:ident) => ({
$mac!(generate_lockfile);
$mac!(git_checkout);
$mac!(help);
$mac!(install);
$mac!(locate_project);
$mac!(login);
$mac!(new);
Expand All @@ -81,6 +82,7 @@ macro_rules! each_subcommand{ ($mac:ident) => ({
$mac!(rustc);
$mac!(search);
$mac!(test);
$mac!(uninstall);
$mac!(update);
$mac!(verify_project);
$mac!(version);
Expand Down
130 changes: 130 additions & 0 deletions src/bin/install.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use std::path::Path;

use cargo::ops;
use cargo::core::{SourceId, GitReference};
use cargo::util::{CliResult, Config, ToUrl, human};

#[derive(RustcDecodable)]
struct Options {
flag_jobs: Option<u32>,
flag_features: Vec<String>,
flag_no_default_features: bool,
flag_debug: bool,
flag_bin: Vec<String>,
flag_example: Vec<String>,
flag_verbose: bool,
flag_quiet: bool,
flag_color: Option<String>,
flag_root: Option<String>,
flag_list: bool,

arg_crate: Option<String>,
flag_vers: Option<String>,

flag_git: Option<String>,
flag_branch: Option<String>,
flag_tag: Option<String>,
flag_rev: Option<String>,

flag_path: Option<String>,
}

pub const USAGE: &'static str = "
Install a Rust binary
Usage:
cargo install [options] [<crate>]
cargo install [options] --list
Specifying what crate to install:
--vers VERS Specify a version to install from crates.io
--git URL Git URL to install the specified crate from
--branch BRANCH Branch to use when installing from git
--tag TAG Tag to use when installing from git
--rev SHA Specific commit to use when installing from git
--path PATH Filesystem path to local crate to install
Build and install options:
-h, --help Print this message
-j N, --jobs N The number of jobs to run in parallel
--features FEATURES Space-separated list of features to activate
--no-default-features Do not build the `default` feature
--debug Build in debug mode instead of release mode
--bin NAME Only install the binary NAME
--example EXAMPLE Install the example EXAMPLE instead of binaries
--root DIR Directory to install packages into
-v, --verbose Use verbose output
-q, --quiet Less output printed to stdout
--color WHEN Coloring: auto, always, never
This command manages Cargo's local set of install binary crates. Only packages
which have [[bin]] targets can be installed, and all binaries are installed into
the installation root's `bin` folder. The installation root is determined, in
order of precedence, by `--root`, `$CARGO_INSTALL_ROOT`, the `install.root`
configuration key, and finally the home directory (which is either
`$CARGO_HOME` if set or `$HOME/.cargo` by default).
There are multiple sources from which a crate can be installed. The default
location is crates.io but the `--git` and `--path` flags can change this source.
If the source contains more than one package (such as crates.io or a git
repository with multiple crates) the `<crate>` argument is required to indicate
which crate should be installed.
Crates from crates.io can optionally specify the version they wish to install
via the `--vers` flags, and similarly packages from git repositories can
optionally specify the branch, tag, or revision that should be installed. If a
crate has multiple binaries, the `--bin` argument can selectively install only
one of them, and if you'd rather install examples the `--example` argument can
be used as well.
The `--list` option will list all installed packages (and their versions).
";

pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));

let compile_opts = ops::CompileOptions {
config: config,
jobs: options.flag_jobs,
target: None,
features: &options.flag_features,
no_default_features: options.flag_no_default_features,
spec: &[],
exec_engine: None,
mode: ops::CompileMode::Build,
release: !options.flag_debug,
filter: ops::CompileFilter::new(false, &options.flag_bin, &[],
&options.flag_example, &[]),
target_rustc_args: None,
};

let source = if let Some(url) = options.flag_git {
let url = try!(url.to_url().map_err(human));
let gitref = if let Some(branch) = options.flag_branch {
GitReference::Branch(branch)
} else if let Some(tag) = options.flag_tag {
GitReference::Tag(tag)
} else if let Some(rev) = options.flag_rev {
GitReference::Rev(rev)
} else {
GitReference::Branch("master".to_string())
};
SourceId::for_git(&url, gitref)
} else if let Some(path) = options.flag_path {
try!(SourceId::for_path(Path::new(&path)))
} else {
try!(SourceId::for_central(config))
};

let krate = options.arg_crate.as_ref().map(|s| &s[..]);
let vers = options.flag_vers.as_ref().map(|s| &s[..]);
let root = options.flag_root.as_ref().map(|s| &s[..]);

if options.flag_list {
try!(ops::install_list(root, config));
} else {
try!(ops::install(root, krate, &source, vers, &compile_opts));
}
Ok(None)
}
43 changes: 43 additions & 0 deletions src/bin/uninstall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use cargo::ops;
use cargo::util::{CliResult, Config};

#[derive(RustcDecodable)]
struct Options {
flag_bin: Vec<String>,
flag_root: Option<String>,
flag_verbose: bool,
flag_quiet: bool,
flag_color: Option<String>,

arg_spec: String,
}

pub const USAGE: &'static str = "
Remove a Rust binary
Usage:
cargo uninstall [options] <spec>
Options:
-h, --help Print this message
--root DIR Directory to uninstall packages from
--bin NAME Only uninstall the binary NAME
-v, --verbose Use verbose output
-q, --quiet Less output printed to stdout
--color WHEN Coloring: auto, always, never
The argument SPEC is a package id specification (see `cargo help pkgid`) to
specify which crate should be uninstalled. By default all binaries are
uninstalled for a crate but the `--bin` and `--example` flags can be used to
only uninstall particular binaries.
";

pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet));
try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..])));

let root = options.flag_root.as_ref().map(|s| &s[..]);
try!(ops::uninstall(root, &options.arg_spec, &options.flag_bin, config));
Ok(None)
}

56 changes: 56 additions & 0 deletions src/cargo/core/package_id_spec.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::collections::HashMap;
use std::fmt;

use semver::Version;
use url::{self, Url, UrlParser};

Expand Down Expand Up @@ -45,6 +47,15 @@ impl PackageIdSpec {
})
}

pub fn query_str<'a, I>(spec: &str, i: I) -> CargoResult<&'a PackageId>
where I: IntoIterator<Item=&'a PackageId>
{
let spec = try!(PackageIdSpec::parse(spec).chain_error(|| {
human(format!("invalid package id specification: `{}`", spec))
}));
spec.query(i)
}

pub fn from_package_id(package_id: &PackageId) -> PackageIdSpec {
PackageIdSpec {
name: package_id.name().to_string(),
Expand Down Expand Up @@ -115,6 +126,51 @@ impl PackageIdSpec {
None => true
}
}

pub fn query<'a, I>(&self, i: I) -> CargoResult<&'a PackageId>
where I: IntoIterator<Item=&'a PackageId>
{
let mut ids = i.into_iter().filter(|p| self.matches(*p));
let ret = match ids.next() {
Some(id) => id,
None => return Err(human(format!("package id specification `{}` \
matched no packages", self))),
};
return match ids.next() {
Some(other) => {
let mut msg = format!("There are multiple `{}` packages in \
your project, and the specification \
`{}` is ambiguous.\n\
Please re-run this command \
with `-p <spec>` where `<spec>` is one \
of the following:",
self.name(), self);
let mut vec = vec![ret, other];
vec.extend(ids);
minimize(&mut msg, vec, self);
Err(human(msg))
}
None => Ok(ret)
};

fn minimize(msg: &mut String,
ids: Vec<&PackageId>,
spec: &PackageIdSpec) {
let mut version_cnt = HashMap::new();
for id in ids.iter() {
*version_cnt.entry(id.version()).or_insert(0) += 1;
}
for id in ids.iter() {
if version_cnt[id.version()] == 1 {
msg.push_str(&format!("\n {}:{}", spec.name(),
id.version()));
} else {
msg.push_str(&format!("\n {}",
PackageIdSpec::from_package_id(*id)));
}
}
}
}
}

fn url(s: &str) -> url::ParseResult<Url> {
Expand Down
13 changes: 11 additions & 2 deletions src/cargo/core/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ impl<'cfg> PackageRegistry<'cfg> {
Ok(())
}

pub fn add_preloaded(&mut self, id: &SourceId, source: Box<Source + 'cfg>) {
self.add_source(id, source, Kind::Locked);
}

fn add_source(&mut self, id: &SourceId, source: Box<Source + 'cfg>,
kind: Kind) {
self.sources.insert(id, source);
self.source_ids.insert(id.clone(), (id.clone(), kind));
}

pub fn add_overrides(&mut self, ids: Vec<SourceId>) -> CargoResult<()> {
for id in ids.iter() {
try!(self.load(id, Kind::Override));
Expand Down Expand Up @@ -183,8 +193,7 @@ impl<'cfg> PackageRegistry<'cfg> {
}

// Save off the source
self.sources.insert(source_id, source);
self.source_ids.insert(source_id.clone(), (source_id.clone(), kind));
self.add_source(source_id, source, kind);

Ok(())
}).chain_error(|| human(format!("Unable to update {}", source_id)))
Expand Down
52 changes: 5 additions & 47 deletions src/cargo/core/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use semver;

use core::{PackageId, Registry, SourceId, Summary, Dependency};
use core::PackageIdSpec;
use util::{CargoResult, Graph, human, ChainError, CargoError};
use util::{CargoResult, Graph, human, CargoError};
use util::profile;
use util::graph::{Nodes, Edges};

Expand Down Expand Up @@ -118,55 +118,13 @@ impl Resolve {
self.graph.edges(pkg)
}

pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
let spec = try!(PackageIdSpec::parse(spec).chain_error(|| {
human(format!("invalid package id specification: `{}`", spec))
}));
let mut ids = self.iter().filter(|p| spec.matches(*p));
let ret = match ids.next() {
Some(id) => id,
None => return Err(human(format!("package id specification `{}` \
matched no packages", spec))),
};
return match ids.next() {
Some(other) => {
let mut msg = format!("There are multiple `{}` packages in \
your project, and the specification \
`{}` is ambiguous.\n\
Please re-run this command \
with `-p <spec>` where `<spec>` is one \
of the following:",
spec.name(), spec);
let mut vec = vec![ret, other];
vec.extend(ids);
minimize(&mut msg, vec, &spec);
Err(human(msg))
}
None => Ok(ret)
};

fn minimize(msg: &mut String,
ids: Vec<&PackageId>,
spec: &PackageIdSpec) {
let mut version_cnt = HashMap::new();
for id in ids.iter() {
*version_cnt.entry(id.version()).or_insert(0) += 1;
}
for id in ids.iter() {
if version_cnt[id.version()] == 1 {
msg.push_str(&format!("\n {}:{}", spec.name(),
id.version()));
} else {
msg.push_str(&format!("\n {}",
PackageIdSpec::from_package_id(*id)));
}
}
}
}

pub fn features(&self, pkg: &PackageId) -> Option<&HashSet<String>> {
self.features.get(pkg)
}

pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
PackageIdSpec::query_str(spec, self.iter())
}
}

impl fmt::Debug for Resolve {
Expand Down
12 changes: 7 additions & 5 deletions src/cargo/core/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,19 @@ impl SourceId {
SourceId::new(Kind::Registry, url)
.with_precise(Some("locked".to_string()))
}
"path" => SourceId::for_path(Path::new(&url[5..])).unwrap(),
"path" => {
let url = url.to_url().unwrap();
SourceId::new(Kind::Path, url)
}
_ => panic!("Unsupported serialized SourceId")
}
}

pub fn to_url(&self) -> String {
match *self.inner {
SourceIdInner { kind: Kind::Path, .. } => {
panic!("Path sources are not included in the lockfile, \
so this is unimplemented")
},
SourceIdInner { kind: Kind::Path, ref url, .. } => {
format!("path+{}", url)
}
SourceIdInner {
kind: Kind::Git(ref reference), ref url, ref precise, ..
} => {
Expand Down
Loading

0 comments on commit 1206e5e

Please sign in to comment.