Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli): Respect CARGO_TERM_COLOR in '--list' and '-Zhelp' #13479

Merged
merged 5 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 133 additions & 117 deletions src/bin/cargo/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,141 +48,155 @@ pub fn main(gctx: &mut GlobalContext) -> CliResult {

let (expanded_args, global_args) = expand_aliases(gctx, args, vec![])?;

let is_verbose = expanded_args.verbose() > 0;

if expanded_args
.get_one::<String>("unstable-features")
.map(String::as_str)
== Some("help")
{
let header = style::HEADER;
let literal = style::LITERAL;
let placeholder = style::PLACEHOLDER;

let options = CliUnstable::help();
let max_length = options
.iter()
.filter(|(_, help)| help.is_some())
.map(|(option_name, _)| option_name.len())
.max()
.unwrap_or(0);
let z_flags = options
.iter()
.filter(|(_, help)| help.is_some())
.map(|(opt, help)| {
let opt = opt.replace("_", "-");
let help = help.unwrap();
format!(" {literal}-Z {opt:<max_length$}{literal:#} {help}")
})
.join("\n");
drop_println!(
// Don't let config errors get in the way of parsing arguments
let _ = config_configure(gctx, &expanded_args, None, global_args, None);
print_zhelp(gctx);
} else if expanded_args.flag("version") {
// Don't let config errors get in the way of parsing arguments
let _ = config_configure(gctx, &expanded_args, None, global_args, None);
let version = get_version_string(is_verbose);
drop_print!(gctx, "{}", version);
} else if let Some(code) = expanded_args.get_one::<String>("explain") {
// Don't let config errors get in the way of parsing arguments
let _ = config_configure(gctx, &expanded_args, None, global_args, None);
let mut procss = gctx.load_global_rustc(None)?.process();
procss.arg("--explain").arg(code).exec()?;
} else if expanded_args.flag("list") {
// Don't let config errors get in the way of parsing arguments
let _ = config_configure(gctx, &expanded_args, None, global_args, None);
print_list(gctx, is_verbose);
} else {
let (cmd, subcommand_args) = match expanded_args.subcommand() {
Some((cmd, args)) => (cmd, args),
_ => {
// No subcommand provided.
cli(gctx).print_help()?;
return Ok(());
}
};
let exec = Exec::infer(cmd)?;
config_configure(
gctx,
"\
&expanded_args,
Some(subcommand_args),
global_args,
Some(&exec),
)?;
super::init_git(gctx);

exec.exec(gctx, subcommand_args)?;
}
Ok(())
}

fn print_zhelp(gctx: &GlobalContext) {
let header = style::HEADER;
let literal = style::LITERAL;
let placeholder = style::PLACEHOLDER;

let options = CliUnstable::help();
let max_length = options
.iter()
.filter(|(_, help)| help.is_some())
.map(|(option_name, _)| option_name.len())
.max()
.unwrap_or(0);
let z_flags = options
.iter()
.filter(|(_, help)| help.is_some())
.map(|(opt, help)| {
let opt = opt.replace("_", "-");
let help = help.unwrap();
format!(" {literal}-Z {opt:<max_length$}{literal:#} {help}")
})
.join("\n");
drop_println!(
gctx,
"\
{header}Available unstable (nightly-only) flags:{header:#}

{z_flags}

Run with `{literal}cargo -Z{literal:#} {placeholder}[FLAG] [COMMAND]{placeholder:#}`",
);
if !gctx.nightly_features_allowed {
drop_println!(
gctx,
"\nUnstable flags are only available on the nightly channel \
of Cargo, but this is the `{}` channel.\n\
{}",
features::channel(),
features::SEE_CHANNELS
);
}
);
if !gctx.nightly_features_allowed {
drop_println!(
gctx,
"\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
for more information about these flags."
"\nUnstable flags are only available on the nightly channel \
of Cargo, but this is the `{}` channel.\n\
{}",
features::channel(),
features::SEE_CHANNELS
);
return Ok(());
}

let is_verbose = expanded_args.verbose() > 0;
if expanded_args.flag("version") {
let version = get_version_string(is_verbose);
drop_print!(gctx, "{}", version);
return Ok(());
}

if let Some(code) = expanded_args.get_one::<String>("explain") {
let mut procss = gctx.load_global_rustc(None)?.process();
procss.arg("--explain").arg(code).exec()?;
return Ok(());
}
drop_println!(
gctx,
"\nSee https://doc.rust-lang.org/nightly/cargo/reference/unstable.html \
for more information about these flags."
);
}

