Skip to content

Commit d325f9b

Browse files
committed
Auto merge of #13463 - epage:term_color, r=weihanglo
fix(cli): Control clap colors through config ### What does this PR try to resolve? Part of #9012 ### How should we test and review this PR? To accomplish this, I pivoted in how we handle `-C`. In #11029, I made the config lazily loaded. Instead, we are now reloading the config for `-C` like we do for "cargo script" and `cargo install`. If there is any regression, it will be felt by those commands as well and we can fix all together. As this is unstable, if there is a regression, the impact is less. This allowed us access to the full config for getting the color setting, rather than taking more limited approaches like checking only `CARGO_TERM_CONFIG`. ### Additional information
2 parents 3e69220 + 6747425 commit d325f9b

File tree

5 files changed

+150
-144
lines changed

5 files changed

+150
-144
lines changed

src/bin/cargo/cli.rs

+27-58
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::{anyhow, Context as _};
2-
use cargo::core::shell::Shell;
32
use cargo::core::{features, CliUnstable};
3+
use cargo::util::config::TermConfig;
44
use cargo::{drop_print, drop_println, CargoResult};
55
use clap::builder::UnknownArgumentValueParser;
66
use itertools::Itertools;
@@ -13,14 +13,17 @@ use super::commands;
1313
use super::list_commands;
1414
use crate::command_prelude::*;
1515
use crate::util::is_rustup;
16+
use cargo::core::shell::ColorChoice;
1617
use cargo::util::style;
1718

