From 1b4747759a89e1acf7e164c5a8c9440cb989fbae Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Thu, 7 Jul 2022 21:02:20 -0500 Subject: [PATCH] Support cargo term variables. Use the `CARGO_TERM_VERBOSE`, `CARGO_TERM_QUIET`, and `CARGO_TERM_COLOR` environment variables for cross terminal output. This enables us to detect term settings via environment variables, which are overridden by command-line flags, which is our own behavior. Cargo has CLI flags override environment variables, and verbose and quiet cannot be set for the same level. So if `CARGO_TERM_VERBOSE=true` and `--quiet` are used, then we use the quiet setting, but if `--verbose` and `--quiet` are used, then we print a fatal error message. --- CHANGELOG.md | 1 + src/bin/commands/clean.rs | 2 +- src/bin/commands/containers.rs | 14 ++-- src/bin/commands/images.rs | 4 +- src/cli.rs | 5 +- src/shell.rs | 135 ++++++++++++++++++++++++++------ xtask/src/build_docker_image.rs | 6 +- xtask/src/crosstool.rs | 2 +- xtask/src/hooks.rs | 4 +- xtask/src/install_git_hooks.rs | 2 +- xtask/src/target_info.rs | 2 +- 11 files changed, 135 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71057813e..4a16f89cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +- #921 - use `CARGO_TERM_VERBOSE`, `CARGO_TERM_QUIET`, and `CARGO_TERM_COLOR` environment variables for cross terminal output. - #913 - added the `x86_64-unknown-illumos` target. - #905 - added `qemu-runner` for musl images, allowing use of native or emulated runners. - #905 - added qemu emulation to `i586-unknown-linux-gnu`, `i686-unknown-linux-musl`, and `i586-unknown-linux-gnu`, so they can run on an `x86` CPU, rather than an `x86_64` CPU. diff --git a/src/bin/commands/clean.rs b/src/bin/commands/clean.rs index e5b4f6914..0a93abf6b 100644 --- a/src/bin/commands/clean.rs +++ b/src/bin/commands/clean.rs @@ -13,7 +13,7 @@ pub struct Clean { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Force removal of images. diff --git a/src/bin/commands/containers.rs b/src/bin/commands/containers.rs index 796894698..51ac76ec0 100644 --- a/src/bin/commands/containers.rs +++ b/src/bin/commands/containers.rs @@ -12,7 +12,7 @@ pub struct ListVolumes { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Container engine (such as docker or podman). @@ -34,7 +34,7 @@ pub struct RemoveAllVolumes { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Force removal of volumes. @@ -62,7 +62,7 @@ pub struct PruneVolumes { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Remove volumes. Default is a dry run. @@ -93,7 +93,7 @@ pub struct CreateVolume { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Container engine (such as docker or podman). @@ -126,7 +126,7 @@ pub struct RemoveVolume { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Container engine (such as docker or podman). @@ -223,7 +223,7 @@ pub struct ListContainers { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Container engine (such as docker or podman). @@ -245,7 +245,7 @@ pub struct RemoveAllContainers { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Force removal of containers. diff --git a/src/bin/commands/images.rs b/src/bin/commands/images.rs index 14012923d..5b70efd25 100644 --- a/src/bin/commands/images.rs +++ b/src/bin/commands/images.rs @@ -20,7 +20,7 @@ pub struct ListImages { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Container engine (such as docker or podman). @@ -46,7 +46,7 @@ pub struct RemoveImages { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Force removal of images. diff --git a/src/cli.rs b/src/cli.rs index 5d618a5df..6f9d8f889 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,7 +3,7 @@ use std::{env, path::PathBuf}; use crate::cargo::Subcommand; use crate::errors::Result; use crate::rustc::TargetList; -use crate::shell::MessageInfo; +use crate::shell::{self, MessageInfo}; use crate::Target; #[derive(Debug)] @@ -143,7 +143,6 @@ pub fn parse(target_list: &TargetList) -> Result { let mut quiet = false; let mut verbose = false; let mut color = None; - let mut default_msg_info = MessageInfo::default(); { let mut args = env::args().skip(1); @@ -164,7 +163,7 @@ pub fn parse(target_list: &TargetList) -> Result { ArgKind::Next => { match parse_next_arg(arg, &mut all, ToOwned::to_owned, &mut args) { Some(c) => Some(c), - None => default_msg_info.fatal_usage("--color ", 1), + None => shell::invalid_color(None), } } ArgKind::Equal => Some(parse_equal_arg(arg, &mut all, ToOwned::to_owned)), diff --git a/src/shell.rs b/src/shell.rs index 3978d918e..90171fed5 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1,8 +1,10 @@ // This file was adapted from: // https://github.com/rust-lang/cargo/blob/ca4edabb28fc96fdf2a1d56fe3851831ac166f8a/src/cargo/core/shell.rs +use std::env; use std::fmt; use std::io::{self, Write}; +use std::str::FromStr; use crate::errors::Result; use owo_colors::{self, OwoColorize}; @@ -77,6 +79,23 @@ impl Verbosity { Self::Normal | Self::Quiet => false, } } + + fn create(color_choice: ColorChoice, verbose: bool, quiet: bool) -> Option { + match (verbose, quiet) { + (true, true) => { + MessageInfo::from(color_choice).fatal("cannot set both --verbose and --quiet", 101) + } + (true, false) => Some(Verbosity::Verbose), + (false, true) => Some(Verbosity::Quiet), + (false, false) => None, + } + } +} + +impl Default for Verbosity { + fn default() -> Verbosity { + Verbosity::Normal + } } /// Whether messages should use color output @@ -90,6 +109,21 @@ pub enum ColorChoice { Auto, } +impl FromStr for ColorChoice { + type Err = eyre::ErrReport; + + fn from_str(s: &str) -> Result { + match s { + "always" => Ok(ColorChoice::Always), + "never" => Ok(ColorChoice::Never), + "auto" => Ok(ColorChoice::Auto), + arg => eyre::bail!( + "argument for --color must be auto, always, or never, but found `{arg}`" + ), + } + } +} + // Should simplify the APIs a lot. #[derive(Debug, Clone, PartialEq, Eq)] pub struct MessageInfo { @@ -241,19 +275,62 @@ impl MessageInfo { } } - pub fn fatal_usage(&mut self, arg: T, code: i32) -> ! { - self.error_usage(arg) + pub fn fatal_usage( + &mut self, + arg: T, + provided: Option<&str>, + possible: Option<&[&str]>, + code: i32, + ) -> ! { + self.error_usage(arg, provided, possible) .expect("could not display usage message"); std::process::exit(code); } - fn error_usage(&mut self, arg: T) -> Result<()> { + fn error_usage( + &mut self, + arg: T, + provided: Option<&str>, + possible: Option<&[&str]>, + ) -> Result<()> { let mut stream = io::stderr(); write_style!(stream, self, cross_prefix!("error"), bold, red); write_style!(stream, self, ":", bold); - write_style!(stream, self, " The argument '"); - write_style!(stream, self, arg, yellow); - write_style!(stream, self, "' requires a value but none was supplied\n"); + match provided { + Some(value) => { + write_style!( + stream, + self, + format_args!(" \"{value}\" isn't a valid value for '") + ); + write_style!(stream, self, arg, yellow); + write_style!(stream, self, "'\n"); + } + None => { + write_style!(stream, self, " The argument '"); + write_style!(stream, self, arg, yellow); + write_style!(stream, self, "' requires a value but none was supplied\n"); + } + } + match possible { + Some(values) if !values.is_empty() => { + let error_indent = cross_prefix!("error: ").len(); + write_style!( + stream, + self, + format_args!("{:error_indent$}[possible values: ", "") + ); + let max_index = values.len() - 1; + for (index, value) in values.iter().enumerate() { + write_style!(stream, self, value, green); + if index < max_index { + write_style!(stream, self, ", "); + } + } + write_style!(stream, self, "]\n"); + } + _ => (), + } write_style!(stream, self, "Usage:\n"); write_style!( stream, @@ -295,27 +372,39 @@ impl From<(ColorChoice, Verbosity)> for MessageInfo { } } -fn get_color_choice(color: Option<&str>) -> Result { - match color { - Some("always") => Ok(ColorChoice::Always), - Some("never") => Ok(ColorChoice::Never), - Some("auto") | None => Ok(ColorChoice::Auto), - Some(arg) => { - eyre::bail!("argument for --color must be auto, always, or never, but found `{arg}`") - } +// cargo only accepts literal booleans for some values. +pub fn cargo_envvar_bool(var: &str) -> Result { + match env::var(var).ok() { + Some(value) => value.parse::().map_err(|_ignore| { + eyre::eyre!("environment variable for `{var}` was not `true` or `false`.") + }), + None => Ok(false), } } +pub fn invalid_color(provided: Option<&str>) -> ! { + let possible = ["auto", "always", "never"]; + MessageInfo::default().fatal_usage("--color ", provided, Some(&possible), 1); +} + +fn get_color_choice(color: Option<&str>) -> Result { + Ok(match color { + Some(arg) => arg.parse().unwrap_or_else(|_| invalid_color(color)), + None => match env::var("CARGO_TERM_COLOR").ok().as_deref() { + Some(arg) => arg.parse().unwrap_or_else(|_| invalid_color(color)), + None => ColorChoice::Auto, + }, + }) +} + fn get_verbosity(color_choice: ColorChoice, verbose: bool, quiet: bool) -> Result { - match (verbose, quiet) { - (true, true) => { - MessageInfo::from(color_choice).error("cannot set both --verbose and --quiet")?; - std::process::exit(101); - } - (true, false) => Ok(Verbosity::Verbose), - (false, true) => Ok(Verbosity::Quiet), - (false, false) => Ok(Verbosity::Normal), - } + // cargo always checks the value of these variables. + let env_verbose = cargo_envvar_bool("CARGO_TERM_VERBOSE")?; + let env_quiet = cargo_envvar_bool("CARGO_TERM_QUIET")?; + Ok(match Verbosity::create(color_choice, verbose, quiet) { + Some(v) => v, + None => Verbosity::create(color_choice, env_verbose, env_quiet).unwrap_or_default(), + }) } pub trait Stream { diff --git a/xtask/src/build_docker_image.rs b/xtask/src/build_docker_image.rs index 81c52ab87..fc00310ba 100644 --- a/xtask/src/build_docker_image.rs +++ b/xtask/src/build_docker_image.rs @@ -29,7 +29,7 @@ pub struct BuildDockerImage { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Print but do not execute the build commands. @@ -112,6 +112,10 @@ pub fn build_docker_image( engine: &docker::Engine, msg_info: &mut MessageInfo, ) -> cross::Result<()> { + let verbose = match verbose { + 0 => msg_info.is_verbose() as u8, + v => v, + }; let metadata = cargo_metadata(msg_info)?; let version = metadata .get_package("cross") diff --git a/xtask/src/crosstool.rs b/xtask/src/crosstool.rs index 6c26455cf..6d12e4295 100644 --- a/xtask/src/crosstool.rs +++ b/xtask/src/crosstool.rs @@ -23,7 +23,7 @@ pub struct ConfigureCrosstool { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// The gcc version to configure for. diff --git a/xtask/src/hooks.rs b/xtask/src/hooks.rs index e1af04846..739b4f75c 100644 --- a/xtask/src/hooks.rs +++ b/xtask/src/hooks.rs @@ -18,7 +18,7 @@ pub struct Check { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Run shellcheck on all files, not just staged files. @@ -34,7 +34,7 @@ pub struct Test { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, } diff --git a/xtask/src/install_git_hooks.rs b/xtask/src/install_git_hooks.rs index 6f52418cb..e4d34fb3b 100644 --- a/xtask/src/install_git_hooks.rs +++ b/xtask/src/install_git_hooks.rs @@ -10,7 +10,7 @@ pub struct InstallGitHooks { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, } diff --git a/xtask/src/target_info.rs b/xtask/src/target_info.rs index 47e849381..08bd8c51c 100644 --- a/xtask/src/target_info.rs +++ b/xtask/src/target_info.rs @@ -19,7 +19,7 @@ pub struct TargetInfo { /// Do not print cross log messages. #[clap(short, long)] pub quiet: bool, - /// Whether messages should use color output. + /// Coloring: auto, always, never #[clap(long)] pub color: Option, /// Image registry.