Skip to main content

cargo_config2/
de.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3//! Cargo configuration that environment variables, config overrides, and
4//! target-specific configurations have not been resolved.
5
6#[path = "gen/de.rs"]
7mod generated;
8
9use alloc::{
10    borrow::{Cow, ToOwned as _},
11    collections::BTreeMap,
12    format,
13    string::String,
14    vec,
15    vec::Vec,
16};
17use core::{fmt, iter, slice, str::FromStr};
18use std::{
19    ffi::OsStr,
20    fs,
21    path::{Path, PathBuf},
22};
23
24use serde::{
25    de::{self, Deserialize, Deserializer},
26    ser::{Serialize, Serializer},
27};
28use serde_derive::{Deserialize, Serialize};
29
30pub use crate::value::{Definition, Value};
31use crate::{
32    easy,
33    error::{Context as _, Error, Result},
34    merge::Merge,
35    resolve::{ResolveContext, TargetTripleRef},
36    value::SetPath,
37    walk,
38};
39
40/// Cargo configuration that environment variables, config overrides, and
41/// target-specific configurations have not been resolved.
42#[derive(Debug, Clone, Default, Serialize, Deserialize)]
43#[serde(rename_all = "kebab-case")]
44#[non_exhaustive]
45pub struct Config {
46    // TODO: paths
47    /// The `[alias]` table.
48    ///
49    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#alias)
50    #[serde(default)]
51    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
52    pub alias: BTreeMap<String, StringList>,
53    /// The `[build]` table.
54    ///
55    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#build)
56    #[serde(default)]
57    #[serde(skip_serializing_if = "BuildConfig::is_none")]
58    pub build: BuildConfig,
59    /// The `[credential-alias]` table.
60    ///
61    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#credential-alias)
62    #[serde(default)]
63    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
64    pub credential_alias: BTreeMap<String, PathAndArgs>,
65    /// The `[doc]` table.
66    ///
67    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#doc)
68    #[serde(default)]
69    #[serde(skip_serializing_if = "DocConfig::is_none")]
70    pub doc: DocConfig,
71    /// The `[env]` table.
72    ///
73    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#env)
74    #[serde(default)]
75    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
76    pub env: BTreeMap<String, EnvConfigValue>,
77    /// The `[future-incompat-report]` table.
78    ///
79    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-report)
80    #[serde(default)]
81    #[serde(skip_serializing_if = "FutureIncompatReportConfig::is_none")]
82    pub future_incompat_report: FutureIncompatReportConfig,
83    /// The `[cargo-new]` table.
84    ///
85    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#cargo-new)
86    #[serde(default)]
87    #[serde(skip_serializing_if = "CargoNewConfig::is_none")]
88    pub cargo_new: CargoNewConfig,
89    /// The `[http]` table.
90    ///
91    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#http)
92    #[serde(default)]
93    #[serde(skip_serializing_if = "HttpConfig::is_none")]
94    pub http: HttpConfig,
95    // TODO: install
96    /// The `[net]` table.
97    ///
98    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#net)
99    #[serde(default)]
100    #[serde(skip_serializing_if = "NetConfig::is_none")]
101    pub net: NetConfig,
102    // TODO: patch
103    // TODO: profile
104    /// The `[registries]` table.
105    ///
106    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries)
107    #[serde(default)]
108    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
109    pub registries: BTreeMap<String, RegistriesConfigValue>,
110    /// The `[registry]` table.
111    ///
112    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registry)
113    #[serde(default)]
114    #[serde(skip_serializing_if = "RegistryConfig::is_none")]
115    pub registry: RegistryConfig,
116    /// The `[source]` table.
117    ///
118    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#source)
119    #[serde(default)]
120    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
121    pub source: BTreeMap<String, SourceConfigValue>,
122    /// The `[target]` table.
123    ///
124    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#target)
125    #[serde(default)]
126    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
127    pub target: BTreeMap<String, TargetConfig>,
128    /// The `[term]` table.
129    ///
130    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#term)
131    #[serde(default)]
132    #[serde(skip_serializing_if = "TermConfig::is_none")]
133    pub term: TermConfig,
134}
135
136impl Config {
137    /// Reads config files hierarchically from the current directory and merges them.
138    pub fn load() -> Result<Self> {
139        Self::load_with_cwd(std::env::current_dir().context("failed to get current directory")?)
140    }
141
142    /// Reads config files hierarchically from the given directory and merges them.
143    pub fn load_with_cwd<P: AsRef<Path>>(cwd: P) -> Result<Self> {
144        let cwd = cwd.as_ref();
145        Self::_load_with_options(cwd, walk::cargo_home_with_cwd(cwd).as_deref())
146    }
147
148    /// Reads config files hierarchically from the given directory and merges them.
149    pub fn load_with_options<P: AsRef<Path>, Q: Into<Option<PathBuf>>>(
150        cwd: P,
151        cargo_home: Q,
152    ) -> Result<Self> {
153        Self::_load_with_options(cwd.as_ref(), cargo_home.into().as_deref())
154    }
155    pub(crate) fn _load_with_options(
156        current_dir: &Path,
157        cargo_home: Option<&Path>,
158    ) -> Result<Config> {
159        let mut base = None;
160        for path in crate::walk::WalkInner::with_cargo_home(current_dir, cargo_home) {
161            let config = Self::_load_file(&path)?;
162            match &mut base {
163                None => base = Some((path, config)),
164                Some((base_path, base)) => base.merge(config, false).with_context(|| {
165                    format!(
166                        "failed to merge config from `{}` into `{}`",
167                        path.display(),
168                        base_path.display()
169                    )
170                })?,
171            }
172        }
173        Ok(base.map(|(_, c)| c).unwrap_or_default())
174    }
175
176    /// Reads cargo config file at the given path.
177    ///
178    /// **Note:** Note: This just reads a file at the given path and does not
179    /// respect the hierarchical structure of the cargo config.
180    pub fn load_file<P: AsRef<Path>>(path: P) -> Result<Self> {
181        Self::_load_file(path.as_ref())
182    }
183    fn _load_file(path: &Path) -> Result<Self> {
184        let buf = fs::read_to_string(path)
185            .with_context(|| format!("failed to read `{}`", path.display()))?;
186        let mut config: Config = toml::de::from_str(&buf).with_context(|| {
187            format!("failed to parse `{}` as cargo configuration", path.display())
188        })?;
189        config.set_path(path);
190        Ok(config)
191    }
192
193    /// Merges the given config into this config.
194    ///
195    /// If `force` is `false`, this matches the way cargo [merges configs in the
196    /// parent directories](https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure).
197    ///
198    /// If `force` is `true`, this matches the way cargo's `--config` CLI option
199    /// overrides config.
200    pub(crate) fn merge(&mut self, low: Self, force: bool) -> Result<()> {
201        crate::merge::Merge::merge(self, low, force)
202    }
203
204    pub(crate) fn set_path(&mut self, path: &Path) {
205        crate::value::SetPath::set_path(self, path);
206    }
207
208    #[allow(clippy::ref_option)]
209    pub(crate) fn resolve_target(
210        cx: &ResolveContext,
211        target_configs: &BTreeMap<String, TargetConfig>,
212        override_target_rustflags: bool,
213        build_rustflags: &Option<Flags>,
214        override_target_rustdocflags: bool,
215        build_rustdocflags: &Option<Flags>,
216        target_triple: &TargetTripleRef<'_>,
217        build_config: &easy::BuildConfig,
218    ) -> Result<Option<TargetConfig>> {
219        let target = target_triple.triple();
220        if target.starts_with("cfg(") {
221            bail!("'{target}' is not valid target triple");
222        }
223        let mut target_config = None;
224
225        if let Some(config) = target_configs.get(target) {
226            target_config = Some(TargetConfig {
227                linker: config.linker.clone(),
228                runner: config.runner.clone(),
229                rustflags: config.rustflags.clone(),
230                rustdocflags: config.rustdocflags.clone(),
231                rest: BTreeMap::new(), // skip cloning rest
232            });
233        } else if let Some((before, rest)) = target.split_once('.') {
234            if let Some(config) = target_configs.get(before) {
235                let mut rest_target_configs = &config.rest;
236                let mut rest_target = rest;
237                loop {
238                    if let Some(config) = rest_target_configs.get(rest_target) {
239                        if let TargetConfigRestValue::Config(config) = config {
240                            target_config = Some(TargetConfig {
241                                linker: config.linker.clone(),
242                                runner: config.runner.clone(),
243                                rustflags: config.rustflags.clone(),
244                                rustdocflags: config.rustdocflags.clone(),
245                                rest: BTreeMap::new(), // skip cloning rest
246                            });
247                        }
248                        break;
249                    }
250                    if let Some((before, rest)) = rest_target.split_once('.') {
251                        if let Some(TargetConfigRestValue::Config(config)) =
252                            rest_target_configs.get(before)
253                        {
254                            rest_target_configs = &config.rest;
255                            rest_target = rest;
256                            continue;
257                        }
258                    }
259                    break;
260                }
261            }
262        }
263
264        let target_u_upper = target_u_upper(target);
265        let mut target_linker = target_config.as_mut().and_then(|c| c.linker.take());
266        let mut target_runner = target_config.as_mut().and_then(|c| c.runner.take());
267        let mut target_rustflags: Option<Flags> =
268            target_config.as_mut().and_then(|c| c.rustflags.take());
269        let mut target_rustdocflags: Option<Flags> =
270            target_config.as_mut().and_then(|c| c.rustdocflags.take());
271        if let Some(linker) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_LINKER"))? {
272            target_linker = Some(linker);
273        }
274        if let Some(runner) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUNNER"))? {
275            target_runner = Some(
276                PathAndArgs::from_string(&runner.val, runner.definition.as_ref())
277                    .context("invalid length 0, expected at least one element")?,
278            );
279        }
280        if let Some(rustflags) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUSTFLAGS"))? {
281            let mut rustflags =
282                Flags::from_space_separated(&rustflags.val, rustflags.definition.as_ref());
283            match &mut target_rustflags {
284                Some(target_rustflags) => {
285                    target_rustflags.flags.append(&mut rustflags.flags);
286                }
287                target_rustflags @ None => *target_rustflags = Some(rustflags),
288            }
289        }
290        if let Some(rustdocflags) =
291            cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUSTDOCFLAGS"))?
292        {
293            let mut rustdocflags =
294                Flags::from_space_separated(&rustdocflags.val, rustdocflags.definition.as_ref());
295            match &mut target_rustdocflags {
296                Some(target_rustdocflags) => {
297                    target_rustdocflags.flags.append(&mut rustdocflags.flags);
298                }
299                target_rustdocflags @ None => *target_rustdocflags = Some(rustdocflags),
300            }
301        }
302        for (k, v) in target_configs {
303            if !k.starts_with("cfg(") {
304                continue;
305            }
306            if cx.eval_cfg(k, target_triple, build_config)? {
307                // https://github.com/rust-lang/cargo/pull/12535
308                if target_linker.is_none() {
309                    if let Some(linker) = v.linker.as_ref() {
310                        target_linker = Some(linker.clone());
311                    }
312                }
313                // Priorities (as of 1.68.0-nightly (2022-12-23)):
314                // 1. CARGO_TARGET_<triple>_RUNNER
315                // 2. target.<triple>.runner
316                // 3. target.<cfg>.runner
317                if target_runner.is_none() {
318                    if let Some(runner) = v.runner.as_ref() {
319                        target_runner = Some(runner.clone());
320                    }
321                }
322                // Applied order (as of 1.68.0-nightly (2022-12-23)):
323                // 1. target.<triple>.rustflags
324                // 2. CARGO_TARGET_<triple>_RUSTFLAGS
325                // 3. target.<cfg>.rustflags
326                if let Some(rustflags) = v.rustflags.as_ref() {
327                    match &mut target_rustflags {
328                        Some(target_rustflags) => {
329                            target_rustflags.flags.extend_from_slice(&rustflags.flags);
330                        }
331                        target_rustflags @ None => *target_rustflags = Some(rustflags.clone()),
332                    }
333                }
334            }
335        }
336        if let Some(linker) = target_linker {
337            target_config.get_or_insert_with(TargetConfig::default).linker = Some(linker);
338        }
339        if let Some(runner) = target_runner {
340            target_config.get_or_insert_with(TargetConfig::default).runner = Some(runner);
341        }
342        if override_target_rustflags {
343            target_config
344                .get_or_insert_with(TargetConfig::default)
345                .rustflags
346                .clone_from(build_rustflags);
347        } else if let Some(rustflags) = target_rustflags {
348            target_config.get_or_insert_with(TargetConfig::default).rustflags = Some(rustflags);
349        } else {
350            target_config
351                .get_or_insert_with(TargetConfig::default)
352                .rustflags
353                .clone_from(build_rustflags);
354        }
355        if override_target_rustdocflags {
356            target_config
357                .get_or_insert_with(TargetConfig::default)
358                .rustdocflags
359                .clone_from(build_rustdocflags);
360        } else if let Some(rustdocflags) = target_rustdocflags {
361            target_config.get_or_insert_with(TargetConfig::default).rustdocflags =
362                Some(rustdocflags);
363        } else {
364            target_config
365                .get_or_insert_with(TargetConfig::default)
366                .rustdocflags
367                .clone_from(build_rustdocflags);
368        }
369        Ok(target_config)
370    }
371}
372
373/// The `[build]` table.
374///
375/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#build)
376#[derive(Debug, Clone, Default, Serialize, Deserialize)]
377#[serde(rename_all = "kebab-case")]
378#[non_exhaustive]
379pub struct BuildConfig {
380    /// Sets the maximum number of compiler processes to run in parallel.
381    /// If negative, it sets the maximum number of compiler processes to the
382    /// number of logical CPUs plus provided value. Should not be 0.
383    ///
384    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildjobs)
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub jobs: Option<Value<i32>>,
387    /// Sets the executable to use for `rustc`.
388    ///
389    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc)
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub rustc: Option<Value<String>>,
392    /// Sets a wrapper to execute instead of `rustc`.
393    ///
394    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-wrapper)
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub rustc_wrapper: Option<Value<String>>,
397    /// Sets a wrapper to execute instead of `rustc`, for workspace members only.
398    ///
399    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-workspace-wrapper)
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub rustc_workspace_wrapper: Option<Value<String>>,
402    /// Sets the executable to use for `rustdoc`.
403    ///
404    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdoc)
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub rustdoc: Option<Value<String>>,
407    /// The default target platform triples to compile to.
408    ///
409    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget)
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub target: Option<StringOrArray>,
412    /// The path to where all compiler output is placed. The default if not
413    /// specified is a directory named target located at the root of the workspace.
414    ///
415    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget)
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub target_dir: Option<Value<String>>,
418    /// The path to where all compiler intermediate artifacts are placed. The default if not
419    /// specified is the value of build.target-dir
420    ///
421    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-dir)
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub build_dir: Option<Value<String>>,
424    /// Extra command-line flags to pass to rustc. The value may be an array
425    /// of strings or a space-separated string.
426    ///
427    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustflags)
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub rustflags: Option<Flags>,
430    /// Extra command-line flags to pass to `rustdoc`. The value may be an array
431    /// of strings or a space-separated string.
432    ///
433    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdocflags)
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub rustdocflags: Option<Flags>,
436    /// Whether or not to perform incremental compilation.
437    ///
438    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildincremental)
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub incremental: Option<Value<bool>>,
441    /// Strips the given path prefix from dep info file paths.
442    ///
443    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#builddep-info-basedir)
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub dep_info_basedir: Option<Value<String>>,
446
447    // Resolve contexts. Completely ignored in serialization and deserialization.
448    #[serde(skip)]
449    pub(crate) override_target_rustflags: bool,
450    #[serde(skip)]
451    pub(crate) override_target_rustdocflags: bool,
452}
453
454// https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/target.rs
455/// A `[target.<triple>]` or `[target.<cfg>]` table.
456///
457/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#target)
458#[derive(Debug, Clone, Default, Serialize, Deserialize)]
459#[serde(rename_all = "kebab-case")]
460#[non_exhaustive]
461pub struct TargetConfig {
462    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplelinker)
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub linker: Option<Value<String>>,
465    /// [reference (`target.<triple>.runner`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerunner)
466    ///
467    /// [reference (`target.<cfg>.runner`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrunner)
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub runner: Option<PathAndArgs>,
470    /// [reference (`target.<triple>.rustflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerustflags)
471    ///
472    /// [reference (`target.<cfg>.rustflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrustflags)
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub rustflags: Option<Flags>,
475    /// [reference (`target.<triple>.rustdocflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerustdocflags)
476    ///
477    /// [reference (`target.<cfg>.rustdocflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrustdocflags)
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub rustdocflags: Option<Flags>,
480    // TODO: links: https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplelinks
481    #[serde(flatten)]
482    pub(crate) rest: BTreeMap<String, TargetConfigRestValue>,
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize)]
486#[serde(untagged)]
487pub(crate) enum TargetConfigRestValue {
488    Config(TargetConfig),
489    Other(toml::Value),
490}
491
492impl Merge for TargetConfigRestValue {
493    fn merge(&mut self, low: Self, force: bool) -> Result<()> {
494        match (self, low) {
495            (Self::Config(this), Self::Config(low)) => this.merge(low, force),
496            (this @ Self::Other(_), low @ Self::Config(_)) => {
497                *this = low;
498                Ok(())
499            }
500            (_, Self::Other(_)) => Ok(()),
501        }
502    }
503}
504impl SetPath for TargetConfigRestValue {
505    fn set_path(&mut self, path: &Path) {
506        match self {
507            Self::Config(v) => {
508                v.set_path(path);
509            }
510            Self::Other(_) => {}
511        }
512    }
513}
514
515/// The `[doc]` table.
516///
517/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#doc)
518#[derive(Debug, Clone, Default, Serialize, Deserialize)]
519#[serde(rename_all = "kebab-case")]
520#[non_exhaustive]
521pub struct DocConfig {
522    /// This option sets the browser to be used by `cargo doc`, overriding the
523    /// `BROWSER` environment variable when opening documentation with the `--open` option.
524    ///
525    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#docbrowser)
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub browser: Option<PathAndArgs>,
528}
529
530// TODO: hide internal repr, change to struct
531/// A value of the `[env]` table.
532///
533/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#env)
534#[derive(Debug, Clone, Serialize, Deserialize)]
535#[serde(untagged)]
536#[non_exhaustive]
537pub enum EnvConfigValue {
538    Value(Value<String>),
539    Table {
540        value: Value<String>,
541        #[serde(skip_serializing_if = "Option::is_none")]
542        force: Option<Value<bool>>,
543        #[serde(skip_serializing_if = "Option::is_none")]
544        relative: Option<Value<bool>>,
545    },
546}
547
548impl EnvConfigValue {
549    pub(crate) const fn kind(&self) -> &'static str {
550        match self {
551            Self::Value(..) => "string",
552            Self::Table { .. } => "table",
553        }
554    }
555
556    pub(crate) fn resolve(&self, current_dir: &Path) -> Cow<'_, OsStr> {
557        match self {
558            Self::Value(v) => OsStr::new(&v.val).into(),
559            Self::Table { value, relative, .. } => {
560                if relative.as_ref().is_some_and(|v| v.val) {
561                    if let Some(def) = &value.definition {
562                        return def.root(current_dir).join(&value.val).into_os_string().into();
563                    }
564                }
565                OsStr::new(&value.val).into()
566            }
567        }
568    }
569}
570
571/// The `[future-incompat-report]` table.
572///
573/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-report)
574#[derive(Debug, Clone, Default, Serialize, Deserialize)]
575#[serde(rename_all = "kebab-case")]
576#[non_exhaustive]
577pub struct FutureIncompatReportConfig {
578    /// Controls how often we display a notification to the terminal when a future incompat report is available.
579    ///
580    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-reportfrequency)
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub frequency: Option<Value<Frequency>>,
583}
584
585/// The `[cargo-new]` table.
586///
587/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#cargo-new)
588#[derive(Debug, Clone, Default, Serialize, Deserialize)]
589#[serde(rename_all = "kebab-case")]
590#[non_exhaustive]
591pub struct CargoNewConfig {
592    /// Specifies the source control system to use for initializing a new repository.
593    /// Valid values are git, hg (for Mercurial), pijul, fossil or none to disable this behavior.
594    /// Defaults to git, or none if already inside a VCS repository. Can be overridden with the --vcs CLI option.
595    #[serde(skip_serializing_if = "Option::is_none")]
596    pub vcs: Option<Value<VersionControlSoftware>>,
597}
598
599#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
600#[serde(rename_all = "kebab-case")]
601#[non_exhaustive]
602pub enum VersionControlSoftware {
603    /// Git
604    Git,
605    /// Mercurial
606    #[serde(rename = "hg")]
607    Mercurial,
608    /// Pijul
609    Pijul,
610    /// Fossil
611    Fossil,
612    /// No VCS
613    None,
614}
615
616impl VersionControlSoftware {
617    pub const fn as_str(self) -> &'static str {
618        match self {
619            VersionControlSoftware::Git => "git",
620            VersionControlSoftware::Mercurial => "hg",
621            VersionControlSoftware::Pijul => "pijul",
622            VersionControlSoftware::Fossil => "fossil",
623            VersionControlSoftware::None => "none",
624        }
625    }
626}
627
628impl FromStr for VersionControlSoftware {
629    type Err = Error;
630
631    fn from_str(vcs: &str) -> Result<Self, Self::Err> {
632        match vcs {
633            "git" => Ok(Self::Git),
634            "hg" => Ok(Self::Mercurial),
635            "pijul" => Ok(Self::Pijul),
636            "fossil" => Ok(Self::Fossil),
637            "none" => Ok(Self::None),
638            other => bail!("must be git, hg, pijul, fossil, none, but found `{other}`"),
639        }
640    }
641}
642
643/// The `[http]` table.
644///
645/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#http)
646#[derive(Debug, Clone, Default, Serialize, Deserialize)]
647#[serde(rename_all = "kebab-case")]
648#[non_exhaustive]
649pub struct HttpConfig {
650    /// If true, enables debugging of HTTP requests.
651    /// The debug information can be seen by setting the `CARGO_LOG=network=debug` environment variable
652    /// (or use `network=trace` for even more information).
653    ///
654    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpdebug)
655    #[serde(skip_serializing_if = "Option::is_none")]
656    pub debug: Option<Value<bool>>,
657    /// Sets an HTTP and HTTPS proxy to use. The format is in libcurl format as in `[protocol://]host[:port]`.
658    /// If not set, Cargo will also check the http.proxy setting in your global git configuration.
659    /// If none of those are set, the HTTPS_PROXY or https_proxy environment variables set the proxy for HTTPS requests,
660    /// and http_proxy sets it for HTTP requests.
661    ///
662    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpproxy)
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub proxy: Option<Value<String>>,
665    /// Sets the timeout for each HTTP request, in seconds.
666    ///
667    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout)
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub timeout: Option<Value<u32>>,
670    /// Path to a Certificate Authority (CA) bundle file, used to verify TLS certificates.
671    /// If not specified, Cargo attempts to use the system certificates.
672    ///
673    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcainfo)
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub cainfo: Option<Value<String>>,
676    /// This determines whether or not TLS certificate revocation checks should be performed.
677    /// This only works on Windows.
678    ///
679    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcheck-revoke)
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub check_revoke: Option<Value<bool>>,
682    // TODO: handle ssl-version
683    /// This setting controls timeout behavior for slow connections.
684    /// If the average transfer speed in bytes per second is below the given value
685    /// for `http.timeout` seconds (default 30 seconds), then the connection is considered too slow and Cargo will abort and retry.
686    ///
687    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httplow-speed-limit)
688    #[serde(skip_serializing_if = "Option::is_none")]
689    pub low_speed_limit: Option<Value<u32>>,
690    /// When true, Cargo will attempt to use the HTTP2 protocol with multiplexing.
691    /// This allows multiple requests to use the same connection, usually improving performance when fetching multiple files.
692    /// If false, Cargo will use HTTP 1.1 without pipelining.
693    ///
694    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpmultiplexing)
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub multiplexing: Option<Value<bool>>,
697    /// Specifies a custom user-agent header to use.
698    /// The default if not specified is a string that includes Cargo’s version.
699    ///
700    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpuser-agent)
701    #[serde(skip_serializing_if = "Option::is_none")]
702    pub user_agent: Option<Value<String>>,
703}
704
705/// The `[net]` table.
706///
707/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#net)
708#[derive(Debug, Clone, Default, Serialize, Deserialize)]
709#[serde(rename_all = "kebab-case")]
710#[non_exhaustive]
711pub struct NetConfig {
712    /// Number of times to retry possibly spurious network errors.
713    ///
714    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netretry)
715    #[serde(skip_serializing_if = "Option::is_none")]
716    pub retry: Option<Value<u32>>,
717    /// If this is `true`, then Cargo will use the `git` executable to fetch
718    /// registry indexes and git dependencies. If `false`, then it uses a
719    /// built-in `git` library.
720    ///
721    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netgit-fetch-with-cli)
722    #[serde(skip_serializing_if = "Option::is_none")]
723    pub git_fetch_with_cli: Option<Value<bool>>,
724    /// If this is `true`, then Cargo will avoid accessing the network, and
725    /// attempt to proceed with locally cached data. If `false`, Cargo will
726    /// access the network as needed, and generate an error if it encounters a
727    /// network error.
728    ///
729    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netoffline)
730    #[serde(skip_serializing_if = "Option::is_none")]
731    pub offline: Option<Value<bool>>,
732}
733
734/// A value of the `[registries]` table.
735///
736/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries)
737#[derive(Clone, Default, Serialize, Deserialize)]
738#[serde(rename_all = "kebab-case")]
739#[non_exhaustive]
740pub struct RegistriesConfigValue {
741    /// Specifies the URL of the git index for the registry.
742    ///
743    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriesnameindex)
744    #[serde(skip_serializing_if = "Option::is_none")]
745    pub index: Option<Value<String>>,
746    /// Specifies the authentication token for the given registry.
747    ///
748    /// Note: This library does not read any values in the
749    /// [credentials](https://doc.rust-lang.org/nightly/cargo/reference/config.html#credentials)
750    /// file.
751    ///
752    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriesnametoken)
753    #[serde(skip_serializing_if = "Option::is_none")]
754    pub token: Option<Value<String>>,
755    /// Specifies the credential provider for the given registry.
756    ///
757    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriesnamecredential-provider)
758    #[serde(skip_serializing_if = "Option::is_none")]
759    pub credential_provider: Option<CredentialProvider>,
760    /// Specifies the protocol used to access crates.io.
761    /// Not allowed for any registries besides crates.io.
762    ///
763    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriescrates-ioprotocol)
764    #[serde(skip_serializing_if = "Option::is_none")]
765    pub protocol: Option<Value<RegistriesProtocol>>,
766}
767
768impl fmt::Debug for RegistriesConfigValue {
769    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770        let Self { index, credential_provider, token, protocol } = self;
771        let redacted_token = token
772            .as_ref()
773            .map(|token| Value { val: "[REDACTED]", definition: token.definition.clone() });
774        f.debug_struct("RegistriesConfigValue")
775            .field("index", &index)
776            .field("token", &redacted_token)
777            .field("credential_provider", credential_provider)
778            .field("protocol", &protocol)
779            .finish()
780    }
781}
782
783/// Specifies the protocol used to access crates.io.
784///
785/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriescrates-ioprotocol)
786#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
787#[serde(rename_all = "kebab-case")]
788#[non_exhaustive]
789pub enum RegistriesProtocol {
790    /// Causes Cargo to clone the entire index of all packages ever published to
791    /// [crates.io](https://crates.io/) from <https://github.com/rust-lang/crates.io-index/>.
792    Git,
793    /// A newer protocol which uses HTTPS to download only what is necessary from
794    /// <https://index.crates.io/>.
795    Sparse,
796}
797
798impl FromStr for RegistriesProtocol {
799    type Err = Error;
800
801    fn from_str(protocol: &str) -> Result<Self, Self::Err> {
802        match protocol {
803            "git" => Ok(Self::Git),
804            "sparse" => Ok(Self::Sparse),
805            other => bail!("must be git or sparse, but found `{other}`"),
806        }
807    }
808}
809
810/// The `[registry]` table.
811///
812/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registry)
813#[derive(Clone, Default, Serialize, Deserialize)]
814#[serde(rename_all = "kebab-case")]
815#[non_exhaustive]
816pub struct RegistryConfig {
817    /// The name of the registry (from the
818    /// [`registries` table](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries))
819    /// to use by default for registry commands like
820    /// [`cargo publish`](https://doc.rust-lang.org/nightly/cargo/commands/cargo-publish.html).
821    ///
822    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrydefault)
823    #[serde(skip_serializing_if = "Option::is_none")]
824    pub default: Option<Value<String>>,
825    /// Specifies the credential provider for crates.io. If not set, the providers in
826    /// [`registry.global-credential-providers`]((https://doc.rust-lang.org/nightly/cargo/reference/config.html#registryglobal-credential-providers))
827    /// will be used.
828    ///
829    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrycredential-provider)
830    #[serde(skip_serializing_if = "Option::is_none")]
831    pub credential_provider: Option<CredentialProvider>,
832    /// Specifies the authentication token for [crates.io](https://crates.io/).
833    ///
834    /// Note: This library does not read any values in the
835    /// [credentials](https://doc.rust-lang.org/nightly/cargo/reference/config.html#credentials)
836    /// file.
837    ///
838    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrytoken)
839    #[serde(skip_serializing_if = "Option::is_none")]
840    pub token: Option<Value<String>>,
841    /// Specifies the list of global credential providers.
842    /// If credential provider is not set for a specific registry using
843    /// [`registries.<name>.credential-provider`](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriesnamecredential-provider),
844    /// Cargo will use the credential providers in this list.
845    /// Providers toward the end of the list have precedence.
846    ///
847    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registryglobal-credential-providers)
848    #[serde(default)]
849    #[serde(skip_serializing_if = "GlobalCredentialProviders::is_none")]
850    pub global_credential_providers: GlobalCredentialProviders,
851}
852
853impl fmt::Debug for RegistryConfig {
854    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
855        let Self { default, credential_provider, token, global_credential_providers } = self;
856        let redacted_token = token
857            .as_ref()
858            .map(|token| Value { val: "[REDACTED]", definition: token.definition.clone() });
859        f.debug_struct("RegistryConfig")
860            .field("default", &default)
861            .field("credential_provider", credential_provider)
862            .field("token", &redacted_token)
863            .field("global_credential_providers", global_credential_providers)
864            .finish()
865    }
866}
867
868/// A value of the `[source]` table.
869///
870/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#source)
871#[derive(Clone, Default, Debug, Serialize, Deserialize)]
872#[serde(rename_all = "kebab-case")]
873#[non_exhaustive]
874pub struct SourceConfigValue {
875    /// If set, replace this source with the given named source or named registry.
876    ///
877    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#sourcenamereplace-with)
878    #[serde(skip_serializing_if = "Option::is_none")]
879    pub replace_with: Option<Value<String>>,
880    /// Sets the path to a directory to use as a directory source.
881    ///
882    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#sourcenamedirectory)
883    #[serde(skip_serializing_if = "Option::is_none")]
884    pub directory: Option<Value<String>>,
885    /// Sets the URL to use for a registry source.
886    ///
887    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#sourcenameregistry)
888    #[serde(skip_serializing_if = "Option::is_none")]
889    pub registry: Option<Value<String>>,
890    /// Sets the path to a directory to use as a local registry source.
891    ///
892    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#sourcenamelocal-registry)
893    #[serde(skip_serializing_if = "Option::is_none")]
894    pub local_registry: Option<Value<String>>,
895    /// Sets the URL to use for a git source.
896    ///
897    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#sourcenamegit)
898    #[serde(skip_serializing_if = "Option::is_none")]
899    pub git: Option<Value<String>>,
900    /// Sets the branch name to use for a git repository.
901    /// If none of branch, tag, or rev is set, defaults to the master branch.
902    ///
903    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#sourcenamebranch)
904    #[serde(skip_serializing_if = "Option::is_none")]
905    pub branch: Option<Value<String>>,
906    /// Sets the tag name to use for a git repository.
907    /// If none of branch, tag, or rev is set, defaults to the master branch.
908    ///
909    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#sourcenametag)
910    #[serde(skip_serializing_if = "Option::is_none")]
911    pub tag: Option<Value<String>>,
912    /// Sets the revision to use for a git repository.
913    /// If none of branch, tag, or rev is set, defaults to the master branch.
914    ///
915    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#sourcenamerev)
916    #[serde(skip_serializing_if = "Option::is_none")]
917    pub rev: Option<Value<String>>,
918}
919
920/// Global credential providers.
921///
922/// [Cargo Reference](https://doc.rust-lang.org/cargo/reference/config.html#registryglobal-credential-providers)
923#[derive(Clone, Debug, Default)]
924pub struct GlobalCredentialProviders(pub(crate) Vec<CredentialProvider>);
925
926impl GlobalCredentialProviders {
927    pub(crate) fn is_none(&self) -> bool {
928        self.0.is_empty()
929    }
930}
931
932impl GlobalCredentialProviders {
933    pub(crate) fn from_list<T>(
934        list: impl IntoIterator<Item = T>,
935        definition: Option<&Definition>,
936    ) -> Result<Self, Error>
937    where
938        T: AsRef<str>,
939    {
940        Ok(Self(
941            list.into_iter()
942                .map(|v| CredentialProvider::from_string(v.as_ref(), definition))
943                .collect::<Result<_, _>>()?,
944        ))
945    }
946}
947
948impl AsRef<[CredentialProvider]> for GlobalCredentialProviders {
949    fn as_ref(&self) -> &[CredentialProvider] {
950        &self.0
951    }
952}
953
954impl Serialize for GlobalCredentialProviders {
955    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
956    where
957        S: Serializer,
958    {
959        self.0.serialize(serializer)
960    }
961}
962
963impl<'de> Deserialize<'de> for GlobalCredentialProviders {
964    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
965    where
966        D: Deserializer<'de>,
967    {
968        // TODO: use visitor?
969        Ok(Self(
970            <Vec<String>>::deserialize(deserializer)?
971                .iter()
972                .map(|s| CredentialProvider::from_string(s, None))
973                .collect::<Result<_, _>>()
974                .map_err(de::Error::custom)?,
975        ))
976    }
977}
978
979/// A registry's credential provider.
980///
981/// [Cargo Reference](https://doc.rust-lang.org/cargo/reference/registry-authentication.html)
982#[derive(Clone, Debug)]
983pub struct CredentialProvider {
984    pub kind: CredentialProviderKind,
985    deserialized_repr: StringListDeserializedRepr,
986}
987
988/// The kind of a registry's credential provider.
989#[derive(Clone, Debug)]
990#[non_exhaustive]
991pub enum CredentialProviderKind {
992    /// Uses Cargo’s credentials file to store tokens unencrypted in plain text.
993    ///
994    /// [Cargo Reference](https://doc.rust-lang.org/cargo/reference/registry-authentication.html#cargotoken)
995    CargoToken,
996    /// Uses the Windows Credential Manager to store tokens.
997    ///
998    /// [Cargo Reference](https://doc.rust-lang.org/cargo/reference/registry-authentication.html#cargowincred)
999    CargoWincred,
1000    /// Uses the macOS Keychain to store tokens.
1001    ///
1002    /// [Cargo Reference](https://doc.rust-lang.org/cargo/reference/registry-authentication.html#cargomacos-keychain)
1003    CargoMacosKeychain,
1004    /// Uses libsecret to store tokens.
1005    ///
1006    /// [Cargo Reference](https://doc.rust-lang.org/cargo/reference/registry-authentication.html#cargolibsecret)
1007    CargoLibsecret,
1008    /// Launch a subprocess that returns a token on stdout. Newlines will be trimmed.
1009    ///
1010    /// [Cargo Reference](https://doc.rust-lang.org/cargo/reference/registry-authentication.html#cargotoken-from-stdout-command-args)
1011    CargoTokenFromStdout(PathAndArgs),
1012    /// For credential provider plugins that follow Cargo’s credential provider protocol,
1013    /// the configuration value should be a string with the path to the executable (or the executable name if on the PATH).
1014    ///
1015    /// [Cargo Reference](https://doc.rust-lang.org/cargo/reference/registry-authentication.html#credential-plugins)
1016    Plugin(PathAndArgs),
1017    /// Maybe an alias, otherwise a plugin command.
1018    MaybeAlias(Value<String>),
1019}
1020
1021impl CredentialProvider {
1022    pub(crate) fn from_string(value: &str, definition: Option<&Definition>) -> Result<Self, Error> {
1023        Ok(Self {
1024            kind: CredentialProviderKind::from_list(
1025                split_space_separated(value)
1026                    .map(|v| Value { val: v.to_owned(), definition: definition.cloned() }),
1027                StringListDeserializedRepr::String,
1028            )?,
1029            deserialized_repr: StringListDeserializedRepr::String,
1030        })
1031    }
1032
1033    pub(crate) fn from_array(list: Vec<Value<String>>) -> Result<Self, Error> {
1034        Ok(Self {
1035            kind: CredentialProviderKind::from_list(list, StringListDeserializedRepr::Array)?,
1036            deserialized_repr: StringListDeserializedRepr::Array,
1037        })
1038    }
1039}
1040
1041impl SetPath for CredentialProviderKind {
1042    fn set_path(&mut self, path: &Path) {
1043        match self {
1044            Self::CargoToken
1045            | Self::CargoWincred
1046            | Self::CargoMacosKeychain
1047            | Self::CargoLibsecret => {}
1048            Self::CargoTokenFromStdout(command) | Self::Plugin(command) => command.set_path(path),
1049            Self::MaybeAlias(s) => s.set_path(path),
1050        }
1051    }
1052}
1053
1054impl Serialize for CredentialProvider {
1055    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1056    where
1057        S: Serializer,
1058    {
1059        match self.deserialized_repr {
1060            StringListDeserializedRepr::String => {
1061                let mut s = String::new();
1062
1063                let command = match &self.kind {
1064                    CredentialProviderKind::CargoToken => {
1065                        return "cargo:token".serialize(serializer);
1066                    }
1067                    CredentialProviderKind::CargoWincred => {
1068                        return "cargo:wincred".serialize(serializer);
1069                    }
1070                    CredentialProviderKind::CargoMacosKeychain => {
1071                        return "cargo:macos-keychain".serialize(serializer);
1072                    }
1073                    CredentialProviderKind::CargoLibsecret => {
1074                        return "cargo:libsecret".serialize(serializer);
1075                    }
1076                    CredentialProviderKind::CargoTokenFromStdout(command) => {
1077                        s.push_str("cargo:token-from-stdout ");
1078
1079                        command
1080                    }
1081                    CredentialProviderKind::Plugin(command) => command,
1082                    CredentialProviderKind::MaybeAlias(value) => {
1083                        return value.serialize(serializer);
1084                    }
1085                };
1086
1087                command.serialize_to_string(&mut s);
1088
1089                s.serialize(serializer)
1090            }
1091            StringListDeserializedRepr::Array => {
1092                let mut array = vec![];
1093
1094                let command = match &self.kind {
1095                    CredentialProviderKind::CargoToken => {
1096                        return ["cargo:token"].serialize(serializer);
1097                    }
1098                    CredentialProviderKind::CargoWincred => {
1099                        return ["cargo:wincred"].serialize(serializer);
1100                    }
1101                    CredentialProviderKind::CargoMacosKeychain => {
1102                        return ["cargo:macos-keychain"].serialize(serializer);
1103                    }
1104                    CredentialProviderKind::CargoLibsecret => {
1105                        return ["cargo:libsecret"].serialize(serializer);
1106                    }
1107                    CredentialProviderKind::CargoTokenFromStdout(command) => {
1108                        array.push("cargo:token-from-stdout");
1109
1110                        command
1111                    }
1112                    CredentialProviderKind::Plugin(command) => command,
1113                    CredentialProviderKind::MaybeAlias(value) => {
1114                        return [value].serialize(serializer);
1115                    }
1116                };
1117
1118                command.serialize_to_array(&mut array);
1119
1120                array.serialize(serializer)
1121            }
1122        }
1123    }
1124}
1125
1126impl<'de> Deserialize<'de> for CredentialProvider {
1127    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1128    where
1129        D: Deserializer<'de>,
1130    {
1131        // TODO: use visitor?
1132        match StringOrArray::deserialize(deserializer)? {
1133            StringOrArray::String(s) => Self::from_string(&s.val, s.definition.as_ref()),
1134            StringOrArray::Array(v) => Self::from_array(v),
1135        }
1136        .map_err(de::Error::custom)
1137    }
1138}
1139
1140impl CredentialProviderKind {
1141    fn from_list<T>(list: T, deserialized_repr: StringListDeserializedRepr) -> Result<Self, Error>
1142    where
1143        T: IntoIterator<Item = Value<String>>,
1144    {
1145        let mut iter = list.into_iter().peekable();
1146        let first = iter
1147            .next()
1148            .ok_or_else(|| format_err!("invalid length 0, expected at least one element"))?;
1149
1150        let empty_args = |kind: Self, mut iter: iter::Peekable<T::IntoIter>| {
1151            if iter.next().is_some() {
1152                bail!(
1153                    "args should be empty for credential provider of kind {:?}",
1154                    kind.builtin_kind().unwrap(),
1155                );
1156            }
1157
1158            Ok(kind)
1159        };
1160
1161        match &*first.val {
1162            "cargo:token" => empty_args(Self::CargoToken, iter),
1163            "cargo:wincred" => empty_args(Self::CargoWincred, iter),
1164            "cargo:macos-keychain" => empty_args(Self::CargoMacosKeychain, iter),
1165            "cargo:libsecret" => empty_args(Self::CargoLibsecret, iter),
1166            "cargo:token-from-stdout" => {
1167                let command = PathAndArgs::from_list(
1168                    iter,
1169                    deserialized_repr,
1170                ).ok_or_else(|| format_err!(r#"invalid length 1, expected at least two elements for registry of kind "cargo:token-from-stdout""#))?;
1171
1172                Ok(Self::CargoTokenFromStdout(command))
1173            }
1174            _ if iter.peek().is_none() => Ok(Self::MaybeAlias(first)),
1175            _ => Ok(Self::Plugin(
1176                PathAndArgs::from_list(iter::once(first).chain(iter), deserialized_repr).unwrap(),
1177            )),
1178        }
1179    }
1180
1181    fn builtin_kind(&self) -> Option<&'static str> {
1182        Some(match self {
1183            Self::CargoToken => "cargo:token",
1184            Self::CargoWincred => "cargo:wincred",
1185            Self::CargoMacosKeychain => "cargo:macos-keychain",
1186            Self::CargoLibsecret => "cargo:libsecret",
1187            Self::CargoTokenFromStdout(_) => "cargo:token-from-stdout",
1188            Self::Plugin(_) | Self::MaybeAlias(_) => return None,
1189        })
1190    }
1191}
1192
1193/// The `[term]` table.
1194///
1195/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#term)
1196#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1197#[serde(rename_all = "kebab-case")]
1198#[non_exhaustive]
1199pub struct TermConfig {
1200    /// Controls whether or not log messages are displayed by Cargo.
1201    ///
1202    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termquiet)
1203    #[serde(skip_serializing_if = "Option::is_none")]
1204    pub quiet: Option<Value<bool>>,
1205    /// Controls whether or not extra detailed messages are displayed by Cargo.
1206    ///
1207    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termverbose)
1208    #[serde(skip_serializing_if = "Option::is_none")]
1209    pub verbose: Option<Value<bool>>,
1210    /// Controls whether or not colored output is used in the terminal.
1211    ///
1212    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termcolor)
1213    #[serde(skip_serializing_if = "Option::is_none")]
1214    pub color: Option<Value<Color>>,
1215    #[serde(default)]
1216    #[serde(skip_serializing_if = "TermProgress::is_none")]
1217    pub progress: TermProgress,
1218}
1219
1220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1221#[serde(rename_all = "kebab-case")]
1222#[non_exhaustive]
1223pub struct TermProgress {
1224    /// Controls whether or not progress bar is shown in the terminal.
1225    ///
1226    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswhen)
1227    #[serde(skip_serializing_if = "Option::is_none")]
1228    pub when: Option<Value<When>>,
1229    /// Sets the width for progress bar.
1230    ///
1231    /// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswidth)
1232    #[serde(skip_serializing_if = "Option::is_none")]
1233    pub width: Option<Value<u32>>,
1234}
1235
1236#[allow(clippy::exhaustive_enums)]
1237#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1238#[serde(rename_all = "kebab-case")]
1239pub enum Color {
1240    /// (default) Automatically detect if color support is available on the terminal.
1241    #[default]
1242    Auto,
1243    /// Always display colors.
1244    Always,
1245    /// Never display colors.
1246    Never,
1247}
1248
1249impl Color {
1250    pub const fn as_str(self) -> &'static str {
1251        match self {
1252            Self::Auto => "auto",
1253            Self::Always => "always",
1254            Self::Never => "never",
1255        }
1256    }
1257}
1258
1259impl FromStr for Color {
1260    type Err = Error;
1261
1262    fn from_str(color: &str) -> Result<Self, Self::Err> {
1263        match color {
1264            "auto" => Ok(Self::Auto),
1265            "always" => Ok(Self::Always),
1266            "never" => Ok(Self::Never),
1267            other => bail!("must be auto, always, or never, but found `{other}`"),
1268        }
1269    }
1270}
1271
1272#[allow(clippy::exhaustive_enums)]
1273#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1274#[serde(rename_all = "kebab-case")]
1275pub enum When {
1276    /// (default) Intelligently guess whether to show progress bar.
1277    #[default]
1278    Auto,
1279    /// Always show progress bar.
1280    Always,
1281    /// Never show progress bar.
1282    Never,
1283}
1284
1285impl When {
1286    pub const fn as_str(self) -> &'static str {
1287        match self {
1288            Self::Auto => "auto",
1289            Self::Always => "always",
1290            Self::Never => "never",
1291        }
1292    }
1293}
1294
1295impl FromStr for When {
1296    type Err = Error;
1297
1298    fn from_str(color: &str) -> Result<Self, Self::Err> {
1299        match color {
1300            "auto" => Ok(Self::Auto),
1301            "always" => Ok(Self::Always),
1302            "never" => Ok(Self::Never),
1303            other => bail!("must be auto, always, or never, but found `{other}`"),
1304        }
1305    }
1306}
1307
1308#[allow(clippy::exhaustive_enums)]
1309#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1310#[serde(rename_all = "kebab-case")]
1311pub enum Frequency {
1312    /// (default) Always display a notification when a command (e.g. `cargo build`)
1313    /// produces a future incompat report.
1314    #[default]
1315    Always,
1316    /// Never display a notification.
1317    Never,
1318}
1319
1320impl Frequency {
1321    pub const fn as_str(self) -> &'static str {
1322        match self {
1323            Self::Always => "always",
1324            Self::Never => "never",
1325        }
1326    }
1327}
1328
1329impl FromStr for Frequency {
1330    type Err = Error;
1331
1332    fn from_str(color: &str) -> Result<Self, Self::Err> {
1333        match color {
1334            "always" => Ok(Self::Always),
1335            "never" => Ok(Self::Never),
1336            other => bail!("must be always or never, but found `{other}`"),
1337        }
1338    }
1339}
1340
1341/// A representation of rustflags and rustdocflags.
1342#[derive(Debug, Clone, Serialize)]
1343#[serde(transparent)]
1344pub struct Flags {
1345    pub flags: Vec<Value<String>>,
1346    // for merge
1347    #[serde(skip)]
1348    pub(crate) deserialized_repr: StringListDeserializedRepr,
1349}
1350
1351impl Flags {
1352    /// Creates a rustflags from a string separated with ASCII unit separator ('\x1f').
1353    ///
1354    /// This is a valid format for the following environment variables:
1355    ///
1356    /// - `CARGO_ENCODED_RUSTFLAGS` (Cargo 1.55+)
1357    /// - `CARGO_ENCODED_RUSTDOCFLAGS` (Cargo 1.55+)
1358    ///
1359    /// See also `encode`.
1360    pub(crate) fn from_encoded(s: &Value<String>) -> Self {
1361        Self {
1362            flags: split_encoded(&s.val)
1363                .map(|v| Value { val: v.to_owned(), definition: s.definition.clone() })
1364                .collect(),
1365            // Encoded rustflags cannot be serialized as a string because they may contain spaces.
1366            deserialized_repr: StringListDeserializedRepr::Array,
1367        }
1368    }
1369
1370    /// Creates a rustflags from a string separated with space (' ').
1371    ///
1372    /// This is a valid format for the following environment variables:
1373    ///
1374    /// - `RUSTFLAGS`
1375    /// - `CARGO_TARGET_<triple>_RUSTFLAGS`
1376    /// - `CARGO_BUILD_RUSTFLAGS`
1377    /// - `RUSTDOCFLAGS`
1378    /// - `CARGO_TARGET_<triple>_RUSTDOCFLAGS`
1379    /// - `CARGO_BUILD_RUSTDOCFLAGS`
1380    ///
1381    /// And the following configs:
1382    ///
1383    /// - `target.<triple>.rustflags`
1384    /// - `target.<cfg>.rustflags`
1385    /// - `build.rustflags`
1386    /// - `target.<triple>.rustdocflags` (Cargo 1.78+)
1387    /// - `build.rustdocflags`
1388    ///
1389    /// See also `encode_space_separated`.
1390    pub(crate) fn from_space_separated(s: &str, def: Option<&Definition>) -> Self {
1391        Self {
1392            flags: split_space_separated(s)
1393                .map(|v| Value { val: v.to_owned(), definition: def.cloned() })
1394                .collect(),
1395            deserialized_repr: StringListDeserializedRepr::String,
1396        }
1397    }
1398
1399    pub(crate) fn from_array(flags: Vec<Value<String>>) -> Self {
1400        Self { flags, deserialized_repr: StringListDeserializedRepr::Array }
1401    }
1402}
1403
1404impl<'de> Deserialize<'de> for Flags {
1405    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1406    where
1407        D: Deserializer<'de>,
1408    {
1409        // TODO: use visitor?
1410        let v: StringOrArray = Deserialize::deserialize(deserializer)?;
1411        match v {
1412            StringOrArray::String(s) => {
1413                Ok(Self::from_space_separated(&s.val, s.definition.as_ref()))
1414            }
1415            StringOrArray::Array(v) => Ok(Self::from_array(v)),
1416        }
1417    }
1418}
1419
1420// https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/path.rs
1421#[derive(Debug, Deserialize, PartialEq, Clone)]
1422#[serde(transparent)]
1423pub struct ConfigRelativePath(pub(crate) Value<String>);
1424
1425impl ConfigRelativePath {
1426    /// Returns the underlying value.
1427    pub fn value(&self) -> &Value<String> {
1428        &self.0
1429    }
1430
1431    /// Returns the raw underlying configuration value for this key.
1432    pub fn raw_value(&self) -> &str {
1433        &self.0.val
1434    }
1435
1436    // /// Resolves this configuration-relative path to an absolute path.
1437    // ///
1438    // /// This will always return an absolute path where it's relative to the
1439    // /// location for configuration for this value.
1440    // pub(crate) fn resolve_path(&self, current_dir: &Path) -> Cow<'_, Path> {
1441    //     self.0.resolve_as_path(current_dir)
1442    // }
1443
1444    /// Resolves this configuration-relative path to either an absolute path or
1445    /// something appropriate to execute from `PATH`.
1446    ///
1447    /// Values which don't look like a filesystem path (don't contain `/` or
1448    /// `\`) will be returned as-is, and everything else will fall through to an
1449    /// absolute path.
1450    pub(crate) fn resolve_program(&self, current_dir: &Path) -> Cow<'_, Path> {
1451        self.0.resolve_as_program_path(current_dir)
1452    }
1453}
1454
1455/// An executable path with arguments.
1456///
1457/// [Cargo Reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#executable-paths-with-arguments)
1458#[derive(Debug, Clone)]
1459#[non_exhaustive]
1460pub struct PathAndArgs {
1461    pub path: ConfigRelativePath,
1462    pub args: Vec<Value<String>>,
1463
1464    // for merge
1465    pub(crate) deserialized_repr: StringListDeserializedRepr,
1466}
1467
1468impl PathAndArgs {
1469    pub(crate) fn from_string(value: &str, definition: Option<&Definition>) -> Option<Self> {
1470        Self::from_list(
1471            split_space_separated(value)
1472                .map(|v| Value { val: v.to_owned(), definition: definition.cloned() }),
1473            StringListDeserializedRepr::String,
1474        )
1475    }
1476
1477    pub(crate) fn from_array(list: Vec<Value<String>>) -> Option<Self> {
1478        Self::from_list(list, StringListDeserializedRepr::Array)
1479    }
1480
1481    fn from_list(
1482        list: impl IntoIterator<Item = Value<String>>,
1483        deserialized_repr: StringListDeserializedRepr,
1484    ) -> Option<Self> {
1485        let mut iter = list.into_iter();
1486
1487        Some(Self {
1488            path: ConfigRelativePath(iter.next()?),
1489            args: iter.collect(),
1490            deserialized_repr,
1491        })
1492    }
1493
1494    fn serialize_to_string(&self, s: &mut String) {
1495        s.push_str(self.path.raw_value());
1496
1497        for arg in &self.args {
1498            s.push(' ');
1499            s.push_str(&arg.val);
1500        }
1501    }
1502
1503    fn array_len(&self) -> usize {
1504        1 + self.args.len()
1505    }
1506
1507    fn serialize_to_array<'a>(&'a self, v: &mut Vec<&'a str>) {
1508        v.push(self.path.raw_value());
1509
1510        for arg in &self.args {
1511            v.push(&arg.val);
1512        }
1513    }
1514}
1515
1516impl Serialize for PathAndArgs {
1517    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1518    where
1519        S: Serializer,
1520    {
1521        match self.deserialized_repr {
1522            StringListDeserializedRepr::String => {
1523                let mut s = String::new();
1524
1525                self.serialize_to_string(&mut s);
1526                s.serialize(serializer)
1527            }
1528            StringListDeserializedRepr::Array => {
1529                let mut v = Vec::with_capacity(self.array_len());
1530
1531                self.serialize_to_array(&mut v);
1532                v.serialize(serializer)
1533            }
1534        }
1535    }
1536}
1537impl<'de> Deserialize<'de> for PathAndArgs {
1538    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1539    where
1540        D: Deserializer<'de>,
1541    {
1542        // TODO: use visitor?
1543        match StringOrArray::deserialize(deserializer)? {
1544            StringOrArray::String(s) => Self::from_string(&s.val, s.definition.as_ref()),
1545            StringOrArray::Array(v) => Self::from_array(v),
1546        }
1547        .ok_or_else(|| de::Error::invalid_length(0, &"at least one element"))
1548    }
1549}
1550
1551#[derive(Debug, Clone)]
1552#[non_exhaustive]
1553pub struct StringList {
1554    pub list: Vec<Value<String>>,
1555
1556    // for merge
1557    pub(crate) deserialized_repr: StringListDeserializedRepr,
1558}
1559
1560impl Default for StringList {
1561    fn default() -> Self {
1562        Self { list: vec![], deserialized_repr: StringListDeserializedRepr::Array }
1563    }
1564}
1565
1566#[derive(Debug, Clone, Copy, PartialEq)]
1567pub(crate) enum StringListDeserializedRepr {
1568    String,
1569    Array,
1570}
1571impl StringListDeserializedRepr {
1572    pub(crate) const fn as_str(self) -> &'static str {
1573        match self {
1574            Self::String => "string",
1575            Self::Array => "array",
1576        }
1577    }
1578}
1579
1580impl StringList {
1581    pub(crate) fn from_string(value: &str, definition: Option<&Definition>) -> Self {
1582        Self {
1583            list: split_space_separated(value)
1584                .map(|v| Value { val: v.to_owned(), definition: definition.cloned() })
1585                .collect(),
1586            deserialized_repr: StringListDeserializedRepr::String,
1587        }
1588    }
1589    pub(crate) fn from_array(list: Vec<Value<String>>) -> Self {
1590        Self { list, deserialized_repr: StringListDeserializedRepr::Array }
1591    }
1592}
1593
1594impl Serialize for StringList {
1595    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1596    where
1597        S: Serializer,
1598    {
1599        match self.deserialized_repr {
1600            StringListDeserializedRepr::String => {
1601                let mut s = String::with_capacity(
1602                    self.list.len().saturating_sub(1)
1603                        + self.list.iter().map(|v| v.val.len()).sum::<usize>(),
1604                );
1605                for arg in &self.list {
1606                    if !s.is_empty() {
1607                        s.push(' ');
1608                    }
1609                    s.push_str(&arg.val);
1610                }
1611                s.serialize(serializer)
1612            }
1613            StringListDeserializedRepr::Array => self.list.serialize(serializer),
1614        }
1615    }
1616}
1617impl<'de> Deserialize<'de> for StringList {
1618    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1619    where
1620        D: Deserializer<'de>,
1621    {
1622        // TODO: use visitor?
1623        let v: StringOrArray = Deserialize::deserialize(deserializer)?;
1624        match v {
1625            StringOrArray::String(s) => Ok(Self::from_string(&s.val, s.definition.as_ref())),
1626            StringOrArray::Array(v) => Ok(Self::from_array(v)),
1627        }
1628    }
1629}
1630
1631/// A string or array of strings.
1632#[allow(clippy::exhaustive_enums)]
1633#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1634#[serde(untagged)]
1635pub enum StringOrArray {
1636    String(Value<String>),
1637    Array(Vec<Value<String>>),
1638}
1639
1640impl StringOrArray {
1641    pub(crate) const fn kind(&self) -> &'static str {
1642        match self {
1643            Self::String(..) => "string",
1644            Self::Array(..) => "array",
1645        }
1646    }
1647
1648    // pub(crate) fn string(&self) -> Option<&Value<String>> {
1649    //     match self {
1650    //         Self::String(s) => Some(s),
1651    //         Self::Array(_) => None,
1652    //     }
1653    // }
1654    // pub(crate) fn array(&self) -> Option<&[Value<String>]> {
1655    //     match self {
1656    //         Self::String(_) => None,
1657    //         Self::Array(v) => Some(v),
1658    //     }
1659    // }
1660    pub(crate) fn as_array_no_split(&self) -> &[Value<String>] {
1661        match self {
1662            Self::String(s) => slice::from_ref(s),
1663            Self::Array(v) => v,
1664        }
1665    }
1666}
1667
1668fn target_u_lower(target: &str) -> String {
1669    target.replace(['-', '.'], "_")
1670}
1671pub(crate) fn target_u_upper(target: &str) -> String {
1672    let mut target = target_u_lower(target);
1673    target.make_ascii_uppercase();
1674    target
1675}
1676
1677pub(crate) fn split_encoded(s: &str) -> impl Iterator<Item = &str> {
1678    s.split('\x1f')
1679}
1680pub(crate) fn split_space_separated(s: &str) -> impl Iterator<Item = &str> {
1681    // TODO: tab/line?
1682    // https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/path.rs#L89
1683    s.split(' ').map(str::trim).filter(|s| !s.is_empty())
1684}