1use crate::core::compiler::{BuildConfig, MessageFormat};
2use crate::core::InternedString;
3use crate::core::Workspace;
4use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionControl};
5use crate::sources::CRATES_IO_REGISTRY;
6use crate::util::important_paths::find_root_manifest_for_wd;
7use crate::util::{paths, toml::TomlProfile, validate_package_name};
8use crate::util::{
9 print_available_benches, print_available_binaries, print_available_examples,
10 print_available_tests,
11};
12use crate::CargoResult;
13use anyhow::bail;
14use clap::{self, SubCommand};
15use std::ffi::{OsStr, OsString};
16use std::fs;
17use std::path::PathBuf;
18
19pub use crate::core::compiler::CompileMode;
20pub use crate::{CliError, CliResult, Config};
21pub use clap::{AppSettings, Arg, ArgMatches};
22
23pub type App = clap::App<'static, 'static>;
24
25pub trait AppExt: Sized {
26 fn _arg(self, arg: Arg<'static, 'static>) -> Self;
27
28 fn arg_package_spec(
29 self,
30 package: &'static str,
31 all: &'static str,
32 exclude: &'static str,
33 ) -> Self {
34 self.arg_package_spec_simple(package)
35 ._arg(opt("all", "Alias for --workspace (deprecated)"))
36 ._arg(opt("workspace", all))
37 ._arg(multi_opt("exclude", "SPEC", exclude))
38 }
39
40 fn arg_package_spec_simple(self, package: &'static str) -> Self {
41 self._arg(multi_opt("package", "SPEC", package).short("p"))
42 }
43
44 fn arg_package(self, package: &'static str) -> Self {
45 self._arg(opt("package", package).short("p").value_name("SPEC"))
46 }
47
48 fn arg_jobs(self) -> Self {
49 self._arg(
50 opt("jobs", "Number of parallel jobs, defaults to # of CPUs")
51 .short("j")
52 .value_name("N"),
53 )
54 }
55
56 fn arg_targets_all(
57 self,
58 lib: &'static str,
59 bin: &'static str,
60 bins: &'static str,
61 example: &'static str,
62 examples: &'static str,
63 test: &'static str,
64 tests: &'static str,
65 bench: &'static str,
66 benches: &'static str,
67 all: &'static str,
68 ) -> Self {
69 self.arg_targets_lib_bin(lib, bin, bins)
70 ._arg(optional_multi_opt("example", "NAME", example))
71 ._arg(opt("examples", examples))
72 ._arg(optional_multi_opt("test", "NAME", test))
73 ._arg(opt("tests", tests))
74 ._arg(optional_multi_opt("bench", "NAME", bench))
75 ._arg(opt("benches", benches))
76 ._arg(opt("all-targets", all))
77 }
78
79 fn arg_targets_lib_bin(self, lib: &'static str, bin: &'static str, bins: &'static str) -> Self {
80 self._arg(opt("lib", lib))
81 ._arg(optional_multi_opt("bin", "NAME", bin))
82 ._arg(opt("bins", bins))
83 }
84
85 fn arg_targets_bins_examples(
86 self,
87 bin: &'static str,
88 bins: &'static str,
89 example: &'static str,
90 examples: &'static str,
91 ) -> Self {
92 self._arg(optional_multi_opt("bin", "NAME", bin))
93 ._arg(opt("bins", bins))
94 ._arg(optional_multi_opt("example", "NAME", example))
95 ._arg(opt("examples", examples))
96 }
97
98 fn arg_targets_bin_example(self, bin: &'static str, example: &'static str) -> Self {
99 self._arg(optional_multi_opt("bin", "NAME", bin))
100 ._arg(optional_multi_opt("example", "NAME", example))
101 }
102
103 fn arg_features(self) -> Self {
104 self._arg(multi_opt(
105 "features",
106 "FEATURES",
107 "Space-separated list of features to activate",
108 ))
109 ._arg(opt("all-features", "Activate all available features"))
110 ._arg(opt(
111 "no-default-features",
112 "Do not activate the `default` feature",
113 ))
114 }
115
116 fn arg_release(self, release: &'static str) -> Self {
117 self._arg(opt("release", release))
118 }
119
120 fn arg_profile(self, profile: &'static str) -> Self {
121 self._arg(opt("profile", profile).value_name("PROFILE-NAME"))
122 }
123
124 fn arg_doc(self, doc: &'static str) -> Self {
125 self._arg(opt("doc", doc))
126 }
127
128 fn arg_target_triple(self, target: &'static str) -> Self {
129 self._arg(opt("target", target).value_name("TRIPLE"))
130 }
131
132 fn arg_target_dir(self) -> Self {
133 self._arg(
134 opt("target-dir", "Directory for all generated artifacts").value_name("DIRECTORY"),
135 )
136 }
137
138 fn arg_manifest_path(self) -> Self {
139 self._arg(opt("manifest-path", "Path to Cargo.toml").value_name("PATH"))
140 }
141
142 fn arg_message_format(self) -> Self {
143 self._arg(multi_opt("message-format", "FMT", "Error format"))
144 }
145
146 fn arg_build_plan(self) -> Self {
147 self._arg(opt(
148 "build-plan",
149 "Output the build plan in JSON (unstable)",
150 ))
151 }
152
153 fn arg_unit_graph(self) -> Self {
154 self._arg(opt("unit-graph", "Output build graph in JSON (unstable)").hidden(true))
155 }
156
157 fn arg_deps(self) -> Self {
158 self._arg(opt(
159 "dependencies",
160 "Build dependencies of the selected packages (unstable)",
161 ).conflicts_with("remote-dependencies"))
162 }
163
164 fn arg_deps_remote(self) -> Self {
165 self._arg(opt(
166 "remote-dependencies",
167 "Build remote dependencies of the selected packages (unstable)",
168 ).conflicts_with("dependencies"))
169 }
170
171 fn arg_new_opts(self) -> Self {
172 self._arg(
173 opt(
174 "vcs",
175 "Initialize a new repository for the given version \
176 control system (git, hg, pijul, or fossil) or do not \
177 initialize any version control at all (none), overriding \
178 a global configuration.",
179 )
180 .value_name("VCS")
181 .possible_values(&["git", "hg", "pijul", "fossil", "none"]),
182 )
183 ._arg(opt("bin", "Use a binary (application) template [default]"))
184 ._arg(opt("lib", "Use a library template"))
185 ._arg(
186 opt("edition", "Edition to set for the crate generated")
187 .possible_values(&["2015", "2018"])
188 .value_name("YEAR"),
189 )
190 ._arg(
191 opt(
192 "name",
193 "Set the resulting package name, defaults to the directory name",
194 )
195 .value_name("NAME"),
196 )
197 }
198
199 fn arg_index(self) -> Self {
200 self._arg(opt("index", "Registry index URL to upload the package to").value_name("INDEX"))
201 ._arg(
202 opt("host", "DEPRECATED, renamed to '--index'")
203 .value_name("HOST")
204 .hidden(true),
205 )
206 }
207
208 fn arg_dry_run(self, dry_run: &'static str) -> Self {
209 self._arg(opt("dry-run", dry_run))
210 }
211}
212
213impl AppExt for App {
214 fn _arg(self, arg: Arg<'static, 'static>) -> Self {
215 self.arg(arg)
216 }
217}
218
219pub fn opt(name: &'static str, help: &'static str) -> Arg<'static, 'static> {
220 Arg::with_name(name).long(name).help(help)
221}
222
223pub fn optional_multi_opt(
224 name: &'static str,
225 value_name: &'static str,
226 help: &'static str,
227) -> Arg<'static, 'static> {
228 opt(name, help)
229 .value_name(value_name)
230 .multiple(true)
231 .min_values(0)
232 .number_of_values(1)
233}
234
235pub fn multi_opt(
236 name: &'static str,
237 value_name: &'static str,
238 help: &'static str,
239) -> Arg<'static, 'static> {
240 opt(name, help)
245 .value_name(value_name)
246 .multiple(true)
247 .number_of_values(1)
248}
249
250pub fn subcommand(name: &'static str) -> App {
251 SubCommand::with_name(name).settings(&[
252 AppSettings::UnifiedHelpMessage,
253 AppSettings::DeriveDisplayOrder,
254 AppSettings::DontCollapseArgsInUsage,
255 ])
256}
257
258pub enum ProfileChecking {
260 Checked,
261 Unchecked,
262}
263
264pub trait ArgMatchesExt {
265 fn value_of_u32(&self, name: &str) -> CargoResult<Option<u32>> {
266 let arg = match self._value_of(name) {
267 None => None,
268 Some(arg) => Some(arg.parse::<u32>().map_err(|_| {
269 clap::Error::value_validation_auto(format!("could not parse `{}` as a number", arg))
270 })?),
271 };
272 Ok(arg)
273 }
274
275 fn value_of_path(&self, name: &str, config: &Config) -> Option<PathBuf> {
277 self._value_of(name).map(|path| config.cwd().join(path))
278 }
279
280 fn root_manifest(&self, config: &Config) -> CargoResult<PathBuf> {
281 if let Some(path) = self.value_of_path("manifest-path", config) {
282 let path = paths::normalize_path(&path);
285 if !path.ends_with("Cargo.toml") {
286 anyhow::bail!("the manifest-path must be a path to a Cargo.toml file")
287 }
288 if fs::metadata(&path).is_err() {
289 anyhow::bail!(
290 "manifest path `{}` does not exist",
291 self._value_of("manifest-path").unwrap()
292 )
293 }
294 return Ok(path);
295 }
296 find_root_manifest_for_wd(config.cwd())
297 }
298
299 fn workspace<'a>(&self, config: &'a Config) -> CargoResult<Workspace<'a>> {
300 let root = self.root_manifest(config)?;
301 let mut ws = Workspace::new(&root, config)?;
302 if config.cli_unstable().avoid_dev_deps {
303 ws.set_require_optional_deps(false);
304 }
305 if ws.is_virtual() && !config.cli_unstable().package_features {
306 for flag in &["features", "no-default-features"] {
309 if self._is_present(flag) {
310 bail!(
311 "--{} is not allowed in the root of a virtual workspace\n\
312 note: while this was previously accepted, it didn't actually do anything",
313 flag
314 );
315 }
316 }
317 }
318 Ok(ws)
319 }
320
321 fn jobs(&self) -> CargoResult<Option<u32>> {
322 self.value_of_u32("jobs")
323 }
324
325 fn target(&self) -> Option<String> {
326 self._value_of("target").map(|s| s.to_string())
327 }
328
329 fn get_profile_name(
330 &self,
331 config: &Config,
332 default: &str,
333 profile_checking: ProfileChecking,
334 ) -> CargoResult<InternedString> {
335 let specified_profile = match self._value_of("profile") {
336 None => None,
337 Some(name) => {
338 TomlProfile::validate_name(name, "profile name")?;
339 Some(InternedString::new(name))
340 }
341 };
342
343 match profile_checking {
344 ProfileChecking::Unchecked => {}
345 ProfileChecking::Checked => {
346 if specified_profile.is_some() && !config.cli_unstable().unstable_options {
347 anyhow::bail!("Usage of `--profile` requires `-Z unstable-options`")
348 }
349 }
350 }
351
352 if self._is_present("release") {
353 if !config.cli_unstable().unstable_options {
354 Ok(InternedString::new("release"))
355 } else {
356 match specified_profile {
357 Some(name) if name != "release" => {
358 anyhow::bail!("Conflicting usage of --profile and --release")
359 }
360 _ => Ok(InternedString::new("release")),
361 }
362 }
363 } else if self._is_present("debug") {
364 if !config.cli_unstable().unstable_options {
365 Ok(InternedString::new("dev"))
366 } else {
367 match specified_profile {
368 Some(name) if name != "dev" => {
369 anyhow::bail!("Conflicting usage of --profile and --debug")
370 }
371 _ => Ok(InternedString::new("dev")),
372 }
373 }
374 } else {
375 Ok(specified_profile.unwrap_or_else(|| InternedString::new(default)))
376 }
377 }
378
379 fn compile_options(
380 &self,
381 config: &Config,
382 mode: CompileMode,
383 workspace: Option<&Workspace<'_>>,
384 profile_checking: ProfileChecking,
385 ) -> CargoResult<CompileOptions> {
386 let spec = Packages::from_flags(
387 self._is_present("workspace") || self._is_present("all"),
389 self._values_of("exclude"),
390 self._values_of("package"),
391 )?;
392
393 let mut message_format = None;
394 let default_json = MessageFormat::Json {
395 short: false,
396 ansi: false,
397 render_diagnostics: false,
398 };
399 for fmt in self._values_of("message-format") {
400 for fmt in fmt.split(',') {
401 let fmt = fmt.to_ascii_lowercase();
402 match fmt.as_str() {
403 "json" => {
404 if message_format.is_some() {
405 bail!("cannot specify two kinds of `message-format` arguments");
406 }
407 message_format = Some(default_json);
408 }
409 "human" => {
410 if message_format.is_some() {
411 bail!("cannot specify two kinds of `message-format` arguments");
412 }
413 message_format = Some(MessageFormat::Human);
414 }
415 "short" => {
416 if message_format.is_some() {
417 bail!("cannot specify two kinds of `message-format` arguments");
418 }
419 message_format = Some(MessageFormat::Short);
420 }
421 "json-render-diagnostics" => {
422 if message_format.is_none() {
423 message_format = Some(default_json);
424 }
425 match &mut message_format {
426 Some(MessageFormat::Json {
427 render_diagnostics, ..
428 }) => *render_diagnostics = true,
429 _ => bail!("cannot specify two kinds of `message-format` arguments"),
430 }
431 }
432 "json-diagnostic-short" => {
433 if message_format.is_none() {
434 message_format = Some(default_json);
435 }
436 match &mut message_format {
437 Some(MessageFormat::Json { short, .. }) => *short = true,
438 _ => bail!("cannot specify two kinds of `message-format` arguments"),
439 }
440 }
441 "json-diagnostic-rendered-ansi" => {
442 if message_format.is_none() {
443 message_format = Some(default_json);
444 }
445 match &mut message_format {
446 Some(MessageFormat::Json { ansi, .. }) => *ansi = true,
447 _ => bail!("cannot specify two kinds of `message-format` arguments"),
448 }
449 }
450 s => bail!("invalid message format specifier: `{}`", s),
451 }
452 }
453 }
454
455 let mut build_config = BuildConfig::new(config, self.jobs()?, &self.target(), mode)?;
456 build_config.message_format = message_format.unwrap_or(MessageFormat::Human);
457 build_config.requested_profile = self.get_profile_name(config, "dev", profile_checking)?;
458 build_config.build_plan = self._is_present("build-plan");
459 build_config.unit_graph = self._is_present("unit-graph");
460 if build_config.build_plan {
461 config
462 .cli_unstable()
463 .fail_if_stable_opt("--build-plan", 5579)?;
464 };
465 if build_config.unit_graph {
466 config
467 .cli_unstable()
468 .fail_if_stable_opt("--unit-graph", 8002)?;
469 }
470
471 let opts = CompileOptions {
472 build_config,
473 features: self._values_of("features"),
474 all_features: self._is_present("all-features"),
475 no_default_features: self._is_present("no-default-features"),
476 spec,
477 filter: CompileFilter::from_raw_arguments(
478 self._is_present("lib"),
479 self._values_of("bin"),
480 self._is_present("bins"),
481 self._values_of("test"),
482 self._is_present("tests"),
483 self._values_of("example"),
484 self._is_present("examples"),
485 self._values_of("bench"),
486 self._is_present("benches"),
487 self._is_present("all-targets"),
488 ),
489 deps_only: false,
490 deps_remote_only: false,
491 target_rustdoc_args: None,
492 target_rustc_args: None,
493 local_rustdoc_args: None,
494 rustdoc_document_private_items: false,
495 export_dir: None,
496 };
497
498 if let Some(ws) = workspace {
499 self.check_optional_opts(ws, &opts)?;
500 }
501
502 Ok(opts)
503 }
504
505 fn compile_options_for_single_package(
506 &self,
507 config: &Config,
508 mode: CompileMode,
509 workspace: Option<&Workspace<'_>>,
510 profile_checking: ProfileChecking,
511 ) -> CargoResult<CompileOptions> {
512 let mut compile_opts = self.compile_options(config, mode, workspace, profile_checking)?;
513 compile_opts.spec = Packages::Packages(self._values_of("package"));
514 Ok(compile_opts)
515 }
516
517 fn new_options(&self, config: &Config) -> CargoResult<NewOptions> {
518 let vcs = self._value_of("vcs").map(|vcs| match vcs {
519 "git" => VersionControl::Git,
520 "hg" => VersionControl::Hg,
521 "pijul" => VersionControl::Pijul,
522 "fossil" => VersionControl::Fossil,
523 "none" => VersionControl::NoVcs,
524 vcs => panic!("Impossible vcs: {:?}", vcs),
525 });
526 NewOptions::new(
527 vcs,
528 self._is_present("bin"),
529 self._is_present("lib"),
530 self.value_of_path("path", config).unwrap(),
531 self._value_of("name").map(|s| s.to_string()),
532 self._value_of("edition").map(|s| s.to_string()),
533 self.registry(config)?,
534 )
535 }
536
537 fn registry(&self, config: &Config) -> CargoResult<Option<String>> {
538 match self._value_of("registry") {
539 Some(registry) => {
540 validate_package_name(registry, "registry name", "")?;
541
542 if registry == CRATES_IO_REGISTRY {
543 Ok(None)
549 } else {
550 Ok(Some(registry.to_string()))
551 }
552 }
553 None => config.default_registry(),
554 }
555 }
556
557 fn index(&self, config: &Config) -> CargoResult<Option<String>> {
558 let msg = "The flag '--host' is no longer valid.
562
563Previous versions of Cargo accepted this flag, but it is being
564deprecated. The flag is being renamed to 'index', as the flag
565wants the location of the index. Please use '--index' instead.
566
567This will soon become a hard error, so it's either recommended
568to update to a fixed version or contact the upstream maintainer
569about this warning.";
570
571 let index = match self._value_of("host") {
572 Some(host) => {
573 config.shell().warn(&msg)?;
574 Some(host.to_string())
575 }
576 None => self._value_of("index").map(|s| s.to_string()),
577 };
578 Ok(index)
579 }
580
581 fn check_optional_opts(
582 &self,
583 workspace: &Workspace<'_>,
584 compile_opts: &CompileOptions,
585 ) -> CargoResult<()> {
586 if self.is_present_with_zero_values("example") {
587 print_available_examples(workspace, compile_opts)?;
588 }
589
590 if self.is_present_with_zero_values("bin") {
591 print_available_binaries(workspace, compile_opts)?;
592 }
593
594 if self.is_present_with_zero_values("bench") {
595 print_available_benches(workspace, compile_opts)?;
596 }
597
598 if self.is_present_with_zero_values("test") {
599 print_available_tests(workspace, compile_opts)?;
600 }
601
602 Ok(())
603 }
604
605 fn is_present_with_zero_values(&self, name: &str) -> bool {
606 self._is_present(name) && self._value_of(name).is_none()
607 }
608
609 fn _value_of(&self, name: &str) -> Option<&str>;
610
611 fn _values_of(&self, name: &str) -> Vec<String>;
612
613 fn _value_of_os(&self, name: &str) -> Option<&OsStr>;
614
615 fn _values_of_os(&self, name: &str) -> Vec<OsString>;
616
617 fn _is_present(&self, name: &str) -> bool;
618}
619
620impl<'a> ArgMatchesExt for ArgMatches<'a> {
621 fn _value_of(&self, name: &str) -> Option<&str> {
622 self.value_of(name)
623 }
624
625 fn _value_of_os(&self, name: &str) -> Option<&OsStr> {
626 self.value_of_os(name)
627 }
628
629 fn _values_of(&self, name: &str) -> Vec<String> {
630 self.values_of(name)
631 .unwrap_or_default()
632 .map(|s| s.to_string())
633 .collect()
634 }
635
636 fn _values_of_os(&self, name: &str) -> Vec<OsString> {
637 self.values_of_os(name)
638 .unwrap_or_default()
639 .map(|s| s.to_os_string())
640 .collect()
641 }
642
643 fn _is_present(&self, name: &str) -> bool {
644 self.is_present(name)
645 }
646}
647
648pub fn values(args: &ArgMatches<'_>, name: &str) -> Vec<String> {
649 args._values_of(name)
650}
651
652pub fn values_os(args: &ArgMatches<'_>, name: &str) -> Vec<OsString> {
653 args._values_of_os(name)
654}
655
656#[derive(PartialEq, PartialOrd, Eq, Ord)]
657pub enum CommandInfo {
658 BuiltIn { name: String, about: Option<String> },
659 External { name: String, path: PathBuf },
660}
661
662impl CommandInfo {
663 pub fn name(&self) -> &str {
664 match self {
665 CommandInfo::BuiltIn { name, .. } => name,
666 CommandInfo::External { name, .. } => name,
667 }
668 }
669}