1 use anyhow::anyhow;
2 use cargo::core::{features, CliUnstable};
3 use cargo::{self, drop_print, drop_println, CliResult, Config};
4 use clap::{AppSettings, Arg, ArgMatches};
5 use itertools::Itertools;
6 use std::collections::HashMap;
7 use std::fmt::Write;
8
9 use super::commands;
10 use super::list_commands;
11 use crate::command_prelude::*;
12 use cargo::core::features::HIDDEN;
13
14 lazy_static::lazy_static! {
15 // Maps from commonly known external commands (not builtin to cargo) to their
16 // description, for the help page. Reserved for external subcommands that are
17 // core within the rust ecosystem (esp ones that might become internal in the future).
18 static ref KNOWN_EXTERNAL_COMMAND_DESCRIPTIONS: HashMap<&'static str, &'static str> = vec![
19 ("clippy", "Checks a package to catch common mistakes and improve your Rust code."),
20 ("fmt", "Formats all bin and lib files of the current crate using rustfmt."),
21 ].into_iter().collect();
22 }
23
main(config: &mut Config) -> CliResult24 pub fn main(config: &mut Config) -> CliResult {
25 // CAUTION: Be careful with using `config` until it is configured below.
26 // In general, try to avoid loading config values unless necessary (like
27 // the [alias] table).
28
29 if commands::help::handle_embedded_help(config) {
30 return Ok(());
31 }
32
33 let args = match cli().get_matches_safe() {
34 Ok(args) => args,
35 Err(e) => {
36 if e.kind == clap::ErrorKind::UnrecognizedSubcommand {
37 // An unrecognized subcommand might be an external subcommand.
38 let cmd = &e.info.as_ref().unwrap()[0].to_owned();
39 return super::execute_external_subcommand(config, cmd, &[cmd, "--help"])
40 .map_err(|_| e.into());
41 } else {
42 return Err(e.into());
43 }
44 }
45 };
46
47 // Global args need to be extracted before expanding aliases because the
48 // clap code for extracting a subcommand discards global options
49 // (appearing before the subcommand).
50 let (expanded_args, global_args) = expand_aliases(config, args, vec![])?;
51
52 if expanded_args.value_of("unstable-features") == Some("help") {
53 let options = CliUnstable::help();
54 let non_hidden_options: Vec<(String, String)> = options
55 .iter()
56 .filter(|(_, help_message)| *help_message != HIDDEN)
57 .map(|(name, help)| (name.to_string(), help.to_string()))
58 .collect();
59 let longest_option = non_hidden_options
60 .iter()
61 .map(|(option_name, _)| option_name.len())
62 .max()
63 .unwrap_or(0);
64 let help_lines: Vec<String> = non_hidden_options
65 .iter()
66 .map(|(option_name, option_help_message)| {
67 let option_name_kebab_case = option_name.replace("_", "-");
68 let padding = " ".repeat(longest_option - option_name.len()); // safe to substract
69 format!(
70 " -Z {}{} -- {}",
71 option_name_kebab_case, padding, option_help_message
72 )
73 })
74 .collect();
75 let joined = help_lines.join("\n");
76 drop_println!(
77 config,
78 "
79 Available unstable (nightly-only) flags:
80
81 {}
82
83 Run with 'cargo -Z [FLAG] [SUBCOMMAND]'",
84 joined
85 );
86 if !config.nightly_features_allowed {
87 drop_println!(
88 config,
89 "\nUnstable flags are only available on the nightly channel \
90 of Cargo, but this is the `{}` channel.\n\
91 {}",
92 features::channel(),
93 features::SEE_CHANNELS
94 );
95 }
96 drop_println!(
97 config,
98 "\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
99 for more information about these flags."
100 );
101 return Ok(());
102 }
103
104 let is_verbose = expanded_args.occurrences_of("verbose") > 0;
105 if expanded_args.is_present("version") {
106 let version = get_version_string(is_verbose);
107 drop_print!(config, "{}", version);
108 return Ok(());
109 }
110
111 if let Some(code) = expanded_args.value_of("explain") {
112 let mut procss = config.load_global_rustc(None)?.process();
113 procss.arg("--explain").arg(code).exec()?;
114 return Ok(());
115 }
116
117 if expanded_args.is_present("list") {
118 drop_println!(config, "Installed Commands:");
119 for (name, command) in list_commands(config) {
120 let known_external_desc = KNOWN_EXTERNAL_COMMAND_DESCRIPTIONS.get(name.as_str());
121 match command {
122 CommandInfo::BuiltIn { about } => {
123 assert!(
124 known_external_desc.is_none(),
125 "KNOWN_EXTERNAL_COMMANDS shouldn't contain builtin \"{}\"",
126 name
127 );
128 let summary = about.unwrap_or_default();
129 let summary = summary.lines().next().unwrap_or(&summary); // display only the first line
130 drop_println!(config, " {:<20} {}", name, summary);
131 }
132 CommandInfo::External { path } => {
133 if let Some(desc) = known_external_desc {
134 drop_println!(config, " {:<20} {}", name, desc);
135 } else if is_verbose {
136 drop_println!(config, " {:<20} {}", name, path.display());
137 } else {
138 drop_println!(config, " {}", name);
139 }
140 }
141 CommandInfo::Alias { target } => {
142 drop_println!(config, " {:<20} {}", name, target.iter().join(" "));
143 }
144 }
145 }
146 return Ok(());
147 }
148
149 let (cmd, subcommand_args) = match expanded_args.subcommand() {
150 (cmd, Some(args)) => (cmd, args),
151 _ => {
152 // No subcommand provided.
153 cli().print_help()?;
154 return Ok(());
155 }
156 };
157 config_configure(config, &expanded_args, subcommand_args, global_args)?;
158 super::init_git_transports(config);
159
160 execute_subcommand(config, cmd, subcommand_args)
161 }
162
get_version_string(is_verbose: bool) -> String163 pub fn get_version_string(is_verbose: bool) -> String {
164 let version = cargo::version();
165 let mut version_string = format!("cargo {}\n", version);
166 if is_verbose {
167 version_string.push_str(&format!(
168 "release: {}.{}.{}\n",
169 version.major, version.minor, version.patch
170 ));
171 if let Some(ref cfg) = version.cfg_info {
172 if let Some(ref ci) = cfg.commit_info {
173 version_string.push_str(&format!("commit-hash: {}\n", ci.commit_hash));
174 version_string.push_str(&format!("commit-date: {}\n", ci.commit_date));
175 }
176 }
177 writeln!(version_string, "host: {}", env!("RUST_HOST_TARGET")).unwrap();
178 add_libgit2(&mut version_string);
179 add_curl(&mut version_string);
180 add_ssl(&mut version_string);
181 writeln!(version_string, "os: {}", os_info::get()).unwrap();
182 }
183 version_string
184 }
185
add_libgit2(version_string: &mut String)186 fn add_libgit2(version_string: &mut String) {
187 let git2_v = git2::Version::get();
188 let lib_v = git2_v.libgit2_version();
189 let vendored = if git2_v.vendored() {
190 format!("vendored")
191 } else {
192 format!("system")
193 };
194 writeln!(
195 version_string,
196 "libgit2: {}.{}.{} (sys:{} {})",
197 lib_v.0,
198 lib_v.1,
199 lib_v.2,
200 git2_v.crate_version(),
201 vendored
202 )
203 .unwrap();
204 }
205
add_curl(version_string: &mut String)206 fn add_curl(version_string: &mut String) {
207 let curl_v = curl::Version::get();
208 let vendored = if curl_v.vendored() {
209 format!("vendored")
210 } else {
211 format!("system")
212 };
213 writeln!(
214 version_string,
215 "libcurl: {} (sys:{} {} ssl:{})",
216 curl_v.version(),
217 curl_sys::rust_crate_version(),
218 vendored,
219 curl_v.ssl_version().unwrap_or("none")
220 )
221 .unwrap();
222 }
223
add_ssl(version_string: &mut String)224 fn add_ssl(version_string: &mut String) {
225 #[cfg(feature = "openssl")]
226 {
227 writeln!(version_string, "ssl: {}", openssl::version::version()).unwrap();
228 }
229 #[cfg(not(feature = "openssl"))]
230 {
231 let _ = version_string; // Silence unused warning.
232 }
233 }
234
expand_aliases( config: &mut Config, args: ArgMatches<'static>, mut already_expanded: Vec<String>, ) -> Result<(ArgMatches<'static>, GlobalArgs), CliError>235 fn expand_aliases(
236 config: &mut Config,
237 args: ArgMatches<'static>,
238 mut already_expanded: Vec<String>,
239 ) -> Result<(ArgMatches<'static>, GlobalArgs), CliError> {
240 if let (cmd, Some(args)) = args.subcommand() {
241 match (
242 commands::builtin_exec(cmd),
243 super::aliased_command(config, cmd)?,
244 ) {
245 (Some(_), Some(_)) => {
246 // User alias conflicts with a built-in subcommand
247 config.shell().warn(format!(
248 "user-defined alias `{}` is ignored, because it is shadowed by a built-in command",
249 cmd,
250 ))?;
251 }
252 (Some(_), None) => {
253 // Command is built-in and is not conflicting with alias, but contains ignored values.
254 if let Some(mut values) = args.values_of("") {
255 config.shell().warn(format!(
256 "trailing arguments after built-in command `{}` are ignored: `{}`",
257 cmd,
258 values.join(" "),
259 ))?;
260 }
261 }
262 (None, None) => {}
263 (_, Some(mut alias)) => {
264 alias.extend(
265 args.values_of("")
266 .unwrap_or_default()
267 .map(|s| s.to_string()),
268 );
269 // new_args strips out everything before the subcommand, so
270 // capture those global options now.
271 // Note that an alias to an external command will not receive
272 // these arguments. That may be confusing, but such is life.
273 let global_args = GlobalArgs::new(args);
274 let new_args = cli()
275 .setting(AppSettings::NoBinaryName)
276 .get_matches_from_safe(alias)?;
277
278 let (new_cmd, _) = new_args.subcommand();
279 already_expanded.push(cmd.to_string());
280 if already_expanded.contains(&new_cmd.to_string()) {
281 // Crash if the aliases are corecursive / unresolvable
282 return Err(anyhow!(
283 "alias {} has unresolvable recursive definition: {} -> {}",
284 already_expanded[0],
285 already_expanded.join(" -> "),
286 new_cmd,
287 )
288 .into());
289 }
290
291 let (expanded_args, _) = expand_aliases(config, new_args, already_expanded)?;
292 return Ok((expanded_args, global_args));
293 }
294 }
295 };
296
297 Ok((args, GlobalArgs::default()))
298 }
299
config_configure( config: &mut Config, args: &ArgMatches<'_>, subcommand_args: &ArgMatches<'_>, global_args: GlobalArgs, ) -> CliResult300 fn config_configure(
301 config: &mut Config,
302 args: &ArgMatches<'_>,
303 subcommand_args: &ArgMatches<'_>,
304 global_args: GlobalArgs,
305 ) -> CliResult {
306 let arg_target_dir = &subcommand_args.value_of_path("target-dir", config);
307 let verbose = global_args.verbose + args.occurrences_of("verbose") as u32;
308 // quiet is unusual because it is redefined in some subcommands in order
309 // to provide custom help text.
310 let quiet =
311 args.is_present("quiet") || subcommand_args.is_present("quiet") || global_args.quiet;
312 let global_color = global_args.color; // Extract so it can take reference.
313 let color = args.value_of("color").or_else(|| global_color.as_deref());
314 let frozen = args.is_present("frozen") || global_args.frozen;
315 let locked = args.is_present("locked") || global_args.locked;
316 let offline = args.is_present("offline") || global_args.offline;
317 let mut unstable_flags = global_args.unstable_flags;
318 if let Some(values) = args.values_of("unstable-features") {
319 unstable_flags.extend(values.map(|s| s.to_string()));
320 }
321 let mut config_args = global_args.config_args;
322 if let Some(values) = args.values_of("config") {
323 config_args.extend(values.map(|s| s.to_string()));
324 }
325 config.configure(
326 verbose,
327 quiet,
328 color,
329 frozen,
330 locked,
331 offline,
332 arg_target_dir,
333 &unstable_flags,
334 &config_args,
335 )?;
336 Ok(())
337 }
338
execute_subcommand( config: &mut Config, cmd: &str, subcommand_args: &ArgMatches<'_>, ) -> CliResult339 fn execute_subcommand(
340 config: &mut Config,
341 cmd: &str,
342 subcommand_args: &ArgMatches<'_>,
343 ) -> CliResult {
344 if let Some(exec) = commands::builtin_exec(cmd) {
345 return exec(config, subcommand_args);
346 }
347
348 let mut ext_args: Vec<&str> = vec![cmd];
349 ext_args.extend(subcommand_args.values_of("").unwrap_or_default());
350 super::execute_external_subcommand(config, cmd, &ext_args)
351 }
352
353 #[derive(Default)]
354 struct GlobalArgs {
355 verbose: u32,
356 quiet: bool,
357 color: Option<String>,
358 frozen: bool,
359 locked: bool,
360 offline: bool,
361 unstable_flags: Vec<String>,
362 config_args: Vec<String>,
363 }
364
365 impl GlobalArgs {
new(args: &ArgMatches<'_>) -> GlobalArgs366 fn new(args: &ArgMatches<'_>) -> GlobalArgs {
367 GlobalArgs {
368 verbose: args.occurrences_of("verbose") as u32,
369 quiet: args.is_present("quiet"),
370 color: args.value_of("color").map(|s| s.to_string()),
371 frozen: args.is_present("frozen"),
372 locked: args.is_present("locked"),
373 offline: args.is_present("offline"),
374 unstable_flags: args
375 .values_of_lossy("unstable-features")
376 .unwrap_or_default(),
377 config_args: args
378 .values_of("config")
379 .unwrap_or_default()
380 .map(|s| s.to_string())
381 .collect(),
382 }
383 }
384 }
385
cli() -> App386 fn cli() -> App {
387 let is_rustup = std::env::var_os("RUSTUP_HOME").is_some();
388 let usage = if is_rustup {
389 "cargo [+toolchain] [OPTIONS] [SUBCOMMAND]"
390 } else {
391 "cargo [OPTIONS] [SUBCOMMAND]"
392 };
393 App::new("cargo")
394 .settings(&[
395 AppSettings::UnifiedHelpMessage,
396 AppSettings::DeriveDisplayOrder,
397 AppSettings::VersionlessSubcommands,
398 AppSettings::AllowExternalSubcommands,
399 ])
400 .usage(usage)
401 .template(
402 "\
403 Rust's package manager
404
405 USAGE:
406 {usage}
407
408 OPTIONS:
409 {unified}
410
411 Some common cargo commands are (see all commands with --list):
412 build, b Compile the current package
413 check, c Analyze the current package and report errors, but don't build object files
414 clean Remove the target directory
415 doc, d Build this package's and its dependencies' documentation
416 new Create a new cargo package
417 init Create a new cargo package in an existing directory
418 run, r Run a binary or example of the local package
419 test, t Run the tests
420 bench Run the benchmarks
421 update Update dependencies listed in Cargo.lock
422 search Search registry for crates
423 publish Package and upload this package to the registry
424 install Install a Rust binary. Default location is $HOME/.cargo/bin
425 uninstall Uninstall a Rust binary
426
427 See 'cargo help <command>' for more information on a specific command.\n",
428 )
429 .arg(opt("version", "Print version info and exit").short("V"))
430 .arg(opt("list", "List installed commands"))
431 .arg(opt("explain", "Run `rustc --explain CODE`").value_name("CODE"))
432 .arg(
433 opt(
434 "verbose",
435 "Use verbose output (-vv very verbose/build.rs output)",
436 )
437 .short("v")
438 .multiple(true)
439 .global(true),
440 )
441 .arg(opt("quiet", "No output printed to stdout").short("q"))
442 .arg(
443 opt("color", "Coloring: auto, always, never")
444 .value_name("WHEN")
445 .global(true),
446 )
447 .arg(opt("frozen", "Require Cargo.lock and cache are up to date").global(true))
448 .arg(opt("locked", "Require Cargo.lock is up to date").global(true))
449 .arg(opt("offline", "Run without accessing the network").global(true))
450 .arg(
451 multi_opt(
452 "config",
453 "KEY=VALUE",
454 "Override a configuration value (unstable)",
455 )
456 .global(true),
457 )
458 .arg(
459 Arg::with_name("unstable-features")
460 .help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details")
461 .short("Z")
462 .value_name("FLAG")
463 .multiple(true)
464 .number_of_values(1)
465 .global(true),
466 )
467 .subcommands(commands::builtin())
468 }
469