blob: 3fc95ffe4d863726261547f8d907ae04f93f1524 [file] [log] [blame] [edit]
extern crate cargo;
extern crate url;
extern crate env_logger;
extern crate git2_curl;
extern crate rustc_serialize;
extern crate toml;
#[macro_use] extern crate log;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::{Path,PathBuf};
use cargo::core::shell::{Verbosity, ColorConfig};
use cargo::util::{self, CliResult, lev_distance, Config, human, CargoResult};
use cargo::util::CliError;
#[derive(RustcDecodable)]
pub struct Flags {
flag_list: bool,
flag_version: bool,
flag_verbose: u32,
flag_quiet: Option<bool>,
flag_color: Option<String>,
flag_explain: Option<String>,
arg_command: String,
arg_args: Vec<String>,
flag_locked: bool,
flag_frozen: bool,
}
const USAGE: &'static str = "
Rust's package manager
Usage:
cargo <command> [<args>...]
cargo [options]
Options:
-h, --help Display this message
-V, --version Print version info and exit
--list List installed commands
--explain CODE Run `rustc --explain CODE`
-v, --verbose ... Use verbose output (-vv very verbose/build.rs output)
-q, --quiet No output printed to stdout
--color WHEN Coloring: auto, always, never
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
Some common cargo commands are (see all commands with --list):
build Compile the current project
check Analyze the current project and report errors, but don't build object files
clean Remove the target directory
doc Build this project's and its dependencies' documentation
new Create a new cargo project
init Create a new cargo project in an existing directory
run Build and execute src/main.rs
test Run the tests
bench Run the benchmarks
update Update dependencies listed in Cargo.lock
search Search registry for crates
publish Package and upload this project to the registry
install Install a Rust binary
See 'cargo help <command>' for more information on a specific command.
";
fn main() {
env_logger::init().unwrap();
let config = match Config::default() {
Ok(cfg) => cfg,
Err(e) => {
let mut shell = cargo::shell(Verbosity::Verbose, ColorConfig::Auto);
cargo::handle_cli_error(e.into(), &mut shell)
}
};
let result = (|| {
let args: Vec<_> = try!(env::args_os().map(|s| {
s.into_string().map_err(|s| {
human(format!("invalid unicode in argument: {:?}", s))
})
}).collect());
let rest = &args;
cargo::call_main_without_stdin(execute, &config, USAGE, rest, true)
})();
match result {
Err(e) => cargo::handle_cli_error(e, &mut *config.shell()),
Ok(None) => {},
Ok(Some(())) => unreachable!(),
}
}
macro_rules! each_subcommand{
($mac:ident) => {
$mac!(bench);
$mac!(build);
$mac!(check);
$mac!(clean);
$mac!(doc);
$mac!(fetch);
$mac!(generate_lockfile);
$mac!(git_checkout);
$mac!(help);
$mac!(init);
$mac!(install);
$mac!(locate_project);
$mac!(login);
$mac!(metadata);
$mac!(new);
$mac!(owner);
$mac!(package);
$mac!(pkgid);
$mac!(publish);
$mac!(read_manifest);
$mac!(run);
$mac!(rustc);
$mac!(rustdoc);
$mac!(search);
$mac!(test);
$mac!(uninstall);
$mac!(update);
$mac!(verify_project);
$mac!(version);
$mac!(yank);
}
}
macro_rules! declare_mod {
($name:ident) => ( pub mod $name; )
}
each_subcommand!(declare_mod);
/**
The top-level `cargo` command handles configuration and project location
because they are fundamental (and intertwined). Other commands can rely
on this top-level information.
*/
fn execute(flags: Flags, config: &Config) -> CliResult<Option<()>> {
config.configure(flags.flag_verbose,
flags.flag_quiet,
&flags.flag_color,
flags.flag_frozen,
flags.flag_locked)?;
init_git_transports(config);
let _token = cargo::util::job::setup();
if flags.flag_version {
let version = cargo::version();
println!("{}", version);
if flags.flag_verbose > 0{
println!("release: {}.{}.{}",
version.major, version.minor, version.patch);
if let Some(ref cfg) = version.cfg_info {
if let Some(ref ci) = cfg.commit_info {
println!("commit-hash: {}", ci.commit_hash);
println!("commit-date: {}", ci.commit_date);
}
}
}
return Ok(None)
}
if flags.flag_list {
println!("Installed Commands:");
for command in list_commands(config) {
println!(" {}", command);
};
return Ok(None)
}
if let Some(ref code) = flags.flag_explain {
let mut procss = config.rustc()?.process();
procss.arg("--explain").arg(code).exec().map_err(human)?;
return Ok(None)
}
let args = match &flags.arg_command[..] {
// For the commands `cargo` and `cargo help`, re-execute ourselves as
// `cargo -h` so we can go through the normal process of printing the
// help message.
"" | "help" if flags.arg_args.is_empty() => {
config.shell().set_verbosity(Verbosity::Verbose);
let args = &["cargo".to_string(), "-h".to_string()];
let r = cargo::call_main_without_stdin(execute, config, USAGE, args,
false);
cargo::process_executed(r, &mut config.shell());
return Ok(None)
}
// For `cargo help -h` and `cargo help --help`, print out the help
// message for `cargo help`
"help" if flags.arg_args[0] == "-h" ||
flags.arg_args[0] == "--help" => {
vec!["cargo".to_string(), "help".to_string(), "-h".to_string()]
}
// For `cargo help foo`, print out the usage message for the specified
// subcommand by executing the command with the `-h` flag.
"help" => vec!["cargo".to_string(), flags.arg_args[0].clone(),
"-h".to_string()],
// For all other invocations, we're of the form `cargo foo args...`. We
// use the exact environment arguments to preserve tokens like `--` for
// example.
_ => {
let mut default_alias = HashMap::new();
default_alias.insert("b", "build".to_string());
default_alias.insert("t", "test".to_string());
default_alias.insert("r", "run".to_string());
let mut args: Vec<String> = env::args().collect();
if let Some(new_command) = default_alias.get(&args[1][..]){
args[1] = new_command.clone();
}
args
}
};
if try_execute(&config, &args) {
return Ok(None)
}
let alias_list = aliased_command(&config, &args[1])?;
let args = match alias_list {
Some(alias_command) => {
let chain = args.iter().take(1)
.chain(alias_command.iter())
.chain(args.iter().skip(2))
.map(|s| s.to_string())
.collect::<Vec<_>>();
if try_execute(&config, &chain) {
return Ok(None)
} else {
chain
}
}
None => args,
};
execute_subcommand(config, &args[1], &args)?;
Ok(None)
}
fn try_execute(config: &Config, args: &[String]) -> bool {
macro_rules! cmd {
($name:ident) => (if args[1] == stringify!($name).replace("_", "-") {
config.shell().set_verbosity(Verbosity::Verbose);
let r = cargo::call_main_without_stdin($name::execute, config,
$name::USAGE,
&args,
false);
cargo::process_executed(r, &mut config.shell());
return true
})
}
each_subcommand!(cmd);
return false
}
fn aliased_command(config: &Config, command: &String) -> CargoResult<Option<Vec<String>>> {
let alias_name = format!("alias.{}", command);
let mut result = Ok(None);
match config.get_string(&alias_name) {
Ok(value) => {
if let Some(record) = value {
let alias_commands = record.val.split_whitespace()
.map(|s| s.to_string())
.collect();
result = Ok(Some(alias_commands));
}
},
Err(_) => {
let value = config.get_list(&alias_name)?;
if let Some(record) = value {
let alias_commands: Vec<String> = record.val.iter()
.map(|s| s.0.to_string()).collect();
result = Ok(Some(alias_commands));
}
}
}
result
}
fn find_closest(config: &Config, cmd: &str) -> Option<String> {
let cmds = list_commands(config);
// Only consider candidates with a lev_distance of 3 or less so we don't
// suggest out-of-the-blue options.
let mut filtered = cmds.iter().map(|c| (lev_distance(&c, cmd), c))
.filter(|&(d, _)| d < 4)
.collect::<Vec<_>>();
filtered.sort_by(|a, b| a.0.cmp(&b.0));
filtered.get(0).map(|slot| slot.1.clone())
}
fn execute_subcommand(config: &Config,
cmd: &str,
args: &[String]) -> CliResult<()> {
let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX);
let path = search_directories(config)
.iter()
.map(|dir| dir.join(&command_exe))
.find(|file| is_executable(file));
let command = match path {
Some(command) => command,
None => {
return Err(human(match find_closest(config, cmd) {
Some(closest) => format!("no such subcommand: `{}`\n\n\t\
Did you mean `{}`?\n", cmd, closest),
None => format!("no such subcommand: `{}`", cmd)
}).into())
}
};
let err = match util::process(&command).args(&args[1..]).exec() {
Ok(()) => return Ok(()),
Err(e) => e,
};
if let Some(code) = err.exit.as_ref().and_then(|c| c.code()) {
Err(CliError::code(code))
} else {
Err(CliError::new(Box::new(err), 101))
}
}
/// List all runnable commands
fn list_commands(config: &Config) -> BTreeSet<String> {
let prefix = "cargo-";
let suffix = env::consts::EXE_SUFFIX;
let mut commands = BTreeSet::new();
for dir in search_directories(config) {
let entries = match fs::read_dir(dir) {
Ok(entries) => entries,
_ => continue
};
for entry in entries.filter_map(|e| e.ok()) {
let path = entry.path();
let filename = match path.file_name().and_then(|s| s.to_str()) {
Some(filename) => filename,
_ => continue
};
if !filename.starts_with(prefix) || !filename.ends_with(suffix) {
continue
}
if is_executable(entry.path()) {
let end = filename.len() - suffix.len();
commands.insert(filename[prefix.len()..end].to_string());
}
}
}
macro_rules! add_cmd {
($cmd:ident) => ({ commands.insert(stringify!($cmd).replace("_", "-")); })
}
each_subcommand!(add_cmd);
commands
}
#[cfg(unix)]
fn is_executable<P: AsRef<Path>>(path: P) -> bool {
use std::os::unix::prelude::*;
fs::metadata(path).map(|metadata| {
metadata.is_file() && metadata.permissions().mode() & 0o111 != 0
}).unwrap_or(false)
}
#[cfg(windows)]
fn is_executable<P: AsRef<Path>>(path: P) -> bool {
fs::metadata(path).map(|metadata| metadata.is_file()).unwrap_or(false)
}
fn search_directories(config: &Config) -> Vec<PathBuf> {
let mut dirs = vec![config.home().clone().into_path_unlocked().join("bin")];
if let Some(val) = env::var_os("PATH") {
dirs.extend(env::split_paths(&val));
}
dirs
}
fn init_git_transports(config: &Config) {
// Only use a custom transport if a proxy is configured, right now libgit2
// doesn't support proxies and we have to use a custom transport in this
// case. The custom transport, however, is not as well battle-tested.
match cargo::ops::http_proxy_exists(config) {
Ok(true) => {}
_ => return
}
let handle = match cargo::ops::http_handle(config) {
Ok(handle) => handle,
Err(..) => return,
};
// The unsafety of the registration function derives from two aspects:
//
// 1. This call must be synchronized with all other registration calls as
// well as construction of new transports.
// 2. The argument is leaked.
//
// We're clear on point (1) because this is only called at the start of this
// binary (we know what the state of the world looks like) and we're mostly
// clear on point (2) because we'd only free it after everything is done
// anyway
unsafe {
git2_curl::register(handle);
}
}