Skip to content

Commit a64beab

Browse files
committed
WIP
1 parent 3c53211 commit a64beab

File tree

7 files changed

+280
-51
lines changed

7 files changed

+280
-51
lines changed

src/cargo/ops/cargo_compile.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ pub fn compile_ws<'a>(
279279
ref export_dir,
280280
} = *options;
281281

282+
// Perform some pre-flight validation.
282283
match build_config.mode {
283284
CompileMode::Test
284285
| CompileMode::Build
@@ -299,6 +300,7 @@ pub fn compile_ws<'a>(
299300
}
300301
}
301302
}
303+
config.validate_term_config()?;
302304

303305
let profiles = Profiles::new(
304306
ws.profiles(),

src/cargo/util/config/de.rs

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,12 @@ macro_rules! deserialize_method {
3737
}
3838
}
3939

40-
impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
41-
type Error = ConfigError;
42-
43-
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
44-
where
45-
V: de::Visitor<'de>,
46-
{
40+
impl<'config> Deserializer<'config> {
41+
/// This is a helper for getting a CV from a file or env var.
42+
///
43+
/// If this returns CV::List, then don't look at the value. Handling lists
44+
/// is deferred to ConfigSeqAccess.
45+
fn get_cv_with_env(&self) -> Result<Option<CV>, ConfigError> {
4746
// Determine if value comes from env, cli, or file, and merge env if
4847
// possible.
4948
let cv = self.config.get_cv(&self.key)?;
@@ -54,36 +53,52 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
5453
(None, Some(_)) => true,
5554
_ => false,
5655
};
57-
if use_env {
58-
// Future note: If you ever need to deserialize a non-self describing
59-
// map type, this should implement a starts_with check (similar to how
60-
// ConfigMapAccess does).
61-
let env = env.unwrap();
62-
let res: Result<V::Value, ConfigError> = if env == "true" || env == "false" {
63-
visitor.visit_bool(env.parse().unwrap())
64-
} else if let Ok(env) = env.parse::<i64>() {
65-
visitor.visit_i64(env)
66-
} else if self.config.cli_unstable().advanced_env
67-
&& env.starts_with('[')
68-
&& env.ends_with(']')
69-
{
70-
visitor.visit_seq(ConfigSeqAccess::new(self.clone())?)
71-
} else {
72-
// Try to merge if possible.
73-
match cv {
74-
Some(CV::List(_cv_list, _cv_def)) => {
75-
visitor.visit_seq(ConfigSeqAccess::new(self.clone())?)
76-
}
77-
_ => {
78-
// Note: CV::Table merging is not implemented, as env
79-
// vars do not support table values.
80-
visitor.visit_str(env)
81-
}
82-
}
83-
};
84-
return res.map_err(|e| e.with_key_context(&self.key, env_def));
56+
if !use_env {
57+
return Ok(cv);
8558
}
59+
// Future note: If you ever need to deserialize a non-self describing
60+
// map type, this should implement a starts_with check (similar to how
61+
// ConfigMapAccess does).
62+
let env = env.unwrap();
63+
if env == "true" {
64+
return Ok(Some(CV::Boolean(true, env_def)));
65+
} else if env == "false" {
66+
return Ok(Some(CV::Boolean(false, env_def)));
67+
} else if let Ok(i) = env.parse::<i64>() {
68+
return Ok(Some(CV::Integer(i, env_def)));
69+
} else if self.config.cli_unstable().advanced_env
70+
&& env.starts_with('[')
71+
&& env.ends_with(']')
72+
{
73+
// Parsing is deferred to ConfigSeqAccess.
74+
return Ok(Some(CV::List(Vec::new(), env_def)));
75+
} else {
76+
// Try to merge if possible.
77+
match cv {
78+
Some(CV::List(cv_list, _cv_def)) => {
79+
// Merging is deferred to ConfigSeqAccess.
80+
return Ok(Some(CV::List(cv_list, env_def)));
81+
}
82+
_ => {
83+
// Note: CV::Table merging is not implemented, as env
84+
// vars do not support table values. In the future, we
85+
// could check for `{}`, and interpret it as TOML if
86+
// that seems useful.
87+
return Ok(Some(CV::String(env.to_string(), env_def)));
88+
}
89+
}
90+
};
91+
}
92+
}
93+
94+
impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
95+
type Error = ConfigError;
8696

97+
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
98+
where
99+
V: de::Visitor<'de>,
100+
{
101+
let cv = self.get_cv_with_env()?;
87102
if let Some(cv) = cv {
88103
let res: (Result<V::Value, ConfigError>, Definition) = match cv {
89104
CV::Integer(i, def) => (visitor.visit_i64(i), def),
@@ -176,11 +191,44 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
176191
visitor.visit_seq(ConfigSeqAccess::new(self)?)
177192
}
178193

194+
fn deserialize_enum<V>(
195+
self,
196+
_name: &'static str,
197+
_variants: &'static [&'static str],
198+
visitor: V,
199+
) -> Result<V::Value, Self::Error>
200+
where
201+
V: de::Visitor<'de>,
202+
{
203+
let cv = self.get_cv_with_env()?;
204+
if let Some(cv) = cv {
205+
let res: (Result<V::Value, ConfigError>, Definition) = match cv {
206+
CV::Integer(i, def) => (visitor.visit_enum((i as u32).into_deserializer()), def),
207+
CV::String(s, def) => (visitor.visit_enum(s.into_deserializer()), def),
208+
CV::List(..) | CV::Boolean(..) => {
209+
return Err(ConfigError::expected(
210+
&self.key,
211+
"an enum-compatible type",
212+
&cv,
213+
))
214+
}
215+
CV::Table(_, def) => (
216+
// TODO: Check how this path can be visited.
217+
visitor.visit_map(ConfigMapAccess::new_map(self.clone())?),
218+
def,
219+
),
220+
};
221+
let (res, def) = res;
222+
return res.map_err(|e| e.with_key_context(&self.key, def));
223+
}
224+
Err(ConfigError::missing(&self.key))
225+
}
226+
179227
// These aren't really supported, yet.
180228
serde::forward_to_deserialize_any! {
181229
f32 f64 char str bytes
182230
byte_buf unit unit_struct
183-
enum identifier ignored_any newtype_struct
231+
identifier ignored_any newtype_struct
184232
}
185233
}
186234

src/cargo/util/config/mod.rs

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ pub struct Config {
172172
net_config: LazyCell<CargoNetConfig>,
173173
build_config: LazyCell<CargoBuildConfig>,
174174
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
175+
progress_config: ProgressConfig,
175176
}
176177

177178
impl Config {
@@ -241,6 +242,7 @@ impl Config {
241242
net_config: LazyCell::new(),
242243
build_config: LazyCell::new(),
243244
target_cfgs: LazyCell::new(),
245+
progress_config: ProgressConfig::default(),
244246
}
245247
}
246248

@@ -442,8 +444,8 @@ impl Config {
442444

443445
/// Get a configuration value by key.
444446
///
445-
/// This does NOT look at environment variables, the caller is responsible
446-
/// for that.
447+
/// This does NOT look at environment variables. See `get_cv_with_env` for
448+
/// a variant that supports environment variables.
447449
fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
448450
log::trace!("get cv {:?}", key);
449451
let vals = self.values()?;
@@ -620,13 +622,9 @@ impl Config {
620622
let extra_verbose = verbose >= 2;
621623
let verbose = verbose != 0;
622624

623-
#[derive(Deserialize, Default)]
624-
struct TermConfig {
625-
verbose: Option<bool>,
626-
color: Option<String>,
627-
}
628-
629-
// Ignore errors in the configuration files.
625+
// Ignore errors in the configuration files. We don't want basic
626+
// commands like `cargo version` to error out due to config file
627+
// problems.
630628
let term = self.get::<TermConfig>("term").unwrap_or_default();
631629

632630
let color = color.or_else(|| term.color.as_ref().map(|s| s.as_ref()));
@@ -654,6 +652,7 @@ impl Config {
654652

655653
self.shell().set_verbosity(verbosity);
656654
self.shell().set_color_choice(color)?;
655+
self.progress_config = term.progress.unwrap_or_default();
657656
self.extra_verbose = extra_verbose;
658657
self.frozen = frozen;
659658
self.locked = locked;
@@ -1054,6 +1053,20 @@ impl Config {
10541053
.try_borrow_with(|| Ok(self.get::<CargoBuildConfig>("build")?))
10551054
}
10561055

1056+
pub fn progress_config(&self) -> &ProgressConfig {
1057+
&self.progress_config
1058+
}
1059+
1060+
/// This is used to validate the `term` table has valid syntax.
1061+
///
1062+
/// This is necessary because loading the term settings happens very
1063+
/// early, and in some situations (like `cargo version`) we don't want to
1064+
/// fail if there are problems with the config file.
1065+
pub fn validate_term_config(&self) -> CargoResult<()> {
1066+
drop(self.get::<TermConfig>("term")?);
1067+
Ok(())
1068+
}
1069+
10571070
/// Returns a list of [target.'cfg()'] tables.
10581071
///
10591072
/// The list is sorted by the table name.
@@ -1640,6 +1653,101 @@ pub struct CargoBuildConfig {
16401653
pub out_dir: Option<ConfigRelativePath>,
16411654
}
16421655

1656+
#[derive(Deserialize, Default)]
1657+
struct TermConfig {
1658+
verbose: Option<bool>,
1659+
color: Option<String>,
1660+
#[serde(default)]
1661+
#[serde(deserialize_with = "progress_or_string")]
1662+
progress: Option<ProgressConfig>,
1663+
}
1664+
1665+
#[derive(Debug, Default, Deserialize)]
1666+
pub struct ProgressConfig {
1667+
pub when: ProgressWhen,
1668+
pub width: Option<usize>,
1669+
}
1670+
1671+
#[derive(Debug, Deserialize)]
1672+
#[serde(rename_all = "lowercase")]
1673+
pub enum ProgressWhen {
1674+
Auto,
1675+
Never,
1676+
Always,
1677+
}
1678+
1679+
impl Default for ProgressWhen {
1680+
fn default() -> ProgressWhen {
1681+
ProgressWhen::Auto
1682+
}
1683+
}
1684+
1685+
fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
1686+
where
1687+
D: serde::de::Deserializer<'de>,
1688+
{
1689+
struct ProgressVisitor;
1690+
1691+
impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
1692+
type Value = Option<ProgressConfig>;
1693+
1694+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1695+
formatter.write_str("a string (\"auto\" or \"never\") or a table")
1696+
}
1697+
1698+
fn visit_none<E>(self) -> Result<Self::Value, E>
1699+
where
1700+
E: serde::de::Error,
1701+
{
1702+
Ok(None)
1703+
}
1704+
1705+
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1706+
where
1707+
D: serde::de::Deserializer<'de>,
1708+
{
1709+
ProgressConfig::deserialize(deserializer).map(Some)
1710+
}
1711+
1712+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1713+
where
1714+
E: serde::de::Error,
1715+
{
1716+
match s {
1717+
"auto" => Ok(Some(ProgressConfig {
1718+
when: ProgressWhen::Auto,
1719+
width: None,
1720+
})),
1721+
"never" => Ok(Some(ProgressConfig {
1722+
when: ProgressWhen::Never,
1723+
width: None,
1724+
})),
1725+
"always" => Err(E::custom("\"always\" progress requires a `width` key")),
1726+
_ => Err(E::unknown_variant(s, &["auto", "never"])),
1727+
}
1728+
}
1729+
1730+
fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
1731+
where
1732+
M: serde::de::MapAccess<'de>,
1733+
{
1734+
let pc = Deserialize::deserialize(serde::de::value::MapAccessDeserializer::new(map))?;
1735+
if let ProgressConfig {
1736+
when: ProgressWhen::Always,
1737+
width: None,
1738+
} = pc
1739+
{
1740+
return Err(serde::de::Error::custom(
1741+
"\"always\" progress requires a `width` key",
1742+
));
1743+
}
1744+
Ok(Some(pc))
1745+
}
1746+
}
1747+
1748+
deserializer.deserialize_any(ProgressVisitor)
1749+
}
1750+
16431751
/// A type to deserialize a list of strings from a toml file.
16441752
///
16451753
/// Supports deserializing either a whitespace-separated list of arguments in a

0 commit comments

Comments
 (0)