1use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
61use std::borrow::Cow;
62use std::collections::hash_map::Entry::{Occupied, Vacant};
63use std::collections::{HashMap, HashSet};
64use std::env;
65use std::ffi::{OsStr, OsString};
66use std::fmt;
67use std::fs::{self, File};
68use std::io::SeekFrom;
69use std::io::prelude::*;
70use std::mem;
71use std::path::{Path, PathBuf};
72use std::str::FromStr;
73use std::sync::{Arc, Mutex, MutexGuard, Once, OnceLock};
74use std::time::Instant;
75
76use self::ConfigValue as CV;
77use crate::core::compiler::rustdoc::RustdocExternMap;
78use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
79use crate::core::shell::Verbosity;
80use crate::core::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig, features};
81use crate::ops::RegistryCredentialConfig;
82use crate::sources::CRATES_IO_INDEX;
83use crate::sources::CRATES_IO_REGISTRY;
84use crate::util::OnceExt as _;
85use crate::util::errors::CargoResult;
86use crate::util::network::http::configure_http_handle;
87use crate::util::network::http::http_handle;
88use crate::util::{CanonicalUrl, closest_msg, internal};
89use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
90use annotate_snippets::Level;
91use anyhow::{Context as _, anyhow, bail, format_err};
92use cargo_credential::Secret;
93use cargo_util::paths;
94use cargo_util_schemas::manifest::RegistryName;
95use curl::easy::Easy;
96use itertools::Itertools;
97use serde::Deserialize;
98use serde::de::IntoDeserializer as _;
99use serde_untagged::UntaggedEnumVisitor;
100use time::OffsetDateTime;
101use toml_edit::Item;
102use url::Url;
103
104mod de;
105use de::Deserializer;
106
107mod error;
108pub use error::ConfigError;
109
110mod value;
111pub use value::{Definition, OptValue, Value};
112
113mod key;
114pub use key::ConfigKey;
115
116mod path;
117pub use path::{ConfigRelativePath, PathAndArgs};
118
119mod target;
120pub use target::{TargetCfgConfig, TargetConfig};
121
122mod environment;
123use environment::Env;
124
125use super::auth::RegistryConfig;
126
127macro_rules! get_value_typed {
129 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
130 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
132 let cv = self.get_cv(key)?;
133 let env = self.get_config_env::<$ty>(key)?;
134 match (cv, env) {
135 (Some(CV::$variant(val, definition)), Some(env)) => {
136 if definition.is_higher_priority(&env.definition) {
137 Ok(Some(Value { val, definition }))
138 } else {
139 Ok(Some(env))
140 }
141 }
142 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
143 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
144 (None, Some(env)) => Ok(Some(env)),
145 (None, None) => Ok(None),
146 }
147 }
148 };
149}
150
151#[derive(Clone, Copy, Debug)]
153enum WhyLoad {
154 Cli,
161 FileDiscovery,
163}
164
165#[derive(Debug)]
167pub struct CredentialCacheValue {
168 pub token_value: Secret<String>,
169 pub expiration: Option<OffsetDateTime>,
170 pub operation_independent: bool,
171}
172
173#[derive(Debug)]
176pub struct GlobalContext {
177 home_path: Filesystem,
179 shell: Mutex<Shell>,
181 values: OnceLock<HashMap<String, ConfigValue>>,
183 credential_values: OnceLock<HashMap<String, ConfigValue>>,
185 cli_config: Option<Vec<String>>,
187 cwd: PathBuf,
189 search_stop_path: Option<PathBuf>,
191 cargo_exe: OnceLock<PathBuf>,
193 rustdoc: OnceLock<PathBuf>,
195 extra_verbose: bool,
197 frozen: bool,
200 locked: bool,
203 offline: bool,
206 jobserver: Option<jobserver::Client>,
208 unstable_flags: CliUnstable,
210 unstable_flags_cli: Option<Vec<String>>,
212 easy: OnceLock<Mutex<Easy>>,
214 crates_io_source_id: OnceLock<SourceId>,
216 cache_rustc_info: bool,
218 creation_time: Instant,
220 target_dir: Option<Filesystem>,
222 env: Env,
224 updated_sources: Mutex<HashSet<SourceId>>,
226 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
229 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
231 package_cache_lock: CacheLocker,
233 http_config: OnceLock<CargoHttpConfig>,
235 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
236 net_config: OnceLock<CargoNetConfig>,
237 build_config: OnceLock<CargoBuildConfig>,
238 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
239 doc_extern_map: OnceLock<RustdocExternMap>,
240 progress_config: ProgressConfig,
241 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
242 pub nightly_features_allowed: bool,
258 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
260 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
262 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
265}
266
267impl GlobalContext {
268 pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
276 static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
277 static INIT: Once = Once::new();
278
279 INIT.call_once(|| unsafe {
282 if let Some(client) = jobserver::Client::from_env() {
283 GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
284 }
285 });
286
287 let env = Env::new();
288
289 let cache_key = "CARGO_CACHE_RUSTC_INFO";
290 let cache_rustc_info = match env.get_env_os(cache_key) {
291 Some(cache) => cache != "0",
292 _ => true,
293 };
294
295 GlobalContext {
296 home_path: Filesystem::new(homedir),
297 shell: Mutex::new(shell),
298 cwd,
299 search_stop_path: None,
300 values: Default::default(),
301 credential_values: Default::default(),
302 cli_config: None,
303 cargo_exe: Default::default(),
304 rustdoc: Default::default(),
305 extra_verbose: false,
306 frozen: false,
307 locked: false,
308 offline: false,
309 jobserver: unsafe {
310 if GLOBAL_JOBSERVER.is_null() {
311 None
312 } else {
313 Some((*GLOBAL_JOBSERVER).clone())
314 }
315 },
316 unstable_flags: CliUnstable::default(),
317 unstable_flags_cli: None,
318 easy: Default::default(),
319 crates_io_source_id: Default::default(),
320 cache_rustc_info,
321 creation_time: Instant::now(),
322 target_dir: None,
323 env,
324 updated_sources: Default::default(),
325 credential_cache: Default::default(),
326 registry_config: Default::default(),
327 package_cache_lock: CacheLocker::new(),
328 http_config: Default::default(),
329 future_incompat_config: Default::default(),
330 net_config: Default::default(),
331 build_config: Default::default(),
332 target_cfgs: Default::default(),
333 doc_extern_map: Default::default(),
334 progress_config: ProgressConfig::default(),
335 env_config: Default::default(),
336 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
337 ws_roots: Default::default(),
338 global_cache_tracker: Default::default(),
339 deferred_global_last_use: Default::default(),
340 }
341 }
342
343 pub fn default() -> CargoResult<GlobalContext> {
348 let shell = Shell::new();
349 let cwd =
350 env::current_dir().context("couldn't get the current directory of the process")?;
351 let homedir = homedir(&cwd).ok_or_else(|| {
352 anyhow!(
353 "Cargo couldn't find your home directory. \
354 This probably means that $HOME was not set."
355 )
356 })?;
357 Ok(GlobalContext::new(shell, cwd, homedir))
358 }
359
360 pub fn home(&self) -> &Filesystem {
362 &self.home_path
363 }
364
365 pub fn diagnostic_home_config(&self) -> String {
369 let home = self.home_path.as_path_unlocked();
370 let path = match self.get_file_path(home, "config", false) {
371 Ok(Some(existing_path)) => existing_path,
372 _ => home.join("config.toml"),
373 };
374 path.to_string_lossy().to_string()
375 }
376
377 pub fn git_path(&self) -> Filesystem {
379 self.home_path.join("git")
380 }
381
382 pub fn git_checkouts_path(&self) -> Filesystem {
385 self.git_path().join("checkouts")
386 }
387
388 pub fn git_db_path(&self) -> Filesystem {
391 self.git_path().join("db")
392 }
393
394 pub fn registry_base_path(&self) -> Filesystem {
396 self.home_path.join("registry")
397 }
398
399 pub fn registry_index_path(&self) -> Filesystem {
401 self.registry_base_path().join("index")
402 }
403
404 pub fn registry_cache_path(&self) -> Filesystem {
406 self.registry_base_path().join("cache")
407 }
408
409 pub fn registry_source_path(&self) -> Filesystem {
411 self.registry_base_path().join("src")
412 }
413
414 pub fn default_registry(&self) -> CargoResult<Option<String>> {
416 Ok(self
417 .get_string("registry.default")?
418 .map(|registry| registry.val))
419 }
420
421 pub fn shell(&self) -> MutexGuard<'_, Shell> {
423 self.shell.lock().unwrap()
424 }
425
426 pub fn debug_assert_shell_not_borrowed(&self) {
432 if cfg!(debug_assertions) {
433 match self.shell.try_lock() {
434 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
435 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
436 }
437 }
438 }
439
440 pub fn rustdoc(&self) -> CargoResult<&Path> {
442 self.rustdoc
443 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
444 .map(AsRef::as_ref)
445 }
446
447 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
449 let cache_location =
450 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
451 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
452 let rustc_workspace_wrapper = self.maybe_get_tool(
453 "rustc_workspace_wrapper",
454 &self.build_config()?.rustc_workspace_wrapper,
455 );
456
457 Rustc::new(
458 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
459 wrapper,
460 rustc_workspace_wrapper,
461 &self
462 .home()
463 .join("bin")
464 .join("rustc")
465 .into_path_unlocked()
466 .with_extension(env::consts::EXE_EXTENSION),
467 if self.cache_rustc_info {
468 cache_location
469 } else {
470 None
471 },
472 self,
473 )
474 }
475
476 pub fn cargo_exe(&self) -> CargoResult<&Path> {
478 self.cargo_exe
479 .try_borrow_with(|| {
480 let from_env = || -> CargoResult<PathBuf> {
481 let exe = self
486 .get_env_os(crate::CARGO_ENV)
487 .map(PathBuf::from)
488 .ok_or_else(|| anyhow!("$CARGO not set"))?;
489 Ok(exe)
490 };
491
492 fn from_current_exe() -> CargoResult<PathBuf> {
493 let exe = env::current_exe()?;
498 Ok(exe)
499 }
500
501 fn from_argv() -> CargoResult<PathBuf> {
502 let argv0 = env::args_os()
509 .map(PathBuf::from)
510 .next()
511 .ok_or_else(|| anyhow!("no argv[0]"))?;
512 paths::resolve_executable(&argv0)
513 }
514
515 fn is_cargo(path: &Path) -> bool {
518 path.file_stem() == Some(OsStr::new("cargo"))
519 }
520
521 let from_current_exe = from_current_exe();
522 if from_current_exe.as_deref().is_ok_and(is_cargo) {
523 return from_current_exe;
524 }
525
526 let from_argv = from_argv();
527 if from_argv.as_deref().is_ok_and(is_cargo) {
528 return from_argv;
529 }
530
531 let exe = from_env()
532 .or(from_current_exe)
533 .or(from_argv)
534 .context("couldn't get the path to cargo executable")?;
535 Ok(exe)
536 })
537 .map(AsRef::as_ref)
538 }
539
540 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
542 self.updated_sources.lock().unwrap()
543 }
544
545 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
547 self.credential_cache.lock().unwrap()
548 }
549
550 pub(crate) fn registry_config(
552 &self,
553 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
554 self.registry_config.lock().unwrap()
555 }
556
557 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
563 self.values.try_borrow_with(|| self.load_values())
564 }
565
566 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
573 let _ = self.values()?;
574 Ok(self.values.get_mut().expect("already loaded config values"))
575 }
576
577 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
579 if self.values.get().is_some() {
580 bail!("config values already found")
581 }
582 match self.values.set(values.into()) {
583 Ok(()) => Ok(()),
584 Err(_) => bail!("could not fill values"),
585 }
586 }
587
588 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
591 let path = path.into();
592 debug_assert!(self.cwd.starts_with(&path));
593 self.search_stop_path = Some(path);
594 }
595
596 pub fn reload_cwd(&mut self) -> CargoResult<()> {
600 let cwd =
601 env::current_dir().context("couldn't get the current directory of the process")?;
602 let homedir = homedir(&cwd).ok_or_else(|| {
603 anyhow!(
604 "Cargo couldn't find your home directory. \
605 This probably means that $HOME was not set."
606 )
607 })?;
608
609 self.cwd = cwd;
610 self.home_path = Filesystem::new(homedir);
611 self.reload_rooted_at(self.cwd.clone())?;
612 Ok(())
613 }
614
615 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
618 let values = self.load_values_from(path.as_ref())?;
619 self.values.replace(values);
620 self.merge_cli_args()?;
621 self.load_unstable_flags_from_config()?;
622 Ok(())
623 }
624
625 pub fn cwd(&self) -> &Path {
627 &self.cwd
628 }
629
630 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
636 if let Some(dir) = &self.target_dir {
637 Ok(Some(dir.clone()))
638 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
639 if dir.is_empty() {
641 bail!(
642 "the target directory is set to an empty string in the \
643 `CARGO_TARGET_DIR` environment variable"
644 )
645 }
646
647 Ok(Some(Filesystem::new(self.cwd.join(dir))))
648 } else if let Some(val) = &self.build_config()?.target_dir {
649 let path = val.resolve_path(self);
650
651 if val.raw_value().is_empty() {
653 bail!(
654 "the target directory is set to an empty string in {}",
655 val.value().definition
656 )
657 }
658
659 Ok(Some(Filesystem::new(path)))
660 } else {
661 Ok(None)
662 }
663 }
664
665 pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
669 let Some(val) = &self.build_config()?.build_dir else {
670 return Ok(None);
671 };
672 self.custom_build_dir(val, workspace_manifest_path)
673 .map(Some)
674 }
675
676 pub fn custom_build_dir(
680 &self,
681 val: &ConfigRelativePath,
682 workspace_manifest_path: &Path,
683 ) -> CargoResult<Filesystem> {
684 let replacements = [
685 (
686 "{workspace-root}",
687 workspace_manifest_path
688 .parent()
689 .unwrap()
690 .to_str()
691 .context("workspace root was not valid utf-8")?
692 .to_string(),
693 ),
694 (
695 "{cargo-cache-home}",
696 self.home()
697 .as_path_unlocked()
698 .to_str()
699 .context("cargo home was not valid utf-8")?
700 .to_string(),
701 ),
702 ("{workspace-path-hash}", {
703 let real_path = std::fs::canonicalize(workspace_manifest_path)?;
704 let hash = crate::util::hex::short_hash(&real_path);
705 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
706 }),
707 ];
708
709 let template_variables = replacements
710 .iter()
711 .map(|(key, _)| key[1..key.len() - 1].to_string())
712 .collect_vec();
713
714 let path = val
715 .resolve_templated_path(self, replacements)
716 .map_err(|e| match e {
717 path::ResolveTemplateError::UnexpectedVariable {
718 variable,
719 raw_template,
720 } => {
721 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
722 if suggestion == "" {
723 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
724 suggestion = format!("\n\nhelp: available template variables are {variables}");
725 }
726 anyhow!(
727 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
728 )
729 },
730 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
731 let (btype, literal) = match bracket_type {
732 path::BracketType::Opening => ("opening", "{"),
733 path::BracketType::Closing => ("closing", "}"),
734 };
735
736 anyhow!(
737 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
738 )
739 }
740 })?;
741
742 if val.raw_value().is_empty() {
744 bail!(
745 "the build directory is set to an empty string in {}",
746 val.value().definition
747 )
748 }
749
750 Ok(Filesystem::new(path))
751 }
752
753 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
758 if let Some(vals) = self.credential_values.get() {
759 let val = self.get_cv_helper(key, vals)?;
760 if val.is_some() {
761 return Ok(val);
762 }
763 }
764 self.get_cv_helper(key, &*self.values()?)
765 }
766
767 fn get_cv_helper(
768 &self,
769 key: &ConfigKey,
770 vals: &HashMap<String, ConfigValue>,
771 ) -> CargoResult<Option<ConfigValue>> {
772 tracing::trace!("get cv {:?}", key);
773 if key.is_root() {
774 return Ok(Some(CV::Table(
777 vals.clone(),
778 Definition::Path(PathBuf::new()),
779 )));
780 }
781 let mut parts = key.parts().enumerate();
782 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
783 return Ok(None);
784 };
785 for (i, part) in parts {
786 match val {
787 CV::Table(map, _) => {
788 val = match map.get(part) {
789 Some(val) => val,
790 None => return Ok(None),
791 }
792 }
793 CV::Integer(_, def)
794 | CV::String(_, def)
795 | CV::List(_, def)
796 | CV::Boolean(_, def) => {
797 let mut key_so_far = ConfigKey::new();
798 for part in key.parts().take(i) {
799 key_so_far.push(part);
800 }
801 bail!(
802 "expected table for configuration key `{}`, \
803 but found {} in {}",
804 key_so_far,
805 val.desc(),
806 def
807 )
808 }
809 }
810 }
811 Ok(Some(val.clone()))
812 }
813
814 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
816 let cv = self.get_cv(key)?;
819 if key.is_root() {
820 return Ok(cv);
822 }
823 let env = self.env.get_str(key.as_env_key());
824 let env_def = Definition::Environment(key.as_env_key().to_string());
825 let use_env = match (&cv, env) {
826 (Some(CV::List(..)), Some(_)) => true,
828 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
829 (None, Some(_)) => true,
830 _ => false,
831 };
832
833 if !use_env {
834 return Ok(cv);
835 }
836
837 let env = env.unwrap();
841 if env == "true" {
842 Ok(Some(CV::Boolean(true, env_def)))
843 } else if env == "false" {
844 Ok(Some(CV::Boolean(false, env_def)))
845 } else if let Ok(i) = env.parse::<i64>() {
846 Ok(Some(CV::Integer(i, env_def)))
847 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
848 match cv {
849 Some(CV::List(mut cv_list, cv_def)) => {
850 self.get_env_list(key, &mut cv_list)?;
852 Ok(Some(CV::List(cv_list, cv_def)))
853 }
854 Some(cv) => {
855 bail!(
859 "unable to merge array env for config `{}`\n\
860 file: {:?}\n\
861 env: {}",
862 key,
863 cv,
864 env
865 );
866 }
867 None => {
868 let mut cv_list = Vec::new();
869 self.get_env_list(key, &mut cv_list)?;
870 Ok(Some(CV::List(cv_list, env_def)))
871 }
872 }
873 } else {
874 match cv {
876 Some(CV::List(mut cv_list, cv_def)) => {
877 self.get_env_list(key, &mut cv_list)?;
879 Ok(Some(CV::List(cv_list, cv_def)))
880 }
881 _ => {
882 Ok(Some(CV::String(env.to_string(), env_def)))
887 }
888 }
889 }
890 }
891
892 pub fn set_env(&mut self, env: HashMap<String, String>) {
894 self.env = Env::from_map(env);
895 }
896
897 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
900 self.env.iter_str()
901 }
902
903 fn env_keys(&self) -> impl Iterator<Item = &str> {
905 self.env.keys_str()
906 }
907
908 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
909 where
910 T: FromStr,
911 <T as FromStr>::Err: fmt::Display,
912 {
913 match self.env.get_str(key.as_env_key()) {
914 Some(value) => {
915 let definition = Definition::Environment(key.as_env_key().to_string());
916 Ok(Some(Value {
917 val: value
918 .parse()
919 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
920 definition,
921 }))
922 }
923 None => {
924 self.check_environment_key_case_mismatch(key);
925 Ok(None)
926 }
927 }
928 }
929
930 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
935 self.env.get_env(key)
936 }
937
938 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
943 self.env.get_env_os(key)
944 }
945
946 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
950 if self.env.contains_key(key.as_env_key()) {
951 return Ok(true);
952 }
953 if env_prefix_ok {
954 let env_prefix = format!("{}_", key.as_env_key());
955 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
956 return Ok(true);
957 }
958 }
959 if self.get_cv(key)?.is_some() {
960 return Ok(true);
961 }
962 self.check_environment_key_case_mismatch(key);
963
964 Ok(false)
965 }
966
967 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
968 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
969 let _ = self.shell().warn(format!(
970 "environment variables are expected to use uppercase letters and underscores, \
971 the variable `{}` will be ignored and have no effect",
972 env_key
973 ));
974 }
975 }
976
977 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
981 self.get::<OptValue<String>>(key)
982 }
983
984 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
990 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
991 v.map(|v| Value {
992 val: v.val.resolve_program(self),
993 definition: v.definition,
994 })
995 })
996 }
997
998 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
999 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1000 if is_path {
1001 definition.root(self).join(value)
1002 } else {
1003 PathBuf::from(value)
1005 }
1006 }
1007
1008 fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1011 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1012 self.check_environment_key_case_mismatch(key);
1013 return Ok(());
1014 };
1015
1016 if is_nonmergable_list(&key) {
1017 output.clear();
1018 }
1019
1020 let def = Definition::Environment(key.as_env_key().to_string());
1021 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1022 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1024 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
1025 })?;
1026 let values = toml_v.as_array().expect("env var was not array");
1027 for value in values {
1028 let s = value.as_str().ok_or_else(|| {
1030 ConfigError::new(
1031 format!("expected string, found {}", value.type_str()),
1032 def.clone(),
1033 )
1034 })?;
1035 output.push(CV::String(s.to_string(), def.clone()))
1036 }
1037 } else {
1038 output.extend(
1039 env_val
1040 .split_whitespace()
1041 .map(|s| CV::String(s.to_string(), def.clone())),
1042 );
1043 }
1044 output.sort_by(|a, b| a.definition().cmp(b.definition()));
1045 Ok(())
1046 }
1047
1048 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1052 match self.get_cv(key)? {
1053 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1054 Some(val) => self.expected("table", key, &val),
1055 None => Ok(None),
1056 }
1057 }
1058
1059 get_value_typed! {get_integer, i64, Integer, "an integer"}
1060 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1061 get_value_typed! {get_string_priv, String, String, "a string"}
1062
1063 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1065 val.expected(ty, &key.to_string())
1066 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1067 }
1068
1069 pub fn configure(
1075 &mut self,
1076 verbose: u32,
1077 quiet: bool,
1078 color: Option<&str>,
1079 frozen: bool,
1080 locked: bool,
1081 offline: bool,
1082 target_dir: &Option<PathBuf>,
1083 unstable_flags: &[String],
1084 cli_config: &[String],
1085 ) -> CargoResult<()> {
1086 for warning in self
1087 .unstable_flags
1088 .parse(unstable_flags, self.nightly_features_allowed)?
1089 {
1090 self.shell().warn(warning)?;
1091 }
1092 if !unstable_flags.is_empty() {
1093 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1096 }
1097 if !cli_config.is_empty() {
1098 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1099 self.merge_cli_args()?;
1100 }
1101
1102 self.load_unstable_flags_from_config()?;
1106 if self.unstable_flags.config_include {
1107 self.reload_rooted_at(self.cwd.clone())?;
1114 }
1115
1116 let term = self.get::<TermConfig>("term").unwrap_or_default();
1120
1121 let extra_verbose = verbose >= 2;
1123 let verbose = verbose != 0;
1124 let verbosity = match (verbose, quiet) {
1125 (true, true) => bail!("cannot set both --verbose and --quiet"),
1126 (true, false) => Verbosity::Verbose,
1127 (false, true) => Verbosity::Quiet,
1128 (false, false) => match (term.verbose, term.quiet) {
1129 (Some(true), Some(true)) => {
1130 bail!("cannot set both `term.verbose` and `term.quiet`")
1131 }
1132 (Some(true), _) => Verbosity::Verbose,
1133 (_, Some(true)) => Verbosity::Quiet,
1134 _ => Verbosity::Normal,
1135 },
1136 };
1137 self.shell().set_verbosity(verbosity);
1138 self.extra_verbose = extra_verbose;
1139
1140 let color = color.or_else(|| term.color.as_deref());
1141 self.shell().set_color_choice(color)?;
1142 if let Some(hyperlinks) = term.hyperlinks {
1143 self.shell().set_hyperlinks(hyperlinks)?;
1144 }
1145 if let Some(unicode) = term.unicode {
1146 self.shell().set_unicode(unicode)?;
1147 }
1148
1149 self.progress_config = term.progress.unwrap_or_default();
1150
1151 self.frozen = frozen;
1152 self.locked = locked;
1153 self.offline = offline
1154 || self
1155 .net_config()
1156 .ok()
1157 .and_then(|n| n.offline)
1158 .unwrap_or(false);
1159 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1160 self.target_dir = cli_target_dir;
1161
1162 Ok(())
1163 }
1164
1165 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1166 if self.nightly_features_allowed {
1169 self.unstable_flags = self
1170 .get::<Option<CliUnstable>>("unstable")?
1171 .unwrap_or_default();
1172 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1173 self.unstable_flags.parse(unstable_flags_cli, true)?;
1178 }
1179 }
1180
1181 Ok(())
1182 }
1183
1184 pub fn cli_unstable(&self) -> &CliUnstable {
1185 &self.unstable_flags
1186 }
1187
1188 pub fn extra_verbose(&self) -> bool {
1189 self.extra_verbose
1190 }
1191
1192 pub fn network_allowed(&self) -> bool {
1193 !self.offline_flag().is_some()
1194 }
1195
1196 pub fn offline_flag(&self) -> Option<&'static str> {
1197 if self.frozen {
1198 Some("--frozen")
1199 } else if self.offline {
1200 Some("--offline")
1201 } else {
1202 None
1203 }
1204 }
1205
1206 pub fn set_locked(&mut self, locked: bool) {
1207 self.locked = locked;
1208 }
1209
1210 pub fn lock_update_allowed(&self) -> bool {
1211 !self.locked_flag().is_some()
1212 }
1213
1214 pub fn locked_flag(&self) -> Option<&'static str> {
1215 if self.frozen {
1216 Some("--frozen")
1217 } else if self.locked {
1218 Some("--locked")
1219 } else {
1220 None
1221 }
1222 }
1223
1224 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1226 self.load_values_from(&self.cwd)
1227 }
1228
1229 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1233 let mut result = Vec::new();
1234 let mut seen = HashSet::new();
1235 let home = self.home_path.clone().into_path_unlocked();
1236 self.walk_tree(&self.cwd, &home, |path| {
1237 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1238 if self.cli_unstable().config_include {
1239 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1240 }
1241 result.push(cv);
1242 Ok(())
1243 })
1244 .context("could not load Cargo configuration")?;
1245 Ok(result)
1246 }
1247
1248 fn load_unmerged_include(
1252 &self,
1253 cv: &mut CV,
1254 seen: &mut HashSet<PathBuf>,
1255 output: &mut Vec<CV>,
1256 ) -> CargoResult<()> {
1257 let includes = self.include_paths(cv, false)?;
1258 for (path, abs_path, def) in includes {
1259 let mut cv = self
1260 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1261 .with_context(|| {
1262 format!("failed to load config include `{}` from `{}`", path, def)
1263 })?;
1264 self.load_unmerged_include(&mut cv, seen, output)?;
1265 output.push(cv);
1266 }
1267 Ok(())
1268 }
1269
1270 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1272 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1275 let home = self.home_path.clone().into_path_unlocked();
1276
1277 self.walk_tree(path, &home, |path| {
1278 let value = self.load_file(path)?;
1279 cfg.merge(value, false).with_context(|| {
1280 format!("failed to merge configuration at `{}`", path.display())
1281 })?;
1282 Ok(())
1283 })
1284 .context("could not load Cargo configuration")?;
1285
1286 match cfg {
1287 CV::Table(map, _) => Ok(map),
1288 _ => unreachable!(),
1289 }
1290 }
1291
1292 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1296 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1297 }
1298
1299 fn _load_file(
1309 &self,
1310 path: &Path,
1311 seen: &mut HashSet<PathBuf>,
1312 includes: bool,
1313 why_load: WhyLoad,
1314 ) -> CargoResult<ConfigValue> {
1315 if !seen.insert(path.to_path_buf()) {
1316 bail!(
1317 "config `include` cycle detected with path `{}`",
1318 path.display()
1319 );
1320 }
1321 tracing::debug!(?path, ?why_load, includes, "load config from file");
1322
1323 let contents = fs::read_to_string(path)
1324 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1325 let toml = parse_document(&contents, path, self).with_context(|| {
1326 format!("could not parse TOML configuration in `{}`", path.display())
1327 })?;
1328 let def = match why_load {
1329 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1330 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1331 };
1332 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1333 format!(
1334 "failed to load TOML configuration from `{}`",
1335 path.display()
1336 )
1337 })?;
1338 if includes {
1339 self.load_includes(value, seen, why_load)
1340 } else {
1341 Ok(value)
1342 }
1343 }
1344
1345 fn load_includes(
1352 &self,
1353 mut value: CV,
1354 seen: &mut HashSet<PathBuf>,
1355 why_load: WhyLoad,
1356 ) -> CargoResult<CV> {
1357 let includes = self.include_paths(&mut value, true)?;
1359 if !self.cli_unstable().config_include {
1361 return Ok(value);
1362 }
1363 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1365 for (path, abs_path, def) in includes {
1366 self._load_file(&abs_path, seen, true, why_load)
1367 .and_then(|include| root.merge(include, true))
1368 .with_context(|| {
1369 format!("failed to load config include `{}` from `{}`", path, def)
1370 })?;
1371 }
1372 root.merge(value, true)?;
1373 Ok(root)
1374 }
1375
1376 fn include_paths(
1378 &self,
1379 cv: &mut CV,
1380 remove: bool,
1381 ) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
1382 let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
1383 let abs_path = match def {
1384 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1385 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => {
1386 self.cwd().join(&path)
1387 }
1388 };
1389 (path.to_string(), abs_path, def.clone())
1390 };
1391 let CV::Table(table, _def) = cv else {
1392 unreachable!()
1393 };
1394 let owned;
1395 let include = if remove {
1396 owned = table.remove("include");
1397 owned.as_ref()
1398 } else {
1399 table.get("include")
1400 };
1401 let includes = match include {
1402 Some(CV::String(s, def)) => {
1403 vec![abs(s, def)]
1404 }
1405 Some(CV::List(list, _def)) => list
1406 .iter()
1407 .map(|cv| match cv {
1408 CV::String(s, def) => Ok(abs(s, def)),
1409 other => bail!(
1410 "`include` expected a string or list of strings, but found {} in list",
1411 other.desc()
1412 ),
1413 })
1414 .collect::<CargoResult<Vec<_>>>()?,
1415 Some(other) => bail!(
1416 "`include` expected a string or list, but found {} in `{}`",
1417 other.desc(),
1418 other.definition()
1419 ),
1420 None => {
1421 return Ok(Vec::new());
1422 }
1423 };
1424
1425 for (path, abs_path, def) in &includes {
1426 if abs_path.extension() != Some(OsStr::new("toml")) {
1427 bail!(
1428 "expected a config include path ending with `.toml`, \
1429 but found `{path}` from `{def}`",
1430 )
1431 }
1432 }
1433
1434 Ok(includes)
1435 }
1436
1437 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1439 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1440 let Some(cli_args) = &self.cli_config else {
1441 return Ok(loaded_args);
1442 };
1443 let mut seen = HashSet::new();
1444 for arg in cli_args {
1445 let arg_as_path = self.cwd.join(arg);
1446 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1447 let str_path = arg_as_path
1449 .to_str()
1450 .ok_or_else(|| {
1451 anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
1452 })?
1453 .to_string();
1454 self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
1455 .with_context(|| format!("failed to load config from `{}`", str_path))?
1456 } else {
1457 let doc = toml_dotted_keys(arg)?;
1458 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1459 .with_context(|| {
1460 format!("failed to parse value from --config argument `{arg}`")
1461 })?;
1462
1463 if doc
1464 .get("registry")
1465 .and_then(|v| v.as_table())
1466 .and_then(|t| t.get("token"))
1467 .is_some()
1468 {
1469 bail!("registry.token cannot be set through --config for security reasons");
1470 } else if let Some((k, _)) = doc
1471 .get("registries")
1472 .and_then(|v| v.as_table())
1473 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1474 {
1475 bail!(
1476 "registries.{}.token cannot be set through --config for security reasons",
1477 k
1478 );
1479 }
1480
1481 if doc
1482 .get("registry")
1483 .and_then(|v| v.as_table())
1484 .and_then(|t| t.get("secret-key"))
1485 .is_some()
1486 {
1487 bail!(
1488 "registry.secret-key cannot be set through --config for security reasons"
1489 );
1490 } else if let Some((k, _)) = doc
1491 .get("registries")
1492 .and_then(|v| v.as_table())
1493 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1494 {
1495 bail!(
1496 "registries.{}.secret-key cannot be set through --config for security reasons",
1497 k
1498 );
1499 }
1500
1501 CV::from_toml(Definition::Cli(None), doc)
1502 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1503 };
1504 let tmp_table = self
1505 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1506 .context("failed to load --config include".to_string())?;
1507 loaded_args
1508 .merge(tmp_table, true)
1509 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1510 }
1511 Ok(loaded_args)
1512 }
1513
1514 fn merge_cli_args(&mut self) -> CargoResult<()> {
1516 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1517 unreachable!()
1518 };
1519 let values = self.values_mut()?;
1520 for (key, value) in loaded_map.into_iter() {
1521 match values.entry(key) {
1522 Vacant(entry) => {
1523 entry.insert(value);
1524 }
1525 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1526 format!(
1527 "failed to merge --config key `{}` into `{}`",
1528 entry.key(),
1529 entry.get().definition(),
1530 )
1531 })?,
1532 };
1533 }
1534 Ok(())
1535 }
1536
1537 fn get_file_path(
1543 &self,
1544 dir: &Path,
1545 filename_without_extension: &str,
1546 warn: bool,
1547 ) -> CargoResult<Option<PathBuf>> {
1548 let possible = dir.join(filename_without_extension);
1549 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1550
1551 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1552 if warn {
1553 if let Ok(possible_with_extension_handle) =
1554 same_file::Handle::from_path(&possible_with_extension)
1555 {
1556 if possible_handle != possible_with_extension_handle {
1562 self.shell().warn(format!(
1563 "both `{}` and `{}` exist. Using `{}`",
1564 possible.display(),
1565 possible_with_extension.display(),
1566 possible.display()
1567 ))?;
1568 }
1569 } else {
1570 self.shell().print_report(&[
1571 Level::WARNING.secondary_title(
1572 format!(
1573 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1574 possible.display(),
1575 )).element(Level::HELP.message(
1576 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1577
1578 ], false)?;
1579 }
1580 }
1581
1582 Ok(Some(possible))
1583 } else if possible_with_extension.exists() {
1584 Ok(Some(possible_with_extension))
1585 } else {
1586 Ok(None)
1587 }
1588 }
1589
1590 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1591 where
1592 F: FnMut(&Path) -> CargoResult<()>,
1593 {
1594 let mut seen_dir = HashSet::new();
1595
1596 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1597 let config_root = current.join(".cargo");
1598 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1599 walk(&path)?;
1600 }
1601 seen_dir.insert(config_root);
1602 }
1603
1604 if !seen_dir.contains(home) {
1608 if let Some(path) = self.get_file_path(home, "config", true)? {
1609 walk(&path)?;
1610 }
1611 }
1612
1613 Ok(())
1614 }
1615
1616 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1618 RegistryName::new(registry)?;
1619 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1620 self.resolve_registry_index(&index).with_context(|| {
1621 format!(
1622 "invalid index URL for registry `{}` defined in {}",
1623 registry, index.definition
1624 )
1625 })
1626 } else {
1627 bail!(
1628 "registry index was not found in any configuration: `{}`",
1629 registry
1630 );
1631 }
1632 }
1633
1634 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1636 if self.get_string("registry.index")?.is_some() {
1637 bail!(
1638 "the `registry.index` config value is no longer supported\n\
1639 Use `[source]` replacement to alter the default index for crates.io."
1640 );
1641 }
1642 Ok(())
1643 }
1644
1645 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1646 let base = index
1648 .definition
1649 .root(self)
1650 .join("truncated-by-url_with_base");
1651 let _parsed = index.val.into_url()?;
1653 let url = index.val.into_url_with_base(Some(&*base))?;
1654 if url.password().is_some() {
1655 bail!("registry URLs may not contain passwords");
1656 }
1657 Ok(url)
1658 }
1659
1660 pub fn load_credentials(&self) -> CargoResult<()> {
1668 if self.credential_values.filled() {
1669 return Ok(());
1670 }
1671
1672 let home_path = self.home_path.clone().into_path_unlocked();
1673 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1674 return Ok(());
1675 };
1676
1677 let mut value = self.load_file(&credentials)?;
1678 {
1680 let CV::Table(ref mut value_map, ref def) = value else {
1681 unreachable!();
1682 };
1683
1684 if let Some(token) = value_map.remove("token") {
1685 if let Vacant(entry) = value_map.entry("registry".into()) {
1686 let map = HashMap::from([("token".into(), token)]);
1687 let table = CV::Table(map, def.clone());
1688 entry.insert(table);
1689 }
1690 }
1691 }
1692
1693 let mut credential_values = HashMap::new();
1694 if let CV::Table(map, _) = value {
1695 let base_map = self.values()?;
1696 for (k, v) in map {
1697 let entry = match base_map.get(&k) {
1698 Some(base_entry) => {
1699 let mut entry = base_entry.clone();
1700 entry.merge(v, true)?;
1701 entry
1702 }
1703 None => v,
1704 };
1705 credential_values.insert(k, entry);
1706 }
1707 }
1708 self.credential_values
1709 .set(credential_values)
1710 .expect("was not filled at beginning of the function");
1711 Ok(())
1712 }
1713
1714 fn maybe_get_tool(
1717 &self,
1718 tool: &str,
1719 from_config: &Option<ConfigRelativePath>,
1720 ) -> Option<PathBuf> {
1721 let var = tool.to_uppercase();
1722
1723 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1724 Some(tool_path) => {
1725 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1726 let path = if maybe_relative {
1727 self.cwd.join(tool_path)
1728 } else {
1729 PathBuf::from(tool_path)
1730 };
1731 Some(path)
1732 }
1733
1734 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1735 }
1736 }
1737
1738 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1749 let tool_str = tool.as_str();
1750 self.maybe_get_tool(tool_str, from_config)
1751 .or_else(|| {
1752 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1766 if toolchain.to_str()?.contains(&['/', '\\']) {
1769 return None;
1770 }
1771 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1774 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1775 let tool_meta = tool_resolved.metadata().ok()?;
1776 let rustup_meta = rustup_resolved.metadata().ok()?;
1777 if tool_meta.len() != rustup_meta.len() {
1782 return None;
1783 }
1784 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1786 let toolchain_exe = home::rustup_home()
1787 .ok()?
1788 .join("toolchains")
1789 .join(&toolchain)
1790 .join("bin")
1791 .join(&tool_exe);
1792 toolchain_exe.exists().then_some(toolchain_exe)
1793 })
1794 .unwrap_or_else(|| PathBuf::from(tool_str))
1795 }
1796
1797 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1799 let key = ConfigKey::from_str("paths");
1800 match self.get_cv(&key)? {
1802 Some(CV::List(val, definition)) => {
1803 let val = val
1804 .into_iter()
1805 .map(|cv| match cv {
1806 CV::String(s, def) => Ok((s, def)),
1807 other => self.expected("string", &key, &other),
1808 })
1809 .collect::<CargoResult<Vec<_>>>()?;
1810 Ok(Some(Value { val, definition }))
1811 }
1812 Some(val) => self.expected("list", &key, &val),
1813 None => Ok(None),
1814 }
1815 }
1816
1817 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1818 self.jobserver.as_ref()
1819 }
1820
1821 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1822 let http = self
1823 .easy
1824 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1825 {
1826 let mut http = http.lock().unwrap();
1827 http.reset();
1828 let timeout = configure_http_handle(self, &mut http)?;
1829 timeout.configure(&mut http)?;
1830 }
1831 Ok(http)
1832 }
1833
1834 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1835 self.http_config.try_borrow_with(|| {
1836 let mut http = self.get::<CargoHttpConfig>("http")?;
1837 let curl_v = curl::Version::get();
1838 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1839 Ok(http)
1840 })
1841 }
1842
1843 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1844 self.future_incompat_config
1845 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1846 }
1847
1848 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1849 self.net_config
1850 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1851 }
1852
1853 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1854 self.build_config
1855 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1856 }
1857
1858 pub fn progress_config(&self) -> &ProgressConfig {
1859 &self.progress_config
1860 }
1861
1862 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1865 let env_config = self.env_config.try_borrow_with(|| {
1866 CargoResult::Ok(Arc::new({
1867 let env_config = self.get::<EnvConfig>("env")?;
1868 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1884 if env_config.contains_key(*disallowed) {
1885 bail!(
1886 "setting the `{disallowed}` environment variable is not supported \
1887 in the `[env]` configuration table"
1888 );
1889 }
1890 }
1891 env_config
1892 .into_iter()
1893 .filter_map(|(k, v)| {
1894 if v.is_force() || self.get_env_os(&k).is_none() {
1895 Some((k, v.resolve(self).to_os_string()))
1896 } else {
1897 None
1898 }
1899 })
1900 .collect()
1901 }))
1902 })?;
1903
1904 Ok(env_config)
1905 }
1906
1907 pub fn validate_term_config(&self) -> CargoResult<()> {
1913 drop(self.get::<TermConfig>("term")?);
1914 Ok(())
1915 }
1916
1917 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1921 self.target_cfgs
1922 .try_borrow_with(|| target::load_target_cfgs(self))
1923 }
1924
1925 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1926 self.doc_extern_map
1930 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1931 }
1932
1933 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1935 target::get_target_applies_to_host(self)
1936 }
1937
1938 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1940 target::load_host_triple(self, target)
1941 }
1942
1943 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1945 target::load_target_triple(self, target)
1946 }
1947
1948 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
1953 let source_id = self.crates_io_source_id.try_borrow_with(|| {
1954 self.check_registry_index_not_set()?;
1955 let url = CRATES_IO_INDEX.into_url().unwrap();
1956 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
1957 })?;
1958 Ok(*source_id)
1959 }
1960
1961 pub fn creation_time(&self) -> Instant {
1962 self.creation_time
1963 }
1964
1965 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
1980 let d = Deserializer {
1981 gctx: self,
1982 key: ConfigKey::from_str(key),
1983 env_prefix_ok: true,
1984 };
1985 T::deserialize(d).map_err(|e| e.into())
1986 }
1987
1988 #[track_caller]
1994 #[tracing::instrument(skip_all)]
1995 pub fn assert_package_cache_locked<'a>(
1996 &self,
1997 mode: CacheLockMode,
1998 f: &'a Filesystem,
1999 ) -> &'a Path {
2000 let ret = f.as_path_unlocked();
2001 assert!(
2002 self.package_cache_lock.is_locked(mode),
2003 "package cache lock is not currently held, Cargo forgot to call \
2004 `acquire_package_cache_lock` before we got to this stack frame",
2005 );
2006 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2007 ret
2008 }
2009
2010 #[tracing::instrument(skip_all)]
2016 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2017 self.package_cache_lock.lock(self, mode)
2018 }
2019
2020 #[tracing::instrument(skip_all)]
2026 pub fn try_acquire_package_cache_lock(
2027 &self,
2028 mode: CacheLockMode,
2029 ) -> CargoResult<Option<CacheLock<'_>>> {
2030 self.package_cache_lock.try_lock(self, mode)
2031 }
2032
2033 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2038 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2039 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2040 })?;
2041 Ok(tracker.lock().unwrap())
2042 }
2043
2044 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2046 let deferred = self
2047 .deferred_global_last_use
2048 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2049 Ok(deferred.lock().unwrap())
2050 }
2051
2052 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2054 if self.unstable_flags.warnings {
2055 Ok(self.build_config()?.warnings.unwrap_or_default())
2056 } else {
2057 Ok(WarningHandling::default())
2058 }
2059 }
2060
2061 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2062 self.ws_roots.lock().unwrap()
2063 }
2064}
2065
2066#[derive(Debug)]
2067enum KeyOrIdx {
2068 Key(String),
2069 Idx(usize),
2070}
2071
2072#[derive(Eq, PartialEq, Clone)]
2074pub enum ConfigValue {
2075 Integer(i64, Definition),
2076 String(String, Definition),
2077 List(Vec<ConfigValue>, Definition),
2078 Table(HashMap<String, ConfigValue>, Definition),
2079 Boolean(bool, Definition),
2080}
2081
2082impl fmt::Debug for ConfigValue {
2083 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2084 match self {
2085 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2086 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2087 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2088 CV::List(list, def) => {
2089 write!(f, "[")?;
2090 for (i, item) in list.iter().enumerate() {
2091 if i > 0 {
2092 write!(f, ", ")?;
2093 }
2094 write!(f, "{item:?}")?;
2095 }
2096 write!(f, "] (from {})", def)
2097 }
2098 CV::Table(table, _) => write!(f, "{:?}", table),
2099 }
2100 }
2101}
2102
2103impl ConfigValue {
2104 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2105 let mut error_path = Vec::new();
2106 Self::from_toml_inner(def, toml, &mut error_path).with_context(|| {
2107 let mut it = error_path.iter().rev().peekable();
2108 let mut key_path = String::with_capacity(error_path.len() * 3);
2109 while let Some(k) = it.next() {
2110 match k {
2111 KeyOrIdx::Key(s) => key_path.push_str(&key::escape_key_part(&s)),
2112 KeyOrIdx::Idx(i) => key_path.push_str(&format!("[{i}]")),
2113 }
2114 if matches!(it.peek(), Some(KeyOrIdx::Key(_))) {
2115 key_path.push('.');
2116 }
2117 }
2118 format!("failed to parse config at `{key_path}`")
2119 })
2120 }
2121
2122 fn from_toml_inner(
2123 def: Definition,
2124 toml: toml::Value,
2125 path: &mut Vec<KeyOrIdx>,
2126 ) -> CargoResult<ConfigValue> {
2127 match toml {
2128 toml::Value::String(val) => Ok(CV::String(val, def)),
2129 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2130 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2131 toml::Value::Array(val) => Ok(CV::List(
2132 val.into_iter()
2133 .enumerate()
2134 .map(|(i, toml)| match toml {
2135 toml::Value::String(val) => Ok(CV::String(val, def.clone())),
2136 v => {
2137 path.push(KeyOrIdx::Idx(i));
2138 bail!("expected string but found {} at index {i}", v.type_str())
2139 }
2140 })
2141 .collect::<CargoResult<_>>()?,
2142 def,
2143 )),
2144 toml::Value::Table(val) => Ok(CV::Table(
2145 val.into_iter()
2146 .map(
2147 |(key, value)| match CV::from_toml_inner(def.clone(), value, path) {
2148 Ok(value) => Ok((key, value)),
2149 Err(e) => {
2150 path.push(KeyOrIdx::Key(key));
2151 Err(e)
2152 }
2153 },
2154 )
2155 .collect::<CargoResult<_>>()?,
2156 def,
2157 )),
2158 v => bail!("unsupported TOML configuration type `{}`", v.type_str()),
2159 }
2160 }
2161
2162 fn into_toml(self) -> toml::Value {
2163 match self {
2164 CV::Boolean(s, _) => toml::Value::Boolean(s),
2165 CV::String(s, _) => toml::Value::String(s),
2166 CV::Integer(i, _) => toml::Value::Integer(i),
2167 CV::List(l, _) => toml::Value::Array(l.into_iter().map(|cv| cv.into_toml()).collect()),
2168 CV::Table(l, _) => {
2169 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2170 }
2171 }
2172 }
2173
2174 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2183 self.merge_helper(from, force, &mut ConfigKey::new())
2184 }
2185
2186 fn merge_helper(
2187 &mut self,
2188 from: ConfigValue,
2189 force: bool,
2190 parts: &mut ConfigKey,
2191 ) -> CargoResult<()> {
2192 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2193 match (self, from) {
2194 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2195 if is_nonmergable_list(&parts) {
2196 if force || is_higher_priority {
2198 mem::swap(new, old);
2199 }
2200 } else {
2201 if force {
2203 old.append(new);
2204 } else {
2205 new.append(old);
2206 mem::swap(new, old);
2207 }
2208 }
2209 old.sort_by(|a, b| a.definition().cmp(b.definition()));
2210 }
2211 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2212 for (key, value) in mem::take(new) {
2213 match old.entry(key.clone()) {
2214 Occupied(mut entry) => {
2215 let new_def = value.definition().clone();
2216 let entry = entry.get_mut();
2217 parts.push(&key);
2218 entry.merge_helper(value, force, parts).with_context(|| {
2219 format!(
2220 "failed to merge key `{}` between \
2221 {} and {}",
2222 key,
2223 entry.definition(),
2224 new_def,
2225 )
2226 })?;
2227 }
2228 Vacant(entry) => {
2229 entry.insert(value);
2230 }
2231 };
2232 }
2233 }
2234 (expected @ &mut CV::List(_, _), found)
2236 | (expected @ &mut CV::Table(_, _), found)
2237 | (expected, found @ CV::List(_, _))
2238 | (expected, found @ CV::Table(_, _)) => {
2239 return Err(anyhow!(
2240 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2241 found.definition(),
2242 expected.definition(),
2243 expected.desc(),
2244 found.desc()
2245 ));
2246 }
2247 (old, mut new) => {
2248 if force || is_higher_priority {
2249 mem::swap(old, &mut new);
2250 }
2251 }
2252 }
2253
2254 Ok(())
2255 }
2256
2257 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2258 match self {
2259 CV::Integer(i, def) => Ok((*i, def)),
2260 _ => self.expected("integer", key),
2261 }
2262 }
2263
2264 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2265 match self {
2266 CV::String(s, def) => Ok((s, def)),
2267 _ => self.expected("string", key),
2268 }
2269 }
2270
2271 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2272 match self {
2273 CV::Table(table, def) => Ok((table, def)),
2274 _ => self.expected("table", key),
2275 }
2276 }
2277
2278 pub fn string_list(&self, key: &str) -> CargoResult<Vec<(String, Definition)>> {
2279 match self {
2280 CV::List(list, _) => list
2281 .iter()
2282 .map(|cv| match cv {
2283 CV::String(s, def) => Ok((s.clone(), def.clone())),
2284 _ => self.expected("string", key),
2285 })
2286 .collect::<CargoResult<_>>(),
2287 _ => self.expected("list", key),
2288 }
2289 }
2290
2291 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2292 match self {
2293 CV::Boolean(b, def) => Ok((*b, def)),
2294 _ => self.expected("bool", key),
2295 }
2296 }
2297
2298 pub fn desc(&self) -> &'static str {
2299 match *self {
2300 CV::Table(..) => "table",
2301 CV::List(..) => "array",
2302 CV::String(..) => "string",
2303 CV::Boolean(..) => "boolean",
2304 CV::Integer(..) => "integer",
2305 }
2306 }
2307
2308 pub fn definition(&self) -> &Definition {
2309 match self {
2310 CV::Boolean(_, def)
2311 | CV::Integer(_, def)
2312 | CV::String(_, def)
2313 | CV::List(_, def)
2314 | CV::Table(_, def) => def,
2315 }
2316 }
2317
2318 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2319 bail!(
2320 "expected a {}, but found a {} for `{}` in {}",
2321 wanted,
2322 self.desc(),
2323 key,
2324 self.definition()
2325 )
2326 }
2327}
2328
2329fn is_nonmergable_list(key: &ConfigKey) -> bool {
2332 key.matches("registry.credential-provider")
2333 || key.matches("registries.*.credential-provider")
2334 || key.matches("target.*.runner")
2335 || key.matches("host.runner")
2336 || key.matches("credential-alias.*")
2337 || key.matches("doc.browser")
2338}
2339
2340pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2341 ::home::cargo_home_with_cwd(cwd).ok()
2342}
2343
2344pub fn save_credentials(
2345 gctx: &GlobalContext,
2346 token: Option<RegistryCredentialConfig>,
2347 registry: &SourceId,
2348) -> CargoResult<()> {
2349 let registry = if registry.is_crates_io() {
2350 None
2351 } else {
2352 let name = registry
2353 .alt_registry_key()
2354 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2355 Some(name)
2356 };
2357
2358 let home_path = gctx.home_path.clone().into_path_unlocked();
2362 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2363 Some(path) => match path.file_name() {
2364 Some(filename) => Path::new(filename).to_owned(),
2365 None => Path::new("credentials.toml").to_owned(),
2366 },
2367 None => Path::new("credentials.toml").to_owned(),
2368 };
2369
2370 let mut file = {
2371 gctx.home_path.create_dir()?;
2372 gctx.home_path
2373 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2374 };
2375
2376 let mut contents = String::new();
2377 file.read_to_string(&mut contents).with_context(|| {
2378 format!(
2379 "failed to read configuration file `{}`",
2380 file.path().display()
2381 )
2382 })?;
2383
2384 let mut toml = parse_document(&contents, file.path(), gctx)?;
2385
2386 if let Some(token) = toml.remove("token") {
2388 let map = HashMap::from([("token".to_string(), token)]);
2389 toml.insert("registry".into(), map.into());
2390 }
2391
2392 if let Some(token) = token {
2393 let path_def = Definition::Path(file.path().to_path_buf());
2396 let (key, mut value) = match token {
2397 RegistryCredentialConfig::Token(token) => {
2398 let key = "token".to_string();
2401 let value = ConfigValue::String(token.expose(), path_def.clone());
2402 let map = HashMap::from([(key, value)]);
2403 let table = CV::Table(map, path_def.clone());
2404
2405 if let Some(registry) = registry {
2406 let map = HashMap::from([(registry.to_string(), table)]);
2407 ("registries".into(), CV::Table(map, path_def.clone()))
2408 } else {
2409 ("registry".into(), table)
2410 }
2411 }
2412 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2413 let key = "secret-key".to_string();
2416 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2417 let mut map = HashMap::from([(key, value)]);
2418 if let Some(key_subject) = key_subject {
2419 let key = "secret-key-subject".to_string();
2420 let value = ConfigValue::String(key_subject, path_def.clone());
2421 map.insert(key, value);
2422 }
2423 let table = CV::Table(map, path_def.clone());
2424
2425 if let Some(registry) = registry {
2426 let map = HashMap::from([(registry.to_string(), table)]);
2427 ("registries".into(), CV::Table(map, path_def.clone()))
2428 } else {
2429 ("registry".into(), table)
2430 }
2431 }
2432 _ => unreachable!(),
2433 };
2434
2435 if registry.is_some() {
2436 if let Some(table) = toml.remove("registries") {
2437 let v = CV::from_toml(path_def, table)?;
2438 value.merge(v, false)?;
2439 }
2440 }
2441 toml.insert(key, value.into_toml());
2442 } else {
2443 if let Some(registry) = registry {
2445 if let Some(registries) = toml.get_mut("registries") {
2446 if let Some(reg) = registries.get_mut(registry) {
2447 let rtable = reg.as_table_mut().ok_or_else(|| {
2448 format_err!("expected `[registries.{}]` to be a table", registry)
2449 })?;
2450 rtable.remove("token");
2451 rtable.remove("secret-key");
2452 rtable.remove("secret-key-subject");
2453 }
2454 }
2455 } else if let Some(registry) = toml.get_mut("registry") {
2456 let reg_table = registry
2457 .as_table_mut()
2458 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2459 reg_table.remove("token");
2460 reg_table.remove("secret-key");
2461 reg_table.remove("secret-key-subject");
2462 }
2463 }
2464
2465 let contents = toml.to_string();
2466 file.seek(SeekFrom::Start(0))?;
2467 file.write_all(contents.as_bytes())
2468 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2469 file.file().set_len(contents.len() as u64)?;
2470 set_permissions(file.file(), 0o600)
2471 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2472
2473 return Ok(());
2474
2475 #[cfg(unix)]
2476 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2477 use std::os::unix::fs::PermissionsExt;
2478
2479 let mut perms = file.metadata()?.permissions();
2480 perms.set_mode(mode);
2481 file.set_permissions(perms)?;
2482 Ok(())
2483 }
2484
2485 #[cfg(not(unix))]
2486 #[allow(unused)]
2487 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2488 Ok(())
2489 }
2490}
2491
2492#[derive(Debug, Default, Deserialize, PartialEq)]
2493#[serde(rename_all = "kebab-case")]
2494pub struct CargoHttpConfig {
2495 pub proxy: Option<String>,
2496 pub low_speed_limit: Option<u32>,
2497 pub timeout: Option<u64>,
2498 pub cainfo: Option<ConfigRelativePath>,
2499 pub proxy_cainfo: Option<ConfigRelativePath>,
2500 pub check_revoke: Option<bool>,
2501 pub user_agent: Option<String>,
2502 pub debug: Option<bool>,
2503 pub multiplexing: Option<bool>,
2504 pub ssl_version: Option<SslVersionConfig>,
2505}
2506
2507#[derive(Debug, Default, Deserialize, PartialEq)]
2508#[serde(rename_all = "kebab-case")]
2509pub struct CargoFutureIncompatConfig {
2510 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2511}
2512
2513#[derive(Debug, Default, Deserialize, PartialEq)]
2514#[serde(rename_all = "kebab-case")]
2515pub enum CargoFutureIncompatFrequencyConfig {
2516 #[default]
2517 Always,
2518 Never,
2519}
2520
2521impl CargoFutureIncompatConfig {
2522 pub fn should_display_message(&self) -> bool {
2523 use CargoFutureIncompatFrequencyConfig::*;
2524
2525 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2526 match frequency {
2527 Always => true,
2528 Never => false,
2529 }
2530 }
2531}
2532
2533#[derive(Clone, Debug, PartialEq)]
2547pub enum SslVersionConfig {
2548 Single(String),
2549 Range(SslVersionConfigRange),
2550}
2551
2552impl<'de> Deserialize<'de> for SslVersionConfig {
2553 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2554 where
2555 D: serde::Deserializer<'de>,
2556 {
2557 UntaggedEnumVisitor::new()
2558 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2559 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2560 .deserialize(deserializer)
2561 }
2562}
2563
2564#[derive(Clone, Debug, Deserialize, PartialEq)]
2565#[serde(rename_all = "kebab-case")]
2566pub struct SslVersionConfigRange {
2567 pub min: Option<String>,
2568 pub max: Option<String>,
2569}
2570
2571#[derive(Debug, Deserialize)]
2572#[serde(rename_all = "kebab-case")]
2573pub struct CargoNetConfig {
2574 pub retry: Option<u32>,
2575 pub offline: Option<bool>,
2576 pub git_fetch_with_cli: Option<bool>,
2577 pub ssh: Option<CargoSshConfig>,
2578}
2579
2580#[derive(Debug, Deserialize)]
2581#[serde(rename_all = "kebab-case")]
2582pub struct CargoSshConfig {
2583 pub known_hosts: Option<Vec<Value<String>>>,
2584}
2585
2586#[derive(Debug, Clone)]
2599pub enum JobsConfig {
2600 Integer(i32),
2601 String(String),
2602}
2603
2604impl<'de> Deserialize<'de> for JobsConfig {
2605 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2606 where
2607 D: serde::Deserializer<'de>,
2608 {
2609 UntaggedEnumVisitor::new()
2610 .i32(|int| Ok(JobsConfig::Integer(int)))
2611 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2612 .deserialize(deserializer)
2613 }
2614}
2615
2616#[derive(Debug, Deserialize)]
2617#[serde(rename_all = "kebab-case")]
2618pub struct CargoBuildConfig {
2619 pub pipelining: Option<bool>,
2621 pub dep_info_basedir: Option<ConfigRelativePath>,
2622 pub target_dir: Option<ConfigRelativePath>,
2623 pub build_dir: Option<ConfigRelativePath>,
2624 pub incremental: Option<bool>,
2625 pub target: Option<BuildTargetConfig>,
2626 pub jobs: Option<JobsConfig>,
2627 pub rustflags: Option<StringList>,
2628 pub rustdocflags: Option<StringList>,
2629 pub rustc_wrapper: Option<ConfigRelativePath>,
2630 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2631 pub rustc: Option<ConfigRelativePath>,
2632 pub rustdoc: Option<ConfigRelativePath>,
2633 pub out_dir: Option<ConfigRelativePath>,
2635 pub artifact_dir: Option<ConfigRelativePath>,
2636 pub warnings: Option<WarningHandling>,
2637 pub sbom: Option<bool>,
2639 pub analysis: Option<CargoBuildAnalysis>,
2641}
2642
2643#[derive(Debug, Deserialize, Default)]
2645#[serde(rename_all = "kebab-case")]
2646pub struct CargoBuildAnalysis {
2647 pub enabled: bool,
2648}
2649
2650#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2652#[serde(rename_all = "kebab-case")]
2653pub enum WarningHandling {
2654 #[default]
2655 Warn,
2657 Allow,
2659 Deny,
2661}
2662
2663#[derive(Debug, Deserialize)]
2673#[serde(transparent)]
2674pub struct BuildTargetConfig {
2675 inner: Value<BuildTargetConfigInner>,
2676}
2677
2678#[derive(Debug)]
2679enum BuildTargetConfigInner {
2680 One(String),
2681 Many(Vec<String>),
2682}
2683
2684impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2685 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2686 where
2687 D: serde::Deserializer<'de>,
2688 {
2689 UntaggedEnumVisitor::new()
2690 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2691 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2692 .deserialize(deserializer)
2693 }
2694}
2695
2696impl BuildTargetConfig {
2697 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2699 let map = |s: &String| {
2700 if s.ends_with(".json") {
2701 self.inner
2704 .definition
2705 .root(gctx)
2706 .join(s)
2707 .to_str()
2708 .expect("must be utf-8 in toml")
2709 .to_string()
2710 } else {
2711 s.to_string()
2713 }
2714 };
2715 let values = match &self.inner.val {
2716 BuildTargetConfigInner::One(s) => vec![map(s)],
2717 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2718 };
2719 Ok(values)
2720 }
2721}
2722
2723#[derive(Debug, Deserialize)]
2724#[serde(rename_all = "kebab-case")]
2725pub struct CargoResolverConfig {
2726 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2727 pub feature_unification: Option<FeatureUnification>,
2728}
2729
2730#[derive(Debug, Deserialize, PartialEq, Eq)]
2731#[serde(rename_all = "kebab-case")]
2732pub enum IncompatibleRustVersions {
2733 Allow,
2734 Fallback,
2735}
2736
2737#[derive(Copy, Clone, Debug, Deserialize)]
2738#[serde(rename_all = "kebab-case")]
2739pub enum FeatureUnification {
2740 Package,
2741 Selected,
2742 Workspace,
2743}
2744
2745#[derive(Deserialize, Default)]
2746#[serde(rename_all = "kebab-case")]
2747pub struct TermConfig {
2748 pub verbose: Option<bool>,
2749 pub quiet: Option<bool>,
2750 pub color: Option<String>,
2751 pub hyperlinks: Option<bool>,
2752 pub unicode: Option<bool>,
2753 #[serde(default)]
2754 #[serde(deserialize_with = "progress_or_string")]
2755 pub progress: Option<ProgressConfig>,
2756}
2757
2758#[derive(Debug, Default, Deserialize)]
2759#[serde(rename_all = "kebab-case")]
2760pub struct ProgressConfig {
2761 #[serde(default)]
2762 pub when: ProgressWhen,
2763 pub width: Option<usize>,
2764 pub term_integration: Option<bool>,
2766}
2767
2768#[derive(Debug, Default, Deserialize)]
2769#[serde(rename_all = "kebab-case")]
2770pub enum ProgressWhen {
2771 #[default]
2772 Auto,
2773 Never,
2774 Always,
2775}
2776
2777fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2778where
2779 D: serde::de::Deserializer<'de>,
2780{
2781 struct ProgressVisitor;
2782
2783 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2784 type Value = Option<ProgressConfig>;
2785
2786 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2787 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2788 }
2789
2790 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2791 where
2792 E: serde::de::Error,
2793 {
2794 match s {
2795 "auto" => Ok(Some(ProgressConfig {
2796 when: ProgressWhen::Auto,
2797 width: None,
2798 term_integration: None,
2799 })),
2800 "never" => Ok(Some(ProgressConfig {
2801 when: ProgressWhen::Never,
2802 width: None,
2803 term_integration: None,
2804 })),
2805 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2806 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2807 }
2808 }
2809
2810 fn visit_none<E>(self) -> Result<Self::Value, E>
2811 where
2812 E: serde::de::Error,
2813 {
2814 Ok(None)
2815 }
2816
2817 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2818 where
2819 D: serde::de::Deserializer<'de>,
2820 {
2821 let pc = ProgressConfig::deserialize(deserializer)?;
2822 if let ProgressConfig {
2823 when: ProgressWhen::Always,
2824 width: None,
2825 ..
2826 } = pc
2827 {
2828 return Err(serde::de::Error::custom(
2829 "\"always\" progress requires a `width` key",
2830 ));
2831 }
2832 Ok(Some(pc))
2833 }
2834 }
2835
2836 deserializer.deserialize_option(ProgressVisitor)
2837}
2838
2839#[derive(Debug)]
2840enum EnvConfigValueInner {
2841 Simple(String),
2842 WithOptions {
2843 value: String,
2844 force: bool,
2845 relative: bool,
2846 },
2847}
2848
2849impl<'de> Deserialize<'de> for EnvConfigValueInner {
2850 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2851 where
2852 D: serde::Deserializer<'de>,
2853 {
2854 #[derive(Deserialize)]
2855 struct WithOptions {
2856 value: String,
2857 #[serde(default)]
2858 force: bool,
2859 #[serde(default)]
2860 relative: bool,
2861 }
2862
2863 UntaggedEnumVisitor::new()
2864 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2865 .map(|map| {
2866 let with_options: WithOptions = map.deserialize()?;
2867 Ok(EnvConfigValueInner::WithOptions {
2868 value: with_options.value,
2869 force: with_options.force,
2870 relative: with_options.relative,
2871 })
2872 })
2873 .deserialize(deserializer)
2874 }
2875}
2876
2877#[derive(Debug, Deserialize)]
2878#[serde(transparent)]
2879pub struct EnvConfigValue {
2880 inner: Value<EnvConfigValueInner>,
2881}
2882
2883impl EnvConfigValue {
2884 pub fn is_force(&self) -> bool {
2885 match self.inner.val {
2886 EnvConfigValueInner::Simple(_) => false,
2887 EnvConfigValueInner::WithOptions { force, .. } => force,
2888 }
2889 }
2890
2891 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
2892 match self.inner.val {
2893 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
2894 EnvConfigValueInner::WithOptions {
2895 ref value,
2896 relative,
2897 ..
2898 } => {
2899 if relative {
2900 let p = self.inner.definition.root(gctx).join(&value);
2901 Cow::Owned(p.into_os_string())
2902 } else {
2903 Cow::Borrowed(OsStr::new(value.as_str()))
2904 }
2905 }
2906 }
2907 }
2908}
2909
2910pub type EnvConfig = HashMap<String, EnvConfigValue>;
2911
2912fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2913 toml.parse().map_err(Into::into)
2915}
2916
2917fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2918 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2924 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2925 })?;
2926 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2927 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2928 }
2929 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2930 non_empty(d.prefix()) || non_empty(d.suffix())
2931 }
2932 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2933 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2934 }
2935 let ok = {
2936 let mut got_to_value = false;
2937 let mut table = doc.as_table();
2938 let mut is_root = true;
2939 while table.is_dotted() || is_root {
2940 is_root = false;
2941 if table.len() != 1 {
2942 break;
2943 }
2944 let (k, n) = table.iter().next().expect("len() == 1 above");
2945 match n {
2946 Item::Table(nt) => {
2947 if table.key(k).map_or(false, non_empty_key_decor)
2948 || non_empty_decor(nt.decor())
2949 {
2950 bail!(
2951 "--config argument `{arg}` \
2952 includes non-whitespace decoration"
2953 )
2954 }
2955 table = nt;
2956 }
2957 Item::Value(v) if v.is_inline_table() => {
2958 bail!(
2959 "--config argument `{arg}` \
2960 sets a value to an inline table, which is not accepted"
2961 );
2962 }
2963 Item::Value(v) => {
2964 if table
2965 .key(k)
2966 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2967 || non_empty_decor(v.decor())
2968 {
2969 bail!(
2970 "--config argument `{arg}` \
2971 includes non-whitespace decoration"
2972 )
2973 }
2974 got_to_value = true;
2975 break;
2976 }
2977 Item::ArrayOfTables(_) => {
2978 bail!(
2979 "--config argument `{arg}` \
2980 sets a value to an array of tables, which is not accepted"
2981 );
2982 }
2983
2984 Item::None => {
2985 bail!("--config argument `{arg}` doesn't provide a value")
2986 }
2987 }
2988 }
2989 got_to_value
2990 };
2991 if !ok {
2992 bail!(
2993 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2994 );
2995 }
2996 Ok(doc)
2997}
2998
2999#[derive(Debug, Deserialize, Clone)]
3010pub struct StringList(Vec<String>);
3011
3012impl StringList {
3013 pub fn as_slice(&self) -> &[String] {
3014 &self.0
3015 }
3016}
3017
3018#[macro_export]
3019macro_rules! __shell_print {
3020 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
3021 let mut shell = $config.shell();
3022 let out = shell.$which();
3023 drop(out.write_fmt(format_args!($($arg)*)));
3024 if $newline {
3025 drop(out.write_all(b"\n"));
3026 }
3027 });
3028}
3029
3030#[macro_export]
3031macro_rules! drop_println {
3032 ($config:expr) => ( $crate::drop_print!($config, "\n") );
3033 ($config:expr, $($arg:tt)*) => (
3034 $crate::__shell_print!($config, out, true, $($arg)*)
3035 );
3036}
3037
3038#[macro_export]
3039macro_rules! drop_eprintln {
3040 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
3041 ($config:expr, $($arg:tt)*) => (
3042 $crate::__shell_print!($config, err, true, $($arg)*)
3043 );
3044}
3045
3046#[macro_export]
3047macro_rules! drop_print {
3048 ($config:expr, $($arg:tt)*) => (
3049 $crate::__shell_print!($config, out, false, $($arg)*)
3050 );
3051}
3052
3053#[macro_export]
3054macro_rules! drop_eprint {
3055 ($config:expr, $($arg:tt)*) => (
3056 $crate::__shell_print!($config, err, false, $($arg)*)
3057 );
3058}
3059
3060enum Tool {
3061 Rustc,
3062 Rustdoc,
3063}
3064
3065impl Tool {
3066 fn as_str(&self) -> &str {
3067 match self {
3068 Tool::Rustc => "rustc",
3069 Tool::Rustdoc => "rustdoc",
3070 }
3071 }
3072}
3073
3074fn disables_multiplexing_for_bad_curl(
3084 curl_version: &str,
3085 http: &mut CargoHttpConfig,
3086 gctx: &GlobalContext,
3087) {
3088 use crate::util::network;
3089
3090 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3091 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3092 if bad_curl_versions
3093 .iter()
3094 .any(|v| curl_version.starts_with(v))
3095 {
3096 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3097 http.multiplexing = Some(false);
3098 }
3099 }
3100}
3101
3102#[cfg(test)]
3103mod tests {
3104 use super::CargoHttpConfig;
3105 use super::GlobalContext;
3106 use super::Shell;
3107 use super::disables_multiplexing_for_bad_curl;
3108
3109 #[test]
3110 fn disables_multiplexing() {
3111 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3112 gctx.set_search_stop_path(std::path::PathBuf::new());
3113 gctx.set_env(Default::default());
3114
3115 let mut http = CargoHttpConfig::default();
3116 http.proxy = Some("127.0.0.1:3128".into());
3117 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3118 assert_eq!(http.multiplexing, Some(false));
3119
3120 let cases = [
3121 (None, None, "7.87.0", None),
3122 (None, None, "7.88.0", None),
3123 (None, None, "7.88.1", None),
3124 (None, None, "8.0.0", None),
3125 (Some("".into()), None, "7.87.0", Some(false)),
3126 (Some("".into()), None, "7.88.0", Some(false)),
3127 (Some("".into()), None, "7.88.1", Some(false)),
3128 (Some("".into()), None, "8.0.0", None),
3129 (Some("".into()), Some(false), "7.87.0", Some(false)),
3130 (Some("".into()), Some(false), "7.88.0", Some(false)),
3131 (Some("".into()), Some(false), "7.88.1", Some(false)),
3132 (Some("".into()), Some(false), "8.0.0", Some(false)),
3133 ];
3134
3135 for (proxy, multiplexing, curl_v, result) in cases {
3136 let mut http = CargoHttpConfig {
3137 multiplexing,
3138 proxy,
3139 ..Default::default()
3140 };
3141 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3142 assert_eq!(http.multiplexing, result);
3143 }
3144 }
3145
3146 #[test]
3147 fn sync_context() {
3148 fn assert_sync<S: Sync>() {}
3149 assert_sync::<GlobalContext>();
3150 }
3151}