1use crate::sources::CRATES_IO_REGISTRY;
14use crate::util::data_structures::HashMap;
15use std::borrow::Cow;
16use std::ffi::OsStr;
17use std::str::FromStr;
18use std::{fmt, hash};
19
20use cargo_credential::Secret;
21use serde::Deserialize;
22use serde::Serialize;
23use serde::de;
24use serde_untagged::UntaggedEnumVisitor;
25
26use std::path::Path;
27
28use crate::CargoResult;
29
30use super::OptValue;
31use super::PathAndArgs;
32use super::StringList;
33use super::Value;
34use super::path::ConfigRelativePath;
35
36#[derive(Debug, Default, Deserialize, PartialEq)]
37#[serde(rename_all = "kebab-case")]
38pub struct CargoCacheConfig {
39 pub auto_clean_frequency: Option<String>,
41 pub global_clean: Option<GlobalCleanConfig>,
43}
44
45#[derive(Debug, Default, Deserialize, PartialEq)]
52#[serde(rename_all = "kebab-case")]
53pub struct GlobalCleanConfig {
54 pub max_src_age: Option<String>,
56 pub max_crate_age: Option<String>,
58 pub max_index_age: Option<String>,
60 pub max_git_co_age: Option<String>,
62 pub max_git_db_age: Option<String>,
64}
65
66#[derive(Deserialize)]
67#[serde(rename_all = "kebab-case")]
68pub struct CargoNewConfig {
69 #[deprecated = "cargo-new no longer supports adding the authors field"]
70 #[expect(dead_code, reason = "deprecated")]
71 name: Option<String>,
72
73 #[deprecated = "cargo-new no longer supports adding the authors field"]
74 #[expect(dead_code, reason = "deprecated")]
75 email: Option<String>,
76
77 #[serde(rename = "vcs")]
78 pub version_control: Option<VersionControl>,
79}
80
81#[derive(Clone, Copy, Debug, PartialEq)]
82pub enum VersionControl {
83 Git,
84 Hg,
85 Pijul,
86 Fossil,
87 NoVcs,
88}
89
90impl VersionControl {
91 pub const VALUES: &[Self] = &[Self::Git, Self::Hg, Self::Pijul, Self::Fossil, Self::NoVcs];
92
93 pub fn as_str(&self) -> &'static str {
94 match self {
95 VersionControl::Git => "git",
96 VersionControl::Hg => "hg",
97 VersionControl::Pijul => "pijul",
98 VersionControl::Fossil => "fossil",
99 VersionControl::NoVcs => "none",
100 }
101 }
102}
103
104impl FromStr for VersionControl {
105 type Err = anyhow::Error;
106
107 fn from_str(s: &str) -> Result<Self, anyhow::Error> {
108 match s {
109 "git" => Ok(VersionControl::Git),
110 "hg" => Ok(VersionControl::Hg),
111 "pijul" => Ok(VersionControl::Pijul),
112 "fossil" => Ok(VersionControl::Fossil),
113 "none" => Ok(VersionControl::NoVcs),
114 other => anyhow::bail!("unknown vcs specification: `{}`", other),
115 }
116 }
117}
118
119impl<'de> de::Deserialize<'de> for VersionControl {
120 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
121 where
122 D: de::Deserializer<'de>,
123 {
124 let s = String::deserialize(deserializer)?;
125 FromStr::from_str(&s).map_err(de::Error::custom)
126 }
127}
128
129#[derive(Debug, Deserialize)]
131#[serde(rename_all = "kebab-case")]
132pub struct SourceConfigDef {
133 pub replace_with: OptValue<String>,
135 pub directory: Option<ConfigRelativePath>,
137 pub registry: OptValue<String>,
139 pub local_registry: Option<ConfigRelativePath>,
141 pub git: OptValue<String>,
143 pub branch: OptValue<String>,
145 pub tag: OptValue<String>,
147 pub rev: OptValue<String>,
149}
150
151#[derive(serde::Deserialize, Debug)]
156#[serde(default)]
157pub struct RustdocExternMap {
158 #[serde(deserialize_with = "default_crates_io_to_docs_rs")]
159 pub registries: HashMap<String, String>,
162 pub std: Option<RustdocExternMode>,
163}
164
165impl Default for RustdocExternMap {
166 fn default() -> Self {
167 Self {
168 registries: HashMap::from_iter([(CRATES_IO_REGISTRY.into(), DOCS_RS_URL.into())]),
169 std: None,
170 }
171 }
172}
173
174const DOCS_RS_URL: &'static str = "https://docs.rs/";
175
176fn default_crates_io_to_docs_rs<'de, D: serde::Deserializer<'de>>(
177 de: D,
178) -> Result<HashMap<String, String>, D::Error> {
179 let mut registries = HashMap::deserialize(de)?;
180 if !registries.contains_key(CRATES_IO_REGISTRY) {
181 registries.insert(CRATES_IO_REGISTRY.into(), DOCS_RS_URL.into());
182 }
183 Ok(registries)
184}
185
186impl hash::Hash for RustdocExternMap {
187 fn hash<H: hash::Hasher>(&self, into: &mut H) {
188 self.std.hash(into);
189 for (key, value) in &self.registries {
190 key.hash(into);
191 value.hash(into);
192 }
193 }
194}
195
196#[derive(Debug, Hash)]
200pub enum RustdocExternMode {
201 Local,
203 Remote,
205 Url(String),
207}
208
209impl From<String> for RustdocExternMode {
210 fn from(s: String) -> RustdocExternMode {
211 match s.as_ref() {
212 "local" => RustdocExternMode::Local,
213 "remote" => RustdocExternMode::Remote,
214 _ => RustdocExternMode::Url(s),
215 }
216 }
217}
218
219impl fmt::Display for RustdocExternMode {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 match self {
222 RustdocExternMode::Local => "local".fmt(f),
223 RustdocExternMode::Remote => "remote".fmt(f),
224 RustdocExternMode::Url(s) => s.fmt(f),
225 }
226 }
227}
228
229impl<'de> serde::de::Deserialize<'de> for RustdocExternMode {
230 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
231 where
232 D: serde::de::Deserializer<'de>,
233 {
234 let s = String::deserialize(deserializer)?;
235 Ok(s.into())
236 }
237}
238
239#[derive(Debug, Default, Deserialize, PartialEq)]
253#[serde(rename_all = "kebab-case")]
254pub struct CargoHttpConfig {
255 pub proxy: Option<String>,
256 pub low_speed_limit: Option<u32>,
257 pub timeout: Option<u64>,
258 pub cainfo: Option<ConfigRelativePath>,
259 pub proxy_cainfo: Option<ConfigRelativePath>,
260 pub check_revoke: Option<bool>,
261 pub user_agent: Option<String>,
262 pub debug: Option<bool>,
263 pub multiplexing: Option<bool>,
264 pub ssl_version: Option<SslVersionConfig>,
265}
266
267#[derive(Debug, Default, Deserialize, PartialEq)]
276#[serde(rename_all = "kebab-case")]
277pub struct CargoFutureIncompatConfig {
278 frequency: Option<CargoFutureIncompatFrequencyConfig>,
279}
280
281#[derive(Debug, Default, Deserialize, PartialEq)]
282#[serde(rename_all = "kebab-case")]
283pub enum CargoFutureIncompatFrequencyConfig {
284 #[default]
285 Always,
286 Never,
287}
288
289impl CargoFutureIncompatConfig {
290 pub fn should_display_message(&self) -> bool {
291 use CargoFutureIncompatFrequencyConfig::*;
292
293 let frequency = self.frequency.as_ref().unwrap_or(&Always);
294 match frequency {
295 Always => true,
296 Never => false,
297 }
298 }
299}
300
301#[derive(Clone, Debug, PartialEq)]
315pub enum SslVersionConfig {
316 Single(String),
317 Range(SslVersionConfigRange),
318}
319
320impl<'de> Deserialize<'de> for SslVersionConfig {
321 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
322 where
323 D: serde::Deserializer<'de>,
324 {
325 UntaggedEnumVisitor::new()
326 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
327 .map(|map| map.deserialize().map(SslVersionConfig::Range))
328 .deserialize(deserializer)
329 }
330}
331
332#[derive(Clone, Debug, Deserialize, PartialEq)]
333#[serde(rename_all = "kebab-case")]
334pub struct SslVersionConfigRange {
335 pub min: Option<String>,
336 pub max: Option<String>,
337}
338
339#[derive(Debug, Deserialize)]
350#[serde(rename_all = "kebab-case")]
351pub struct CargoNetConfig {
352 pub retry: Option<u32>,
353 pub offline: Option<bool>,
354 pub git_fetch_with_cli: Option<bool>,
355 pub ssh: Option<CargoSshConfig>,
356}
357
358#[derive(Debug, Deserialize)]
359#[serde(rename_all = "kebab-case")]
360pub struct CargoSshConfig {
361 pub known_hosts: Option<Vec<Value<String>>>,
362}
363
364#[derive(Debug, Clone)]
377pub enum JobsConfig {
378 Integer(i32),
379 String(String),
380}
381
382impl<'de> Deserialize<'de> for JobsConfig {
383 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
384 where
385 D: serde::Deserializer<'de>,
386 {
387 UntaggedEnumVisitor::new()
388 .i32(|int| Ok(JobsConfig::Integer(int)))
389 .string(|string| Ok(JobsConfig::String(string.to_owned())))
390 .deserialize(deserializer)
391 }
392}
393
394#[derive(Debug, Deserialize)]
407#[serde(rename_all = "kebab-case")]
408pub struct CargoBuildConfig {
409 pub pipelining: Option<bool>,
411 pub dep_info_basedir: Option<ConfigRelativePath>,
412 pub target_dir: Option<ConfigRelativePath>,
413 pub build_dir: Option<ConfigRelativePath>,
414 pub incremental: Option<bool>,
415 pub target: Option<BuildTargetConfig>,
416 pub jobs: Option<JobsConfig>,
417 pub rustflags: Option<StringList>,
418 pub rustdocflags: Option<StringList>,
419 pub rustc_wrapper: Option<ConfigRelativePath>,
420 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
421 pub rustc: Option<ConfigRelativePath>,
422 pub rustdoc: Option<ConfigRelativePath>,
423 pub artifact_dir: Option<ConfigRelativePath>,
424 pub warnings: Option<WarningHandling>,
425 pub sbom: Option<bool>,
427 pub analysis: Option<CargoBuildAnalysis>,
429 pub fingerprint: Option<FingerprintMethod>,
431}
432
433#[derive(Debug, Deserialize, Default)]
435#[serde(rename_all = "kebab-case")]
436pub struct CargoBuildAnalysis {
437 pub enabled: bool,
438}
439
440#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
442#[serde(rename_all = "kebab-case")]
443pub enum WarningHandling {
444 #[default]
445 Warn,
447 Allow,
449 Deny,
451}
452
453#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Default)]
454#[serde(rename_all = "kebab-case")]
455pub enum FingerprintMethod {
456 #[default]
457 Mtime,
458 Content,
459}
460
461impl FingerprintMethod {
462 pub fn as_str(&self) -> &'static str {
463 match self {
464 Self::Mtime => "mtime",
465 Self::Content => "content",
466 }
467 }
468}
469
470impl std::fmt::Display for FingerprintMethod {
471 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472 self.as_str().fmt(f)
473 }
474}
475
476#[derive(Debug, Deserialize)]
486#[serde(transparent)]
487pub struct BuildTargetConfig {
488 inner: Value<BuildTargetConfigInner>,
489}
490
491#[derive(Debug)]
492enum BuildTargetConfigInner {
493 One(String),
494 Many(Vec<String>),
495}
496
497impl<'de> Deserialize<'de> for BuildTargetConfigInner {
498 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
499 where
500 D: serde::Deserializer<'de>,
501 {
502 UntaggedEnumVisitor::new()
503 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
504 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
505 .deserialize(deserializer)
506 }
507}
508
509impl BuildTargetConfig {
510 pub fn values(&self, cwd: &Path) -> CargoResult<Vec<String>> {
512 let map = |s: &String| {
513 if s.ends_with(".json") {
514 self.inner
517 .definition
518 .root(cwd)
519 .join(s)
520 .to_str()
521 .expect("must be utf-8 in toml")
522 .to_string()
523 } else {
524 s.to_string()
526 }
527 };
528 let values = match &self.inner.val {
529 BuildTargetConfigInner::One(s) => vec![map(s)],
530 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
531 };
532 Ok(values)
533 }
534}
535
536#[derive(Debug, Deserialize)]
548#[serde(rename_all = "kebab-case")]
549pub struct CargoResolverConfig {
550 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
551 pub incompatible_publish_age: Option<IncompatiblePublishAge>,
552 pub feature_unification: Option<FeatureUnification>,
553 pub lockfile_path: Option<ConfigRelativePath>,
554}
555
556#[derive(Debug, Deserialize, PartialEq, Eq)]
557#[serde(rename_all = "kebab-case")]
558pub enum IncompatibleRustVersions {
559 Allow,
560 Fallback,
561}
562
563#[derive(Debug, Deserialize, PartialEq, Eq)]
564#[serde(rename_all = "kebab-case")]
565pub enum IncompatiblePublishAge {
566 Allow,
567 Deny,
568}
569
570#[derive(Copy, Clone, Debug, Deserialize)]
571#[serde(rename_all = "kebab-case")]
572pub enum FeatureUnification {
573 Package,
574 Selected,
575 Workspace,
576}
577
578#[derive(Debug, Deserialize, Default)]
590#[serde(rename_all = "kebab-case")]
591pub struct TermConfig {
592 pub verbose: Option<bool>,
593 pub quiet: Option<bool>,
594 pub color: Option<String>,
595 pub hyperlinks: Option<bool>,
596 pub unicode: Option<bool>,
597 pub progress: Option<ProgressConfig>,
598}
599
600#[derive(Debug, Default)]
615pub struct ProgressConfig {
616 pub when: ProgressWhen,
617 pub width: Option<usize>,
618 pub term_integration: Option<bool>,
620}
621
622#[derive(Debug, Default, Deserialize)]
623#[serde(rename_all = "kebab-case")]
624pub enum ProgressWhen {
625 #[default]
626 Auto,
627 Never,
628 Always,
629}
630
631impl<'de> Deserialize<'de> for ProgressConfig {
634 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
635 where
636 D: serde::Deserializer<'de>,
637 {
638 #[derive(Deserialize)]
639 #[serde(rename_all = "kebab-case")]
640 struct ProgressConfigInner {
641 #[serde(default)]
642 when: ProgressWhen,
643 width: Option<usize>,
644 term_integration: Option<bool>,
645 }
646
647 let pc = ProgressConfigInner::deserialize(deserializer)?;
648 if let ProgressConfigInner {
649 when: ProgressWhen::Always,
650 width: None,
651 ..
652 } = pc
653 {
654 return Err(serde::de::Error::custom(
655 "\"always\" progress requires a `width` key",
656 ));
657 }
658 Ok(ProgressConfig {
659 when: pc.when,
660 width: pc.width,
661 term_integration: pc.term_integration,
662 })
663 }
664}
665
666#[derive(Debug)]
667enum EnvConfigValueInner {
668 Simple(String),
669 WithOptions {
670 value: ConfigRelativePath,
671 force: bool,
672 relative: bool,
673 },
674}
675
676impl<'de> Deserialize<'de> for EnvConfigValueInner {
677 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
678 where
679 D: serde::Deserializer<'de>,
680 {
681 #[derive(Deserialize)]
682 struct WithOptions {
683 value: ConfigRelativePath,
684 #[serde(default)]
685 force: bool,
686 #[serde(default)]
687 relative: bool,
688 }
689
690 UntaggedEnumVisitor::new()
691 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
692 .map(|map| {
693 let with_options: WithOptions = map.deserialize()?;
694 Ok(EnvConfigValueInner::WithOptions {
695 value: with_options.value,
696 force: with_options.force,
697 relative: with_options.relative,
698 })
699 })
700 .deserialize(deserializer)
701 }
702}
703
704#[derive(Debug, Deserialize)]
719#[serde(transparent)]
720pub struct EnvConfigValue {
721 inner: EnvConfigValueInner,
722}
723
724impl EnvConfigValue {
725 pub fn is_force(&self) -> bool {
727 match self.inner {
728 EnvConfigValueInner::Simple(_) => false,
729 EnvConfigValueInner::WithOptions { force, .. } => force,
730 }
731 }
732
733 pub fn resolve<'a>(&'a self, cwd: &Path) -> Cow<'a, OsStr> {
738 match self.inner {
739 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
740 EnvConfigValueInner::WithOptions {
741 ref value,
742 relative,
743 ..
744 } => {
745 if relative {
746 let p = value.value().definition.root(cwd).join(value.raw_value());
747 Cow::Owned(p.into_os_string())
748 } else {
749 Cow::Borrowed(OsStr::new(value.raw_value()))
750 }
751 }
752 }
753 }
754}
755
756pub type EnvConfig = HashMap<String, EnvConfigValue>;
757
758#[derive(Deserialize, Clone, Debug)]
762#[serde(rename_all = "kebab-case")]
763pub struct RegistryConfig {
764 pub index: Option<String>,
765 pub token: OptValue<Secret<String>>,
766 pub credential_provider: Option<PathAndArgs>,
767 pub secret_key: OptValue<Secret<String>>,
768 pub secret_key_subject: Option<String>,
769 pub min_publish_age: Option<String>,
771 #[serde(rename = "protocol")]
772 _protocol: Option<String>,
773}
774
775#[derive(Deserialize)]
780#[serde(rename_all = "kebab-case")]
781pub struct GlobalRegistryConfig {
782 pub index: Option<String>,
783 pub token: OptValue<Secret<String>>,
784 pub credential_provider: Option<PathAndArgs>,
785 pub secret_key: OptValue<Secret<String>>,
786 pub secret_key_subject: Option<String>,
787 pub global_min_publish_age: Option<String>,
789 #[serde(rename = "default")]
790 _default: Option<String>,
791 #[serde(rename = "global-credential-providers")]
792 _global_credential_providers: Option<Vec<String>>,
793}
794
795impl GlobalRegistryConfig {
796 pub fn to_registry_config(self) -> RegistryConfig {
797 RegistryConfig {
798 index: self.index,
799 token: self.token,
800 credential_provider: self.credential_provider,
801 secret_key: self.secret_key,
802 secret_key_subject: self.secret_key_subject,
803 min_publish_age: None,
805 _protocol: None,
806 }
807 }
808}