if expanded_args.flag("list") {
// Maps from commonly known external commands (not builtin to cargo)
// to their description, for the help page. Reserved for external
// subcommands that are core within the rust ecosystem (esp ones that
// might become internal in the future).
let known_external_command_descriptions = HashMap::from([
(
"clippy",
"Checks a package to catch common mistakes and improve your Rust code.",
),
(
"fmt",
"Formats all bin and lib files of the current crate using rustfmt.",
),
]);
drop_println!(
gctx,
color_print::cstr!("<green,bold>Installed Commands:</>")
);
for (name, command) in list_commands(gctx) {
let known_external_desc = known_external_command_descriptions.get(name.as_str());
let literal = style::LITERAL;
match command {
CommandInfo::BuiltIn { about } => {
assert!(
known_external_desc.is_none(),
"known_external_commands shouldn't contain builtin `{name}`",
);
let summary = about.unwrap_or_default();
let summary = summary.lines().next().unwrap_or(&summary); // display only the first line
drop_println!(gctx, " {literal}{name:<20}{literal:#} {summary}");
}
CommandInfo::External { path } => {
if let Some(desc) = known_external_desc {
drop_println!(gctx, " {literal}{name:<20}{literal:#} {desc}");
} else if is_verbose {
drop_println!(
gctx,
" {literal}{name:<20}{literal:#} {}",
path.display()
);
} else {
drop_println!(gctx, " {literal}{name}{literal:#}");
}
}
CommandInfo::Alias { target } => {
fn print_list(gctx: &GlobalContext, is_verbose: bool) {
// Maps from commonly known external commands (not builtin to cargo)
// to their description, for the help page. Reserved for external
// subcommands that are core within the rust ecosystem (esp ones that
// might become internal in the future).
let known_external_command_descriptions = HashMap::from([
(
"clippy",
"Checks a package to catch common mistakes and improve your Rust code.",
),
(
"fmt",
"Formats all bin and lib files of the current crate using rustfmt.",
),
]);
drop_println!(
gctx,
color_print::cstr!("<green,bold>Installed Commands:</>")
);
for (name, command) in list_commands(gctx) {
let known_external_desc = known_external_command_descriptions.get(name.as_str());
let literal = style::LITERAL;
match command {
CommandInfo::BuiltIn { about } => {
assert!(
known_external_desc.is_none(),
"known_external_commands shouldn't contain builtin `{name}`",
);
let summary = about.unwrap_or_default();
let summary = summary.lines().next().unwrap_or(&summary); // display only the first line
drop_println!(gctx, " {literal}{name:<20}{literal:#} {summary}");
}
CommandInfo::External { path } => {
if let Some(desc) = known_external_desc {
drop_println!(gctx, " {literal}{name:<20}{literal:#} {desc}");
} else if is_verbose {
drop_println!(
gctx,
" {literal}{name:<20}{literal:#} alias: {}",
target.iter().join(" ")
" {literal}{name:<20}{literal:#} {}",
path.display()
);
} else {
drop_println!(gctx, " {literal}{name}{literal:#}");
}
}
CommandInfo::Alias { target } => {
drop_println!(
gctx,
" {literal}{name:<20}{literal:#} alias: {}",
target.iter().join(" ")
);
}
}
return Ok(());
}

let (cmd, subcommand_args) = match expanded_args.subcommand() {
Some((cmd, args)) => (cmd, args),
_ => {
// No subcommand provided.
cli(gctx).print_help()?;
return Ok(());
}
};
let exec = Exec::infer(cmd)?;
config_configure(gctx, &expanded_args, subcommand_args, global_args, &exec)?;
super::init_git(gctx);

exec.exec(gctx, subcommand_args)
}

pub fn get_version_string(is_verbose: bool) -> String {
Expand Down Expand Up @@ -365,16 +379,18 @@ For more information, see issue #12207 <https://github.com/rust-lang/cargo/issue
fn config_configure(
gctx: &mut GlobalContext,
args: &ArgMatches,
subcommand_args: &ArgMatches,
subcommand_args: Option<&ArgMatches>,
global_args: GlobalArgs,
exec: &Exec,
exec: Option<&Exec>,
) -> CliResult {
let arg_target_dir = &subcommand_args.value_of_path("target-dir", gctx);
let arg_target_dir = &subcommand_args.and_then(|a| a.value_of_path("target-dir", gctx));
let mut verbose = global_args.verbose + args.verbose();
// quiet is unusual because it is redefined in some subcommands in order
// to provide custom help text.
let mut quiet = args.flag("quiet") || subcommand_args.flag("quiet") || global_args.quiet;
if matches!(exec, Exec::Manifest(_)) && !quiet {
let mut quiet = args.flag("quiet")
|| subcommand_args.map(|a| a.flag("quiet")).unwrap_or_default()
|| global_args.quiet;
if matches!(exec, Some(Exec::Manifest(_))) && !quiet {
// Verbosity is shifted quieter for `Exec::Manifest` as it is can be used as if you ran
// `cargo install` and we especially shouldn't pollute programmatic output.
//
Expand Down
66 changes: 33 additions & 33 deletions src/cargo/util/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1018,41 +1018,14 @@ impl GlobalContext {
unstable_flags: &[String],
cli_config: &[String],
) -> CargoResult<()> {
for warning in self
.unstable_flags
.parse(unstable_flags, self.nightly_features_allowed)?
{
self.shell().warn(warning)?;
}
if !unstable_flags.is_empty() {
// store a copy of the cli flags separately for `load_unstable_flags_from_config`
// (we might also need it again for `reload_rooted_at`)
self.unstable_flags_cli = Some(unstable_flags.to_vec());
}
if !cli_config.is_empty() {
self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
self.merge_cli_args()?;
}
if self.unstable_flags.config_include {
// If the config was already loaded (like when fetching the
// `[alias]` table), it was loaded with includes disabled because
// the `unstable_flags` hadn't been set up, yet. Any values
// fetched before this step will not process includes, but that
// should be fine (`[alias]` is one of the only things loaded
// before configure). This can be removed when stabilized.
self.reload_rooted_at(self.cwd.clone())?;
}
let extra_verbose = verbose >= 2;
let verbose = verbose != 0;

// Ignore errors in the configuration files. We don't want basic
// commands like `cargo version` to error out due to config file
// problems.
let term = self.get::<TermConfig>("term").unwrap_or_default();

let color = color.or_else(|| term.color.as_deref());

// The command line takes precedence over configuration.
let extra_verbose = verbose >= 2;
let verbose = verbose != 0;
let verbosity = match (verbose, quiet) {
(true, true) => bail!("cannot set both --verbose and --quiet"),
(true, false) => Verbosity::Verbose,
Expand All @@ -1066,16 +1039,17 @@ impl GlobalContext {
_ => Verbosity::Normal,
},
};

let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));

self.shell().set_verbosity(verbosity);
self.extra_verbose = extra_verbose;

let color = color.or_else(|| term.color.as_deref());
self.shell().set_color_choice(color)?;
if let Some(hyperlinks) = term.hyperlinks {
self.shell().set_hyperlinks(hyperlinks)?;
}

self.progress_config = term.progress.unwrap_or_default();
self.extra_verbose = extra_verbose;

self.frozen = frozen;
self.locked = locked;
self.offline = offline
Expand All @@ -1084,8 +1058,34 @@ impl GlobalContext {
.ok()
.and_then(|n| n.offline)
.unwrap_or(false);
let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
self.target_dir = cli_target_dir;

for warning in self
.unstable_flags
.parse(unstable_flags, self.nightly_features_allowed)?
{
self.shell().warn(warning)?;
}
if !unstable_flags.is_empty() {
// store a copy of the cli flags separately for `load_unstable_flags_from_config`
// (we might also need it again for `reload_rooted_at`)
self.unstable_flags_cli = Some(unstable_flags.to_vec());
}
if !cli_config.is_empty() {
self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
self.merge_cli_args()?;
}
if self.unstable_flags.config_include {
// If the config was already loaded (like when fetching the
// `[alias]` table), it was loaded with includes disabled because
// the `unstable_flags` hadn't been set up, yet. Any values
// fetched before this step will not process includes, but that
// should be fine (`[alias]` is one of the only things loaded
// before configure). This can be removed when stabilized.
self.reload_rooted_at(self.cwd.clone())?;
}

self.load_unstable_flags_from_config()?;

Ok(())
Expand Down