#
cargo/util/context/
schema.rs

    1//! Cargo configuration schemas.
    2//!
    3//! This module contains types that define the schema for various configuration
    4//! sections found in Cargo configuration.
    5//!
    6//! These types are mostly used by [`GlobalContext::get`](super::GlobalContext::get)
    7//! to deserialize configuration values from TOML files, environment variables,
    8//! and CLI arguments.
    9//!
    10//! Schema types here should only contain data and simple accessor methods.
    11//! Avoid depending on [`GlobalContext`](super::GlobalContext) directly.
    12
    13use std::borrow::Cow;
    14use std::collections::HashMap;
    15use std::ffi::OsStr;
    16
    17use serde::Deserialize;
    18use serde_untagged::UntaggedEnumVisitor;
    19
    20use std::path::Path;
    21
    22use crate::CargoResult;
    23
    24use super::StringList;
    25use super::Value;
    26use super::path::ConfigRelativePath;
    27
    28/// The `[http]` table.
    29///
    30/// Example configuration:
    31///
    32/// ```toml
    33/// [http]
    34/// proxy = "host:port"
    35/// timeout = 30
    36/// cainfo = "/path/to/ca-bundle.crt"
    37/// check-revoke = true
    38/// multiplexing = true
    39/// ssl-version = "tlsv1.3"
    40/// ```
    41#[derive(Debug, Default, Deserialize, PartialEq)]
    42#[serde(rename_all = "kebab-case")]
    43pub struct CargoHttpConfig {
    44    pub proxy: Option<String>,
    45    pub low_speed_limit: Option<u32>,
    46    pub timeout: Option<u64>,
    47    pub cainfo: Option<ConfigRelativePath>,
    48    pub proxy_cainfo: Option<ConfigRelativePath>,
    49    pub check_revoke: Option<bool>,
    50    pub user_agent: Option<String>,
    51    pub debug: Option<bool>,
    52    pub multiplexing: Option<bool>,
    53    pub ssl_version: Option<SslVersionConfig>,
    54}
    55
    56/// The `[future-incompat-report]` stable
    57///
    58/// Example configuration:
    59///
    60/// ```toml
    61/// [future-incompat-report]
    62/// frequency = "always"
    63/// ```
    64#[derive(Debug, Default, Deserialize, PartialEq)]
    65#[serde(rename_all = "kebab-case")]
    66pub struct CargoFutureIncompatConfig {
    67    frequency: Option<CargoFutureIncompatFrequencyConfig>,
    68}
    69
    70#[derive(Debug, Default, Deserialize, PartialEq)]
    71#[serde(rename_all = "kebab-case")]
    72pub enum CargoFutureIncompatFrequencyConfig {
    73    #[default]
    74    Always,
    75    Never,
    76}
    77
    78impl CargoFutureIncompatConfig {
    79    pub fn should_display_message(&self) -> bool {
    80        use CargoFutureIncompatFrequencyConfig::*;
    81
    82        let frequency = self.frequency.as_ref().unwrap_or(&Always);
    83        match frequency {
    84            Always => true,
    85            Never => false,
    86        }
    87    }
    88}
    89
    90/// Configuration for `ssl-version` in `http` section
    91/// There are two ways to configure:
    92///
    93/// ```text
    94/// [http]
    95/// ssl-version = "tlsv1.3"
    96/// ```
    97///
    98/// ```text
    99/// [http]
    100/// ssl-version.min = "tlsv1.2"
    101/// ssl-version.max = "tlsv1.3"
    102/// ```
    103#[derive(Clone, Debug, PartialEq)]
    104pub enum SslVersionConfig {
    105    Single(String),
    106    Range(SslVersionConfigRange),
    107}
    108
    109impl<'de> Deserialize<'de> for SslVersionConfig {
    110    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    111    where
    112        D: serde::Deserializer<'de>,
    113    {
    114        UntaggedEnumVisitor::new()
    115            .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
    116            .map(|map| map.deserialize().map(SslVersionConfig::Range))
    117            .deserialize(deserializer)
    118    }
    119}
    120
    121#[derive(Clone, Debug, Deserialize, PartialEq)]
    122#[serde(rename_all = "kebab-case")]
    123pub struct SslVersionConfigRange {
    124    pub min: Option<String>,
    125    pub max: Option<String>,
    126}
    127
    128/// The `[net]` table.
    129///
    130/// Example configuration:
    131///
    132/// ```toml
    133/// [net]
    134/// retry = 2
    135/// offline = false
    136/// git-fetch-with-cli = true
    137/// ```
    138#[derive(Debug, Deserialize)]
    139#[serde(rename_all = "kebab-case")]
    140pub struct CargoNetConfig {
    141    pub retry: Option<u32>,
    142    pub offline: Option<bool>,
    143    pub git_fetch_with_cli: Option<bool>,
    144    pub ssh: Option<CargoSshConfig>,
    145}
    146
    147#[derive(Debug, Deserialize)]
    148#[serde(rename_all = "kebab-case")]
    149pub struct CargoSshConfig {
    150    pub known_hosts: Option<Vec<Value<String>>>,
    151}
    152
    153/// Configuration for `jobs` in `build` section. There are two
    154/// ways to configure: An integer or a simple string expression.
    155///
    156/// ```toml
    157/// [build]
    158/// jobs = 1
    159/// ```
    160///
    161/// ```toml
    162/// [build]
    163/// jobs = "default" # Currently only support "default".
    164/// ```
    165#[derive(Debug, Clone)]
    166pub enum JobsConfig {
    167    Integer(i32),
    168    String(String),
    169}
    170
    171impl<'de> Deserialize<'de> for JobsConfig {
    172    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    173    where
    174        D: serde::Deserializer<'de>,
    175    {
    176        UntaggedEnumVisitor::new()
    177            .i32(|int| Ok(JobsConfig::Integer(int)))
    178            .string(|string| Ok(JobsConfig::String(string.to_owned())))
    179            .deserialize(deserializer)
    180    }
    181}
    182
    183/// The `[build]` table.
    184///
    185/// Example configuration:
    186///
    187/// ```toml
    188/// [build]
    189/// jobs = 4
    190/// target = "x86_64-unknown-linux-gnu"
    191/// target-dir = "target"
    192/// rustflags = ["-C", "link-arg=-fuse-ld=lld"]
    193/// incremental = true
    194/// ```
    195#[derive(Debug, Deserialize)]
    196#[serde(rename_all = "kebab-case")]
    197pub struct CargoBuildConfig {
    198    // deprecated, but preserved for compatibility
    199    pub pipelining: Option<bool>,
    200    pub dep_info_basedir: Option<ConfigRelativePath>,
    201    pub target_dir: Option<ConfigRelativePath>,
    202    pub build_dir: Option<ConfigRelativePath>,
    203    pub incremental: Option<bool>,
    204    pub target: Option<BuildTargetConfig>,
    205    pub jobs: Option<JobsConfig>,
    206    pub rustflags: Option<StringList>,
    207    pub rustdocflags: Option<StringList>,
    208    pub rustc_wrapper: Option<ConfigRelativePath>,
    209    pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
    210    pub rustc: Option<ConfigRelativePath>,
    211    pub rustdoc: Option<ConfigRelativePath>,
    212    // deprecated alias for artifact-dir
    213    pub out_dir: Option<ConfigRelativePath>,
    214    pub artifact_dir: Option<ConfigRelativePath>,
    215    pub warnings: Option<WarningHandling>,
    216    /// Unstable feature `-Zsbom`.
    217    pub sbom: Option<bool>,
    218    /// Unstable feature `-Zbuild-analysis`.
    219    pub analysis: Option<CargoBuildAnalysis>,
    220}
    221
    222/// Metrics collection for build analysis.
    223#[derive(Debug, Deserialize, Default)]
    224#[serde(rename_all = "kebab-case")]
    225pub struct CargoBuildAnalysis {
    226    pub enabled: bool,
    227}
    228
    229/// Whether warnings should warn, be allowed, or cause an error.
    230#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
    231#[serde(rename_all = "kebab-case")]
    232pub enum WarningHandling {
    233    #[default]
    234    /// Output warnings.
    235    Warn,
    236    /// Allow warnings (do not output them).
    237    Allow,
    238    /// Error if  warnings are emitted.
    239    Deny,
    240}
    241
    242/// Configuration for `build.target`.
    243///
    244/// Accepts in the following forms:
    245///
    246/// ```toml
    247/// target = "a"
    248/// target = ["a"]
    249/// target = ["a", "b"]
    250/// ```
    251#[derive(Debug, Deserialize)]
    252#[serde(transparent)]
    253pub struct BuildTargetConfig {
    254    inner: Value<BuildTargetConfigInner>,
    255}
    256
    257#[derive(Debug)]
    258enum BuildTargetConfigInner {
    259    One(String),
    260    Many(Vec<String>),
    261}
    262
    263impl<'de> Deserialize<'de> for BuildTargetConfigInner {
    264    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    265    where
    266        D: serde::Deserializer<'de>,
    267    {
    268        UntaggedEnumVisitor::new()
    269            .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
    270            .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
    271            .deserialize(deserializer)
    272    }
    273}
    274
    275impl BuildTargetConfig {
    276    /// Gets values of `build.target` as a list of strings.
    277    pub fn values(&self, cwd: &Path) -> CargoResult<Vec<String>> {
    278        let map = |s: &String| {
    279            if s.ends_with(".json") {
    280                // Path to a target specification file (in JSON).
    281                // <https://doc.rust-lang.org/rustc/targets/custom.html>
    282                self.inner
    283                    .definition
    284                    .root(cwd)
    285                    .join(s)
    286                    .to_str()
    287                    .expect("must be utf-8 in toml")
    288                    .to_string()
    289            } else {
    290                // A string. Probably a target triple.
    291                s.to_string()
    292            }
    293        };
    294        let values = match &self.inner.val {
    295            BuildTargetConfigInner::One(s) => vec![map(s)],
    296            BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
    297        };
    298        Ok(values)
    299    }
    300}
    301
    302/// The `[resolver]` table.
    303///
    304/// Example configuration:
    305///
    306/// ```toml
    307/// [resolver]
    308/// incompatible-rust-versions = "fallback"
    309/// feature-unification = "workspace"
    310/// lockfile-path = "my/Cargo.lock"
    311/// ```
    312#[derive(Debug, Deserialize)]
    313#[serde(rename_all = "kebab-case")]
    314pub struct CargoResolverConfig {
    315    pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
    316    pub feature_unification: Option<FeatureUnification>,
    317    pub lockfile_path: Option<ConfigRelativePath>,
    318}
    319
    320#[derive(Debug, Deserialize, PartialEq, Eq)]
    321#[serde(rename_all = "kebab-case")]
    322pub enum IncompatibleRustVersions {
    323    Allow,
    324    Fallback,
    325}
    326
    327#[derive(Copy, Clone, Debug, Deserialize)]
    328#[serde(rename_all = "kebab-case")]
    329pub enum FeatureUnification {
    330    Package,
    331    Selected,
    332    Workspace,
    333}
    334
    335/// The `[term]` table.
    336///
    337/// Example configuration:
    338///
    339/// ```toml
    340/// [term]
    341/// verbose = false
    342/// quiet = false
    343/// color = "auto"
    344/// progress.when = "auto"
    345/// ```
    346#[derive(Debug, Deserialize, Default)]
    347#[serde(rename_all = "kebab-case")]
    348pub struct TermConfig {
    349    pub verbose: Option<bool>,
    350    pub quiet: Option<bool>,
    351    pub color: Option<String>,
    352    pub hyperlinks: Option<bool>,
    353    pub unicode: Option<bool>,
    354    pub progress: Option<ProgressConfig>,
    355}
    356
    357/// The `term.progress` configuration.
    358///
    359/// Example configuration:
    360///
    361/// ```toml
    362/// [term]
    363/// progress.when = "never" # or "auto"
    364/// ```
    365///
    366/// ```toml
    367/// # `when = "always"` requires a `width` field
    368/// [term]
    369/// progress = { when = "always", width = 80 }
    370/// ```
    371#[derive(Debug, Default)]
    372pub struct ProgressConfig {
    373    pub when: ProgressWhen,
    374    pub width: Option<usize>,
    375    /// Communicate progress status with a terminal
    376    pub term_integration: Option<bool>,
    377}
    378
    379#[derive(Debug, Default, Deserialize)]
    380#[serde(rename_all = "kebab-case")]
    381pub enum ProgressWhen {
    382    #[default]
    383    Auto,
    384    Never,
    385    Always,
    386}
    387
    388// We need this custom deserialization for validadting the rule of
    389// `when = "always"` requiring a `width` field.
    390impl<'de> Deserialize<'de> for ProgressConfig {
    391    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    392    where
    393        D: serde::Deserializer<'de>,
    394    {
    395        #[derive(Deserialize)]
    396        #[serde(rename_all = "kebab-case")]
    397        struct ProgressConfigInner {
    398            #[serde(default)]
    399            when: ProgressWhen,
    400            width: Option<usize>,
    401            term_integration: Option<bool>,
    402        }
    403
    404        let pc = ProgressConfigInner::deserialize(deserializer)?;
    405        if let ProgressConfigInner {
    406            when: ProgressWhen::Always,
    407            width: None,
    408            ..
    409        } = pc
    410        {
    411            return Err(serde::de::Error::custom(
    412                "\"always\" progress requires a `width` key",
    413            ));
    414        }
    415        Ok(ProgressConfig {
    416            when: pc.when,
    417            width: pc.width,
    418            term_integration: pc.term_integration,
    419        })
    420    }
    421}
    422
    423#[derive(Debug)]
    424enum EnvConfigValueInner {
    425    Simple(String),
    426    WithOptions {
    427        value: String,
    428        force: bool,
    429        relative: bool,
    430    },
    431}
    432
    433impl<'de> Deserialize<'de> for EnvConfigValueInner {
    434    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    435    where
    436        D: serde::Deserializer<'de>,
    437    {
    438        #[derive(Deserialize)]
    439        struct WithOptions {
    440            value: String,
    441            #[serde(default)]
    442            force: bool,
    443            #[serde(default)]
    444            relative: bool,
    445        }
    446
    447        UntaggedEnumVisitor::new()
    448            .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
    449            .map(|map| {
    450                let with_options: WithOptions = map.deserialize()?;
    451                Ok(EnvConfigValueInner::WithOptions {
    452                    value: with_options.value,
    453                    force: with_options.force,
    454                    relative: with_options.relative,
    455                })
    456            })
    457            .deserialize(deserializer)
    458    }
    459}
    460
    461/// Configuration value for environment variables in `[env]` section.
    462///
    463/// Supports two formats: simple string and with options.
    464///
    465/// ```toml
    466/// [env]
    467/// FOO = "value"
    468/// ```
    469///
    470/// ```toml
    471/// [env]
    472/// BAR = { value = "relative/path", relative = true }
    473/// BAZ = { value = "override", force = true }
    474/// ```
    475#[derive(Debug, Deserialize)]
    476#[serde(transparent)]
    477pub struct EnvConfigValue {
    478    inner: Value<EnvConfigValueInner>,
    479}
    480
    481impl EnvConfigValue {
    482    /// Whether this value should override existing environment variables.
    483    pub fn is_force(&self) -> bool {
    484        match self.inner.val {
    485            EnvConfigValueInner::Simple(_) => false,
    486            EnvConfigValueInner::WithOptions { force, .. } => force,
    487        }
    488    }
    489
    490    /// Resolves the environment variable value.
    491    ///
    492    /// If `relative = true`,
    493    /// the value is interpreted as a [`ConfigRelativePath`]-like path.
    494    pub fn resolve<'a>(&'a self, cwd: &Path) -> Cow<'a, OsStr> {
    495        match self.inner.val {
    496            EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
    497            EnvConfigValueInner::WithOptions {
    498                ref value,
    499                relative,
    500                ..
    501            } => {
    502                if relative {
    503                    let p = self.inner.definition.root(cwd).join(&value);
    504                    Cow::Owned(p.into_os_string())
    505                } else {
    506                    Cow::Borrowed(OsStr::new(value.as_str()))
    507                }
    508            }
    509        }
    510    }
    511}
    512
    513pub type EnvConfig = HashMap<String, EnvConfigValue>;