1#[path = "gen/de.rs"]
7mod generated;
8
9use alloc::{
10 borrow::{Cow, ToOwned as _},
11 collections::BTreeMap,
12 format,
13 string::String,
14 vec,
15 vec::Vec,
16};
17use core::{fmt, iter, slice, str::FromStr};
18use std::{
19 ffi::OsStr,
20 fs,
21 path::{Path, PathBuf},
22};
23
24use serde::{
25 de::{self, Deserialize, Deserializer},
26 ser::{Serialize, Serializer},
27};
28use serde_derive::{Deserialize, Serialize};
29
30pub use crate::value::{Definition, Value};
31use crate::{
32 easy,
33 error::{Context as _, Error, Result},
34 merge::Merge,
35 resolve::{ResolveContext, TargetTripleRef},
36 value::SetPath,
37 walk,
38};
39
40#[derive(Debug, Clone, Default, Serialize, Deserialize)]
43#[serde(rename_all = "kebab-case")]
44#[non_exhaustive]
45pub struct Config {
46 #[serde(default)]
51 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
52 pub alias: BTreeMap<String, StringList>,
53 #[serde(default)]
57 #[serde(skip_serializing_if = "BuildConfig::is_none")]
58 pub build: BuildConfig,
59 #[serde(default)]
63 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
64 pub credential_alias: BTreeMap<String, PathAndArgs>,
65 #[serde(default)]
69 #[serde(skip_serializing_if = "DocConfig::is_none")]
70 pub doc: DocConfig,
71 #[serde(default)]
75 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
76 pub env: BTreeMap<String, EnvConfigValue>,
77 #[serde(default)]
81 #[serde(skip_serializing_if = "FutureIncompatReportConfig::is_none")]
82 pub future_incompat_report: FutureIncompatReportConfig,
83 #[serde(default)]
87 #[serde(skip_serializing_if = "CargoNewConfig::is_none")]
88 pub cargo_new: CargoNewConfig,
89 #[serde(default)]
93 #[serde(skip_serializing_if = "HttpConfig::is_none")]
94 pub http: HttpConfig,
95 #[serde(default)]
100 #[serde(skip_serializing_if = "NetConfig::is_none")]
101 pub net: NetConfig,
102 #[serde(default)]
108 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
109 pub registries: BTreeMap<String, RegistriesConfigValue>,
110 #[serde(default)]
114 #[serde(skip_serializing_if = "RegistryConfig::is_none")]
115 pub registry: RegistryConfig,
116 #[serde(default)]
120 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
121 pub source: BTreeMap<String, SourceConfigValue>,
122 #[serde(default)]
126 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
127 pub target: BTreeMap<String, TargetConfig>,
128 #[serde(default)]
132 #[serde(skip_serializing_if = "TermConfig::is_none")]
133 pub term: TermConfig,
134}
135
136impl Config {
137 pub fn load() -> Result<Self> {
139 Self::load_with_cwd(std::env::current_dir().context("failed to get current directory")?)
140 }
141
142 pub fn load_with_cwd<P: AsRef<Path>>(cwd: P) -> Result<Self> {
144 let cwd = cwd.as_ref();
145 Self::_load_with_options(cwd, walk::cargo_home_with_cwd(cwd).as_deref())
146 }
147
148 pub fn load_with_options<P: AsRef<Path>, Q: Into<Option<PathBuf>>>(
150 cwd: P,
151 cargo_home: Q,
152 ) -> Result<Self> {
153 Self::_load_with_options(cwd.as_ref(), cargo_home.into().as_deref())
154 }
155 pub(crate) fn _load_with_options(
156 current_dir: &Path,
157 cargo_home: Option<&Path>,
158 ) -> Result<Config> {
159 let mut base = None;
160 for path in crate::walk::WalkInner::with_cargo_home(current_dir, cargo_home) {
161 let config = Self::_load_file(&path)?;
162 match &mut base {
163 None => base = Some((path, config)),
164 Some((base_path, base)) => base.merge(config, false).with_context(|| {
165 format!(
166 "failed to merge config from `{}` into `{}`",
167 path.display(),
168 base_path.display()
169 )
170 })?,
171 }
172 }
173 Ok(base.map(|(_, c)| c).unwrap_or_default())
174 }
175
176 pub fn load_file<P: AsRef<Path>>(path: P) -> Result<Self> {
181 Self::_load_file(path.as_ref())
182 }
183 fn _load_file(path: &Path) -> Result<Self> {
184 let buf = fs::read_to_string(path)
185 .with_context(|| format!("failed to read `{}`", path.display()))?;
186 let mut config: Config = toml::de::from_str(&buf).with_context(|| {
187 format!("failed to parse `{}` as cargo configuration", path.display())
188 })?;
189 config.set_path(path);
190 Ok(config)
191 }
192
193 pub(crate) fn merge(&mut self, low: Self, force: bool) -> Result<()> {
201 crate::merge::Merge::merge(self, low, force)
202 }
203
204 pub(crate) fn set_path(&mut self, path: &Path) {
205 crate::value::SetPath::set_path(self, path);
206 }
207
208 #[allow(clippy::ref_option)]
209 pub(crate) fn resolve_target(
210 cx: &ResolveContext,
211 target_configs: &BTreeMap<String, TargetConfig>,
212 override_target_rustflags: bool,
213 build_rustflags: &Option<Flags>,
214 override_target_rustdocflags: bool,
215 build_rustdocflags: &Option<Flags>,
216 target_triple: &TargetTripleRef<'_>,
217 build_config: &easy::BuildConfig,
218 ) -> Result<Option<TargetConfig>> {
219 let target = target_triple.triple();
220 if target.starts_with("cfg(") {
221 bail!("'{target}' is not valid target triple");
222 }
223 let mut target_config = None;
224
225 if let Some(config) = target_configs.get(target) {
226 target_config = Some(TargetConfig {
227 linker: config.linker.clone(),
228 runner: config.runner.clone(),
229 rustflags: config.rustflags.clone(),
230 rustdocflags: config.rustdocflags.clone(),
231 rest: BTreeMap::new(), });
233 } else if let Some((before, rest)) = target.split_once('.') {
234 if let Some(config) = target_configs.get(before) {
235 let mut rest_target_configs = &config.rest;
236 let mut rest_target = rest;
237 loop {
238 if let Some(config) = rest_target_configs.get(rest_target) {
239 if let TargetConfigRestValue::Config(config) = config {
240 target_config = Some(TargetConfig {
241 linker: config.linker.clone(),
242 runner: config.runner.clone(),
243 rustflags: config.rustflags.clone(),
244 rustdocflags: config.rustdocflags.clone(),
245 rest: BTreeMap::new(), });
247 }
248 break;
249 }
250 if let Some((before, rest)) = rest_target.split_once('.') {
251 if let Some(TargetConfigRestValue::Config(config)) =
252 rest_target_configs.get(before)
253 {
254 rest_target_configs = &config.rest;
255 rest_target = rest;
256 continue;
257 }
258 }
259 break;
260 }
261 }
262 }
263
264 let target_u_upper = target_u_upper(target);
265 let mut target_linker = target_config.as_mut().and_then(|c| c.linker.take());
266 let mut target_runner = target_config.as_mut().and_then(|c| c.runner.take());
267 let mut target_rustflags: Option<Flags> =
268 target_config.as_mut().and_then(|c| c.rustflags.take());
269 let mut target_rustdocflags: Option<Flags> =
270 target_config.as_mut().and_then(|c| c.rustdocflags.take());
271 if let Some(linker) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_LINKER"))? {
272 target_linker = Some(linker);
273 }
274 if let Some(runner) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUNNER"))? {
275 target_runner = Some(
276 PathAndArgs::from_string(&runner.val, runner.definition.as_ref())
277 .context("invalid length 0, expected at least one element")?,
278 );
279 }
280 if let Some(rustflags) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUSTFLAGS"))? {
281 let mut rustflags =
282 Flags::from_space_separated(&rustflags.val, rustflags.definition.as_ref());
283 match &mut target_rustflags {
284 Some(target_rustflags) => {
285 target_rustflags.flags.append(&mut rustflags.flags);
286 }
287 target_rustflags @ None => *target_rustflags = Some(rustflags),
288 }
289 }
290 if let Some(rustdocflags) =
291 cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUSTDOCFLAGS"))?
292 {
293 let mut rustdocflags =
294 Flags::from_space_separated(&rustdocflags.val, rustdocflags.definition.as_ref());
295 match &mut target_rustdocflags {
296 Some(target_rustdocflags) => {
297 target_rustdocflags.flags.append(&mut rustdocflags.flags);
298 }
299 target_rustdocflags @ None => *target_rustdocflags = Some(rustdocflags),
300 }
301 }
302 for (k, v) in target_configs {
303 if !k.starts_with("cfg(") {
304 continue;
305 }
306 if cx.eval_cfg(k, target_triple, build_config)? {
307 if target_linker.is_none() {
309 if let Some(linker) = v.linker.as_ref() {
310 target_linker = Some(linker.clone());
311 }
312 }
313 if target_runner.is_none() {
318 if let Some(runner) = v.runner.as_ref() {
319 target_runner = Some(runner.clone());
320 }
321 }
322 if let Some(rustflags) = v.rustflags.as_ref() {
327 match &mut target_rustflags {
328 Some(target_rustflags) => {
329 target_rustflags.flags.extend_from_slice(&rustflags.flags);
330 }
331 target_rustflags @ None => *target_rustflags = Some(rustflags.clone()),
332 }
333 }
334 }
335 }
336 if let Some(linker) = target_linker {
337 target_config.get_or_insert_with(TargetConfig::default).linker = Some(linker);
338 }
339 if let Some(runner) = target_runner {
340 target_config.get_or_insert_with(TargetConfig::default).runner = Some(runner);
341 }
342 if override_target_rustflags {
343 target_config
344 .get_or_insert_with(TargetConfig::default)
345 .rustflags
346 .clone_from(build_rustflags);
347 } else if let Some(rustflags) = target_rustflags {
348 target_config.get_or_insert_with(TargetConfig::default).rustflags = Some(rustflags);
349 } else {
350 target_config
351 .get_or_insert_with(TargetConfig::default)
352 .rustflags
353 .clone_from(build_rustflags);
354 }
355 if override_target_rustdocflags {
356 target_config
357 .get_or_insert_with(TargetConfig::default)
358 .rustdocflags
359 .clone_from(build_rustdocflags);
360 } else if let Some(rustdocflags) = target_rustdocflags {
361 target_config.get_or_insert_with(TargetConfig::default).rustdocflags =
362 Some(rustdocflags);
363 } else {
364 target_config
365 .get_or_insert_with(TargetConfig::default)
366 .rustdocflags
367 .clone_from(build_rustdocflags);
368 }
369 Ok(target_config)
370 }
371}
372
373#[derive(Debug, Clone, Default, Serialize, Deserialize)]
377#[serde(rename_all = "kebab-case")]
378#[non_exhaustive]
379pub struct BuildConfig {
380 #[serde(skip_serializing_if = "Option::is_none")]
386 pub jobs: Option<Value<i32>>,
387 #[serde(skip_serializing_if = "Option::is_none")]
391 pub rustc: Option<Value<String>>,
392 #[serde(skip_serializing_if = "Option::is_none")]
396 pub rustc_wrapper: Option<Value<String>>,
397 #[serde(skip_serializing_if = "Option::is_none")]
401 pub rustc_workspace_wrapper: Option<Value<String>>,
402 #[serde(skip_serializing_if = "Option::is_none")]
406 pub rustdoc: Option<Value<String>>,
407 #[serde(skip_serializing_if = "Option::is_none")]
411 pub target: Option<StringOrArray>,
412 #[serde(skip_serializing_if = "Option::is_none")]
417 pub target_dir: Option<Value<String>>,
418 #[serde(skip_serializing_if = "Option::is_none")]
423 pub build_dir: Option<Value<String>>,
424 #[serde(skip_serializing_if = "Option::is_none")]
429 pub rustflags: Option<Flags>,
430 #[serde(skip_serializing_if = "Option::is_none")]
435 pub rustdocflags: Option<Flags>,
436 #[serde(skip_serializing_if = "Option::is_none")]
440 pub incremental: Option<Value<bool>>,
441 #[serde(skip_serializing_if = "Option::is_none")]
445 pub dep_info_basedir: Option<Value<String>>,
446
447 #[serde(skip)]
449 pub(crate) override_target_rustflags: bool,
450 #[serde(skip)]
451 pub(crate) override_target_rustdocflags: bool,
452}
453
454#[derive(Debug, Clone, Default, Serialize, Deserialize)]
459#[serde(rename_all = "kebab-case")]
460#[non_exhaustive]
461pub struct TargetConfig {
462 #[serde(skip_serializing_if = "Option::is_none")]
464 pub linker: Option<Value<String>>,
465 #[serde(skip_serializing_if = "Option::is_none")]
469 pub runner: Option<PathAndArgs>,
470 #[serde(skip_serializing_if = "Option::is_none")]
474 pub rustflags: Option<Flags>,
475 #[serde(skip_serializing_if = "Option::is_none")]
479 pub rustdocflags: Option<Flags>,
480 #[serde(flatten)]
482 pub(crate) rest: BTreeMap<String, TargetConfigRestValue>,
483}
484
485#[derive(Debug, Clone, Serialize, Deserialize)]
486#[serde(untagged)]
487pub(crate) enum TargetConfigRestValue {
488 Config(TargetConfig),
489 Other(toml::Value),
490}
491
492impl Merge for TargetConfigRestValue {
493 fn merge(&mut self, low: Self, force: bool) -> Result<()> {
494 match (self, low) {
495 (Self::Config(this), Self::Config(low)) => this.merge(low, force),
496 (this @ Self::Other(_), low @ Self::Config(_)) => {
497 *this = low;
498 Ok(())
499 }
500 (_, Self::Other(_)) => Ok(()),
501 }
502 }
503}
504impl SetPath for TargetConfigRestValue {
505 fn set_path(&mut self, path: &Path) {
506 match self {
507 Self::Config(v) => {
508 v.set_path(path);
509 }
510 Self::Other(_) => {}
511 }
512 }
513}
514
515#[derive(Debug, Clone, Default, Serialize, Deserialize)]
519#[serde(rename_all = "kebab-case")]
520#[non_exhaustive]
521pub struct DocConfig {
522 #[serde(skip_serializing_if = "Option::is_none")]
527 pub browser: Option<PathAndArgs>,
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
535#[serde(untagged)]
536#[non_exhaustive]
537pub enum EnvConfigValue {
538 Value(Value<String>),
539 Table {
540 value: Value<String>,
541 #[serde(skip_serializing_if = "Option::is_none")]
542 force: Option<Value<bool>>,
543 #[serde(skip_serializing_if = "Option::is_none")]
544 relative: Option<Value<bool>>,
545 },
546}
547
548impl EnvConfigValue {
549 pub(crate) const fn kind(&self) -> &'static str {
550 match self {
551 Self::Value(..) => "string",
552 Self::Table { .. } => "table",
553 }
554 }
555
556 pub(crate) fn resolve(&self, current_dir: &Path) -> Cow<'_, OsStr> {
557 match self {
558 Self::Value(v) => OsStr::new(&v.val).into(),
559 Self::Table { value, relative, .. } => {
560 if relative.as_ref().is_some_and(|v| v.val) {
561 if let Some(def) = &value.definition {
562 return def.root(current_dir).join(&value.val).into_os_string().into();
563 }
564 }
565 OsStr::new(&value.val).into()
566 }
567 }
568 }
569}
570
571#[derive(Debug, Clone, Default, Serialize, Deserialize)]
575#[serde(rename_all = "kebab-case")]
576#[non_exhaustive]
577pub struct FutureIncompatReportConfig {
578 #[serde(skip_serializing_if = "Option::is_none")]
582 pub frequency: Option<Value<Frequency>>,
583}
584
585#[derive(Debug, Clone, Default, Serialize, Deserialize)]
589#[serde(rename_all = "kebab-case")]
590#[non_exhaustive]
591pub struct CargoNewConfig {
592 #[serde(skip_serializing_if = "Option::is_none")]
596 pub vcs: Option<Value<VersionControlSoftware>>,
597}
598
599#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
600#[serde(rename_all = "kebab-case")]
601#[non_exhaustive]
602pub enum VersionControlSoftware {
603 Git,
605 #[serde(rename = "hg")]
607 Mercurial,
608 Pijul,
610 Fossil,
612 None,
614}
615
616impl VersionControlSoftware {
617 pub const fn as_str(self) -> &'static str {
618 match self {
619 VersionControlSoftware::Git => "git",
620 VersionControlSoftware::Mercurial => "hg",
621 VersionControlSoftware::Pijul => "pijul",
622 VersionControlSoftware::Fossil => "fossil",
623 VersionControlSoftware::None => "none",
624 }
625 }
626}
627
628impl FromStr for VersionControlSoftware {
629 type Err = Error;
630
631 fn from_str(vcs: &str) -> Result<Self, Self::Err> {
632 match vcs {
633 "git" => Ok(Self::Git),
634 "hg" => Ok(Self::Mercurial),
635 "pijul" => Ok(Self::Pijul),
636 "fossil" => Ok(Self::Fossil),
637 "none" => Ok(Self::None),
638 other => bail!("must be git, hg, pijul, fossil, none, but found `{other}`"),
639 }
640 }
641}
642
643#[derive(Debug, Clone, Default, Serialize, Deserialize)]
647#[serde(rename_all = "kebab-case")]
648#[non_exhaustive]
649pub struct HttpConfig {
650 #[serde(skip_serializing_if = "Option::is_none")]
656 pub debug: Option<Value<bool>>,
657 #[serde(skip_serializing_if = "Option::is_none")]
664 pub proxy: Option<Value<String>>,
665 #[serde(skip_serializing_if = "Option::is_none")]
669 pub timeout: Option<Value<u32>>,
670 #[serde(skip_serializing_if = "Option::is_none")]
675 pub cainfo: Option<Value<String>>,
676 #[serde(skip_serializing_if = "Option::is_none")]
681 pub check_revoke: Option<Value<bool>>,
682 #[serde(skip_serializing_if = "Option::is_none")]
689 pub low_speed_limit: Option<Value<u32>>,
690 #[serde(skip_serializing_if = "Option::is_none")]
696 pub multiplexing: Option<Value<bool>>,
697 #[serde(skip_serializing_if = "Option::is_none")]
702 pub user_agent: Option<Value<String>>,
703}
704
705#[derive(Debug, Clone, Default, Serialize, Deserialize)]
709#[serde(rename_all = "kebab-case")]
710#[non_exhaustive]
711pub struct NetConfig {
712 #[serde(skip_serializing_if = "Option::is_none")]
716 pub retry: Option<Value<u32>>,
717 #[serde(skip_serializing_if = "Option::is_none")]
723 pub git_fetch_with_cli: Option<Value<bool>>,
724 #[serde(skip_serializing_if = "Option::is_none")]
731 pub offline: Option<Value<bool>>,
732}
733
734#[derive(Clone, Default, Serialize, Deserialize)]
738#[serde(rename_all = "kebab-case")]
739#[non_exhaustive]
740pub struct RegistriesConfigValue {
741 #[serde(skip_serializing_if = "Option::is_none")]
745 pub index: Option<Value<String>>,
746 #[serde(skip_serializing_if = "Option::is_none")]
754 pub token: Option<Value<String>>,
755 #[serde(skip_serializing_if = "Option::is_none")]
759 pub credential_provider: Option<CredentialProvider>,
760 #[serde(skip_serializing_if = "Option::is_none")]
765 pub protocol: Option<Value<RegistriesProtocol>>,
766}
767
768impl fmt::Debug for RegistriesConfigValue {
769 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770 let Self { index, credential_provider, token, protocol } = self;
771 let redacted_token = token
772 .as_ref()
773 .map(|token| Value { val: "[REDACTED]", definition: token.definition.clone() });
774 f.debug_struct("RegistriesConfigValue")
775 .field("index", &index)
776 .field("token", &redacted_token)
777 .field("credential_provider", credential_provider)
778 .field("protocol", &protocol)
779 .finish()
780 }
781}
782
783#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
787#[serde(rename_all = "kebab-case")]
788#[non_exhaustive]
789pub enum RegistriesProtocol {
790 Git,
793 Sparse,
796}
797
798impl FromStr for RegistriesProtocol {
799 type Err = Error;
800
801 fn from_str(protocol: &str) -> Result<Self, Self::Err> {
802 match protocol {
803 "git" => Ok(Self::Git),
804 "sparse" => Ok(Self::Sparse),
805 other => bail!("must be git or sparse, but found `{other}`"),
806 }
807 }
808}
809
810#[derive(Clone, Default, Serialize, Deserialize)]
814#[serde(rename_all = "kebab-case")]
815#[non_exhaustive]
816pub struct RegistryConfig {
817 #[serde(skip_serializing_if = "Option::is_none")]
824 pub default: Option<Value<String>>,
825 #[serde(skip_serializing_if = "Option::is_none")]
831 pub credential_provider: Option<CredentialProvider>,
832 #[serde(skip_serializing_if = "Option::is_none")]
840 pub token: Option<Value<String>>,
841 #[serde(default)]
849 #[serde(skip_serializing_if = "GlobalCredentialProviders::is_none")]
850 pub global_credential_providers: GlobalCredentialProviders,
851}
852
853impl fmt::Debug for RegistryConfig {
854 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
855 let Self { default, credential_provider, token, global_credential_providers } = self;
856 let redacted_token = token
857 .as_ref()
858 .map(|token| Value { val: "[REDACTED]", definition: token.definition.clone() });
859 f.debug_struct("RegistryConfig")
860 .field("default", &default)
861 .field("credential_provider", credential_provider)
862 .field("token", &redacted_token)
863 .field("global_credential_providers", global_credential_providers)
864 .finish()
865 }
866}
867
868#[derive(Clone, Default, Debug, Serialize, Deserialize)]
872#[serde(rename_all = "kebab-case")]
873#[non_exhaustive]
874pub struct SourceConfigValue {
875 #[serde(skip_serializing_if = "Option::is_none")]
879 pub replace_with: Option<Value<String>>,
880 #[serde(skip_serializing_if = "Option::is_none")]
884 pub directory: Option<Value<String>>,
885 #[serde(skip_serializing_if = "Option::is_none")]
889 pub registry: Option<Value<String>>,
890 #[serde(skip_serializing_if = "Option::is_none")]
894 pub local_registry: Option<Value<String>>,
895 #[serde(skip_serializing_if = "Option::is_none")]
899 pub git: Option<Value<String>>,
900 #[serde(skip_serializing_if = "Option::is_none")]
905 pub branch: Option<Value<String>>,
906 #[serde(skip_serializing_if = "Option::is_none")]
911 pub tag: Option<Value<String>>,
912 #[serde(skip_serializing_if = "Option::is_none")]
917 pub rev: Option<Value<String>>,
918}
919
920#[derive(Clone, Debug, Default)]
924pub struct GlobalCredentialProviders(pub(crate) Vec<CredentialProvider>);
925
926impl GlobalCredentialProviders {
927 pub(crate) fn is_none(&self) -> bool {
928 self.0.is_empty()
929 }
930}
931
932impl GlobalCredentialProviders {
933 pub(crate) fn from_list<T>(
934 list: impl IntoIterator<Item = T>,
935 definition: Option<&Definition>,
936 ) -> Result<Self, Error>
937 where
938 T: AsRef<str>,
939 {
940 Ok(Self(
941 list.into_iter()
942 .map(|v| CredentialProvider::from_string(v.as_ref(), definition))
943 .collect::<Result<_, _>>()?,
944 ))
945 }
946}
947
948impl AsRef<[CredentialProvider]> for GlobalCredentialProviders {
949 fn as_ref(&self) -> &[CredentialProvider] {
950 &self.0
951 }
952}
953
954impl Serialize for GlobalCredentialProviders {
955 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
956 where
957 S: Serializer,
958 {
959 self.0.serialize(serializer)
960 }
961}
962
963impl<'de> Deserialize<'de> for GlobalCredentialProviders {
964 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
965 where
966 D: Deserializer<'de>,
967 {
968 Ok(Self(
970 <Vec<String>>::deserialize(deserializer)?
971 .iter()
972 .map(|s| CredentialProvider::from_string(s, None))
973 .collect::<Result<_, _>>()
974 .map_err(de::Error::custom)?,
975 ))
976 }
977}
978
979#[derive(Clone, Debug)]
983pub struct CredentialProvider {
984 pub kind: CredentialProviderKind,
985 deserialized_repr: StringListDeserializedRepr,
986}
987
988#[derive(Clone, Debug)]
990#[non_exhaustive]
991pub enum CredentialProviderKind {
992 CargoToken,
996 CargoWincred,
1000 CargoMacosKeychain,
1004 CargoLibsecret,
1008 CargoTokenFromStdout(PathAndArgs),
1012 Plugin(PathAndArgs),
1017 MaybeAlias(Value<String>),
1019}
1020
1021impl CredentialProvider {
1022 pub(crate) fn from_string(value: &str, definition: Option<&Definition>) -> Result<Self, Error> {
1023 Ok(Self {
1024 kind: CredentialProviderKind::from_list(
1025 split_space_separated(value)
1026 .map(|v| Value { val: v.to_owned(), definition: definition.cloned() }),
1027 StringListDeserializedRepr::String,
1028 )?,
1029 deserialized_repr: StringListDeserializedRepr::String,
1030 })
1031 }
1032
1033 pub(crate) fn from_array(list: Vec<Value<String>>) -> Result<Self, Error> {
1034 Ok(Self {
1035 kind: CredentialProviderKind::from_list(list, StringListDeserializedRepr::Array)?,
1036 deserialized_repr: StringListDeserializedRepr::Array,
1037 })
1038 }
1039}
1040
1041impl SetPath for CredentialProviderKind {
1042 fn set_path(&mut self, path: &Path) {
1043 match self {
1044 Self::CargoToken
1045 | Self::CargoWincred
1046 | Self::CargoMacosKeychain
1047 | Self::CargoLibsecret => {}
1048 Self::CargoTokenFromStdout(command) | Self::Plugin(command) => command.set_path(path),
1049 Self::MaybeAlias(s) => s.set_path(path),
1050 }
1051 }
1052}
1053
1054impl Serialize for CredentialProvider {
1055 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1056 where
1057 S: Serializer,
1058 {
1059 match self.deserialized_repr {
1060 StringListDeserializedRepr::String => {
1061 let mut s = String::new();
1062
1063 let command = match &self.kind {
1064 CredentialProviderKind::CargoToken => {
1065 return "cargo:token".serialize(serializer);
1066 }
1067 CredentialProviderKind::CargoWincred => {
1068 return "cargo:wincred".serialize(serializer);
1069 }
1070 CredentialProviderKind::CargoMacosKeychain => {
1071 return "cargo:macos-keychain".serialize(serializer);
1072 }
1073 CredentialProviderKind::CargoLibsecret => {
1074 return "cargo:libsecret".serialize(serializer);
1075 }
1076 CredentialProviderKind::CargoTokenFromStdout(command) => {
1077 s.push_str("cargo:token-from-stdout ");
1078
1079 command
1080 }
1081 CredentialProviderKind::Plugin(command) => command,
1082 CredentialProviderKind::MaybeAlias(value) => {
1083 return value.serialize(serializer);
1084 }
1085 };
1086
1087 command.serialize_to_string(&mut s);
1088
1089 s.serialize(serializer)
1090 }
1091 StringListDeserializedRepr::Array => {
1092 let mut array = vec![];
1093
1094 let command = match &self.kind {
1095 CredentialProviderKind::CargoToken => {
1096 return ["cargo:token"].serialize(serializer);
1097 }
1098 CredentialProviderKind::CargoWincred => {
1099 return ["cargo:wincred"].serialize(serializer);
1100 }
1101 CredentialProviderKind::CargoMacosKeychain => {
1102 return ["cargo:macos-keychain"].serialize(serializer);
1103 }
1104 CredentialProviderKind::CargoLibsecret => {
1105 return ["cargo:libsecret"].serialize(serializer);
1106 }
1107 CredentialProviderKind::CargoTokenFromStdout(command) => {
1108 array.push("cargo:token-from-stdout");
1109
1110 command
1111 }
1112 CredentialProviderKind::Plugin(command) => command,
1113 CredentialProviderKind::MaybeAlias(value) => {
1114 return [value].serialize(serializer);
1115 }
1116 };
1117
1118 command.serialize_to_array(&mut array);
1119
1120 array.serialize(serializer)
1121 }
1122 }
1123 }
1124}
1125
1126impl<'de> Deserialize<'de> for CredentialProvider {
1127 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1128 where
1129 D: Deserializer<'de>,
1130 {
1131 match StringOrArray::deserialize(deserializer)? {
1133 StringOrArray::String(s) => Self::from_string(&s.val, s.definition.as_ref()),
1134 StringOrArray::Array(v) => Self::from_array(v),
1135 }
1136 .map_err(de::Error::custom)
1137 }
1138}
1139
1140impl CredentialProviderKind {
1141 fn from_list<T>(list: T, deserialized_repr: StringListDeserializedRepr) -> Result<Self, Error>
1142 where
1143 T: IntoIterator<Item = Value<String>>,
1144 {
1145 let mut iter = list.into_iter().peekable();
1146 let first = iter
1147 .next()
1148 .ok_or_else(|| format_err!("invalid length 0, expected at least one element"))?;
1149
1150 let empty_args = |kind: Self, mut iter: iter::Peekable<T::IntoIter>| {
1151 if iter.next().is_some() {
1152 bail!(
1153 "args should be empty for credential provider of kind {:?}",
1154 kind.builtin_kind().unwrap(),
1155 );
1156 }
1157
1158 Ok(kind)
1159 };
1160
1161 match &*first.val {
1162 "cargo:token" => empty_args(Self::CargoToken, iter),
1163 "cargo:wincred" => empty_args(Self::CargoWincred, iter),
1164 "cargo:macos-keychain" => empty_args(Self::CargoMacosKeychain, iter),
1165 "cargo:libsecret" => empty_args(Self::CargoLibsecret, iter),
1166 "cargo:token-from-stdout" => {
1167 let command = PathAndArgs::from_list(
1168 iter,
1169 deserialized_repr,
1170 ).ok_or_else(|| format_err!(r#"invalid length 1, expected at least two elements for registry of kind "cargo:token-from-stdout""#))?;
1171
1172 Ok(Self::CargoTokenFromStdout(command))
1173 }
1174 _ if iter.peek().is_none() => Ok(Self::MaybeAlias(first)),
1175 _ => Ok(Self::Plugin(
1176 PathAndArgs::from_list(iter::once(first).chain(iter), deserialized_repr).unwrap(),
1177 )),
1178 }
1179 }
1180
1181 fn builtin_kind(&self) -> Option<&'static str> {
1182 Some(match self {
1183 Self::CargoToken => "cargo:token",
1184 Self::CargoWincred => "cargo:wincred",
1185 Self::CargoMacosKeychain => "cargo:macos-keychain",
1186 Self::CargoLibsecret => "cargo:libsecret",
1187 Self::CargoTokenFromStdout(_) => "cargo:token-from-stdout",
1188 Self::Plugin(_) | Self::MaybeAlias(_) => return None,
1189 })
1190 }
1191}
1192
1193#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1197#[serde(rename_all = "kebab-case")]
1198#[non_exhaustive]
1199pub struct TermConfig {
1200 #[serde(skip_serializing_if = "Option::is_none")]
1204 pub quiet: Option<Value<bool>>,
1205 #[serde(skip_serializing_if = "Option::is_none")]
1209 pub verbose: Option<Value<bool>>,
1210 #[serde(skip_serializing_if = "Option::is_none")]
1214 pub color: Option<Value<Color>>,
1215 #[serde(default)]
1216 #[serde(skip_serializing_if = "TermProgress::is_none")]
1217 pub progress: TermProgress,
1218}
1219
1220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1221#[serde(rename_all = "kebab-case")]
1222#[non_exhaustive]
1223pub struct TermProgress {
1224 #[serde(skip_serializing_if = "Option::is_none")]
1228 pub when: Option<Value<When>>,
1229 #[serde(skip_serializing_if = "Option::is_none")]
1233 pub width: Option<Value<u32>>,
1234}
1235
1236#[allow(clippy::exhaustive_enums)]
1237#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1238#[serde(rename_all = "kebab-case")]
1239pub enum Color {
1240 #[default]
1242 Auto,
1243 Always,
1245 Never,
1247}
1248
1249impl Color {
1250 pub const fn as_str(self) -> &'static str {
1251 match self {
1252 Self::Auto => "auto",
1253 Self::Always => "always",
1254 Self::Never => "never",
1255 }
1256 }
1257}
1258
1259impl FromStr for Color {
1260 type Err = Error;
1261
1262 fn from_str(color: &str) -> Result<Self, Self::Err> {
1263 match color {
1264 "auto" => Ok(Self::Auto),
1265 "always" => Ok(Self::Always),
1266 "never" => Ok(Self::Never),
1267 other => bail!("must be auto, always, or never, but found `{other}`"),
1268 }
1269 }
1270}
1271
1272#[allow(clippy::exhaustive_enums)]
1273#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1274#[serde(rename_all = "kebab-case")]
1275pub enum When {
1276 #[default]
1278 Auto,
1279 Always,
1281 Never,
1283}
1284
1285impl When {
1286 pub const fn as_str(self) -> &'static str {
1287 match self {
1288 Self::Auto => "auto",
1289 Self::Always => "always",
1290 Self::Never => "never",
1291 }
1292 }
1293}
1294
1295impl FromStr for When {
1296 type Err = Error;
1297
1298 fn from_str(color: &str) -> Result<Self, Self::Err> {
1299 match color {
1300 "auto" => Ok(Self::Auto),
1301 "always" => Ok(Self::Always),
1302 "never" => Ok(Self::Never),
1303 other => bail!("must be auto, always, or never, but found `{other}`"),
1304 }
1305 }
1306}
1307
1308#[allow(clippy::exhaustive_enums)]
1309#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1310#[serde(rename_all = "kebab-case")]
1311pub enum Frequency {
1312 #[default]
1315 Always,
1316 Never,
1318}
1319
1320impl Frequency {
1321 pub const fn as_str(self) -> &'static str {
1322 match self {
1323 Self::Always => "always",
1324 Self::Never => "never",
1325 }
1326 }
1327}
1328
1329impl FromStr for Frequency {
1330 type Err = Error;
1331
1332 fn from_str(color: &str) -> Result<Self, Self::Err> {
1333 match color {
1334 "always" => Ok(Self::Always),
1335 "never" => Ok(Self::Never),
1336 other => bail!("must be always or never, but found `{other}`"),
1337 }
1338 }
1339}
1340
1341#[derive(Debug, Clone, Serialize)]
1343#[serde(transparent)]
1344pub struct Flags {
1345 pub flags: Vec<Value<String>>,
1346 #[serde(skip)]
1348 pub(crate) deserialized_repr: StringListDeserializedRepr,
1349}
1350
1351impl Flags {
1352 pub(crate) fn from_encoded(s: &Value<String>) -> Self {
1361 Self {
1362 flags: split_encoded(&s.val)
1363 .map(|v| Value { val: v.to_owned(), definition: s.definition.clone() })
1364 .collect(),
1365 deserialized_repr: StringListDeserializedRepr::Array,
1367 }
1368 }
1369
1370 pub(crate) fn from_space_separated(s: &str, def: Option<&Definition>) -> Self {
1391 Self {
1392 flags: split_space_separated(s)
1393 .map(|v| Value { val: v.to_owned(), definition: def.cloned() })
1394 .collect(),
1395 deserialized_repr: StringListDeserializedRepr::String,
1396 }
1397 }
1398
1399 pub(crate) fn from_array(flags: Vec<Value<String>>) -> Self {
1400 Self { flags, deserialized_repr: StringListDeserializedRepr::Array }
1401 }
1402}
1403
1404impl<'de> Deserialize<'de> for Flags {
1405 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1406 where
1407 D: Deserializer<'de>,
1408 {
1409 let v: StringOrArray = Deserialize::deserialize(deserializer)?;
1411 match v {
1412 StringOrArray::String(s) => {
1413 Ok(Self::from_space_separated(&s.val, s.definition.as_ref()))
1414 }
1415 StringOrArray::Array(v) => Ok(Self::from_array(v)),
1416 }
1417 }
1418}
1419
1420#[derive(Debug, Deserialize, PartialEq, Clone)]
1422#[serde(transparent)]
1423pub struct ConfigRelativePath(pub(crate) Value<String>);
1424
1425impl ConfigRelativePath {
1426 pub fn value(&self) -> &Value<String> {
1428 &self.0
1429 }
1430
1431 pub fn raw_value(&self) -> &str {
1433 &self.0.val
1434 }
1435
1436 pub(crate) fn resolve_program(&self, current_dir: &Path) -> Cow<'_, Path> {
1451 self.0.resolve_as_program_path(current_dir)
1452 }
1453}
1454
1455#[derive(Debug, Clone)]
1459#[non_exhaustive]
1460pub struct PathAndArgs {
1461 pub path: ConfigRelativePath,
1462 pub args: Vec<Value<String>>,
1463
1464 pub(crate) deserialized_repr: StringListDeserializedRepr,
1466}
1467
1468impl PathAndArgs {
1469 pub(crate) fn from_string(value: &str, definition: Option<&Definition>) -> Option<Self> {
1470 Self::from_list(
1471 split_space_separated(value)
1472 .map(|v| Value { val: v.to_owned(), definition: definition.cloned() }),
1473 StringListDeserializedRepr::String,
1474 )
1475 }
1476
1477 pub(crate) fn from_array(list: Vec<Value<String>>) -> Option<Self> {
1478 Self::from_list(list, StringListDeserializedRepr::Array)
1479 }
1480
1481 fn from_list(
1482 list: impl IntoIterator<Item = Value<String>>,
1483 deserialized_repr: StringListDeserializedRepr,
1484 ) -> Option<Self> {
1485 let mut iter = list.into_iter();
1486
1487 Some(Self {
1488 path: ConfigRelativePath(iter.next()?),
1489 args: iter.collect(),
1490 deserialized_repr,
1491 })
1492 }
1493
1494 fn serialize_to_string(&self, s: &mut String) {
1495 s.push_str(self.path.raw_value());
1496
1497 for arg in &self.args {
1498 s.push(' ');
1499 s.push_str(&arg.val);
1500 }
1501 }
1502
1503 fn array_len(&self) -> usize {
1504 1 + self.args.len()
1505 }
1506
1507 fn serialize_to_array<'a>(&'a self, v: &mut Vec<&'a str>) {
1508 v.push(self.path.raw_value());
1509
1510 for arg in &self.args {
1511 v.push(&arg.val);
1512 }
1513 }
1514}
1515
1516impl Serialize for PathAndArgs {
1517 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1518 where
1519 S: Serializer,
1520 {
1521 match self.deserialized_repr {
1522 StringListDeserializedRepr::String => {
1523 let mut s = String::new();
1524
1525 self.serialize_to_string(&mut s);
1526 s.serialize(serializer)
1527 }
1528 StringListDeserializedRepr::Array => {
1529 let mut v = Vec::with_capacity(self.array_len());
1530
1531 self.serialize_to_array(&mut v);
1532 v.serialize(serializer)
1533 }
1534 }
1535 }
1536}
1537impl<'de> Deserialize<'de> for PathAndArgs {
1538 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1539 where
1540 D: Deserializer<'de>,
1541 {
1542 match StringOrArray::deserialize(deserializer)? {
1544 StringOrArray::String(s) => Self::from_string(&s.val, s.definition.as_ref()),
1545 StringOrArray::Array(v) => Self::from_array(v),
1546 }
1547 .ok_or_else(|| de::Error::invalid_length(0, &"at least one element"))
1548 }
1549}
1550
1551#[derive(Debug, Clone)]
1552#[non_exhaustive]
1553pub struct StringList {
1554 pub list: Vec<Value<String>>,
1555
1556 pub(crate) deserialized_repr: StringListDeserializedRepr,
1558}
1559
1560impl Default for StringList {
1561 fn default() -> Self {
1562 Self { list: vec![], deserialized_repr: StringListDeserializedRepr::Array }
1563 }
1564}
1565
1566#[derive(Debug, Clone, Copy, PartialEq)]
1567pub(crate) enum StringListDeserializedRepr {
1568 String,
1569 Array,
1570}
1571impl StringListDeserializedRepr {
1572 pub(crate) const fn as_str(self) -> &'static str {
1573 match self {
1574 Self::String => "string",
1575 Self::Array => "array",
1576 }
1577 }
1578}
1579
1580impl StringList {
1581 pub(crate) fn from_string(value: &str, definition: Option<&Definition>) -> Self {
1582 Self {
1583 list: split_space_separated(value)
1584 .map(|v| Value { val: v.to_owned(), definition: definition.cloned() })
1585 .collect(),
1586 deserialized_repr: StringListDeserializedRepr::String,
1587 }
1588 }
1589 pub(crate) fn from_array(list: Vec<Value<String>>) -> Self {
1590 Self { list, deserialized_repr: StringListDeserializedRepr::Array }
1591 }
1592}
1593
1594impl Serialize for StringList {
1595 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1596 where
1597 S: Serializer,
1598 {
1599 match self.deserialized_repr {
1600 StringListDeserializedRepr::String => {
1601 let mut s = String::with_capacity(
1602 self.list.len().saturating_sub(1)
1603 + self.list.iter().map(|v| v.val.len()).sum::<usize>(),
1604 );
1605 for arg in &self.list {
1606 if !s.is_empty() {
1607 s.push(' ');
1608 }
1609 s.push_str(&arg.val);
1610 }
1611 s.serialize(serializer)
1612 }
1613 StringListDeserializedRepr::Array => self.list.serialize(serializer),
1614 }
1615 }
1616}
1617impl<'de> Deserialize<'de> for StringList {
1618 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1619 where
1620 D: Deserializer<'de>,
1621 {
1622 let v: StringOrArray = Deserialize::deserialize(deserializer)?;
1624 match v {
1625 StringOrArray::String(s) => Ok(Self::from_string(&s.val, s.definition.as_ref())),
1626 StringOrArray::Array(v) => Ok(Self::from_array(v)),
1627 }
1628 }
1629}
1630
1631#[allow(clippy::exhaustive_enums)]
1633#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1634#[serde(untagged)]
1635pub enum StringOrArray {
1636 String(Value<String>),
1637 Array(Vec<Value<String>>),
1638}
1639
1640impl StringOrArray {
1641 pub(crate) const fn kind(&self) -> &'static str {
1642 match self {
1643 Self::String(..) => "string",
1644 Self::Array(..) => "array",
1645 }
1646 }
1647
1648 pub(crate) fn as_array_no_split(&self) -> &[Value<String>] {
1661 match self {
1662 Self::String(s) => slice::from_ref(s),
1663 Self::Array(v) => v,
1664 }
1665 }
1666}
1667
1668fn target_u_lower(target: &str) -> String {
1669 target.replace(['-', '.'], "_")
1670}
1671pub(crate) fn target_u_upper(target: &str) -> String {
1672 let mut target = target_u_lower(target);
1673 target.make_ascii_uppercase();
1674 target
1675}
1676
1677pub(crate) fn split_encoded(s: &str) -> impl Iterator<Item = &str> {
1678 s.split('\x1f')
1679}
1680pub(crate) fn split_space_separated(s: &str) -> impl Iterator<Item = &str> {
1681 s.split(' ').map(str::trim).filter(|s| !s.is_empty())
1684}