Skip to content

Commit 972b9f5

Browse files
committed
Auto merge of #7837 - ehuss:fix-global-opt-alias, r=alexcrichton
Fix using global options before an alias. Options before an alias were being ignored (like `cargo -v b`). The solution is to extract those global options before expanding an alias, and then merging it later. An alternative to this is to try to avoid discarding the options during expansion, but I couldn't figure out a way to get the position of the subcommand in the argument list. Clap only provides a way to get the arguments *following* the subcommand. I also cleaned up some of the code in `Config::configure`, which was carrying some weird baggage from previous refactorings. Fixes #7834
2 parents db11258 + 0279e8e commit 972b9f5

File tree

5 files changed

+104
-40
lines changed

5 files changed

+104
-40
lines changed

crates/resolver-tests/tests/resolve.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ proptest! {
6161
config
6262
.configure(
6363
1,
64-
None,
64+
false,
6565
None,
6666
false,
6767
false,
@@ -569,7 +569,7 @@ fn test_resolving_minimum_version_with_transitive_deps() {
569569
config
570570
.configure(
571571
1,
572-
None,
572+
false,
573573
None,
574574
false,
575575
false,

src/bin/cargo/cli.rs

+76-21
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,19 @@ Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
9191
return Ok(());
9292
}
9393

94-
let args = expand_aliases(config, args)?;
95-
let (cmd, subcommand_args) = match args.subcommand() {
94+
// Global args need to be extracted before expanding aliases because the
95+
// clap code for extracting a subcommand discards global options
96+
// (appearing before the subcommand).
97+
let (expanded_args, global_args) = expand_aliases(config, args)?;
98+
let (cmd, subcommand_args) = match expanded_args.subcommand() {
9699
(cmd, Some(args)) => (cmd, args),
97100
_ => {
98101
// No subcommand provided.
99102
cli().print_help()?;
100103
return Ok(());
101104
}
102105
};
103-
config_configure(config, &args, subcommand_args)?;
106+
config_configure(config, &expanded_args, subcommand_args, global_args)?;
104107
super::init_git_transports(config);
105108

106109
execute_subcommand(config, cmd, subcommand_args)
@@ -128,7 +131,7 @@ pub fn get_version_string(is_verbose: bool) -> String {
128131
fn expand_aliases(
129132
config: &mut Config,
130133
args: ArgMatches<'static>,
131-
) -> Result<ArgMatches<'static>, CliError> {
134+
) -> Result<(ArgMatches<'static>, GlobalArgs), CliError> {
132135
if let (cmd, Some(args)) = args.subcommand() {
133136
match (
134137
commands::builtin_exec(cmd),
@@ -147,41 +150,60 @@ fn expand_aliases(
147150
.unwrap_or_default()
148151
.map(|s| s.to_string()),
149152
);
150-
let args = cli()
153+
// new_args strips out everything before the subcommand, so
154+
// capture those global options now.
155+
// Note that an alias to an external command will not receive
156+
// these arguments. That may be confusing, but such is life.
157+
let global_args = GlobalArgs::new(&args);
158+
let new_args = cli()
151159
.setting(AppSettings::NoBinaryName)
152160
.get_matches_from_safe(alias)?;
153-
return expand_aliases(config, args);
161+
let (expanded_args, _) = expand_aliases(config, new_args)?;
162+
return Ok((expanded_args, global_args));
154163
}
155164
(_, None) => {}
156165
}
157166
};
158167

159-
Ok(args)
168+
Ok((args, GlobalArgs::default()))
160169
}
161170

162171
fn config_configure(
163172
config: &mut Config,
164173
args: &ArgMatches<'_>,
165174
subcommand_args: &ArgMatches<'_>,
175+
global_args: GlobalArgs,
166176
) -> CliResult {
167177
let arg_target_dir = &subcommand_args.value_of_path("target-dir", config);
168-
let config_args: Vec<&str> = args.values_of("config").unwrap_or_default().collect();
169-
let quiet = if args.is_present("quiet") || subcommand_args.is_present("quiet") {
170-
Some(true)
171-
} else {
172-
None
173-
};
178+
let verbose = global_args.verbose + args.occurrences_of("verbose") as u32;
179+
// quiet is unusual because it is redefined in some subcommands in order
180+
// to provide custom help text.
181+
let quiet =
182+
args.is_present("quiet") || subcommand_args.is_present("quiet") || global_args.quiet;
183+
let global_color = global_args.color; // Extract so it can take reference.
184+
let color = args
185+
.value_of("color")
186+
.or_else(|| global_color.as_ref().map(|s| s.as_ref()));
187+
let frozen = args.is_present("frozen") || global_args.frozen;
188+
let locked = args.is_present("locked") || global_args.locked;
189+
let offline = args.is_present("offline") || global_args.offline;
190+
let mut unstable_flags = global_args.unstable_flags;
191+
if let Some(values) = args.values_of("unstable-features") {
192+
unstable_flags.extend(values.map(|s| s.to_string()));
193+
}
194+
let mut config_args = global_args.config_args;
195+
if let Some(values) = args.values_of("config") {
196+
config_args.extend(values.map(|s| s.to_string()));
197+
}
174198
config.configure(
175-
args.occurrences_of("verbose") as u32,
199+
verbose,
176200
quiet,
177-
args.value_of("color"),
178-
args.is_present("frozen"),
179-
args.is_present("locked"),
180-
args.is_present("offline"),
201+
color,
202+
frozen,
203+
locked,
204+
offline,
181205
arg_target_dir,
182-
&args
183-
.values_of_lossy("unstable-features")
184-
.unwrap_or_default(),
206+
&unstable_flags,
185207
&config_args,
186208
)?;
187209
Ok(())
@@ -201,6 +223,39 @@ fn execute_subcommand(
201223
super::execute_external_subcommand(config, cmd, &ext_args)
202224
}
203225

226+
#[derive(Default)]
227+
struct GlobalArgs {
228+
verbose: u32,
229+
quiet: bool,
230+
color: Option<String>,
231+
frozen: bool,
232+
locked: bool,
233+
offline: bool,
234+
unstable_flags: Vec<String>,
235+
config_args: Vec<String>,
236+
}
237+
238+
impl GlobalArgs {
239+
fn new(args: &ArgMatches<'_>) -> GlobalArgs {
240+
GlobalArgs {
241+
verbose: args.occurrences_of("verbose") as u32,
242+
quiet: args.is_present("quiet"),
243+
color: args.value_of("color").map(|s| s.to_string()),
244+
frozen: args.is_present("frozen"),
245+
locked: args.is_present("locked"),
246+
offline: args.is_present("offline"),
247+
unstable_flags: args
248+
.values_of_lossy("unstable-features")
249+
.unwrap_or_default(),
250+
config_args: args
251+
.values_of("config")
252+
.unwrap_or_default()
253+
.map(|s| s.to_string())
254+
.collect(),
255+
}
256+
}
257+
}
258+
204259
fn cli() -> App {
205260
App::new("cargo")
206261
.settings(&[

src/cargo/util/config/mod.rs

+8-14
Original file line numberDiff line numberDiff line change
@@ -602,14 +602,14 @@ impl Config {
602602
pub fn configure(
603603
&mut self,
604604
verbose: u32,
605-
quiet: Option<bool>,
605+
quiet: bool,
606606
color: Option<&str>,
607607
frozen: bool,
608608
locked: bool,
609609
offline: bool,
610610
target_dir: &Option<PathBuf>,
611611
unstable_flags: &[String],
612-
cli_config: &[&str],
612+
cli_config: &[String],
613613
) -> CargoResult<()> {
614614
self.unstable_flags.parse(unstable_flags)?;
615615
if !cli_config.is_empty() {
@@ -618,7 +618,7 @@ impl Config {
618618
self.merge_cli_args()?;
619619
}
620620
let extra_verbose = verbose >= 2;
621-
let verbose = if verbose == 0 { None } else { Some(true) };
621+
let verbose = verbose != 0;
622622

623623
#[derive(Deserialize, Default)]
624624
struct TermConfig {
@@ -632,25 +632,19 @@ impl Config {
632632
let color = color.or_else(|| term.color.as_ref().map(|s| s.as_ref()));
633633

634634
let verbosity = match (verbose, term.verbose, quiet) {
635-
(Some(true), _, None) | (None, Some(true), None) => Verbosity::Verbose,
635+
(true, _, false) | (_, Some(true), false) => Verbosity::Verbose,
636636

637637
// Command line takes precedence over configuration, so ignore the
638638
// configuration..
639-
(None, _, Some(true)) => Verbosity::Quiet,
639+
(false, _, true) => Verbosity::Quiet,
640640

641641
// Can't pass both at the same time on the command line regardless
642642
// of configuration.
643-
(Some(true), _, Some(true)) => {
643+
(true, _, true) => {
644644
bail!("cannot set both --verbose and --quiet");
645645
}
646646

647-
// Can't actually get `Some(false)` as a value from the command
648-
// line, so just ignore them here to appease exhaustiveness checking
649-
// in match statements.
650-
(Some(false), _, _)
651-
| (_, _, Some(false))
652-
| (None, Some(false), None)
653-
| (None, None, None) => Verbosity::Normal,
647+
(false, _, false) => Verbosity::Normal,
654648
};
655649

656650
let cli_target_dir = match target_dir.as_ref() {
@@ -659,7 +653,7 @@ impl Config {
659653
};
660654

661655
self.shell().set_verbosity(verbosity);
662-
self.shell().set_color_choice(color.map(|s| &s[..]))?;
656+
self.shell().set_color_choice(color)?;
663657
self.extra_verbose = extra_verbose;
664658
self.frozen = frozen;
665659
self.locked = locked;

tests/testsuite/cargo_alias_config.rs

+16
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,19 @@ fn builtin_alias_takes_options() {
176176

177177
p.cargo("r --example ex1 -- asdf").with_stdout("asdf").run();
178178
}
179+
180+
#[cargo_test]
181+
fn global_options_with_alias() {
182+
// Check that global options are passed through.
183+
let p = project().file("src/lib.rs", "").build();
184+
185+
p.cargo("-v c")
186+
.with_stderr(
187+
"\
188+
[CHECKING] foo [..]
189+
[RUNNING] `rustc [..]
190+
[FINISHED] dev [..]
191+
",
192+
)
193+
.run();
194+
}

tests/testsuite/config.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,16 @@ impl ConfigBuilder {
7676
let homedir = paths::home();
7777
let mut config = Config::new(shell, cwd, homedir);
7878
config.set_env(self.env.clone());
79-
let config_args: Vec<&str> = self.config_args.iter().map(AsRef::as_ref).collect();
8079
config.configure(
8180
0,
82-
None,
81+
false,
8382
None,
8483
false,
8584
false,
8685
false,
8786
&None,
8887
&self.unstable,
89-
&config_args,
88+
&self.config_args,
9089
)?;
9190
Ok(config)
9291
}

0 commit comments

Comments
 (0)