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