Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ehuss committed Feb 8, 2020
1 parent 3c53211 commit a64beab
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 51 deletions.
2 changes: 2 additions & 0 deletions src/cargo/ops/cargo_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ pub fn compile_ws<'a>(
ref export_dir,
} = *options;

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

let profiles = Profiles::new(
ws.profiles(),
Expand Down
120 changes: 84 additions & 36 deletions src/cargo/util/config/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,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 @@ -54,36 +53,52 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
(None, Some(_)) => true,
_ => 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)
}
}
};
return res.map_err(|e| e.with_key_context(&self.key, env_def));
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" {
return Ok(Some(CV::Boolean(true, env_def)));
} else if env == "false" {
return Ok(Some(CV::Boolean(false, env_def)));
} else if let Ok(i) = env.parse::<i64>() {
return 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.
return 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.
return Ok(Some(CV::List(cv_list, 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.
return 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 Expand Up @@ -176,11 +191,44 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> {
visitor.visit_seq(ConfigSeqAccess::new(self)?)
}

fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
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_enum((i as u32).into_deserializer()), def),
CV::String(s, def) => (visitor.visit_enum(s.into_deserializer()), def),
CV::List(..) | CV::Boolean(..) => {
return Err(ConfigError::expected(
&self.key,
"an enum-compatible type",
&cv,
))
}
CV::Table(_, def) => (
// TODO: Check how this path can be visited.
visitor.visit_map(ConfigMapAccess::new_map(self.clone())?),
def,
),
};
let (res, def) = res;
return res.map_err(|e| e.with_key_context(&self.key, def));
}
Err(ConfigError::missing(&self.key))
}

// These aren't really supported, yet.
serde::forward_to_deserialize_any! {
f32 f64 char str bytes
byte_buf unit unit_struct
enum identifier ignored_any newtype_struct
identifier ignored_any newtype_struct
}
}

Expand Down
126 changes: 117 additions & 9 deletions src/cargo/util/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ pub struct Config {
net_config: LazyCell<CargoNetConfig>,
build_config: LazyCell<CargoBuildConfig>,
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
progress_config: ProgressConfig,
}

impl Config {
Expand Down Expand Up @@ -241,6 +242,7 @@ impl Config {
net_config: LazyCell::new(),
build_config: LazyCell::new(),
target_cfgs: LazyCell::new(),
progress_config: ProgressConfig::default(),
}
}

Expand Down Expand Up @@ -442,8 +444,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 @@ -620,13 +622,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_ref().map(|s| s.as_ref()));
Expand Down Expand Up @@ -654,6 +652,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 @@ -1054,6 +1053,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 @@ -1640,6 +1653,101 @@ 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_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>,
{
ProgressConfig::deserialize(deserializer).map(Some)
}

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_map<M>(self, map: M) -> Result<Self::Value, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let pc = Deserialize::deserialize(serde::de::value::MapAccessDeserializer::new(map))?;
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_any(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
Loading

0 comments on commit a64beab

Please sign in to comment.