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