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