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}
434
435#[derive(Debug, Deserialize, Default)]
437#[serde(rename_all = "kebab-case")]
438pub struct CargoBuildAnalysis {
439 pub enabled: bool,
440}
441
442#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
444#[serde(rename_all = "kebab-case")]
445pub enum WarningHandling {
446 #[default]
447 Warn,
449 Allow,
451 Deny,
453}
454
455#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Default)]
456#[serde(rename_all = "kebab-case")]
457pub enum FingerprintMethod {
458 #[default]
459 Mtime,
460 Content,
461}
462
463impl FingerprintMethod {
464 pub fn as_str(&self) -> &'static str {
465 match self {
466 Self::Mtime => "mtime",
467 Self::Content => "content",
468 }
469 }
470}
471
472impl std::fmt::Display for FingerprintMethod {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 self.as_str().fmt(f)
475 }
476}
477
478#[derive(Debug, Deserialize)]
488#[serde(transparent)]
489pub struct BuildTargetConfig {
490 inner: Value<BuildTargetConfigInner>,
491}
492
493#[derive(Debug)]
494enum BuildTargetConfigInner {
495 One(String),
496 Many(Vec<String>),
497}
498
499impl<'de> Deserialize<'de> for BuildTargetConfigInner {
500 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
501 where
502 D: serde::Deserializer<'de>,
503 {
504 UntaggedEnumVisitor::new()
505 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
506 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
507 .deserialize(deserializer)
508 }
509}
510
511impl BuildTargetConfig {
512 pub fn values(&self, cwd: &Path) -> CargoResult<Vec<String>> {
514 let map = |s: &String| {
515 if s.ends_with(".json") {
516 self.inner
519 .definition
520 .root(cwd)
521 .join(s)
522 .to_str()
523 .expect("must be utf-8 in toml")
524 .to_string()
525 } else {
526 s.to_string()
528 }
529 };
530 let values = match &self.inner.val {
531 BuildTargetConfigInner::One(s) => vec![map(s)],
532 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
533 };
534 Ok(values)
535 }
536}
537
538#[derive(Debug, Deserialize)]
550#[serde(rename_all = "kebab-case")]
551pub struct CargoResolverConfig {
552 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
553 pub incompatible_publish_age: Option<IncompatiblePublishAge>,
554 pub feature_unification: Option<FeatureUnification>,
555 pub lockfile_path: Option<ConfigRelativePath>,
556}
557
558#[derive(Debug, Deserialize, PartialEq, Eq)]
559#[serde(rename_all = "kebab-case")]
560pub enum IncompatibleRustVersions {
561 Allow,
562 Fallback,
563}
564
565#[derive(Debug, Deserialize, PartialEq, Eq)]
566#[serde(rename_all = "kebab-case")]
567pub enum IncompatiblePublishAge {
568 Allow,
569 Deny,
570}
571
572#[derive(Copy, Clone, Debug, Deserialize)]
573#[serde(rename_all = "kebab-case")]
574pub enum FeatureUnification {
575 Package,
576 Selected,
577 Workspace,
578}
579
580#[derive(Debug, Deserialize, Default)]
592#[serde(rename_all = "kebab-case")]
593pub struct TermConfig {
594 pub verbose: Option<bool>,
595 pub quiet: Option<bool>,
596 pub color: Option<String>,
597 pub hyperlinks: Option<bool>,
598 pub unicode: Option<bool>,
599 pub progress: Option<ProgressConfig>,
600}
601
602#[derive(Debug, Default)]
617pub struct ProgressConfig {
618 pub when: ProgressWhen,
619 pub width: Option<usize>,
620 pub term_integration: Option<bool>,
622}
623
624#[derive(Debug, Default, Deserialize)]
625#[serde(rename_all = "kebab-case")]
626pub enum ProgressWhen {
627 #[default]
628 Auto,
629 Never,
630 Always,
631}
632
633impl<'de> Deserialize<'de> for ProgressConfig {
636 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
637 where
638 D: serde::Deserializer<'de>,
639 {
640 #[derive(Deserialize)]
641 #[serde(rename_all = "kebab-case")]
642 struct ProgressConfigInner {
643 #[serde(default)]
644 when: ProgressWhen,
645 width: Option<usize>,
646 term_integration: Option<bool>,
647 }
648
649 let pc = ProgressConfigInner::deserialize(deserializer)?;
650 if let ProgressConfigInner {
651 when: ProgressWhen::Always,
652 width: None,
653 ..
654 } = pc
655 {
656 return Err(serde::de::Error::custom(
657 "\"always\" progress requires a `width` key",
658 ));
659 }
660 Ok(ProgressConfig {
661 when: pc.when,
662 width: pc.width,
663 term_integration: pc.term_integration,
664 })
665 }
666}
667
668#[derive(Debug)]
669enum EnvConfigValueInner {
670 Simple(String),
671 WithOptions {
672 value: ConfigRelativePath,
673 force: bool,
674 relative: bool,
675 },
676}
677
678impl<'de> Deserialize<'de> for EnvConfigValueInner {
679 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
680 where
681 D: serde::Deserializer<'de>,
682 {
683 #[derive(Deserialize)]
684 struct WithOptions {
685 value: ConfigRelativePath,
686 #[serde(default)]
687 force: bool,
688 #[serde(default)]
689 relative: bool,
690 }
691
692 UntaggedEnumVisitor::new()
693 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
694 .map(|map| {
695 let with_options: WithOptions = map.deserialize()?;
696 Ok(EnvConfigValueInner::WithOptions {
697 value: with_options.value,
698 force: with_options.force,
699 relative: with_options.relative,
700 })
701 })
702 .deserialize(deserializer)
703 }
704}
705
706#[derive(Debug, Deserialize)]
721#[serde(transparent)]
722pub struct EnvConfigValue {
723 inner: EnvConfigValueInner,
724}
725
726impl EnvConfigValue {
727 pub fn is_force(&self) -> bool {
729 match self.inner {
730 EnvConfigValueInner::Simple(_) => false,
731 EnvConfigValueInner::WithOptions { force, .. } => force,
732 }
733 }
734
735 pub fn resolve<'a>(&'a self, cwd: &Path) -> Cow<'a, OsStr> {
740 match self.inner {
741 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
742 EnvConfigValueInner::WithOptions {
743 ref value,
744 relative,
745 ..
746 } => {
747 if relative {
748 let p = value.value().definition.root(cwd).join(value.raw_value());
749 Cow::Owned(p.into_os_string())
750 } else {
751 Cow::Borrowed(OsStr::new(value.raw_value()))
752 }
753 }
754 }
755 }
756}
757
758pub type EnvConfig = HashMap<String, EnvConfigValue>;
759
760#[derive(Deserialize, Clone, Debug)]
764#[serde(rename_all = "kebab-case")]
765pub struct RegistryConfig {
766 pub index: Option<String>,
767 pub token: OptValue<Secret<String>>,
768 pub credential_provider: Option<PathAndArgs>,
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 global_min_publish_age: Option<String>,
787 #[serde(rename = "default")]
788 _default: Option<String>,
789 #[serde(rename = "global-credential-providers")]
790 _global_credential_providers: Option<Vec<String>>,
791}
792
793impl GlobalRegistryConfig {
794 pub fn to_registry_config(self) -> RegistryConfig {
795 RegistryConfig {
796 index: self.index,
797 token: self.token,
798 credential_provider: self.credential_provider,
799 min_publish_age: None,
801 _protocol: None,
802 }
803 }
804}