Skip to content

Commit c369b8c

Browse files
committedSep 23, 2020
Auto merge of #8165 - mchernyavsky:force-progress, r=ehuss
Add a term option to configure the progress bar Closes #7587.
2 parents 13b73cd + d649c66 commit c369b8c

File tree

9 files changed

+368
-46
lines changed

9 files changed

+368
-46
lines changed
 

‎crates/cargo-test-support/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,7 @@ fn substitute_macros(input: &str) -> String {
16451645
("[IGNORED]", " Ignored"),
16461646
("[INSTALLED]", " Installed"),
16471647
("[REPLACED]", " Replaced"),
1648+
("[BUILDING]", " Building"),
16481649
];
16491650
let mut result = input.to_owned();
16501651
for &(pat, subst) in &macros {

‎src/cargo/ops/cargo_compile.rs

+2
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ pub fn create_bcx<'a, 'cfg>(
291291
} = *options;
292292
let config = ws.config();
293293

294+
// Perform some pre-flight validation.
294295
match build_config.mode {
295296
CompileMode::Test
296297
| CompileMode::Build
@@ -311,6 +312,7 @@ pub fn create_bcx<'a, 'cfg>(
311312
}
312313
}
313314
}
315+
config.validate_term_config()?;
314316

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

‎src/cargo/util/config/de.rs

+50-34
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,12 @@ macro_rules! deserialize_method {
4040
};
4141
}
4242

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

61-
if use_env {
62-
// Future note: If you ever need to deserialize a non-self describing
63-
// map type, this should implement a starts_with check (similar to how
64-
// ConfigMapAccess does).
65-
let env = env.unwrap();
66-
let res: Result<V::Value, ConfigError> = if env == "true" || env == "false" {
67-
visitor.visit_bool(env.parse().unwrap())
68-
} else if let Ok(env) = env.parse::<i64>() {
69-
visitor.visit_i64(env)
70-
} else if self.config.cli_unstable().advanced_env
71-
&& env.starts_with('[')
72-
&& env.ends_with(']')
73-
{
74-
visitor.visit_seq(ConfigSeqAccess::new(self.clone())?)
75-
} else {
76-
// Try to merge if possible.
77-
match cv {
78-
Some(CV::List(_cv_list, _cv_def)) => {
79-
visitor.visit_seq(ConfigSeqAccess::new(self.clone())?)
80-
}
81-
_ => {
82-
// Note: CV::Table merging is not implemented, as env
83-
// vars do not support table values.
84-
visitor.visit_str(env)
85-
}
60+
if !use_env {
61+
return Ok(cv);
62+
}
63+
64+
// Future note: If you ever need to deserialize a non-self describing
65+
// map type, this should implement a starts_with check (similar to how
66+
// ConfigMapAccess does).
67+
let env = env.unwrap();
68+
if env == "true" {
69+
Ok(Some(CV::Boolean(true, env_def)))
70+
} else if env == "false" {
71+
Ok(Some(CV::Boolean(false, env_def)))
72+
} else if let Ok(i) = env.parse::<i64>() {
73+
Ok(Some(CV::Integer(i, env_def)))
74+
} else if self.config.cli_unstable().advanced_env
75+
&& env.starts_with('[')
76+
&& env.ends_with(']')
77+
{
78+
// Parsing is deferred to ConfigSeqAccess.
79+
Ok(Some(CV::List(Vec::new(), env_def)))
80+
} else {
81+
// Try to merge if possible.
82+
match cv {
83+
Some(CV::List(cv_list, _cv_def)) => {
84+
// Merging is deferred to ConfigSeqAccess.
85+
Ok(Some(CV::List(cv_list, env_def)))
8686
}
87-
};
88-
return res.map_err(|e| e.with_key_context(&self.key, env_def));
87+
_ => {
88+
// Note: CV::Table merging is not implemented, as env
89+
// vars do not support table values. In the future, we
90+
// could check for `{}`, and interpret it as TOML if
91+
// that seems useful.
92+
Ok(Some(CV::String(env.to_string(), env_def)))
93+
}
94+
}
8995
}
96+
}
97+
}
9098

