]>
Commit | Line | Data |
---|---|---|
55321111 | 1 | extern crate cargo; |
98854f6f | 2 | extern crate env_logger; |
964e72ff | 3 | extern crate git2_curl; |
a6dad622 | 4 | extern crate toml; |
ef4c09f9 | 5 | #[macro_use] |
6 | extern crate log; | |
a5a298f1 AC |
7 | #[macro_use] |
8 | extern crate serde_derive; | |
9 | extern crate serde_json; | |
3a15f5b7 | 10 | |
8ab8b58b | 11 | use std::collections::BTreeSet; |
66739f1c | 12 | use std::collections::HashMap; |
ee5e24ff | 13 | use std::env; |
a6dad622 | 14 | use std::fs; |
ef4c09f9 | 15 | use std::path::{Path, PathBuf}; |
ba273af5 | 16 | |
f8fb0a02 | 17 | use cargo::core::shell::{Shell, Verbosity}; |
c7de4859 | 18 | use cargo::util::{self, CliResult, lev_distance, Config, CargoResult, CargoError, CargoErrorKind}; |
fdc5b07c | 19 | use cargo::util::CliError; |
3a15f5b7 | 20 | |
10373f40 | 21 | #[derive(Deserialize)] |
dd342960 | 22 | pub 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 | 37 | const USAGE: &'static str = " |
ba273af5 AC |
38 | Rust's package manager |
39 | ||
40 | Usage: | |
41 | cargo <command> [<args>...] | |
fff5de37 | 42 | cargo [options] |
ba273af5 AC |
43 | |
44 | Options: | |
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 | 56 | Some 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 | |
72 | See 'cargo help <command>' for more information on a specific command. | |
3512d997 AC |
73 | "; |
74 | ||
75 | fn 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 | 104 | macro_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 |
139 | macro_rules! declare_mod { |
140 | ($name:ident) => ( pub mod $name; ) | |
20b768e6 | 141 | } |
bd0e5123 | 142 | each_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 | 149 | fn 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 | 253 | fn 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 |
269 | fn 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 |
296 | fn 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 | 308 | fn 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 |
347 | fn 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 | 380 | fn 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 |
387 | fn is_executable<P: AsRef<Path>>(path: P) -> bool { |
388 | fs::metadata(path).map(|metadata| metadata.is_file()).unwrap_or(false) | |
a3f6a404 | 389 | } |
390 | ||
20b768e6 | 391 | fn 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 | |
399 | fn 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 | } |