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