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 cargo_util_schemas::manifest::ProfileName;
22use serde::Deserialize;
23use serde::Serialize;
24use serde::de;
25use serde_untagged::UntaggedEnumVisitor;
26
27use std::path::Path;
28
29use crate::CargoResult;
30
31use super::OptValue;
32use super::PathAndArgs;
33use super::StringList;
34use super::Value;
35use super::path::ConfigRelativePath;
36
37#[derive(Debug, Default, Deserialize, PartialEq)]
38#[serde(rename_all = "kebab-case")]
39pub struct CargoCacheConfig {
40 pub auto_clean_frequency: Option<String>,
42 pub global_clean: Option<GlobalCleanConfig>,
44}
45
46#[derive(Debug, Default, Deserialize, PartialEq)]
53#[serde(rename_all = "kebab-case")]
54pub struct GlobalCleanConfig {
55 pub max_src_age: Option<String>,
57 pub max_crate_age: Option<String>,
59 pub max_index_age: Option<String>,
61 pub max_git_co_age: Option<String>,
63 pub max_git_db_age: Option<String>,
65}
66
67#[derive(Deserialize)]
68#[serde(rename_all = "kebab-case")]
69pub struct CargoNewConfig {
70 #[deprecated = "cargo-new no longer supports adding the authors field"]
71 #[expect(dead_code, reason = "deprecated")]
72 name: Option<String>,
73
74 #[deprecated = "cargo-new no longer supports adding the authors field"]
75 #[expect(dead_code, reason = "deprecated")]
76 email: Option<String>,
77
78 #[serde(rename = "vcs")]
79 pub version_control: Option<VersionControl>,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq)]
83pub enum VersionControl {
84 Git,
85 Hg,
86 Pijul,
87 Fossil,
88 NoVcs,
89}
90
91impl VersionControl {
92 pub const VALUES: &[Self] = &[Self::Git, Self::Hg, Self::Pijul, Self::Fossil, Self::NoVcs];
93
94 pub fn as_str(&self) -> &'static str {
95 match self {
96 VersionControl::Git => "git",
97 VersionControl::Hg => "hg",
98 VersionControl::Pijul => "pijul",
99 VersionControl::Fossil => "fossil",
100 VersionControl::NoVcs => "none",
101 }
102 }
103}
104
105impl FromStr for VersionControl {
106 type Err = anyhow::Error;
107
108 fn from_str(s: &str) -> Result<Self, anyhow::Error> {
109 match s {
110 "git" => Ok(VersionControl::Git),
111 "hg" => Ok(VersionControl::Hg),
112 "pijul" => Ok(VersionControl::Pijul),
113 "fossil" => Ok(VersionControl::Fossil),
114 "none" => Ok(VersionControl::NoVcs),
115 other => anyhow::bail!("unknown vcs specification: `{}`", other),
116 }
117 }
118}
119
120impl<'de> de::Deserialize<'de> for VersionControl {
121 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
122 where
123 D: de::Deserializer<'de>,
124 {
125 let s = String::deserialize(deserializer)?;
126 FromStr::from_str(&s).map_err(de::Error::custom)
127 }
128}
129
130#[derive(Debug, Deserialize)]
132#[serde(rename_all = "kebab-case")]
133pub struct SourceConfigDef {
134 pub replace_with: OptValue<String>,
136 pub directory: Option<ConfigRelativePath>,
138 pub registry: OptValue<String>,
140 pub local_registry: Option<ConfigRelativePath>,
142 pub git: OptValue<String>,
144 pub branch: OptValue<String>,
146 pub tag: OptValue<String>,
148 pub rev: OptValue<String>,
150}
151
152#[derive(serde::Deserialize, Debug)]
157#[serde(default)]
158pub struct RustdocExternMap {
159 #[serde(deserialize_with = "default_crates_io_to_docs_rs")]
160 pub registries: HashMap<String, String>,
163 pub std: Option<RustdocExternMode>,
164}
165
166impl Default for RustdocExternMap {
167 fn default() -> Self {
168 Self {
169 registries: HashMap::from_iter([(CRATES_IO_REGISTRY.into(), DOCS_RS_URL.into())]),
170 std: None,
171 }
172 }
173}
174
175const DOCS_RS_URL: &'static str = "https://docs.rs/";
176
177fn default_crates_io_to_docs_rs<'de, D: serde::Deserializer<'de>>(
178 de: D,
179) -> Result<HashMap<String, String>, D::Error> {
180 let mut registries = HashMap::deserialize(de)?;
181 if !registries.contains_key(CRATES_IO_REGISTRY) {
182 registries.insert(CRATES_IO_REGISTRY.into(), DOCS_RS_URL.into());
183 }
184 Ok(registries)
185}
186
187impl hash::Hash for RustdocExternMap {
188 fn hash<H: hash::Hasher>(&self, into: &mut H) {
189 self.std.hash(into);
190 for (key, value) in &self.registries {
191 key.hash(into);
192 value.hash(into);
193 }
194 }
195}
196
197#[derive(Debug, Hash)]
201pub enum RustdocExternMode {
202 Local,
204 Remote,
206 Url(String),
208}
209
210impl From<String> for RustdocExternMode {
211 fn from(s: String) -> RustdocExternMode {
212 match s.as_ref() {
213 "local" => RustdocExternMode::Local,
214 "remote" => RustdocExternMode::Remote,
215 _ => RustdocExternMode::Url(s),
216 }
217 }
218}
219
220impl fmt::Display for RustdocExternMode {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 match self {
223 RustdocExternMode::Local => "local".fmt(f),
224 RustdocExternMode::Remote => "remote".fmt(f),
225 RustdocExternMode::Url(s) => s.fmt(f),
226 }
227 }
228}
229
230impl<'de> serde::de::Deserialize<'de> for RustdocExternMode {
231 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
232 where
233 D: serde::de::Deserializer<'de>,
234 {
235 let s = String::deserialize(deserializer)?;
236 Ok(s.into())
237 }
238}
239
240#[derive(Debug, Default, Deserialize, PartialEq)]
254#[serde(rename_all = "kebab-case")]
255pub struct CargoHttpConfig {
256 pub proxy: Option<String>,
257 pub low_speed_limit: Option<u32>,
258 pub timeout: Option<u64>,
259 pub cainfo: Option<ConfigRelativePath>,
260 pub proxy_cainfo: Option<ConfigRelativePath>,
261 pub check_revoke: Option<bool>,
262 pub user_agent: Option<String>,
263 pub debug: Option<bool>,
264 pub multiplexing: Option<bool>,
265 pub ssl_version: Option<SslVersionConfig>,
266}
267
268#[derive(Debug, Default, Deserialize, PartialEq)]
277#[serde(rename_all = "kebab-case")]
278pub struct CargoFutureIncompatConfig {
279 frequency: Option<CargoFutureIncompatFrequencyConfig>,
280}
281
282#[derive(Debug, Default, Deserialize, PartialEq)]
283#[serde(rename_all = "kebab-case")]
284pub enum CargoFutureIncompatFrequencyConfig {
285 #[default]
286 Always,
287 Never,
288}
289
290impl CargoFutureIncompatConfig {
291 pub fn should_display_message(&self) -> bool {
292 use CargoFutureIncompatFrequencyConfig::*;
293
294 let frequency = self.frequency.as_ref().unwrap_or(&Always);
295 match frequency {
296 Always => true,
297 Never => false,
298 }
299 }
300}
301
302#[derive(Clone, Debug, PartialEq)]
316pub enum SslVersionConfig {
317 Single(String),
318 Range(SslVersionConfigRange),
319}
320
321impl<'de> Deserialize<'de> for SslVersionConfig {
322 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
323 where
324 D: serde::Deserializer<'de>,
325 {
326 UntaggedEnumVisitor::new()
327 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
328 .map(|map| map.deserialize().map(SslVersionConfig::Range))
329 .deserialize(deserializer)
330 }
331}
332
333#[derive(Clone, Debug, Deserialize, PartialEq)]
334#[serde(rename_all = "kebab-case")]
335pub struct SslVersionConfigRange {
336 pub min: Option<String>,
337 pub max: Option<String>,
338}
339
340#[derive(Debug, Deserialize)]
351#[serde(rename_all = "kebab-case")]
352pub struct CargoNetConfig {
353 pub retry: Option<u32>,
354 pub offline: Option<bool>,
355 pub git_fetch_with_cli: Option<bool>,
356 pub ssh: Option<CargoSshConfig>,
357}
358
359#[derive(Debug, Deserialize)]
360#[serde(rename_all = "kebab-case")]
361pub struct CargoSshConfig {
362 pub known_hosts: Option<Vec<Value<String>>>,
363}
364
365#[derive(Debug, Clone)]
378pub enum JobsConfig {
379 Integer(i32),
380 String(String),
381}
382
383impl<'de> Deserialize<'de> for JobsConfig {
384 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
385 where
386 D: serde::Deserializer<'de>,
387 {
388 UntaggedEnumVisitor::new()
389 .i32(|int| Ok(JobsConfig::Integer(int)))
390 .string(|string| Ok(JobsConfig::String(string.to_owned())))
391 .deserialize(deserializer)
392 }
393}
394
395#[derive(Debug, Deserialize)]
408#[serde(rename_all = "kebab-case")]
409pub struct CargoBuildConfig {
410 pub pipelining: Option<bool>,
412 pub dep_info_basedir: Option<ConfigRelativePath>,
413 pub target_dir: Option<ConfigRelativePath>,
414 pub build_dir: Option<ConfigRelativePath>,
415 pub incremental: Option<bool>,
416 pub target: Option<BuildTargetConfig>,
417 pub profile: Option<ProfileName>,
418 pub jobs: Option<JobsConfig>,
419 pub rustflags: Option<StringList>,
420 pub rustdocflags: Option<StringList>,
421 pub rustc_wrapper: Option<ConfigRelativePath>,
422 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
423 pub rustc: Option<ConfigRelativePath>,
424 pub rustdoc: Option<ConfigRelativePath>,
425 pub artifact_dir: Option<ConfigRelativePath>,
426 pub warnings: Option<WarningHandling>,
427 pub sbom: Option<bool>,
429 pub analysis: Option<CargoBuildAnalysis>,
431 pub fingerprint: Option<FingerprintMethod>,
433 pub build_std: Option<BuildStdConfig>,
434}
435
436#[derive(Debug, Deserialize, Default)]
438#[serde(rename_all = "kebab-case")]
439pub struct CargoBuildAnalysis {
440 pub enabled: bool,
441}
442
443#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
445#[serde(rename_all = "kebab-case")]
446pub enum WarningHandling {
447 #[default]
448 Warn,
450 Allow,
452 Deny,
454}
455
456#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Default)]
457#[serde(rename_all = "kebab-case")]
458pub enum FingerprintMethod {
459 #[default]
460 Mtime,
461 Content,
462}
463
464impl FingerprintMethod {
465 pub fn as_str(&self) -> &'static str {
466 match self {
467 Self::Mtime => "mtime",
468 Self::Content => "content",
469 }
470 }
471}
472
473impl std::fmt::Display for FingerprintMethod {
474 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
475 self.as_str().fmt(f)
476 }
477}
478
479#[derive(Debug, Deserialize)]
481#[serde(rename_all = "kebab-case")]
482pub struct BuildStdConfig {
483 pub when: Option<BuildStdWhen>,
484 pub crates: Option<BuildStdCrates>,
485}
486
487#[derive(Debug, Deserialize, PartialEq, Eq)]
489#[serde(rename_all = "kebab-case")]
490pub enum BuildStdWhen {
491 Never,
492 Always,
493}
494
495#[derive(Debug, PartialEq, Eq)]
498pub enum BuildStdCrates {
499 Core,
500 Alloc,
501 Std,
502 List(Vec<String>),
504}
505
506impl<'de> Deserialize<'de> for BuildStdCrates {
507 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
508 where
509 D: de::Deserializer<'de>,
510 {
511 UntaggedEnumVisitor::new()
512 .string(|s| match s {
513 "core" => Ok(Self::Core),
514 "alloc" => Ok(Self::Alloc),
515 "std" => Ok(Self::Std),
516 _ => Err(serde::de::Error::unknown_variant(
517 s,
518 &["core", "alloc", "std"],
519 )),
520 })
521 .seq(|seq| seq.deserialize().map(Self::List))
522 .deserialize(deserializer)
523 }
524}
525
526#[derive(Debug, Deserialize)]
536#[serde(transparent)]
537pub struct BuildTargetConfig {
538 inner: Value<BuildTargetConfigInner>,
539}
540
541#[derive(Debug)]
542enum BuildTargetConfigInner {
543 One(String),
544 Many(Vec<String>),
545}
546
547impl<'de> Deserialize<'de> for BuildTargetConfigInner {
548 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
549 where
550 D: serde::Deserializer<'de>,
551 {
552 UntaggedEnumVisitor::new()
553 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
554 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
555 .deserialize(deserializer)
556 }
557}
558
559impl BuildTargetConfig {
560 pub fn values(&self, cwd: &Path) -> CargoResult<Vec<String>> {
562 let map = |s: &String| {
563 if s.ends_with(".json") {
564 self.inner
567 .definition
568 .root(cwd)
569 .join(s)
570 .to_str()
571 .expect("must be utf-8 in toml")
572 .to_string()
573 } else {
574 s.to_string()
576 }
577 };
578 let values = match &self.inner.val {
579 BuildTargetConfigInner::One(s) => vec![map(s)],
580 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
581 };
582 Ok(values)
583 }
584}
585
586#[derive(Debug, Deserialize)]
598#[serde(rename_all = "kebab-case")]
599pub struct CargoResolverConfig {
600 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
601 pub incompatible_publish_age: Option<IncompatiblePublishAge>,
602 pub feature_unification: Option<FeatureUnification>,
603 pub lockfile_path: Option<ConfigRelativePath>,
604}
605
606#[derive(Debug, Deserialize, PartialEq, Eq)]
607#[serde(rename_all = "kebab-case")]
608pub enum IncompatibleRustVersions {
609 Allow,
610 Fallback,
611}
612
613#[derive(Debug, Deserialize, PartialEq, Eq)]
614#[serde(rename_all = "kebab-case")]
615pub enum IncompatiblePublishAge {
616 Allow,
617 Deny,
618}
619
620#[derive(Copy, Clone, Debug, Deserialize)]
621#[serde(rename_all = "kebab-case")]
622pub enum FeatureUnification {
623 Package,
624 Selected,
625 Workspace,
626}
627
628#[derive(Debug, Deserialize, Default)]
640#[serde(rename_all = "kebab-case")]
641pub struct TermConfig {
642 pub verbose: Option<bool>,
643 pub quiet: Option<bool>,
644 pub color: Option<String>,
645 pub hyperlinks: Option<bool>,
646 pub unicode: Option<bool>,
647 pub progress: Option<ProgressConfig>,
648}
649
650#[derive(Debug, Default)]
665pub struct ProgressConfig {
666 pub when: ProgressWhen,
667 pub width: Option<usize>,
668 pub term_integration: Option<bool>,
670}
671
672#[derive(Debug, Default, Deserialize)]
673#[serde(rename_all = "kebab-case")]
674pub enum ProgressWhen {
675 #[default]
676 Auto,
677 Never,
678 Always,
679}
680
681impl<'de> Deserialize<'de> for ProgressConfig {
684 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
685 where
686 D: serde::Deserializer<'de>,
687 {
688 #[derive(Deserialize)]
689 #[serde(rename_all = "kebab-case")]
690 struct ProgressConfigInner {
691 #[serde(default)]
692 when: ProgressWhen,
693 width: Option<usize>,
694 term_integration: Option<bool>,
695 }
696
697 let pc = ProgressConfigInner::deserialize(deserializer)?;
698 if let ProgressConfigInner {
699 when: ProgressWhen::Always,
700 width: None,
701 ..
702 } = pc
703 {
704 return Err(serde::de::Error::custom(
705 "\"always\" progress requires a `width` key",
706 ));
707 }
708 Ok(ProgressConfig {
709 when: pc.when,
710 width: pc.width,
711 term_integration: pc.term_integration,
712 })
713 }
714}
715
716#[derive(Debug)]
717enum EnvConfigValueInner {
718 Simple(String),
719 WithOptions {
720 value: ConfigRelativePath,
721 force: bool,
722 relative: bool,
723 },
724}
725
726impl<'de> Deserialize<'de> for EnvConfigValueInner {
727 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
728 where
729 D: serde::Deserializer<'de>,
730 {
731 #[derive(Deserialize)]
732 struct WithOptions {
733 value: ConfigRelativePath,
734 #[serde(default)]
735 force: bool,
736 #[serde(default)]
737 relative: bool,
738 }
739
740 UntaggedEnumVisitor::new()
741 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
742 .map(|map| {
743 let with_options: WithOptions = map.deserialize()?;
744 Ok(EnvConfigValueInner::WithOptions {
745 value: with_options.value,
746 force: with_options.force,
747 relative: with_options.relative,
748 })
749 })
750 .deserialize(deserializer)
751 }
752}
753
754#[derive(Debug, Deserialize)]
769#[serde(transparent)]
770pub struct EnvConfigValue {
771 inner: EnvConfigValueInner,
772}
773
774impl EnvConfigValue {
775 pub fn is_force(&self) -> bool {
777 match self.inner {
778 EnvConfigValueInner::Simple(_) => false,
779 EnvConfigValueInner::WithOptions { force, .. } => force,
780 }
781 }
782
783 pub fn resolve<'a>(&'a self, cwd: &Path) -> Cow<'a, OsStr> {
788 match self.inner {
789 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
790 EnvConfigValueInner::WithOptions {
791 ref value,
792 relative,
793 ..
794 } => {
795 if relative {
796 let p = value.value().definition.root(cwd).join(value.raw_value());
797 Cow::Owned(p.into_os_string())
798 } else {
799 Cow::Borrowed(OsStr::new(value.raw_value()))
800 }
801 }
802 }
803 }
804}
805
806pub type EnvConfig = HashMap<String, EnvConfigValue>;
807
808#[derive(Clone, Debug, Default, Deserialize)]
810#[serde(rename_all = "kebab-case")]
811pub struct RegistryCredential {
812 pub token: OptValue<Secret<String>>,
813}
814
815#[derive(Deserialize, Clone, Debug)]
819#[serde(rename_all = "kebab-case")]
820pub struct RegistryConfig {
821 pub index: Option<String>,
822 #[deprecated = "see `RegistryCredential::token` instead"]
823 pub token: OptValue<Secret<String>>,
824 pub credential_provider: Option<PathAndArgs>,
825 pub min_publish_age: Option<String>,
827 #[serde(rename = "protocol")]
828 _protocol: Option<String>,
829}
830
831#[derive(Deserialize)]
836#[serde(rename_all = "kebab-case")]
837pub struct GlobalRegistryConfig {
838 pub index: Option<String>,
839 #[deprecated = "see `RegistryCredential::token` instead"]
840 pub token: OptValue<Secret<String>>,
841 pub credential_provider: Option<PathAndArgs>,
842 pub global_min_publish_age: Option<String>,
844 #[serde(rename = "default")]
845 _default: Option<String>,
846 #[serde(rename = "global-credential-providers")]
847 _global_credential_providers: Option<Vec<String>>,
848}
849
850impl GlobalRegistryConfig {
851 pub fn to_registry_config(self) -> RegistryConfig {
852 RegistryConfig {
853 index: self.index,
854 #[expect(deprecated)]
855 token: self.token,
856 credential_provider: self.credential_provider,
857 min_publish_age: None,
859 _protocol: None,
860 }
861 }
862}