18-
pub fn main(lazy_gctx: &mut LazyContext) -> CliResult {
19-
let args = cli().try_get_matches()?;
19+
pub fn main(gctx: &mut GlobalContext) -> CliResult {
20+
// CAUTION: Be careful with using `config` until it is configured below.
21+
// In general, try to avoid loading config values unless necessary (like
22+
// the [alias] table).
23+
24+
let args = cli(gctx).try_get_matches()?;
2025

2126
// Update the process-level notion of cwd
22-
// This must be completed before config is initialized
23-
assert_eq!(lazy_gctx.is_init(), false);
2427
if let Some(new_cwd) = args.get_one::<std::path::PathBuf>("directory") {
2528
// This is a temporary hack. This cannot access `Config`, so this is a bit messy.
2629
// This does not properly parse `-Z` flags that appear after the subcommand.
@@ -40,13 +43,9 @@ pub fn main(lazy_gctx: &mut LazyContext) -> CliResult {
4043
.into());
4144
}
4245
std::env::set_current_dir(&new_cwd).context("could not change to requested directory")?;
46+
gctx.reload_cwd()?;
4347
}
4448

45-
// CAUTION: Be careful with using `config` until it is configured below.
46-
// In general, try to avoid loading config values unless necessary (like
47-
// the [alias] table).
48-
let gctx = lazy_gctx.get_mut();
49-
5049
let (expanded_args, global_args) = expand_aliases(gctx, args, vec![])?;
5150

5251
if expanded_args
@@ -175,7 +174,7 @@ Run with `{literal}cargo -Z{literal:#} {placeholder}[FLAG] [COMMAND]{placeholder
175174
Some((cmd, args)) => (cmd, args),
176175
_ => {
177176
// No subcommand provided.
178-
cli().print_help()?;
177+
cli(gctx).print_help()?;
179178
return Ok(());
180179
}
181180
};
@@ -338,7 +337,7 @@ For more information, see issue #12207 <https://github.com/rust-lang/cargo/issue
338337
// Note that an alias to an external command will not receive
339338
// these arguments. That may be confusing, but such is life.
340339
let global_args = GlobalArgs::new(sub_args);
341-
let new_args = cli().no_binary_name(true).try_get_matches_from(alias)?;
340+
let new_args = cli(gctx).no_binary_name(true).try_get_matches_from(alias)?;
342341

343342
let new_cmd = new_args.subcommand_name().expect("subcommand is required");
344343
already_expanded.push(cmd.to_string());
@@ -514,7 +513,19 @@ impl GlobalArgs {
514513
}
515514
}
516515

517-
pub fn cli() -> Command {
516+
pub fn cli(gctx: &GlobalContext) -> Command {
517+
// Don't let config errors get in the way of parsing arguments
518+
let term = gctx.get::<TermConfig>("term").unwrap_or_default();
519+
let color = term
520+
.color
521+
.and_then(|c| c.parse().ok())
522+
.unwrap_or(ColorChoice::CargoAuto);
523+
let color = match color {
524+
ColorChoice::Always => clap::ColorChoice::Always,
525+
ColorChoice::Never => clap::ColorChoice::Never,
526+
ColorChoice::CargoAuto => clap::ColorChoice::Auto,
527+
};
528+
518529
let usage = if is_rustup() {
519530
color_print::cstr!("<cyan,bold>cargo</> <cyan>[+toolchain] [OPTIONS] [COMMAND]</>\n <cyan,bold>cargo</> <cyan>[+toolchain] [OPTIONS]</> <cyan,bold>-Zscript</> <cyan><<MANIFEST_RS>> [ARGS]...</>")
520531
} else {
@@ -539,6 +550,7 @@ pub fn cli() -> Command {
539550
// We also want these to come before auto-generated `--help`
540551
.next_display_order(800)
541552
.allow_external_subcommands(true)
553+
.color(color)
542554
.styles(styles)
543555
// Provide a custom help subcommand for calling into man pages
544556
.disable_help_subcommand(true)
@@ -646,53 +658,10 @@ See '<cyan,bold>cargo help</> <cyan><<command>></>' for more information on a sp
646658
.subcommands(commands::builtin())
647659
}
648660

649-
/// Delay loading [`GlobalContext`] until access.
650-
///
651-
/// In the common path, the [`GlobalContext`] is dependent on CLI parsing and shouldn't be loaded until
652-
/// after that is done but some other paths (like fix or earlier errors) might need access to it,
653-
/// so this provides a way to share the instance and the implementation across these different
654-
/// accesses.
655-
pub struct LazyContext {
656-
gctx: Option<GlobalContext>,
657-
}
658-
659-
impl LazyContext {
660-
pub fn new() -> Self {
661-
Self { gctx: None }
662-
}
663-
664-
/// Check whether the config is loaded
665-
///
666-
/// This is useful for asserts in case the environment needs to be setup before loading
667-
pub fn is_init(&self) -> bool {
668-
self.gctx.is_some()
669-
}
670-
671-
/// Get the config, loading it if needed
672-
///
673-
/// On error, the process is terminated
674-
pub fn get(&mut self) -> &GlobalContext {
675-
self.get_mut()
676-
}
677-
678-
/// Get the config, loading it if needed
679-
///
680-
/// On error, the process is terminated
681-
pub fn get_mut(&mut self) -> &mut GlobalContext {
682-
self.gctx
683-
.get_or_insert_with(|| match GlobalContext::default() {
684-
Ok(cfg) => cfg,
685-
Err(e) => {
686-
let mut shell = Shell::new();
687-
cargo::exit_with_error(e.into(), &mut shell)
688-
}
689-
})
690-
}
691-
}
692-
693661
#[test]
694662
fn verify_cli() {
695-
cli().debug_assert();
663+
let gctx = GlobalContext::default().unwrap();
664+
cli(&gctx).debug_assert();
696665
}
697666

698667
#[test]

src/bin/cargo/commands/help.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
3939
}
4040
}
4141
} else {
42-
let mut cmd = crate::cli::cli();
42+
let mut cmd = crate::cli::cli(gctx);
4343
let _ = cmd.print_help();
4444
}
4545
Ok(())

src/bin/cargo/main.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(clippy::self_named_module_files)] // false positive in `commands/build.rs`
22

3+
use cargo::core::shell::Shell;
34
use cargo::util::network::http::http_handle;
45
use cargo::util::network::http::needs_custom_http_transport;
56
use cargo::util::{self, closest_msg, command_prelude, CargoResult};
@@ -19,17 +20,23 @@ use crate::command_prelude::*;
1920
fn main() {
2021
setup_logger();
2122

22-
let mut lazy_gctx = cli::LazyContext::new();
23+
let mut gctx = match GlobalContext::default() {
24+
Ok(gctx) => gctx,
25+
Err(e) => {
26+
let mut shell = Shell::new();
27+
cargo::exit_with_error(e.into(), &mut shell)
28+
}
29+
};
2330

2431
let result = if let Some(lock_addr) = cargo::ops::fix_get_proxy_lock_addr() {
25-
cargo::ops::fix_exec_rustc(lazy_gctx.get(), &lock_addr).map_err(|e| CliError::from(e))
32+
cargo::ops::fix_exec_rustc(&gctx, &lock_addr).map_err(|e| CliError::from(e))
2633
} else {
2734
let _token = cargo::util::job::setup();
28-
cli::main(&mut lazy_gctx)
35+
cli::main(&mut gctx)
2936
};
3037

3138
match result {
32-
Err(e) => cargo::exit_with_error(e, &mut lazy_gctx.get_mut().shell()),
39+
Err(e) => cargo::exit_with_error(e, &mut *gctx.shell()),
3340
Ok(()) => {}
3441
}
3542
}

src/cargo/core/shell.rs

+86-75
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,6 @@ use crate::util::errors::CargoResult;
99
use crate::util::hostname;
1010
use crate::util::style::*;
1111

12-
pub enum TtyWidth {
13-
NoTty,
14-
Known(usize),
15-
Guess(usize),
16-
}
17-
18-
impl TtyWidth {
19-
/// Returns the width of the terminal to use for diagnostics (which is
20-
/// relayed to rustc via `--diagnostic-width`).
21-
pub fn diagnostic_terminal_width(&self) -> Option<usize> {
22-
// ALLOWED: For testing cargo itself only.
23-
#[allow(clippy::disallowed_methods)]
24-
if let Ok(width) = std::env::var("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS") {
25-
return Some(width.parse().unwrap());
26-
}
27-
match *self {
28-
TtyWidth::NoTty | TtyWidth::Guess(_) => None,
29-
TtyWidth::Known(width) => Some(width),
30-
}
31-
}
32-
33-
/// Returns the width used by progress bars for the tty.
34-
pub fn progress_max_width(&self) -> Option<usize> {
35-
match *self {
36-
TtyWidth::NoTty => None,
37-
TtyWidth::Known(width) | TtyWidth::Guess(width) => Some(width),
38-
}
39-
}
40-
}
41-
42-
/// The requested verbosity of output.
43-
#[derive(Debug, Clone, Copy, PartialEq)]
44-
pub enum Verbosity {
45-
Verbose,
46-
Normal,
47-
Quiet,
48-
}
49-
5012
/// An abstraction around console output that remembers preferences for output
5113
/// verbosity and color.
5214
pub struct Shell {
@@ -77,31 +39,6 @@ impl fmt::Debug for Shell {
7739
}
7840
}
7941

80-
/// A `Write`able object, either with or without color support
81-
enum ShellOut {
82-
/// A plain write object without color support
83-
Write(AutoStream<Box<dyn Write>>),
84-
/// Color-enabled stdio, with information on whether color should be used
85-
Stream {
86-
stdout: AutoStream<std::io::Stdout>,
87-
stderr: AutoStream<std::io::Stderr>,
88-
stderr_tty: bool,
89-
color_choice: ColorChoice,
90-
hyperlinks: bool,
91-
},
92-
}
93-
94-
/// Whether messages should use color output
95-
#[derive(Debug, PartialEq, Clone, Copy)]
96-
pub enum ColorChoice {
97-
/// Force color output
98-
Always,
99-
/// Force disable color output
100-
Never,
101-
/// Intelligently guess whether to use color output
102-
CargoAuto,
103-
}
104-
10542
impl Shell {
10643
/// Creates a new shell (color choice and verbosity), defaulting to 'auto' color and verbose
10744
/// output.
@@ -299,18 +236,10 @@ impl Shell {
299236
..
300237
} = self.output
301238
{
302-
let cfg = match color {
303-
Some("always") => ColorChoice::Always,
304-
Some("never") => ColorChoice::Never,
305-
306-
Some("auto") | None => ColorChoice::CargoAuto,
307-
308-
Some(arg) => anyhow::bail!(
309-
"argument for --color must be auto, always, or \
310-
never, but found `{}`",
311-
arg
312-
),
313-
};
239+
let cfg = color
240+
.map(|c| c.parse())
241+
.transpose()?
242+
.unwrap_or(ColorChoice::CargoAuto);
314243
*color_choice = cfg;
315244
let stdout_choice = cfg.to_anstream_color_choice();
316245
let stderr_choice = cfg.to_anstream_color_choice();
@@ -444,6 +373,20 @@ impl Default for Shell {
444373
}
445374
}
446375

376+
/// A `Write`able object, either with or without color support
377+
enum ShellOut {
378+
/// A plain write object without color support
379+
Write(AutoStream<Box<dyn Write>>),
380+
/// Color-enabled stdio, with information on whether color should be used
381+
Stream {
382+
stdout: AutoStream<std::io::Stdout>,
383+
stderr: AutoStream<std::io::Stderr>,
384+
stderr_tty: bool,
385+
color_choice: ColorChoice,
386+
hyperlinks: bool,
387+
},
388+
}
389+
447390
impl ShellOut {
448391
/// Prints out a message with a status. The status comes first, and is bold plus the given
449392
/// color. The status can be justified, in which case the max width that will right align is
@@ -488,6 +431,55 @@ impl ShellOut {
488431
}
489432
}
490433

434+
pub enum TtyWidth {
435+
NoTty,
436+
Known(usize),
437+
Guess(usize),
438+
}
439+
440+
impl TtyWidth {
441+
/// Returns the width of the terminal to use for diagnostics (which is
442+
/// relayed to rustc via `--diagnostic-width`).
443+
pub fn diagnostic_terminal_width(&self) -> Option<usize> {
444+
// ALLOWED: For testing cargo itself only.
445+
#[allow(clippy::disallowed_methods)]
446+
if let Ok(width) = std::env::var("__CARGO_TEST_TTY_WIDTH_DO_NOT_USE_THIS") {
447+
return Some(width.parse().unwrap());
448+
}
449+
match *self {
450+
TtyWidth::NoTty | TtyWidth::Guess(_) => None,
451+
TtyWidth::Known(width) => Some(width),
452+
}
453+
}
454+
455+
/// Returns the width used by progress bars for the tty.
456+
pub fn progress_max_width(&self) -> Option<usize> {
457+
match *self {
458+
TtyWidth::NoTty => None,
459+
TtyWidth::Known(width) | TtyWidth::Guess(width) => Some(width),
460+
}
461+
}
462+
}
463+
464+
/// The requested verbosity of output.
465+
#[derive(Debug, Clone, Copy, PartialEq)]
466+
pub enum Verbosity {
467+
Verbose,
468+
Normal,
469+
Quiet,
470+
}
471+
472+
/// Whether messages should use color output
473+
#[derive(Debug, PartialEq, Clone, Copy)]
474+
pub enum ColorChoice {
475+
/// Force color output
476+
Always,
477+
/// Force disable color output
478+
Never,
479+
/// Intelligently guess whether to use color output
480+
CargoAuto,
481+
}
482+
491483
impl ColorChoice {
492484
/// Converts our color choice to anstream's version.
493485
fn to_anstream_color_choice(self) -> anstream::ColorChoice {
@@ -499,6 +491,25 @@ impl ColorChoice {
499491
}
500492
}
501493

494+
impl std::str::FromStr for ColorChoice {
495+
type Err = anyhow::Error;
496+
fn from_str(color: &str) -> Result<Self, Self::Err> {
497+
let cfg = match color {
498+
"always" => ColorChoice::Always,
499+
"never" => ColorChoice::Never,
500+
501+
"auto" => ColorChoice::CargoAuto,
502+
503+
arg => anyhow::bail!(
504+
"argument for --color must be auto, always, or \
505+
never, but found `{}`",
506+
arg
507+
),
508+
};
509+
Ok(cfg)
510+
}
511+
}
512+
502513
fn supports_color(choice: anstream::ColorChoice) -> bool {
503514
match choice {
504515
anstream::ColorChoice::Always

0 commit comments

Comments
 (0)