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