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