Skip to content

Commit

Permalink
Auto merge of #8165 - mchernyavsky:force-progress, r=ehuss
Browse files Browse the repository at this point in the history
Add a term option to configure the progress bar

Closes #7587.
  • Loading branch information
bors committed Sep 23, 2020
2 parents 13b73cd + d649c66 commit c369b8c
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 46 deletions.
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 @@ -291,6 +291,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 @@ -311,6 +312,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

0 comments on commit c369b8c

Please sign in to comment.