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

Add a term option to configure the progress bar #8165

Merged
merged 1 commit into from
Sep 23, 2020
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
1 change: 1 addition & 0 deletions crates/cargo-test-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,7 @@ fn substitute_macros(input: &str) -> String {
("[IGNORED]", " Ignored"),
("[INSTALLED]", " Installed"),
("[REPLACED]", " Replaced"),
("[BUILDING]", " Building"),
];
let mut result = input.to_owned();
for &(pat, subst) in &macros {
Expand Down
2 changes: 2 additions & 0 deletions src/cargo/ops/cargo_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ pub fn create_bcx<'a, 'cfg>(
} = *options;
let config = ws.config();

// Perform some pre-flight validation.
match build_config.mode {
CompileMode::Test
| CompileMode::Build
Expand All @@ -309,6 +310,7 @@ pub fn create_bcx<'a, 'cfg>(
}
}
}
config.validate_term_config()?;

let target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?;

Expand Down
84 changes: 50 additions & 34 deletions src/cargo/util/config/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,12 @@ macro_rules! deserialize_method {
};
}

impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
type Error = ConfigError;

fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
impl<'config> Deserializer<'config> {
/// This is a helper for getting a CV from a file or env var.
///
/// If this returns CV::List, then don't look at the value. Handling lists
/// is deferred to ConfigSeqAccess.
fn get_cv_with_env(&self) -> Result<Option<CV>, ConfigError> {
// Determine if value comes from env, cli, or file, and merge env if
// possible.
let cv = self.config.get_cv(&self.key)?;
Expand All @@ -58,36 +57,53 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
_ => false,
};

if use_env {
// Future note: If you ever need to deserialize a non-self describing
// map type, this should implement a starts_with check (similar to how
// ConfigMapAccess does).
let env = env.unwrap();
let res: Result<V::Value, ConfigError> = if env == "true" || env == "false" {
visitor.visit_bool(env.parse().unwrap())
} else if let Ok(env) = env.parse::<i64>() {
visitor.visit_i64(env)
} else if self.config.cli_unstable().advanced_env
&& env.starts_with('[')
&& env.ends_with(']')
{
visitor.visit_seq(ConfigSeqAccess::new(self.clone())?)
} else {
// Try to merge if possible.
match cv {
Some(CV::List(_cv_list, _cv_def)) => {
visitor.visit_seq(ConfigSeqAccess::new(self.clone())?)
}
_ => {
// Note: CV::Table merging is not implemented, as env
// vars do not support table values.
visitor.visit_str(env)
}
if !use_env {
return Ok(cv);
}

// Future note: If you ever need to deserialize a non-self describing
// map type, this should implement a starts_with check (similar to how
// ConfigMapAccess does).
let env = env.unwrap();
if env == "true" {
Ok(Some(CV::Boolean(true, env_def)))
} else if env == "false" {
Ok(Some(CV::Boolean(false, env_def)))
} else if let Ok(i) = env.parse::<i64>() {
Ok(Some(CV::Integer(i, env_def)))
} else if self.config.cli_unstable().advanced_env
&& env.starts_with('[')
&& env.ends_with(']')
{
// Parsing is deferred to ConfigSeqAccess.
Ok(Some(CV::List(Vec::new(), env_def)))
} else {
// Try to merge if possible.
match cv {
Some(CV::List(cv_list, _cv_def)) => {
// Merging is deferred to ConfigSeqAccess.
Ok(Some(CV::List(cv_list, env_def)))
}
};
return res.map_err(|e| e.with_key_context(&self.key, env_def));
_ => {
// Note: CV::Table merging is not implemented, as env
// vars do not support table values. In the future, we
// could check for `{}`, and interpret it as TOML if
// that seems useful.
Ok(Some(CV::String(env.to_string(), env_def)))
}
}
}
}
}

impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
type Error = ConfigError;

fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
let cv = self.get_cv_with_env()?;
if let Some(cv) = cv {
let res: (Result<V::Value, ConfigError>, Definition) = match cv {
CV::Integer(i, def) => (visitor.visit_i64(i), def),
Expand Down
119 changes: 110 additions & 9 deletions src/cargo/util/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ pub struct Config {
build_config: LazyCell<CargoBuildConfig>,
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
doc_extern_map: LazyCell<RustdocExternMap>,
progress_config: ProgressConfig,
}

impl Config {
Expand Down Expand Up @@ -247,6 +248,7 @@ impl Config {
build_config: LazyCell::new(),
target_cfgs: LazyCell::new(),
doc_extern_map: LazyCell::new(),
progress_config: ProgressConfig::default(),
}
}

Expand Down Expand Up @@ -459,8 +461,8 @@ impl Config {

/// Get a configuration value by key.
///
/// This does NOT look at environment variables, the caller is responsible
/// for that.
/// This does NOT look at environment variables. See `get_cv_with_env` for
/// a variant that supports environment variables.
fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
log::trace!("get cv {:?}", key);
let vals = self.values()?;
Expand Down Expand Up @@ -720,13 +722,9 @@ impl Config {
let extra_verbose = verbose >= 2;
let verbose = verbose != 0;

#[derive(Deserialize, Default)]
struct TermConfig {
verbose: Option<bool>,
color: Option<String>,
}

// Ignore errors in the configuration files.
// 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());
Expand Down Expand Up @@ -754,6 +752,7 @@ impl Config {

self.shell().set_verbosity(verbosity);
self.shell().set_color_choice(color)?;
self.progress_config = term.progress.unwrap_or_default();
self.extra_verbose = extra_verbose;
self.frozen = frozen;
self.locked = locked;
Expand Down Expand Up @@ -1192,6 +1191,20 @@ impl Config {
.try_borrow_with(|| Ok(self.get::<CargoBuildConfig>("build")?))
}

pub fn progress_config(&self) -> &ProgressConfig {
&self.progress_config
}

/// This is used to validate the `term` table has valid syntax.
///
/// This is necessary because loading the term settings happens very
/// early, and in some situations (like `cargo version`) we don't want to
/// fail if there are problems with the config file.
pub fn validate_term_config(&self) -> CargoResult<()> {
drop(self.get::<TermConfig>("term")?);
Ok(())
}

/// Returns a list of [target.'cfg()'] tables.
///
/// The list is sorted by the table name.
Expand Down Expand Up @@ -1778,6 +1791,94 @@ pub struct CargoBuildConfig {
pub out_dir: Option<ConfigRelativePath>,
}

#[derive(Deserialize, Default)]
struct TermConfig {
verbose: Option<bool>,
color: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "progress_or_string")]
progress: Option<ProgressConfig>,
}

#[derive(Debug, Default, Deserialize)]
pub struct ProgressConfig {
pub when: ProgressWhen,
pub width: Option<usize>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ProgressWhen {
Auto,
Never,
Always,
}

impl Default for ProgressWhen {
fn default() -> ProgressWhen {
ProgressWhen::Auto
}
}

fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct ProgressVisitor;

impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
type Value = Option<ProgressConfig>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a string (\"auto\" or \"never\") or a table")
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match s {
"auto" => Ok(Some(ProgressConfig {
when: ProgressWhen::Auto,
width: None,
})),
"never" => Ok(Some(ProgressConfig {
when: ProgressWhen::Never,
width: None,
})),
"always" => Err(E::custom("\"always\" progress requires a `width` key")),
_ => Err(E::unknown_variant(s, &["auto", "never"])),
}
}

fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let pc = ProgressConfig::deserialize(deserializer)?;
if let ProgressConfig {
when: ProgressWhen::Always,
width: None,
} = pc
{
return Err(serde::de::Error::custom(
"\"always\" progress requires a `width` key",
));
}
Ok(Some(pc))
}
}

deserializer.deserialize_option(ProgressVisitor)
}

/// A type to deserialize a list of strings from a toml file.
///
/// Supports deserializing either a whitespace-separated list of arguments in a
Expand Down
25 changes: 22 additions & 3 deletions src/cargo/util/progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::env;
use std::time::{Duration, Instant};

use crate::core::shell::Verbosity;
use crate::util::config::ProgressWhen;
use crate::util::{is_ci, CargoResult, Config};

use unicode_width::UnicodeWidthChar;
Expand All @@ -28,6 +29,7 @@ struct State<'cfg> {
done: bool,
throttle: Throttle,
last_line: Option<String>,
fixed_width: Option<usize>,
}

