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}