99+
impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
100+
type Error = ConfigError;
101+
102+
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
103+
where
104+
V: de::Visitor<'de>,
105+
{
106+
let cv = self.get_cv_with_env()?;
91107
if let Some(cv) = cv {
92108
let res: (Result<V::Value, ConfigError>, Definition) = match cv {
93109
CV::Integer(i, def) => (visitor.visit_i64(i), def),

‎src/cargo/util/config/mod.rs

+110-9
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ pub struct Config {
176176
build_config: LazyCell<CargoBuildConfig>,
177177
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
178178
doc_extern_map: LazyCell<RustdocExternMap>,
179+
progress_config: ProgressConfig,
179180
}
180181

181182
impl Config {
@@ -247,6 +248,7 @@ impl Config {
247248
build_config: LazyCell::new(),
248249
target_cfgs: LazyCell::new(),
249250
doc_extern_map: LazyCell::new(),
251+
progress_config: ProgressConfig::default(),
250252
}
251253
}
252254

@@ -459,8 +461,8 @@ impl Config {
459461

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

723-
#[derive(Deserialize, Default)]
724-
struct TermConfig {
725-
verbose: Option<bool>,
726-
color: Option<String>,
727-
}
728-
729-
// Ignore errors in the configuration files.
725+
// Ignore errors in the configuration files. We don't want basic
726+
// commands like `cargo version` to error out due to config file
727+
// problems.
730728
let term = self.get::<TermConfig>("term").unwrap_or_default();
731729

732730
let color = color.or_else(|| term.color.as_deref());
@@ -754,6 +752,7 @@ impl Config {
754752

755753
self.shell().set_verbosity(verbosity);
756754
self.shell().set_color_choice(color)?;
755+
self.progress_config = term.progress.unwrap_or_default();
757756
self.extra_verbose = extra_verbose;
758757
self.frozen = frozen;
759758
self.locked = locked;
@@ -1192,6 +1191,20 @@ impl Config {
11921191
.try_borrow_with(|| Ok(self.get::<CargoBuildConfig>("build")?))
11931192
}
11941193

1194+
pub fn progress_config(&self) -> &ProgressConfig {
1195+
&self.progress_config
1196+
}
1197+
1198+
/// This is used to validate the `term` table has valid syntax.
1199+
///
1200+
/// This is necessary because loading the term settings happens very
1201+
/// early, and in some situations (like `cargo version`) we don't want to
1202+
/// fail if there are problems with the config file.
1203+
pub fn validate_term_config(&self) -> CargoResult<()> {
1204+
drop(self.get::<TermConfig>("term")?);
1205+
Ok(())
1206+
}
1207+
11951208
/// Returns a list of [target.'cfg()'] tables.
11961209
///
11971210
/// The list is sorted by the table name.
@@ -1778,6 +1791,94 @@ pub struct CargoBuildConfig {
17781791
pub out_dir: Option<ConfigRelativePath>,
17791792
}
17801793

1794+
#[derive(Deserialize, Default)]
1795+
struct TermConfig {
1796+
verbose: Option<bool>,
1797+
color: Option<String>,
1798+
#[serde(default)]
1799+
#[serde(deserialize_with = "progress_or_string")]
1800+
progress: Option<ProgressConfig>,
1801+
}
1802+
1803+
#[derive(Debug, Default, Deserialize)]
1804+
pub struct ProgressConfig {
1805+
pub when: ProgressWhen,
1806+
pub width: Option<usize>,
1807+
}
1808+
1809+
#[derive(Debug, Deserialize)]
1810+
#[serde(rename_all = "lowercase")]
1811+
pub enum ProgressWhen {
1812+
Auto,
1813+
Never,
1814+
Always,
1815+
}
1816+
1817+
impl Default for ProgressWhen {
1818+
fn default() -> ProgressWhen {
1819+
ProgressWhen::Auto
1820+
}
1821+
}
1822+
1823+
fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
1824+
where
1825+
D: serde::de::Deserializer<'de>,
1826+
{
1827+
struct ProgressVisitor;
1828+
1829+
impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
1830+
type Value = Option<ProgressConfig>;
1831+
1832+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1833+
formatter.write_str("a string (\"auto\" or \"never\") or a table")
1834+
}
1835+
1836+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1837+
where
1838+
E: serde::de::Error,
1839+
{
1840+
match s {
1841+
"auto" => Ok(Some(ProgressConfig {
1842+
when: ProgressWhen::Auto,
1843+
width: None,
1844+
})),
1845+
"never" => Ok(Some(ProgressConfig {
1846+
when: ProgressWhen::Never,
1847+
width: None,
1848+
})),
1849+
"always" => Err(E::custom("\"always\" progress requires a `width` key")),
1850+
_ => Err(E::unknown_variant(s, &["auto", "never"])),
1851+
}
1852+
}
1853+
1854+
fn visit_none<E>(self) -> Result<Self::Value, E>
1855+
where
1856+
E: serde::de::Error,
1857+
{
1858+
Ok(None)
1859+
}
1860+
1861+
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1862+
where
1863+
D: serde::de::Deserializer<'de>,
1864+
{
1865+
let pc = ProgressConfig::deserialize(deserializer)?;
1866+
if let ProgressConfig {
1867+
when: ProgressWhen::Always,
1868+
width: None,
1869+
} = pc
1870+
{
1871+
return Err(serde::de::Error::custom(
1872+
"\"always\" progress requires a `width` key",
1873+
));
1874+
}
1875+
Ok(Some(pc))
1876+
}
1877+
}
1878+
1879+
deserializer.deserialize_option(ProgressVisitor)
1880+
}
1881+
17811882
/// A type to deserialize a list of strings from a toml file.
17821883
///
17831884
/// Supports deserializing either a whitespace-separated list of arguments in a

‎src/cargo/util/progress.rs

+22-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::env;
33
use std::time::{Duration, Instant};
44

55
use crate::core::shell::Verbosity;
6+
use crate::util::config::ProgressWhen;
67
use crate::util::{is_ci, CargoResult, Config};
78

89
use unicode_width::UnicodeWidthChar;
@@ -28,6 +29,7 @@ struct State<'cfg> {
2829
done: bool,
2930
throttle: Throttle,
3031
last_line: Option<String>,
32+
fixed_width: Option<usize>,
3133
}
3234

3335
struct Format {
@@ -45,12 +47,26 @@ impl<'cfg> Progress<'cfg> {
4547
Ok(term) => term == "dumb",
4648
Err(_) => false,
4749
};
50+
let progress_config = cfg.progress_config();
51+
match progress_config.when {
52+
ProgressWhen::Always => return Progress::new_priv(name, style, cfg),
53+
ProgressWhen::Never => return Progress { state: None },
54+
ProgressWhen::Auto => {}
55+
}
4856
if cfg.shell().verbosity() == Verbosity::Quiet || dumb || is_ci() {
4957
return Progress { state: None };
5058
}
59+
Progress::new_priv(name, style, cfg)
60+
}
61+
62+
fn new_priv(name: &str, style: ProgressStyle, cfg: &'cfg Config) -> Progress<'cfg> {
63+
let progress_config = cfg.progress_config();
64+
let width = progress_config
65+
.width
66+
.or_else(|| cfg.shell().err_width().progress_max_width());
5167

5268
Progress {
53-
state: cfg.shell().err_width().progress_max_width().map(|n| State {
69+
state: width.map(|n| State {
5470
config: cfg,
5571
format: Format {
5672
style,
@@ -61,6 +77,7 @@ impl<'cfg> Progress<'cfg> {
6177
done: false,
6278
throttle: Throttle::new(),
6379
last_line: None,
80+
fixed_width: progress_config.width,
6481
}),
6582
}
6683
}
@@ -216,8 +233,10 @@ impl<'cfg> State<'cfg> {
216233
}
217234

218235
fn try_update_max_width(&mut self) {
219-
if let Some(n) = self.config.shell().err_width().progress_max_width() {
220-
self.format.max_width = n;
236+
if self.fixed_width.is_none() {
237+
if let Some(n) = self.config.shell().err_width().progress_max_width() {
238+
self.format.max_width = n;
239+
}
221240
}
222241
}
223242
}

‎src/doc/src/reference/config.md

+19
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ metadata_key2 = "value"
147147
[term]
148148
verbose = false # whether cargo provides verbose output
149149
color = 'auto' # whether cargo colorizes output
150+
progress.when = 'auto' # whether cargo shows progress bar
151+
progress.width = 80 # width of progress bar
150152
```
151153

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

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

908+
##### `term.progress.when`
909+
* Type: string
910+
* Default: "auto"
911+
* Environment: `CARGO_TERM_PROGRESS_WHEN`
912+
913+
Controls whether or not progress bar is shown in the terminal. Possible values:
914+
915+
* `auto` (default): Intelligently guess whether to show progress bar.
916+
* `always`: Always show progress bar.
917+
* `never`: Never show progress bar.
918+
919+
##### `term.progress.width`
920+
* Type: integer
921+
* Default: none
922+
* Environment: `CARGO_TERM_PROGRESS_WIDTH`
923+
924+
Sets the width for progress bar.
906925

907926
[`cargo bench`]: ../commands/cargo-bench.md
908927
[`cargo login`]: ../commands/cargo-login.md

‎src/doc/src/reference/environment-variables.md

+4
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ supported environment variables are:
103103
* `CARGO_TARGET_<triple>_RUSTFLAGS` — Extra `rustc` flags for a target, see [`target.<triple>.rustflags`].
104104
* `CARGO_TERM_VERBOSE` — The default terminal verbosity, see [`term.verbose`].
105105
* `CARGO_TERM_COLOR` — The default color mode, see [`term.color`].
106+
* `CARGO_TERM_PROGRESS_WHEN` — The default progress bar showing mode, see [`term.progress.when`].
107+
* `CARGO_TERM_PROGRESS_WIDTH` — The default progress bar width, see [`term.progress.width`].
106108

107109
[`cargo doc`]: ../commands/cargo-doc.md
108110
[`cargo install`]: ../commands/cargo-install.md
@@ -158,6 +160,8 @@ supported environment variables are:
158160
[`target.<triple>.rustflags`]: config.md#targettriplerustflags
159161
[`term.verbose`]: config.md#termverbose
160162
[`term.color`]: config.md#termcolor
163+
[`term.progress.when`]: config.md#termprogresswhen
164+
[`term.progress.width`]: config.md#termprogresswidth
161165

162166
### Environment variables Cargo sets for crates
163167

‎tests/testsuite/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ mod profile_custom;
8888
mod profile_overrides;
8989
mod profile_targets;
9090
mod profiles;
91+
mod progress;
9192
mod pub_priv;
9293
mod publish;
9394
mod publish_lockfile;

‎tests/testsuite/progress.rs

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//! Tests for progress bar.
2+
3+
use cargo_test_support::project;
4+
use cargo_test_support::registry::Package;
5+
6+
#[cargo_test]
7+
fn bad_progress_config_unknown_when() {
8+
let p = project()
9+
.file(
10+
".cargo/config",
11+
r#"
12+
[term]
13+
progress = { when = 'unknown' }
14+
"#,
15+
)
16+
.file("src/lib.rs", "")
17+
.build();
18+
19+
p.cargo("build")
20+
.with_status(101)
21+
.with_stderr(
22+
"\
23+
[ERROR] error in [..].cargo/config: \
24+
could not load config key `term.progress.when`
25+
26+
Caused by:
27+
unknown variant `unknown`, expected one of `auto`, `never`, `always`
28+
",
29+
)
30+
.run();
31+
}
32+
33+
#[cargo_test]
34+
fn bad_progress_config_missing_width() {
35+
let p = project()
36+
.file(
37+
".cargo/config",
38+
r#"
39+
[term]
40+
progress = { when = 'always' }
41+
"#,
42+
)
43+
.file("src/lib.rs", "")
44+
.build();
45+
46+
p.cargo("build")
47+
.with_status(101)
48+
.with_stderr(
49+
"\
50+
[ERROR] \"always\" progress requires a `width` key
51+
",
52+
)
53+
.run();
54+
}
55+
56+
#[cargo_test]
57+
fn bad_progress_config_missing_when() {
58+
let p = project()
59+
.file(
60+
".cargo/config",
61+
r#"
62+
[term]
63+
progress = { width = 1000 }
64+
"#,
65+
)
66+
.file("src/lib.rs", "")
67+
.build();
68+
69+
p.cargo("build")
70+
.with_status(101)
71+
.with_stderr(
72+
"\
73+
error: missing field `when`
74+
",
75+
)
76+
.run();
77+
}
78+
79+
#[cargo_test]
80+
fn always_shows_progress() {
81+
const N: usize = 3;
82+
let mut deps = String::new();
83+
for i in 1..=N {
84+
Package::new(&format!("dep{}", i), "1.0.0").publish();
85+
deps.push_str(&format!("dep{} = \"1.0\"\n", i));
86+
}
87+
88+
let p = project()
89+
.file(
90+
".cargo/config",
91+
r#"
92+
[term]
93+
progress = { when = 'always', width = 100 }
94+
"#,
95+
)
96+
.file(
97+
"Cargo.toml",
98+
&format!(
99+
r#"
100+
[package]
101+
name = "foo"
102+
version = "0.1.0"
103+
104+
[dependencies]
105+
{}
106+
"#,
107+
deps
108+
),
109+
)
110+
.file("src/lib.rs", "")
111+
.build();
112+
113+
p.cargo("build")
114+
.with_stderr_contains("[DOWNLOADING] [..] crates [..]")
115+
.with_stderr_contains("[..][DOWNLOADED] 3 crates ([..]) in [..]")
116+
.with_stderr_contains("[BUILDING] [..] [..]/4: [..]")
117+
.run();
118+
}
119+
120+
#[cargo_test]
121+
fn never_progress() {
122+
const N: usize = 3;
123+
let mut deps = String::new();
124+
for i in 1..=N {
125+
Package::new(&format!("dep{}", i), "1.0.0").publish();
126+
deps.push_str(&format!("dep{} = \"1.0\"\n", i));
127+
}
128+
129+
let p = project()
130+
.file(
131+
".cargo/config",
132+
r#"
133+
[term]
134+
progress = { when = 'never' }
135+
"#,
136+
)
137+
.file(
138+
"Cargo.toml",
139+
&format!(
140+
r#"
141+
[package]
142+
name = "foo"
143+
version = "0.1.0"
144+
145+
[dependencies]
146+
{}
147+
"#,
148+
deps
149+
),
150+
)
151+
.file("src/lib.rs", "")
152+
.build();
153+
154+
p.cargo("build")
155+
.with_stderr_does_not_contain("[DOWNLOADING] [..] crates [..]")
156+
.with_stderr_does_not_contain("[..][DOWNLOADED] 3 crates ([..]) in [..]")
157+
.with_stderr_does_not_contain("[BUILDING] [..] [..]/4: [..]")
158+
.run();
159+
}

0 commit comments

Comments
 (0)
Please sign in to comment.