| //! Cargo configuration schemas. |
| //! |
| //! This module contains types that define the schema for various configuration |
| //! sections found in Cargo configuration. |
| //! |
| //! These types are mostly used by [`GlobalContext::get`](super::GlobalContext::get) |
| //! to deserialize configuration values from TOML files, environment variables, |
| //! and CLI arguments. |
| //! |
| //! Schema types here should only contain data and simple accessor methods. |
| //! Avoid depending on [`GlobalContext`](super::GlobalContext) directly. |
| |
| use crate::util::data_structures::HashMap; |
| use std::borrow::Cow; |
| use std::ffi::OsStr; |
| |
| use cargo_credential::Secret; |
| use serde::Deserialize; |
| use serde_untagged::UntaggedEnumVisitor; |
| |
| use std::path::Path; |
| |
| use crate::CargoResult; |
| |
| use super::OptValue; |
| use super::PathAndArgs; |
| use super::StringList; |
| use super::Value; |
| use super::path::ConfigRelativePath; |
| |
| /// The `[http]` table. |
| /// |
| /// Example configuration: |
| /// |
| /// ```toml |
| /// [http] |
| /// proxy = "host:port" |
| /// timeout = 30 |
| /// cainfo = "/path/to/ca-bundle.crt" |
| /// check-revoke = true |
| /// multiplexing = true |
| /// ssl-version = "tlsv1.3" |
| /// ``` |
| #[derive(Debug, Default, Deserialize, PartialEq)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoHttpConfig { |
| pub proxy: Option<String>, |
| pub low_speed_limit: Option<u32>, |
| pub timeout: Option<u64>, |
| pub cainfo: Option<ConfigRelativePath>, |
| pub proxy_cainfo: Option<ConfigRelativePath>, |
| pub check_revoke: Option<bool>, |
| pub user_agent: Option<String>, |
| pub debug: Option<bool>, |
| pub multiplexing: Option<bool>, |
| pub ssl_version: Option<SslVersionConfig>, |
| } |
| |
| /// The `[future-incompat-report]` stable |
| /// |
| /// Example configuration: |
| /// |
| /// ```toml |
| /// [future-incompat-report] |
| /// frequency = "always" |
| /// ``` |
| #[derive(Debug, Default, Deserialize, PartialEq)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoFutureIncompatConfig { |
| frequency: Option<CargoFutureIncompatFrequencyConfig>, |
| } |
| |
| #[derive(Debug, Default, Deserialize, PartialEq)] |
| #[serde(rename_all = "kebab-case")] |
| pub enum CargoFutureIncompatFrequencyConfig { |
| #[default] |
| Always, |
| Never, |
| } |
| |
| impl CargoFutureIncompatConfig { |
| pub fn should_display_message(&self) -> bool { |
| use CargoFutureIncompatFrequencyConfig::*; |
| |
| let frequency = self.frequency.as_ref().unwrap_or(&Always); |
| match frequency { |
| Always => true, |
| Never => false, |
| } |
| } |
| } |
| |
| /// Configuration for `ssl-version` in `http` section |
| /// There are two ways to configure: |
| /// |
| /// ```text |
| /// [http] |
| /// ssl-version = "tlsv1.3" |
| /// ``` |
| /// |
| /// ```text |
| /// [http] |
| /// ssl-version.min = "tlsv1.2" |
| /// ssl-version.max = "tlsv1.3" |
| /// ``` |
| #[derive(Clone, Debug, PartialEq)] |
| pub enum SslVersionConfig { |
| Single(String), |
| Range(SslVersionConfigRange), |
| } |
| |
| impl<'de> Deserialize<'de> for SslVersionConfig { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: serde::Deserializer<'de>, |
| { |
| UntaggedEnumVisitor::new() |
| .string(|single| Ok(SslVersionConfig::Single(single.to_owned()))) |
| .map(|map| map.deserialize().map(SslVersionConfig::Range)) |
| .deserialize(deserializer) |
| } |
| } |
| |
| #[derive(Clone, Debug, Deserialize, PartialEq)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct SslVersionConfigRange { |
| pub min: Option<String>, |
| pub max: Option<String>, |
| } |
| |
| /// The `[net]` table. |
| /// |
| /// Example configuration: |
| /// |
| /// ```toml |
| /// [net] |
| /// retry = 2 |
| /// offline = false |
| /// git-fetch-with-cli = true |
| /// ``` |
| #[derive(Debug, Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoNetConfig { |
| pub retry: Option<u32>, |
| pub offline: Option<bool>, |
| pub git_fetch_with_cli: Option<bool>, |
| pub ssh: Option<CargoSshConfig>, |
| } |
| |
| #[derive(Debug, Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoSshConfig { |
| pub known_hosts: Option<Vec<Value<String>>>, |
| } |
| |
| /// Configuration for `jobs` in `build` section. There are two |
| /// ways to configure: An integer or a simple string expression. |
| /// |
| /// ```toml |
| /// [build] |
| /// jobs = 1 |
| /// ``` |
| /// |
| /// ```toml |
| /// [build] |
| /// jobs = "default" # Currently only support "default". |
| /// ``` |
| #[derive(Debug, Clone)] |
| pub enum JobsConfig { |
| Integer(i32), |
| String(String), |
| } |
| |
| impl<'de> Deserialize<'de> for JobsConfig { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: serde::Deserializer<'de>, |
| { |
| UntaggedEnumVisitor::new() |
| .i32(|int| Ok(JobsConfig::Integer(int))) |
| .string(|string| Ok(JobsConfig::String(string.to_owned()))) |
| .deserialize(deserializer) |
| } |
| } |
| |
| /// The `[build]` table. |
| /// |
| /// Example configuration: |
| /// |
| /// ```toml |
| /// [build] |
| /// jobs = 4 |
| /// target = "x86_64-unknown-linux-gnu" |
| /// target-dir = "target" |
| /// rustflags = ["-C", "link-arg=-fuse-ld=lld"] |
| /// incremental = true |
| /// ``` |
| #[derive(Debug, Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoBuildConfig { |
| // deprecated, but preserved for compatibility |
| pub pipelining: Option<bool>, |
| pub dep_info_basedir: Option<ConfigRelativePath>, |
| pub target_dir: Option<ConfigRelativePath>, |
| pub build_dir: Option<ConfigRelativePath>, |
| pub incremental: Option<bool>, |
| pub target: Option<BuildTargetConfig>, |
| pub jobs: Option<JobsConfig>, |
| pub rustflags: Option<StringList>, |
| pub rustdocflags: Option<StringList>, |
| pub rustc_wrapper: Option<ConfigRelativePath>, |
| pub rustc_workspace_wrapper: Option<ConfigRelativePath>, |
| pub rustc: Option<ConfigRelativePath>, |
| pub rustdoc: Option<ConfigRelativePath>, |
| pub artifact_dir: Option<ConfigRelativePath>, |
| pub warnings: Option<WarningHandling>, |
| /// Unstable feature `-Zsbom`. |
| pub sbom: Option<bool>, |
| /// Unstable feature `-Zbuild-analysis`. |
| pub analysis: Option<CargoBuildAnalysis>, |
| } |
| |
| /// Metrics collection for build analysis. |
| #[derive(Debug, Deserialize, Default)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoBuildAnalysis { |
| pub enabled: bool, |
| } |
| |
| /// Whether warnings should warn, be allowed, or cause an error. |
| #[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)] |
| #[serde(rename_all = "kebab-case")] |
| pub enum WarningHandling { |
| #[default] |
| /// Output warnings. |
| Warn, |
| /// Allow warnings (do not output them). |
| Allow, |
| /// Error if warnings are emitted. |
| Deny, |
| } |
| |
| /// Configuration for `build.target`. |
| /// |
| /// Accepts in the following forms: |
| /// |
| /// ```toml |
| /// target = "a" |
| /// target = ["a"] |
| /// target = ["a", "b"] |
| /// ``` |
| #[derive(Debug, Deserialize)] |
| #[serde(transparent)] |
| pub struct BuildTargetConfig { |
| inner: Value<BuildTargetConfigInner>, |
| } |
| |
| #[derive(Debug)] |
| enum BuildTargetConfigInner { |
| One(String), |
| Many(Vec<String>), |
| } |
| |
| impl<'de> Deserialize<'de> for BuildTargetConfigInner { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: serde::Deserializer<'de>, |
| { |
| UntaggedEnumVisitor::new() |
| .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned()))) |
| .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many)) |
| .deserialize(deserializer) |
| } |
| } |
| |
| impl BuildTargetConfig { |
| /// Gets values of `build.target` as a list of strings. |
| pub fn values(&self, cwd: &Path) -> CargoResult<Vec<String>> { |
| let map = |s: &String| { |
| if s.ends_with(".json") { |
| // Path to a target specification file (in JSON). |
| // <https://doc.rust-lang.org/rustc/targets/custom.html> |
| self.inner |
| .definition |
| .root(cwd) |
| .join(s) |
| .to_str() |
| .expect("must be utf-8 in toml") |
| .to_string() |
| } else { |
| // A string. Probably a target triple. |
| s.to_string() |
| } |
| }; |
| let values = match &self.inner.val { |
| BuildTargetConfigInner::One(s) => vec![map(s)], |
| BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(), |
| }; |
| Ok(values) |
| } |
| } |
| |
| /// The `[resolver]` table. |
| /// |
| /// Example configuration: |
| /// |
| /// ```toml |
| /// [resolver] |
| /// incompatible-rust-versions = "fallback" |
| /// incompatible-publish-age = "deny" |
| /// feature-unification = "workspace" |
| /// lockfile-path = "my/Cargo.lock" |
| /// ``` |
| #[derive(Debug, Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct CargoResolverConfig { |
| pub incompatible_rust_versions: Option<IncompatibleRustVersions>, |
| pub incompatible_publish_age: Option<IncompatiblePublishAge>, |
| pub feature_unification: Option<FeatureUnification>, |
| pub lockfile_path: Option<ConfigRelativePath>, |
| } |
| |
| #[derive(Debug, Deserialize, PartialEq, Eq)] |
| #[serde(rename_all = "kebab-case")] |
| pub enum IncompatibleRustVersions { |
| Allow, |
| Fallback, |
| } |
| |
| #[derive(Debug, Deserialize, PartialEq, Eq)] |
| #[serde(rename_all = "kebab-case")] |
| pub enum IncompatiblePublishAge { |
| Allow, |
| Deny, |
| } |
| |
| #[derive(Copy, Clone, Debug, Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub enum FeatureUnification { |
| Package, |
| Selected, |
| Workspace, |
| } |
| |
| /// The `[term]` table. |
| /// |
| /// Example configuration: |
| /// |
| /// ```toml |
| /// [term] |
| /// verbose = false |
| /// quiet = false |
| /// color = "auto" |
| /// progress.when = "auto" |
| /// ``` |
| #[derive(Debug, Deserialize, Default)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct TermConfig { |
| pub verbose: Option<bool>, |
| pub quiet: Option<bool>, |
| pub color: Option<String>, |
| pub hyperlinks: Option<bool>, |
| pub unicode: Option<bool>, |
| pub progress: Option<ProgressConfig>, |
| } |
| |
| /// The `term.progress` configuration. |
| /// |
| /// Example configuration: |
| /// |
| /// ```toml |
| /// [term] |
| /// progress.when = "never" # or "auto" |
| /// ``` |
| /// |
| /// ```toml |
| /// # `when = "always"` requires a `width` field |
| /// [term] |
| /// progress = { when = "always", width = 80 } |
| /// ``` |
| #[derive(Debug, Default)] |
| pub struct ProgressConfig { |
| pub when: ProgressWhen, |
| pub width: Option<usize>, |
| /// Communicate progress status with a terminal |
| pub term_integration: Option<bool>, |
| } |
| |
| #[derive(Debug, Default, Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub enum ProgressWhen { |
| #[default] |
| Auto, |
| Never, |
| Always, |
| } |
| |
| // We need this custom deserialization for validadting the rule of |
| // `when = "always"` requiring a `width` field. |
| impl<'de> Deserialize<'de> for ProgressConfig { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: serde::Deserializer<'de>, |
| { |
| #[derive(Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| struct ProgressConfigInner { |
| #[serde(default)] |
| when: ProgressWhen, |
| width: Option<usize>, |
| term_integration: Option<bool>, |
| } |
| |
| let pc = ProgressConfigInner::deserialize(deserializer)?; |
| if let ProgressConfigInner { |
| when: ProgressWhen::Always, |
| width: None, |
| .. |
| } = pc |
| { |
| return Err(serde::de::Error::custom( |
| "\"always\" progress requires a `width` key", |
| )); |
| } |
| Ok(ProgressConfig { |
| when: pc.when, |
| width: pc.width, |
| term_integration: pc.term_integration, |
| }) |
| } |
| } |
| |
| #[derive(Debug)] |
| enum EnvConfigValueInner { |
| Simple(String), |
| WithOptions { |
| value: ConfigRelativePath, |
| force: bool, |
| relative: bool, |
| }, |
| } |
| |
| impl<'de> Deserialize<'de> for EnvConfigValueInner { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: serde::Deserializer<'de>, |
| { |
| #[derive(Deserialize)] |
| struct WithOptions { |
| value: ConfigRelativePath, |
| #[serde(default)] |
| force: bool, |
| #[serde(default)] |
| relative: bool, |
| } |
| |
| UntaggedEnumVisitor::new() |
| .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned()))) |
| .map(|map| { |
| let with_options: WithOptions = map.deserialize()?; |
| Ok(EnvConfigValueInner::WithOptions { |
| value: with_options.value, |
| force: with_options.force, |
| relative: with_options.relative, |
| }) |
| }) |
| .deserialize(deserializer) |
| } |
| } |
| |
| /// Configuration value for environment variables in `[env]` section. |
| /// |
| /// Supports two formats: simple string and with options. |
| /// |
| /// ```toml |
| /// [env] |
| /// FOO = "value" |
| /// ``` |
| /// |
| /// ```toml |
| /// [env] |
| /// BAR = { value = "relative/path", relative = true } |
| /// BAZ = { value = "override", force = true } |
| /// ``` |
| #[derive(Debug, Deserialize)] |
| #[serde(transparent)] |
| pub struct EnvConfigValue { |
| inner: EnvConfigValueInner, |
| } |
| |
| impl EnvConfigValue { |
| /// Whether this value should override existing environment variables. |
| pub fn is_force(&self) -> bool { |
| match self.inner { |
| EnvConfigValueInner::Simple(_) => false, |
| EnvConfigValueInner::WithOptions { force, .. } => force, |
| } |
| } |
| |
| /// Resolves the environment variable value. |
| /// |
| /// If `relative = true`, |
| /// the value is interpreted as a [`ConfigRelativePath`]-like path. |
| pub fn resolve<'a>(&'a self, cwd: &Path) -> Cow<'a, OsStr> { |
| match self.inner { |
| EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())), |
| EnvConfigValueInner::WithOptions { |
| ref value, |
| relative, |
| .. |
| } => { |
| if relative { |
| let p = value.value().definition.root(cwd).join(value.raw_value()); |
| Cow::Owned(p.into_os_string()) |
| } else { |
| Cow::Borrowed(OsStr::new(value.raw_value())) |
| } |
| } |
| } |
| } |
| } |
| |
| pub type EnvConfig = HashMap<String, EnvConfigValue>; |
| |
| /// `[registries.NAME]` tables. |
| /// |
| /// The values here should be kept in sync with `GlobalRegistryConfig` |
| #[derive(Deserialize, Clone, Debug)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct RegistryConfig { |
| pub index: Option<String>, |
| pub token: OptValue<Secret<String>>, |
| pub credential_provider: Option<PathAndArgs>, |
| pub secret_key: OptValue<Secret<String>>, |
| pub secret_key_subject: Option<String>, |
| /// Minimum publish age threshold for RFC 3923 |
| pub min_publish_age: Option<String>, |
| #[serde(rename = "protocol")] |
| _protocol: Option<String>, |
| } |
| |
| /// The `[registry]` table, which has more keys than the `[registries.NAME]` tables. |
| /// |
| /// Note: nesting `RegistryConfig` inside this struct and using `serde(flatten)` *should* work |
| /// but fails with "invalid type: sequence, expected a value" when attempting to deserialize. |
| #[derive(Deserialize)] |
| #[serde(rename_all = "kebab-case")] |
| pub struct GlobalRegistryConfig { |
| pub index: Option<String>, |
| pub token: OptValue<Secret<String>>, |
| pub credential_provider: Option<PathAndArgs>, |
| pub secret_key: OptValue<Secret<String>>, |
| pub secret_key_subject: Option<String>, |
| /// Minimum publish age threshold for RFC 3923 |
| pub min_publish_age: Option<String>, |
| /// Global default Minimum publish age threshold for RFC 3923 |
| pub global_min_publish_age: Option<String>, |
| #[serde(rename = "default")] |
| _default: Option<String>, |
| #[serde(rename = "global-credential-providers")] |
| _global_credential_providers: Option<Vec<String>>, |
| } |
| |
| impl GlobalRegistryConfig { |
| pub fn to_registry_config(self) -> RegistryConfig { |
| RegistryConfig { |
| index: self.index, |
| token: self.token, |
| credential_provider: self.credential_provider, |
| secret_key: self.secret_key, |
| secret_key_subject: self.secret_key_subject, |
| min_publish_age: self.min_publish_age, |
| _protocol: None, |
| } |
| } |
| } |