cargo_config2/
easy.rs

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