]>
Commit | Line | Data |
---|---|---|
55321111 | 1 | extern crate cargo; |
134edb20 | 2 | extern crate url; |
98854f6f | 3 | extern crate env_logger; |
964e72ff AC |
4 | extern crate git2_curl; |
5 | extern crate rustc_serialize; | |
a6dad622 | 6 | extern crate toml; |
ee5e24ff | 7 | #[macro_use] extern crate log; |
3a15f5b7 | 8 | |
8ab8b58b | 9 | use std::collections::BTreeSet; |
66739f1c | 10 | use std::collections::HashMap; |
ee5e24ff | 11 | use std::env; |
a6dad622 | 12 | use std::fs; |
7ed31fab | 13 | use std::path::{Path,PathBuf}; |
ba273af5 | 14 | |
3b93c575 | 15 | use cargo::core::shell::{Verbosity, ColorConfig}; |
66739f1c | 16 | use cargo::util::{self, CliResult, lev_distance, Config, human, CargoResult}; |
fdc5b07c | 17 | use cargo::util::CliError; |
3a15f5b7 | 18 | |
ba280047 | 19 | #[derive(RustcDecodable)] |
dd342960 | 20 | pub 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 | 33 | const USAGE: &'static str = " |
ba273af5 AC |
34 | Rust's package manager |
35 | ||
36 | Usage: | |
37 | cargo <command> [<args>...] | |
fff5de37 | 38 | cargo [options] |
ba273af5 AC |
39 | |
40 | Options: | |
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 | 51 | Some 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 | |
66 | See 'cargo help <command>' for more information on a specific command. | |
3512d997 AC |
67 | "; |
68 | ||
69 | fn 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 | 96 | macro_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 |
131 | macro_rules! declare_mod { |
132 | ($name:ident) => ( pub mod $name; ) | |
20b768e6 | 133 | } |
bd0e5123 | 134 | each_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 | 141 | fn 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 | 243 | fn 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 |
259 | fn 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 |
283 | fn 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 |
294 | fn 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 |
325 | fn 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 | 358 | fn 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 |
365 | fn is_executable<P: AsRef<Path>>(path: P) -> bool { |
366 | fs::metadata(path).map(|metadata| metadata.is_file()).unwrap_or(false) | |
a3f6a404 | 367 | } |
368 | ||
20b768e6 | 369 | fn 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 | |
377 | fn 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 | } |