]> git.proxmox.com Git - cargo.git/blame - src/bin/cargo.rs
Auto merge of #3632 - Susurrus:master, r=alexcrichton
[cargo.git] / src / bin / cargo.rs
CommitLineData
55321111 1extern crate cargo;
134edb20 2extern crate url;
98854f6f 3extern crate env_logger;
964e72ff
AC
4extern crate git2_curl;
5extern crate rustc_serialize;
a6dad622 6extern crate toml;
ee5e24ff 7#[macro_use] extern crate log;
3a15f5b7 8
8ab8b58b 9use std::collections::BTreeSet;
66739f1c 10use std::collections::HashMap;
ee5e24ff 11use std::env;
a6dad622 12use std::fs;
7ed31fab 13use std::path::{Path,PathBuf};
ba273af5 14
3b93c575 15use cargo::core::shell::{Verbosity, ColorConfig};
66739f1c 16use cargo::util::{self, CliResult, lev_distance, Config, human, CargoResult};
fdc5b07c 17use cargo::util::CliError;
3a15f5b7 18
ba280047 19#[derive(RustcDecodable)]
dd342960 20pub struct Flags {
3512d997 21 flag_list: bool,
3fb50545 22 flag_version: bool,
805dfe4e 23 flag_verbose: u32,
be48d5b1 24 flag_quiet: Option<bool>,
6f2b241b 25 flag_color: Option<String>,
8dad57e8 26 flag_explain: Option<String>,
3512d997
AC
27 arg_command: String,
28 arg_args: Vec<String>,
a504f480
AC
29 flag_locked: bool,
30 flag_frozen: bool,
3a15f5b7
YK
31}
32
3512d997 33const USAGE: &'static str = "
ba273af5
AC
34Rust's package manager
35
36Usage:
37 cargo <command> [<args>...]
fff5de37 38 cargo [options]
ba273af5
AC
39
40Options:
6f2b241b
MB
41 -h, --help Display this message
42 -V, --version Print version info and exit
43 --list List installed commands
8dad57e8 44 --explain CODE Run `rustc --explain CODE`
d6d69a91 45 -v, --verbose ... Use verbose output (-vv very verbose/build.rs output)
6f2b241b
MB
46 -q, --quiet No output printed to stdout
47 --color WHEN Coloring: auto, always, never
a504f480
AC
48 --frozen Require Cargo.lock and cache are up to date
49 --locked Require Cargo.lock is up to date
ba273af5 50
350223e9 51Some common cargo commands are (see all commands with --list):
ba273af5 52 build Compile the current project
4b82fdc0 53 check Analyze the current project and report errors, but don't build object files
ba273af5
AC
54 clean Remove the target directory
55 doc Build this project's and its dependencies' documentation
56 new Create a new cargo project
81d1ab24 57 init Create a new cargo project in an existing directory
ba273af5
AC
58 run Build and execute src/main.rs
59 test Run the tests
92e40c43 60 bench Run the benchmarks
620b2fe1 61 update Update dependencies listed in Cargo.lock
ae783c6c 62 search Search registry for crates
9672e3bd 63 publish Package and upload this project to the registry
5a427b47 64 install Install a Rust binary
ba273af5
AC
65
66See 'cargo help <command>' for more information on a specific command.
3512d997
AC
67";
68
69fn main() {
98854f6f 70 env_logger::init().unwrap();
3b93c575
AK
71
72 let config = match Config::default() {
73 Ok(cfg) => cfg,
74 Err(e) => {
75 let mut shell = cargo::shell(Verbosity::Verbose, ColorConfig::Auto);
ef043577 76 cargo::exit_with_error(e.into(), &mut shell)
3b93c575
AK
77 }
78 };
79
80 let result = (|| {
81 let args: Vec<_> = try!(env::args_os().map(|s| {
82 s.into_string().map_err(|s| {
83 human(format!("invalid unicode in argument: {:?}", s))
84 })
85 }).collect());
86 let rest = &args;
87 cargo::call_main_without_stdin(execute, &config, USAGE, rest, true)
88 })();
89
90 match result {
ef043577 91 Err(e) => cargo::exit_with_error(e, &mut *config.shell()),
0c7e73c2 92 Ok(()) => {},
3b93c575 93 }
3512d997 94}
3a15f5b7 95
20b768e6 96macro_rules! each_subcommand{
d521a9a0 97 ($mac:ident) => {
20b768e6
AC
98 $mac!(bench);
99 $mac!(build);
4b82fdc0 100 $mac!(check);
20b768e6
AC
101 $mac!(clean);
102 $mac!(doc);
103 $mac!(fetch);
104 $mac!(generate_lockfile);
105 $mac!(git_checkout);
106 $mac!(help);
800172fb 107 $mac!(init);
20b768e6
AC
108 $mac!(install);
109 $mac!(locate_project);
110 $mac!(login);
fec3ce92 111 $mac!(metadata);
20b768e6
AC
112 $mac!(new);
113 $mac!(owner);
114 $mac!(package);
115 $mac!(pkgid);
116 $mac!(publish);
117 $mac!(read_manifest);
118 $mac!(run);
119 $mac!(rustc);
120 $mac!(rustdoc);
121 $mac!(search);
122 $mac!(test);
123 $mac!(uninstall);
124 $mac!(update);
125 $mac!(verify_project);
126 $mac!(version);
127 $mac!(yank);
d521a9a0
JS
128 }
129}
130
bd0e5123
JS
131macro_rules! declare_mod {
132 ($name:ident) => ( pub mod $name; )
20b768e6 133}
bd0e5123 134each_subcommand!(declare_mod);
8cce8996 135
739b1309
YK
136/**
137 The top-level `cargo` command handles configuration and project location
138 because they are fundamental (and intertwined). Other commands can rely
139 on this top-level information.
140*/
0c7e73c2 141fn execute(flags: Flags, config: &Config) -> CliResult {
82655b46 142 config.configure(flags.flag_verbose,
9c7ef529
SG
143 flags.flag_quiet,
144 &flags.flag_color,
145 flags.flag_frozen,
146 flags.flag_locked)?;
8cce8996 147
b20b0f6b 148 init_git_transports(config);
5ede71e5 149 let _token = cargo::util::job::setup();
b20b0f6b 150
3fb50545 151 if flags.flag_version {
50e1c1a5
NF
152 let version = cargo::version();
153 println!("{}", version);
154 if flags.flag_verbose > 0{
155 println!("release: {}.{}.{}",
156 version.major, version.minor, version.patch);
157 if let Some(ref cfg) = version.cfg_info {
158 if let Some(ref ci) = cfg.commit_info {
159 println!("commit-hash: {}", ci.commit_hash);
160 println!("commit-date: {}", ci.commit_date);
161 }
162 }
163 }
0c7e73c2 164 return Ok(())
3fb50545
AL
165 }
166
a3f6a404 167 if flags.flag_list {
168 println!("Installed Commands:");
20b768e6 169 for command in list_commands(config) {
a3f6a404 170 println!(" {}", command);
a3f6a404 171 };
0c7e73c2 172 return Ok(())
a3f6a404 173 }
8cce8996 174
8dad57e8 175 if let Some(ref code) = flags.flag_explain {
82655b46
SG
176 let mut procss = config.rustc()?.process();
177 procss.arg("--explain").arg(code).exec().map_err(human)?;
0c7e73c2 178 return Ok(())
8dad57e8
AC
179 }
180
e1eb81ea 181 let args = match &flags.arg_command[..] {
22e7ede6
AC
182 // For the commands `cargo` and `cargo help`, re-execute ourselves as
183 // `cargo -h` so we can go through the normal process of printing the
184 // help message.
3ce79da0 185 "" | "help" if flags.arg_args.is_empty() => {
b40e1db7 186 config.shell().set_verbosity(Verbosity::Verbose);
22e7ede6 187 let args = &["cargo".to_string(), "-h".to_string()];
ef043577 188 return cargo::call_main_without_stdin(execute, config, USAGE, args, false);
a3f6a404 189 }
22e7ede6
AC
190
191 // For `cargo help -h` and `cargo help --help`, print out the help
192 // message for `cargo help`
25e537aa 193 "help" if flags.arg_args[0] == "-h" ||
22e7ede6 194 flags.arg_args[0] == "--help" => {
2ff5f53b 195 vec!["cargo".to_string(), "help".to_string(), "-h".to_string()]
22e7ede6
AC
196 }
197
198 // For `cargo help foo`, print out the usage message for the specified
199 // subcommand by executing the command with the `-h` flag.
a1b3d153 200 "help" => vec!["cargo".to_string(), flags.arg_args[0].clone(),
201 "-h".to_string()],
22e7ede6
AC
202
203 // For all other invocations, we're of the form `cargo foo args...`. We
204 // use the exact environment arguments to preserve tokens like `--` for
205 // example.
66739f1c
SBI
206 _ => {
207 let mut default_alias = HashMap::new();
208 default_alias.insert("b", "build".to_string());
209 default_alias.insert("t", "test".to_string());
210 default_alias.insert("r", "run".to_string());
211 let mut args: Vec<String> = env::args().collect();
212 if let Some(new_command) = default_alias.get(&args[1][..]){
213 args[1] = new_command.clone();
214 }
215 args
216 }
8cce8996 217 };
8cce8996 218
ef043577
AK
219 if let Some(r) = try_execute_builtin_command(&config, &args) {
220 return r;
66739f1c
SBI
221 }
222
82655b46 223 let alias_list = aliased_command(&config, &args[1])?;
e1eb81ea
AC
224 let args = match alias_list {
225 Some(alias_command) => {
226 let chain = args.iter().take(1)
227 .chain(alias_command.iter())
228 .chain(args.iter().skip(2))
229 .map(|s| s.to_string())
230 .collect::<Vec<_>>();
ef043577
AK
231 if let Some(r) = try_execute_builtin_command(&config, &chain) {
232 return r;
e1eb81ea
AC
233 } else {
234 chain
235 }
236 }
237 None => args,
238 };
ef043577
AK
239
240 execute_external_subcommand(config, &args[1], &args)
e1eb81ea
AC
241}
242
ef043577 243fn try_execute_builtin_command(config: &Config, args: &[String]) -> Option<CliResult> {
e1eb81ea 244 macro_rules! cmd {
20b768e6 245 ($name:ident) => (if args[1] == stringify!($name).replace("_", "-") {
b40e1db7 246 config.shell().set_verbosity(Verbosity::Verbose);
bd0e5123
JS
247 let r = cargo::call_main_without_stdin($name::execute, config,
248 $name::USAGE,
25e537aa 249 &args,
8cce8996 250 false);
ef043577 251 return Some(r);
20b768e6
AC
252 })
253 }
157d639a 254 each_subcommand!(cmd);
8cce8996 255
ef043577 256 None
a3f6a404 257}
258
66739f1c
SBI
259fn aliased_command(config: &Config, command: &String) -> CargoResult<Option<Vec<String>>> {
260 let alias_name = format!("alias.{}", command);
261 let mut result = Ok(None);
262 match config.get_string(&alias_name) {
263 Ok(value) => {
264 if let Some(record) = value {
265 let alias_commands = record.val.split_whitespace()
266 .map(|s| s.to_string())
267 .collect();
268 result = Ok(Some(alias_commands));
269 }
270 },
271 Err(_) => {
82655b46 272 let value = config.get_list(&alias_name)?;
66739f1c
SBI
273 if let Some(record) = value {
274 let alias_commands: Vec<String> = record.val.iter()
275 .map(|s| s.0.to_string()).collect();
276 result = Ok(Some(alias_commands));
277 }
278 }
279 }
280 result
281}
282
20b768e6
AC
283fn find_closest(config: &Config, cmd: &str) -> Option<String> {
284 let cmds = list_commands(config);
80fe0e6d
AC
285 // Only consider candidates with a lev_distance of 3 or less so we don't
286 // suggest out-of-the-blue options.
287 let mut filtered = cmds.iter().map(|c| (lev_distance(&c, cmd), c))
288 .filter(|&(d, _)| d < 4)
289 .collect::<Vec<_>>();
290 filtered.sort_by(|a, b| a.0.cmp(&b.0));
a1b3d153 291 filtered.get(0).map(|slot| slot.1.clone())
12f5de8e
PW
292}
293
ef043577
AK
294fn execute_external_subcommand(config: &Config,
295 cmd: &str,
296 args: &[String]) -> CliResult {
20b768e6
AC
297 let command_exe = format!("cargo-{}{}", cmd, env::consts::EXE_SUFFIX);
298 let path = search_directories(config)
299 .iter()
300 .map(|dir| dir.join(&command_exe))
7ed31fab 301 .find(|file| is_executable(file));
20b768e6 302 let command = match path {
7ed31fab 303 Some(command) => command,
12f5de8e 304 None => {
20b768e6 305 return Err(human(match find_closest(config, cmd) {
579fa323
KA
306 Some(closest) => format!("no such subcommand: `{}`\n\n\t\
307 Did you mean `{}`?\n", cmd, closest),
79858995 308 None => format!("no such subcommand: `{}`", cmd)
fdc5b07c 309 }).into())
12f5de8e 310 }
8cce8996 311 };
fdc5b07c
AC
312 let err = match util::process(&command).args(&args[1..]).exec() {
313 Ok(()) => return Ok(()),
314 Err(e) => e,
315 };
316
317 if let Some(code) = err.exit.as_ref().and_then(|c| c.code()) {
860f38a1 318 Err(CliError::code(code))
fdc5b07c 319 } else {
860f38a1 320 Err(CliError::new(Box::new(err), 101))
fdc5b07c 321 }
a3f6a404 322}
323
84045231 324/// List all runnable commands
20b768e6
AC
325fn list_commands(config: &Config) -> BTreeSet<String> {
326 let prefix = "cargo-";
327 let suffix = env::consts::EXE_SUFFIX;
8ab8b58b 328 let mut commands = BTreeSet::new();
20b768e6 329 for dir in search_directories(config) {
a6dad622 330 let entries = match fs::read_dir(dir) {
a3f6a404 331 Ok(entries) => entries,
332 _ => continue
333 };
20b768e6
AC
334 for entry in entries.filter_map(|e| e.ok()) {
335 let path = entry.path();
336 let filename = match path.file_name().and_then(|s| s.to_str()) {
a3f6a404 337 Some(filename) => filename,
338 _ => continue
339 };
20b768e6
AC
340 if !filename.starts_with(prefix) || !filename.ends_with(suffix) {
341 continue
342 }
7ed31fab
JB
343 if is_executable(entry.path()) {
344 let end = filename.len() - suffix.len();
345 commands.insert(filename[prefix.len()..end].to_string());
64ff29ff 346 }
6a809efb 347 }
92b449b6 348 }
8cce8996 349
20b768e6 350 macro_rules! add_cmd {
d521a9a0 351 ($cmd:ident) => ({ commands.insert(stringify!($cmd).replace("_", "-")); })
20b768e6 352 }
8cce8996 353 each_subcommand!(add_cmd);
a3f6a404 354 commands
355}
356
a6dad622 357#[cfg(unix)]
7ed31fab 358fn is_executable<P: AsRef<Path>>(path: P) -> bool {
a6dad622 359 use std::os::unix::prelude::*;
8094633a 360 fs::metadata(path).map(|metadata| {
7ed31fab 361 metadata.is_file() && metadata.permissions().mode() & 0o111 != 0
8094633a 362 }).unwrap_or(false)
a6dad622
AC
363}
364#[cfg(windows)]
7ed31fab
JB
365fn is_executable<P: AsRef<Path>>(path: P) -> bool {
366 fs::metadata(path).map(|metadata| metadata.is_file()).unwrap_or(false)
a3f6a404 367}
368
20b768e6 369fn search_directories(config: &Config) -> Vec<PathBuf> {
8eac1d62 370 let mut dirs = vec![config.home().clone().into_path_unlocked().join("bin")];
1384050e 371 if let Some(val) = env::var_os("PATH") {
ee5e24ff
AC
372 dirs.extend(env::split_paths(&val));
373 }
a1b3d153 374 dirs
3a15f5b7 375}
b20b0f6b
AC
376
377fn init_git_transports(config: &Config) {
378 // Only use a custom transport if a proxy is configured, right now libgit2
379 // doesn't support proxies and we have to use a custom transport in this
380 // case. The custom transport, however, is not as well battle-tested.
8eb28ad6 381 match cargo::ops::http_proxy_exists(config) {
382 Ok(true) => {}
b20b0f6b
AC
383 _ => return
384 }
385
386 let handle = match cargo::ops::http_handle(config) {
387 Ok(handle) => handle,
388 Err(..) => return,
389 };
390
391 // The unsafety of the registration function derives from two aspects:
392 //
393 // 1. This call must be synchronized with all other registration calls as
394 // well as construction of new transports.
395 // 2. The argument is leaked.
396 //
397 // We're clear on point (1) because this is only called at the start of this
398 // binary (we know what the state of the world looks like) and we're mostly
399 // clear on point (2) because we'd only free it after everything is done
400 // anyway
401 unsafe {
402 git2_curl::register(handle);
403 }
404}