struct Format {
Expand All @@ -45,12 +47,26 @@ impl<'cfg> Progress<'cfg> {
Ok(term) => term == "dumb",
Err(_) => false,
};
let progress_config = cfg.progress_config();
match progress_config.when {
ProgressWhen::Always => return Progress::new_priv(name, style, cfg),
ProgressWhen::Never => return Progress { state: None },
ProgressWhen::Auto => {}
}
if cfg.shell().verbosity() == Verbosity::Quiet || dumb || is_ci() {
return Progress { state: None };
}
Progress::new_priv(name, style, cfg)
}

fn new_priv(name: &str, style: ProgressStyle, cfg: &'cfg Config) -> Progress<'cfg> {
let progress_config = cfg.progress_config();
let width = progress_config
.width
.or_else(|| cfg.shell().err_width().progress_max_width());

Progress {
state: cfg.shell().err_width().progress_max_width().map(|n| State {
state: width.map(|n| State {
config: cfg,
format: Format {
style,
Expand All @@ -61,6 +77,7 @@ impl<'cfg> Progress<'cfg> {
done: false,
throttle: Throttle::new(),
last_line: None,
fixed_width: progress_config.width,
}),
}
}
Expand Down Expand Up @@ -216,8 +233,10 @@ impl<'cfg> State<'cfg> {
}

fn try_update_max_width(&mut self) {
if let Some(n) = self.config.shell().err_width().progress_max_width() {
self.format.max_width = n;
if self.fixed_width.is_none() {
if let Some(n) = self.config.shell().err_width().progress_max_width() {
self.format.max_width = n;
}
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/doc/src/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ metadata_key2 = "value"
[term]
verbose = false # whether cargo provides verbose output
color = 'auto' # whether cargo colorizes output
progress.when = 'auto' # whether cargo shows progress bar
progress.width = 80 # width of progress bar
```

### Environment variables
Expand Down Expand Up @@ -903,6 +905,23 @@ Controls whether or not colored output is used in the terminal. Possible values:

Can be overridden with the `--color` command-line option.

##### `term.progress.when`
* Type: string
* Default: "auto"
* Environment: `CARGO_TERM_PROGRESS_WHEN`

Controls whether or not progress bar is shown in the terminal. Possible values:

* `auto` (default): Intelligently guess whether to show progress bar.
* `always`: Always show progress bar.
* `never`: Never show progress bar.

##### `term.progress.width`
* Type: integer
* Default: none
* Environment: `CARGO_TERM_PROGRESS_WIDTH`

Sets the width for progress bar.

[`cargo bench`]: ../commands/cargo-bench.md
[`cargo login`]: ../commands/cargo-login.md
Expand Down
Loading