Skip to main content

cargo_config2/
easy.rs

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