1use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
53use std::borrow::Cow;
54use std::collections::hash_map::Entry::{Occupied, Vacant};
55use std::collections::{HashMap, HashSet};
56use std::env;
57use std::ffi::{OsStr, OsString};
58use std::fmt;
59use std::fs::{self, File};
60use std::io::SeekFrom;
61use std::io::prelude::*;
62use std::mem;
63use std::path::{Path, PathBuf};
64use std::str::FromStr;
65use std::sync::{Arc, Mutex, MutexGuard, Once, OnceLock};
66use std::time::Instant;
67
68use self::ConfigValue as CV;
69use crate::core::compiler::rustdoc::RustdocExternMap;
70use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
71use crate::core::shell::Verbosity;
72use crate::core::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig, features};
73use crate::ops::RegistryCredentialConfig;
74use crate::sources::CRATES_IO_INDEX;
75use crate::sources::CRATES_IO_REGISTRY;
76use crate::util::OnceExt as _;
77use crate::util::errors::CargoResult;
78use crate::util::network::http::configure_http_handle;
79use crate::util::network::http::http_handle;
80use crate::util::{CanonicalUrl, closest_msg, internal};
81use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
82use annotate_snippets::Level;
83use anyhow::{Context as _, anyhow, bail, format_err};
84use cargo_credential::Secret;
85use cargo_util::paths;
86use cargo_util_schemas::manifest::RegistryName;
87use curl::easy::Easy;
88use itertools::Itertools;
89use serde::Deserialize;
90use serde::de::IntoDeserializer as _;
91use serde_untagged::UntaggedEnumVisitor;
92use time::OffsetDateTime;
93use toml_edit::Item;
94use url::Url;
95
96mod de;
97use de::Deserializer;
98
99mod value;
100pub use value::{Definition, OptValue, Value};
101
102mod key;
103pub use key::ConfigKey;
104
105mod path;
106pub use path::{ConfigRelativePath, PathAndArgs};
107
108mod target;
109pub use target::{TargetCfgConfig, TargetConfig};
110
111mod environment;
112use environment::Env;
113
114use super::auth::RegistryConfig;
115
116macro_rules! get_value_typed {
118 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
119 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
121 let cv = self.get_cv(key)?;
122 let env = self.get_config_env::<$ty>(key)?;
123 match (cv, env) {
124 (Some(CV::$variant(val, definition)), Some(env)) => {
125 if definition.is_higher_priority(&env.definition) {
126 Ok(Some(Value { val, definition }))
127 } else {
128 Ok(Some(env))
129 }
130 }
131 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
132 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
133 (None, Some(env)) => Ok(Some(env)),
134 (None, None) => Ok(None),
135 }
136 }
137 };
138}
139
140#[derive(Clone, Copy, Debug)]
142enum WhyLoad {
143 Cli,
150 FileDiscovery,
152}
153
154#[derive(Debug)]
156pub struct CredentialCacheValue {
157 pub token_value: Secret<String>,
158 pub expiration: Option<OffsetDateTime>,
159 pub operation_independent: bool,
160}
161
162#[derive(Debug)]
165pub struct GlobalContext {
166 home_path: Filesystem,
168 shell: Mutex<Shell>,
170 values: OnceLock<HashMap<String, ConfigValue>>,
172 credential_values: OnceLock<HashMap<String, ConfigValue>>,
174 cli_config: Option<Vec<String>>,
176 cwd: PathBuf,
178 search_stop_path: Option<PathBuf>,
180 cargo_exe: OnceLock<PathBuf>,
182 rustdoc: OnceLock<PathBuf>,
184 extra_verbose: bool,
186 frozen: bool,
189 locked: bool,
192 offline: bool,
195 jobserver: Option<jobserver::Client>,
197 unstable_flags: CliUnstable,
199 unstable_flags_cli: Option<Vec<String>>,
201 easy: OnceLock<Mutex<Easy>>,
203 crates_io_source_id: OnceLock<SourceId>,
205 cache_rustc_info: bool,
207 creation_time: Instant,
209 target_dir: Option<Filesystem>,
211 env: Env,
213 updated_sources: Mutex<HashSet<SourceId>>,
215 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
218 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
220 package_cache_lock: CacheLocker,
222 http_config: OnceLock<CargoHttpConfig>,
224 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
225 net_config: OnceLock<CargoNetConfig>,
226 build_config: OnceLock<CargoBuildConfig>,
227 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
228 doc_extern_map: OnceLock<RustdocExternMap>,
229 progress_config: ProgressConfig,
230 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
231 pub nightly_features_allowed: bool,
247 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
249 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
251 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
254}
255
256impl GlobalContext {
257 pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
265 static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
266 static INIT: Once = Once::new();
267
268 INIT.call_once(|| unsafe {
271 if let Some(client) = jobserver::Client::from_env() {
272 GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
273 }
274 });
275
276 let env = Env::new();
277
278 let cache_key = "CARGO_CACHE_RUSTC_INFO";
279 let cache_rustc_info = match env.get_env_os(cache_key) {
280 Some(cache) => cache != "0",
281 _ => true,
282 };
283
284 GlobalContext {
285 home_path: Filesystem::new(homedir),
286 shell: Mutex::new(shell),
287 cwd,
288 search_stop_path: None,
289 values: Default::default(),
290 credential_values: Default::default(),
291 cli_config: None,
292 cargo_exe: Default::default(),
293 rustdoc: Default::default(),
294 extra_verbose: false,
295 frozen: false,
296 locked: false,
297 offline: false,
298 jobserver: unsafe {
299 if GLOBAL_JOBSERVER.is_null() {
300 None
301 } else {
302 Some((*GLOBAL_JOBSERVER).clone())
303 }
304 },
305 unstable_flags: CliUnstable::default(),
306 unstable_flags_cli: None,
307 easy: Default::default(),
308 crates_io_source_id: Default::default(),
309 cache_rustc_info,
310 creation_time: Instant::now(),
311 target_dir: None,
312 env,
313 updated_sources: Default::default(),
314 credential_cache: Default::default(),
315 registry_config: Default::default(),
316 package_cache_lock: CacheLocker::new(),
317 http_config: Default::default(),
318 future_incompat_config: Default::default(),
319 net_config: Default::default(),
320 build_config: Default::default(),
321 target_cfgs: Default::default(),
322 doc_extern_map: Default::default(),
323 progress_config: ProgressConfig::default(),
324 env_config: Default::default(),
325 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
326 ws_roots: Default::default(),
327 global_cache_tracker: Default::default(),
328 deferred_global_last_use: Default::default(),
329 }
330 }
331
332 pub fn default() -> CargoResult<GlobalContext> {
337 let shell = Shell::new();
338 let cwd =
339 env::current_dir().context("couldn't get the current directory of the process")?;
340 let homedir = homedir(&cwd).ok_or_else(|| {
341 anyhow!(
342 "Cargo couldn't find your home directory. \
343 This probably means that $HOME was not set."
344 )
345 })?;
346 Ok(GlobalContext::new(shell, cwd, homedir))
347 }
348
349 pub fn home(&self) -> &Filesystem {
351 &self.home_path
352 }
353
354 pub fn diagnostic_home_config(&self) -> String {
358 let home = self.home_path.as_path_unlocked();
359 let path = match self.get_file_path(home, "config", false) {
360 Ok(Some(existing_path)) => existing_path,
361 _ => home.join("config.toml"),
362 };
363 path.to_string_lossy().to_string()
364 }
365
366 pub fn git_path(&self) -> Filesystem {
368 self.home_path.join("git")
369 }
370
371 pub fn git_checkouts_path(&self) -> Filesystem {
374 self.git_path().join("checkouts")
375 }
376
377 pub fn git_db_path(&self) -> Filesystem {
380 self.git_path().join("db")
381 }
382
383 pub fn registry_base_path(&self) -> Filesystem {
385 self.home_path.join("registry")
386 }
387
388 pub fn registry_index_path(&self) -> Filesystem {
390 self.registry_base_path().join("index")
391 }
392
393 pub fn registry_cache_path(&self) -> Filesystem {
395 self.registry_base_path().join("cache")
396 }
397
398 pub fn registry_source_path(&self) -> Filesystem {
400 self.registry_base_path().join("src")
401 }
402
403 pub fn default_registry(&self) -> CargoResult<Option<String>> {
405 Ok(self
406 .get_string("registry.default")?
407 .map(|registry| registry.val))
408 }
409
410 pub fn shell(&self) -> MutexGuard<'_, Shell> {
412 self.shell.lock().unwrap()
413 }
414
415 pub fn debug_assert_shell_not_borrowed(&self) {
421 if cfg!(debug_assertions) {
422 match self.shell.try_lock() {
423 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
424 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
425 }
426 }
427 }
428
429 pub fn rustdoc(&self) -> CargoResult<&Path> {
431 self.rustdoc
432 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
433 .map(AsRef::as_ref)
434 }
435
436 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
438 let cache_location =
439 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
440 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
441 let rustc_workspace_wrapper = self.maybe_get_tool(
442 "rustc_workspace_wrapper",
443 &self.build_config()?.rustc_workspace_wrapper,
444 );
445
446 Rustc::new(
447 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
448 wrapper,
449 rustc_workspace_wrapper,
450 &self
451 .home()
452 .join("bin")
453 .join("rustc")
454 .into_path_unlocked()
455 .with_extension(env::consts::EXE_EXTENSION),
456 if self.cache_rustc_info {
457 cache_location
458 } else {
459 None
460 },
461 self,
462 )
463 }
464
465 pub fn cargo_exe(&self) -> CargoResult<&Path> {
467 self.cargo_exe
468 .try_borrow_with(|| {
469 let from_env = || -> CargoResult<PathBuf> {
470 let exe = self
475 .get_env_os(crate::CARGO_ENV)
476 .map(PathBuf::from)
477 .ok_or_else(|| anyhow!("$CARGO not set"))?;
478 Ok(exe)
479 };
480
481 fn from_current_exe() -> CargoResult<PathBuf> {
482 let exe = env::current_exe()?;
487 Ok(exe)
488 }
489
490 fn from_argv() -> CargoResult<PathBuf> {
491 let argv0 = env::args_os()
498 .map(PathBuf::from)
499 .next()
500 .ok_or_else(|| anyhow!("no argv[0]"))?;
501 paths::resolve_executable(&argv0)
502 }
503
504 fn is_cargo(path: &Path) -> bool {
507 path.file_stem() == Some(OsStr::new("cargo"))
508 }
509
510 let from_current_exe = from_current_exe();
511 if from_current_exe.as_deref().is_ok_and(is_cargo) {
512 return from_current_exe;
513 }
514
515 let from_argv = from_argv();
516 if from_argv.as_deref().is_ok_and(is_cargo) {
517 return from_argv;
518 }
519
520 let exe = from_env()
521 .or(from_current_exe)
522 .or(from_argv)
523 .context("couldn't get the path to cargo executable")?;
524 Ok(exe)
525 })
526 .map(AsRef::as_ref)
527 }
528
529 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
531 self.updated_sources.lock().unwrap()
532 }
533
534 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
536 self.credential_cache.lock().unwrap()
537 }
538
539 pub(crate) fn registry_config(
541 &self,
542 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
543 self.registry_config.lock().unwrap()
544 }
545
546 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
552 self.values.try_borrow_with(|| self.load_values())
553 }
554
555 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
562 let _ = self.values()?;
563 Ok(self.values.get_mut().expect("already loaded config values"))
564 }
565
566 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
568 if self.values.get().is_some() {
569 bail!("config values already found")
570 }
571 match self.values.set(values.into()) {
572 Ok(()) => Ok(()),
573 Err(_) => bail!("could not fill values"),
574 }
575 }
576
577 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
580 let path = path.into();
581 debug_assert!(self.cwd.starts_with(&path));
582 self.search_stop_path = Some(path);
583 }
584
585 pub fn reload_cwd(&mut self) -> CargoResult<()> {
589 let cwd =
590 env::current_dir().context("couldn't get the current directory of the process")?;
591 let homedir = homedir(&cwd).ok_or_else(|| {
592 anyhow!(
593 "Cargo couldn't find your home directory. \
594 This probably means that $HOME was not set."
595 )
596 })?;
597
598 self.cwd = cwd;
599 self.home_path = Filesystem::new(homedir);
600 self.reload_rooted_at(self.cwd.clone())?;
601 Ok(())
602 }
603
604 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
607 let values = self.load_values_from(path.as_ref())?;
608 self.values.replace(values);
609 self.merge_cli_args()?;
610 self.load_unstable_flags_from_config()?;
611 Ok(())
612 }
613
614 pub fn cwd(&self) -> &Path {
616 &self.cwd
617 }
618
619 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
625 if let Some(dir) = &self.target_dir {
626 Ok(Some(dir.clone()))
627 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
628 if dir.is_empty() {
630 bail!(
631 "the target directory is set to an empty string in the \
632 `CARGO_TARGET_DIR` environment variable"
633 )
634 }
635
636 Ok(Some(Filesystem::new(self.cwd.join(dir))))
637 } else if let Some(val) = &self.build_config()?.target_dir {
638 let path = val.resolve_path(self);
639
640 if val.raw_value().is_empty() {
642 bail!(
643 "the target directory is set to an empty string in {}",
644 val.value().definition
645 )
646 }
647
648 Ok(Some(Filesystem::new(path)))
649 } else {
650 Ok(None)
651 }
652 }
653
654 pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
658 let Some(val) = &self.build_config()?.build_dir else {
659 return Ok(None);
660 };
661 self.custom_build_dir(val, workspace_manifest_path)
662 .map(Some)
663 }
664
665 pub fn custom_build_dir(
669 &self,
670 val: &ConfigRelativePath,
671 workspace_manifest_path: &Path,
672 ) -> CargoResult<Filesystem> {
673 let replacements = [
674 (
675 "{workspace-root}",
676 workspace_manifest_path
677 .parent()
678 .unwrap()
679 .to_str()
680 .context("workspace root was not valid utf-8")?
681 .to_string(),
682 ),
683 (
684 "{cargo-cache-home}",
685 self.home()
686 .as_path_unlocked()
687 .to_str()
688 .context("cargo home was not valid utf-8")?
689 .to_string(),
690 ),
691 ("{workspace-path-hash}", {
692 let real_path = std::fs::canonicalize(workspace_manifest_path)?;
693 let hash = crate::util::hex::short_hash(&real_path);
694 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
695 }),
696 ];
697
698 let template_variables = replacements
699 .iter()
700 .map(|(key, _)| key[1..key.len() - 1].to_string())
701 .collect_vec();
702
703 let path = val
704 .resolve_templated_path(self, replacements)
705 .map_err(|e| match e {
706 path::ResolveTemplateError::UnexpectedVariable {
707 variable,
708 raw_template,
709 } => {
710 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
711 if suggestion == "" {
712 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
713 suggestion = format!("\n\nhelp: available template variables are {variables}");
714 }
715 anyhow!(
716 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
717 )
718 },
719 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
720 let (btype, literal) = match bracket_type {
721 path::BracketType::Opening => ("opening", "{"),
722 path::BracketType::Closing => ("closing", "}"),
723 };
724
725 anyhow!(
726 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
727 )
728 }
729 })?;
730
731 if val.raw_value().is_empty() {
733 bail!(
734 "the build directory is set to an empty string in {}",
735 val.value().definition
736 )
737 }
738
739 Ok(Filesystem::new(path))
740 }
741
742 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
747 if let Some(vals) = self.credential_values.get() {
748 let val = self.get_cv_helper(key, vals)?;
749 if val.is_some() {
750 return Ok(val);
751 }
752 }
753 self.get_cv_helper(key, &*self.values()?)
754 }
755
756 fn get_cv_helper(
757 &self,
758 key: &ConfigKey,
759 vals: &HashMap<String, ConfigValue>,
760 ) -> CargoResult<Option<ConfigValue>> {
761 tracing::trace!("get cv {:?}", key);
762 if key.is_root() {
763 return Ok(Some(CV::Table(
766 vals.clone(),
767 Definition::Path(PathBuf::new()),
768 )));
769 }
770 let mut parts = key.parts().enumerate();
771 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
772 return Ok(None);
773 };
774 for (i, part) in parts {
775 match val {
776 CV::Table(map, _) => {
777 val = match map.get(part) {
778 Some(val) => val,
779 None => return Ok(None),
780 }
781 }
782 CV::Integer(_, def)
783 | CV::String(_, def)
784 | CV::List(_, def)
785 | CV::Boolean(_, def) => {
786 let mut key_so_far = ConfigKey::new();
787 for part in key.parts().take(i) {
788 key_so_far.push(part);
789 }
790 bail!(
791 "expected table for configuration key `{}`, \
792 but found {} in {}",
793 key_so_far,
794 val.desc(),
795 def
796 )
797 }
798 }
799 }
800 Ok(Some(val.clone()))
801 }
802
803 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
805 let cv = self.get_cv(key)?;
808 if key.is_root() {
809 return Ok(cv);
811 }
812 let env = self.env.get_str(key.as_env_key());
813 let env_def = Definition::Environment(key.as_env_key().to_string());
814 let use_env = match (&cv, env) {
815 (Some(CV::List(..)), Some(_)) => true,
817 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
818 (None, Some(_)) => true,
819 _ => false,
820 };
821
822 if !use_env {
823 return Ok(cv);
824 }
825
826 let env = env.unwrap();
830 if env == "true" {
831 Ok(Some(CV::Boolean(true, env_def)))
832 } else if env == "false" {
833 Ok(Some(CV::Boolean(false, env_def)))
834 } else if let Ok(i) = env.parse::<i64>() {
835 Ok(Some(CV::Integer(i, env_def)))
836 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
837 match cv {
838 Some(CV::List(mut cv_list, cv_def)) => {
839 self.get_env_list(key, &mut cv_list)?;
841 Ok(Some(CV::List(cv_list, cv_def)))
842 }
843 Some(cv) => {
844 bail!(
848 "unable to merge array env for config `{}`\n\
849 file: {:?}\n\
850 env: {}",
851 key,
852 cv,
853 env
854 );
855 }
856 None => {
857 let mut cv_list = Vec::new();
858 self.get_env_list(key, &mut cv_list)?;
859 Ok(Some(CV::List(cv_list, env_def)))
860 }
861 }
862 } else {
863 match cv {
865 Some(CV::List(mut cv_list, cv_def)) => {
866 self.get_env_list(key, &mut cv_list)?;
868 Ok(Some(CV::List(cv_list, cv_def)))
869 }
870 _ => {
871 Ok(Some(CV::String(env.to_string(), env_def)))
876 }
877 }
878 }
879 }
880
881 pub fn set_env(&mut self, env: HashMap<String, String>) {
883 self.env = Env::from_map(env);
884 }
885
886 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
889 self.env.iter_str()
890 }
891
892 fn env_keys(&self) -> impl Iterator<Item = &str> {
894 self.env.keys_str()
895 }
896
897 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
898 where
899 T: FromStr,
900 <T as FromStr>::Err: fmt::Display,
901 {
902 match self.env.get_str(key.as_env_key()) {
903 Some(value) => {
904 let definition = Definition::Environment(key.as_env_key().to_string());
905 Ok(Some(Value {
906 val: value
907 .parse()
908 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
909 definition,
910 }))
911 }
912 None => {
913 self.check_environment_key_case_mismatch(key);
914 Ok(None)
915 }
916 }
917 }
918
919 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
924 self.env.get_env(key)
925 }
926
927 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
932 self.env.get_env_os(key)
933 }
934
935 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
939 if self.env.contains_key(key.as_env_key()) {
940 return Ok(true);
941 }
942 if env_prefix_ok {
943 let env_prefix = format!("{}_", key.as_env_key());
944 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
945 return Ok(true);
946 }
947 }
948 if self.get_cv(key)?.is_some() {
949 return Ok(true);
950 }
951 self.check_environment_key_case_mismatch(key);
952
953 Ok(false)
954 }
955
956 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
957 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
958 let _ = self.shell().warn(format!(
959 "environment variables are expected to use uppercase letters and underscores, \
960 the variable `{}` will be ignored and have no effect",
961 env_key
962 ));
963 }
964 }
965
966 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
970 self.get::<OptValue<String>>(key)
971 }
972
973 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
979 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
980 v.map(|v| Value {
981 val: v.val.resolve_program(self),
982 definition: v.definition,
983 })
984 })
985 }
986
987 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
988 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
989 if is_path {
990 definition.root(self).join(value)
991 } else {
992 PathBuf::from(value)
994 }
995 }
996
997 fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1000 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1001 self.check_environment_key_case_mismatch(key);
1002 return Ok(());
1003 };
1004
1005 if is_nonmergable_list(&key) {
1006 output.clear();
1007 }
1008
1009 let def = Definition::Environment(key.as_env_key().to_string());
1010 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1011 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1013 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
1014 })?;
1015 let values = toml_v.as_array().expect("env var was not array");
1016 for value in values {
1017 let s = value.as_str().ok_or_else(|| {
1019 ConfigError::new(
1020 format!("expected string, found {}", value.type_str()),
1021 def.clone(),
1022 )
1023 })?;
1024 output.push(CV::String(s.to_string(), def.clone()))
1025 }
1026 } else {
1027 output.extend(
1028 env_val
1029 .split_whitespace()
1030 .map(|s| CV::String(s.to_string(), def.clone())),
1031 );
1032 }
1033 output.sort_by(|a, b| a.definition().cmp(b.definition()));
1034 Ok(())
1035 }
1036
1037 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1041 match self.get_cv(key)? {
1042 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1043 Some(val) => self.expected("table", key, &val),
1044 None => Ok(None),
1045 }
1046 }
1047
1048 get_value_typed! {get_integer, i64, Integer, "an integer"}
1049 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1050 get_value_typed! {get_string_priv, String, String, "a string"}
1051
1052 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1054 val.expected(ty, &key.to_string())
1055 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1056 }
1057
1058 pub fn configure(
1064 &mut self,
1065 verbose: u32,
1066 quiet: bool,
1067 color: Option<&str>,
1068 frozen: bool,
1069 locked: bool,
1070 offline: bool,
1071 target_dir: &Option<PathBuf>,
1072 unstable_flags: &[String],
1073 cli_config: &[String],
1074 ) -> CargoResult<()> {
1075 for warning in self
1076 .unstable_flags
1077 .parse(unstable_flags, self.nightly_features_allowed)?
1078 {
1079 self.shell().warn(warning)?;
1080 }
1081 if !unstable_flags.is_empty() {
1082 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1085 }
1086 if !cli_config.is_empty() {
1087 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1088 self.merge_cli_args()?;
1089 }
1090
1091 self.load_unstable_flags_from_config()?;
1095 if self.unstable_flags.config_include {
1096 self.reload_rooted_at(self.cwd.clone())?;
1103 }
1104
1105 let term = self.get::<TermConfig>("term").unwrap_or_default();
1109
1110 let extra_verbose = verbose >= 2;
1112 let verbose = verbose != 0;
1113 let verbosity = match (verbose, quiet) {
1114 (true, true) => bail!("cannot set both --verbose and --quiet"),
1115 (true, false) => Verbosity::Verbose,
1116 (false, true) => Verbosity::Quiet,
1117 (false, false) => match (term.verbose, term.quiet) {
1118 (Some(true), Some(true)) => {
1119 bail!("cannot set both `term.verbose` and `term.quiet`")
1120 }
1121 (Some(true), _) => Verbosity::Verbose,
1122 (_, Some(true)) => Verbosity::Quiet,
1123 _ => Verbosity::Normal,
1124 },
1125 };
1126 self.shell().set_verbosity(verbosity);
1127 self.extra_verbose = extra_verbose;
1128
1129 let color = color.or_else(|| term.color.as_deref());
1130 self.shell().set_color_choice(color)?;
1131 if let Some(hyperlinks) = term.hyperlinks {
1132 self.shell().set_hyperlinks(hyperlinks)?;
1133 }
1134 if let Some(unicode) = term.unicode {
1135 self.shell().set_unicode(unicode)?;
1136 }
1137
1138 self.progress_config = term.progress.unwrap_or_default();
1139
1140 self.frozen = frozen;
1141 self.locked = locked;
1142 self.offline = offline
1143 || self
1144 .net_config()
1145 .ok()
1146 .and_then(|n| n.offline)
1147 .unwrap_or(false);
1148 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1149 self.target_dir = cli_target_dir;
1150
1151 Ok(())
1152 }
1153
1154 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1155 if self.nightly_features_allowed {
1158 self.unstable_flags = self
1159 .get::<Option<CliUnstable>>("unstable")?
1160 .unwrap_or_default();
1161 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1162 self.unstable_flags.parse(unstable_flags_cli, true)?;
1167 }
1168 }
1169
1170 Ok(())
1171 }
1172
1173 pub fn cli_unstable(&self) -> &CliUnstable {
1174 &self.unstable_flags
1175 }
1176
1177 pub fn extra_verbose(&self) -> bool {
1178 self.extra_verbose
1179 }
1180
1181 pub fn network_allowed(&self) -> bool {
1182 !self.offline_flag().is_some()
1183 }
1184
1185 pub fn offline_flag(&self) -> Option<&'static str> {
1186 if self.frozen {
1187 Some("--frozen")
1188 } else if self.offline {
1189 Some("--offline")
1190 } else {
1191 None
1192 }
1193 }
1194
1195 pub fn set_locked(&mut self, locked: bool) {
1196 self.locked = locked;
1197 }
1198
1199 pub fn lock_update_allowed(&self) -> bool {
1200 !self.locked_flag().is_some()
1201 }
1202
1203 pub fn locked_flag(&self) -> Option<&'static str> {
1204 if self.frozen {
1205 Some("--frozen")
1206 } else if self.locked {
1207 Some("--locked")
1208 } else {
1209 None
1210 }
1211 }
1212
1213 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1215 self.load_values_from(&self.cwd)
1216 }
1217
1218 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1222 let mut result = Vec::new();
1223 let mut seen = HashSet::new();
1224 let home = self.home_path.clone().into_path_unlocked();
1225 self.walk_tree(&self.cwd, &home, |path| {
1226 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1227 if self.cli_unstable().config_include {
1228 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1229 }
1230 result.push(cv);
1231 Ok(())
1232 })
1233 .context("could not load Cargo configuration")?;
1234 Ok(result)
1235 }
1236
1237 fn load_unmerged_include(
1241 &self,
1242 cv: &mut CV,
1243 seen: &mut HashSet<PathBuf>,
1244 output: &mut Vec<CV>,
1245 ) -> CargoResult<()> {
1246 let includes = self.include_paths(cv, false)?;
1247 for (path, abs_path, def) in includes {
1248 let mut cv = self
1249 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1250 .with_context(|| {
1251 format!("failed to load config include `{}` from `{}`", path, def)
1252 })?;
1253 self.load_unmerged_include(&mut cv, seen, output)?;
1254 output.push(cv);
1255 }
1256 Ok(())
1257 }
1258
1259 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1261 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1264 let home = self.home_path.clone().into_path_unlocked();
1265
1266 self.walk_tree(path, &home, |path| {
1267 let value = self.load_file(path)?;
1268 cfg.merge(value, false).with_context(|| {
1269 format!("failed to merge configuration at `{}`", path.display())
1270 })?;
1271 Ok(())
1272 })
1273 .context("could not load Cargo configuration")?;
1274
1275 match cfg {
1276 CV::Table(map, _) => Ok(map),
1277 _ => unreachable!(),
1278 }
1279 }
1280
1281 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1285 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1286 }
1287
1288 fn _load_file(
1298 &self,
1299 path: &Path,
1300 seen: &mut HashSet<PathBuf>,
1301 includes: bool,
1302 why_load: WhyLoad,
1303 ) -> CargoResult<ConfigValue> {
1304 if !seen.insert(path.to_path_buf()) {
1305 bail!(
1306 "config `include` cycle detected with path `{}`",
1307 path.display()
1308 );
1309 }
1310 tracing::debug!(?path, ?why_load, includes, "load config from file");
1311
1312 let contents = fs::read_to_string(path)
1313 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1314 let toml = parse_document(&contents, path, self).with_context(|| {
1315 format!("could not parse TOML configuration in `{}`", path.display())
1316 })?;
1317 let def = match why_load {
1318 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1319 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1320 };
1321 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1322 format!(
1323 "failed to load TOML configuration from `{}`",
1324 path.display()
1325 )
1326 })?;
1327 if includes {
1328 self.load_includes(value, seen, why_load)
1329 } else {
1330 Ok(value)
1331 }
1332 }
1333
1334 fn load_includes(
1341 &self,
1342 mut value: CV,
1343 seen: &mut HashSet<PathBuf>,
1344 why_load: WhyLoad,
1345 ) -> CargoResult<CV> {
1346 let includes = self.include_paths(&mut value, true)?;
1348 if !self.cli_unstable().config_include {
1350 return Ok(value);
1351 }
1352 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1354 for (path, abs_path, def) in includes {
1355 self._load_file(&abs_path, seen, true, why_load)
1356 .and_then(|include| root.merge(include, true))
1357 .with_context(|| {
1358 format!("failed to load config include `{}` from `{}`", path, def)
1359 })?;
1360 }
1361 root.merge(value, true)?;
1362 Ok(root)
1363 }
1364
1365 fn include_paths(
1367 &self,
1368 cv: &mut CV,
1369 remove: bool,
1370 ) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
1371 let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
1372 let abs_path = match def {
1373 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1374 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => {
1375 self.cwd().join(&path)
1376 }
1377 };
1378 (path.to_string(), abs_path, def.clone())
1379 };
1380 let CV::Table(table, _def) = cv else {
1381 unreachable!()
1382 };
1383 let owned;
1384 let include = if remove {
1385 owned = table.remove("include");
1386 owned.as_ref()
1387 } else {
1388 table.get("include")
1389 };
1390 let includes = match include {
1391 Some(CV::String(s, def)) => {
1392 vec![abs(s, def)]
1393 }
1394 Some(CV::List(list, _def)) => list
1395 .iter()
1396 .map(|cv| match cv {
1397 CV::String(s, def) => Ok(abs(s, def)),
1398 other => bail!(
1399 "`include` expected a string or list of strings, but found {} in list",
1400 other.desc()
1401 ),
1402 })
1403 .collect::<CargoResult<Vec<_>>>()?,
1404 Some(other) => bail!(
1405 "`include` expected a string or list, but found {} in `{}`",
1406 other.desc(),
1407 other.definition()
1408 ),
1409 None => {
1410 return Ok(Vec::new());
1411 }
1412 };
1413
1414 for (path, abs_path, def) in &includes {
1415 if abs_path.extension() != Some(OsStr::new("toml")) {
1416 bail!(
1417 "expected a config include path ending with `.toml`, \
1418 but found `{path}` from `{def}`",
1419 )
1420 }
1421 }
1422
1423 Ok(includes)
1424 }
1425
1426 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1428 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1429 let Some(cli_args) = &self.cli_config else {
1430 return Ok(loaded_args);
1431 };
1432 let mut seen = HashSet::new();
1433 for arg in cli_args {
1434 let arg_as_path = self.cwd.join(arg);
1435 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1436 let str_path = arg_as_path
1438 .to_str()
1439 .ok_or_else(|| {
1440 anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
1441 })?
1442 .to_string();
1443 self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
1444 .with_context(|| format!("failed to load config from `{}`", str_path))?
1445 } else {
1446 let doc = toml_dotted_keys(arg)?;
1447 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1448 .with_context(|| {
1449 format!("failed to parse value from --config argument `{arg}`")
1450 })?;
1451
1452 if doc
1453 .get("registry")
1454 .and_then(|v| v.as_table())
1455 .and_then(|t| t.get("token"))
1456 .is_some()
1457 {
1458 bail!("registry.token cannot be set through --config for security reasons");
1459 } else if let Some((k, _)) = doc
1460 .get("registries")
1461 .and_then(|v| v.as_table())
1462 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1463 {
1464 bail!(
1465 "registries.{}.token cannot be set through --config for security reasons",
1466 k
1467 );
1468 }
1469
1470 if doc
1471 .get("registry")
1472 .and_then(|v| v.as_table())
1473 .and_then(|t| t.get("secret-key"))
1474 .is_some()
1475 {
1476 bail!(
1477 "registry.secret-key cannot be set through --config for security reasons"
1478 );
1479 } else if let Some((k, _)) = doc
1480 .get("registries")
1481 .and_then(|v| v.as_table())
1482 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1483 {
1484 bail!(
1485 "registries.{}.secret-key cannot be set through --config for security reasons",
1486 k
1487 );
1488 }
1489
1490 CV::from_toml(Definition::Cli(None), doc)
1491 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1492 };
1493 let tmp_table = self
1494 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1495 .context("failed to load --config include".to_string())?;
1496 loaded_args
1497 .merge(tmp_table, true)
1498 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1499 }
1500 Ok(loaded_args)
1501 }
1502
1503 fn merge_cli_args(&mut self) -> CargoResult<()> {
1505 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1506 unreachable!()
1507 };
1508 let values = self.values_mut()?;
1509 for (key, value) in loaded_map.into_iter() {
1510 match values.entry(key) {
1511 Vacant(entry) => {
1512 entry.insert(value);
1513 }
1514 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1515 format!(
1516 "failed to merge --config key `{}` into `{}`",
1517 entry.key(),
1518 entry.get().definition(),
1519 )
1520 })?,
1521 };
1522 }
1523 Ok(())
1524 }
1525
1526 fn get_file_path(
1532 &self,
1533 dir: &Path,
1534 filename_without_extension: &str,
1535 warn: bool,
1536 ) -> CargoResult<Option<PathBuf>> {
1537 let possible = dir.join(filename_without_extension);
1538 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1539
1540 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1541 if warn {
1542 if let Ok(possible_with_extension_handle) =
1543 same_file::Handle::from_path(&possible_with_extension)
1544 {
1545 if possible_handle != possible_with_extension_handle {
1551 self.shell().warn(format!(
1552 "both `{}` and `{}` exist. Using `{}`",
1553 possible.display(),
1554 possible_with_extension.display(),
1555 possible.display()
1556 ))?;
1557 }
1558 } else {
1559 self.shell().print_report(&[
1560 Level::WARNING.secondary_title(
1561 format!(
1562 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1563 possible.display(),
1564 )).element(Level::HELP.message(
1565 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1566
1567 ], false)?;
1568 }
1569 }
1570
1571 Ok(Some(possible))
1572 } else if possible_with_extension.exists() {
1573 Ok(Some(possible_with_extension))
1574 } else {
1575 Ok(None)
1576 }
1577 }
1578
1579 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1580 where
1581 F: FnMut(&Path) -> CargoResult<()>,
1582 {
1583 let mut seen_dir = HashSet::new();
1584
1585 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1586 let config_root = current.join(".cargo");
1587 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1588 walk(&path)?;
1589 }
1590 seen_dir.insert(config_root);
1591 }
1592
1593 if !seen_dir.contains(home) {
1597 if let Some(path) = self.get_file_path(home, "config", true)? {
1598 walk(&path)?;
1599 }
1600 }
1601
1602 Ok(())
1603 }
1604
1605 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1607 RegistryName::new(registry)?;
1608 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1609 self.resolve_registry_index(&index).with_context(|| {
1610 format!(
1611 "invalid index URL for registry `{}` defined in {}",
1612 registry, index.definition
1613 )
1614 })
1615 } else {
1616 bail!(
1617 "registry index was not found in any configuration: `{}`",
1618 registry
1619 );
1620 }
1621 }
1622
1623 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1625 if self.get_string("registry.index")?.is_some() {
1626 bail!(
1627 "the `registry.index` config value is no longer supported\n\
1628 Use `[source]` replacement to alter the default index for crates.io."
1629 );
1630 }
1631 Ok(())
1632 }
1633
1634 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1635 let base = index
1637 .definition
1638 .root(self)
1639 .join("truncated-by-url_with_base");
1640 let _parsed = index.val.into_url()?;
1642 let url = index.val.into_url_with_base(Some(&*base))?;
1643 if url.password().is_some() {
1644 bail!("registry URLs may not contain passwords");
1645 }
1646 Ok(url)
1647 }
1648
1649 pub fn load_credentials(&self) -> CargoResult<()> {
1657 if self.credential_values.filled() {
1658 return Ok(());
1659 }
1660
1661 let home_path = self.home_path.clone().into_path_unlocked();
1662 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1663 return Ok(());
1664 };
1665
1666 let mut value = self.load_file(&credentials)?;
1667 {
1669 let CV::Table(ref mut value_map, ref def) = value else {
1670 unreachable!();
1671 };
1672
1673 if let Some(token) = value_map.remove("token") {
1674 if let Vacant(entry) = value_map.entry("registry".into()) {
1675 let map = HashMap::from([("token".into(), token)]);
1676 let table = CV::Table(map, def.clone());
1677 entry.insert(table);
1678 }
1679 }
1680 }
1681
1682 let mut credential_values = HashMap::new();
1683 if let CV::Table(map, _) = value {
1684 let base_map = self.values()?;
1685 for (k, v) in map {
1686 let entry = match base_map.get(&k) {
1687 Some(base_entry) => {
1688 let mut entry = base_entry.clone();
1689 entry.merge(v, true)?;
1690 entry
1691 }
1692 None => v,
1693 };
1694 credential_values.insert(k, entry);
1695 }
1696 }
1697 self.credential_values
1698 .set(credential_values)
1699 .expect("was not filled at beginning of the function");
1700 Ok(())
1701 }
1702
1703 fn maybe_get_tool(
1706 &self,
1707 tool: &str,
1708 from_config: &Option<ConfigRelativePath>,
1709 ) -> Option<PathBuf> {
1710 let var = tool.to_uppercase();
1711
1712 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1713 Some(tool_path) => {
1714 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1715 let path = if maybe_relative {
1716 self.cwd.join(tool_path)
1717 } else {
1718 PathBuf::from(tool_path)
1719 };
1720 Some(path)
1721 }
1722
1723 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1724 }
1725 }
1726
1727 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1738 let tool_str = tool.as_str();
1739 self.maybe_get_tool(tool_str, from_config)
1740 .or_else(|| {
1741 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1755 if toolchain.to_str()?.contains(&['/', '\\']) {
1758 return None;
1759 }
1760 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1763 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1764 let tool_meta = tool_resolved.metadata().ok()?;
1765 let rustup_meta = rustup_resolved.metadata().ok()?;
1766 if tool_meta.len() != rustup_meta.len() {
1771 return None;
1772 }
1773 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1775 let toolchain_exe = home::rustup_home()
1776 .ok()?
1777 .join("toolchains")
1778 .join(&toolchain)
1779 .join("bin")
1780 .join(&tool_exe);
1781 toolchain_exe.exists().then_some(toolchain_exe)
1782 })
1783 .unwrap_or_else(|| PathBuf::from(tool_str))
1784 }
1785
1786 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1788 let key = ConfigKey::from_str("paths");
1789 match self.get_cv(&key)? {
1791 Some(CV::List(val, definition)) => {
1792 let val = val
1793 .into_iter()
1794 .map(|cv| match cv {
1795 CV::String(s, def) => Ok((s, def)),
1796 other => self.expected("string", &key, &other),
1797 })
1798 .collect::<CargoResult<Vec<_>>>()?;
1799 Ok(Some(Value { val, definition }))
1800 }
1801 Some(val) => self.expected("list", &key, &val),
1802 None => Ok(None),
1803 }
1804 }
1805
1806 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1807 self.jobserver.as_ref()
1808 }
1809
1810 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1811 let http = self
1812 .easy
1813 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1814 {
1815 let mut http = http.lock().unwrap();
1816 http.reset();
1817 let timeout = configure_http_handle(self, &mut http)?;
1818 timeout.configure(&mut http)?;
1819 }
1820 Ok(http)
1821 }
1822
1823 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1824 self.http_config.try_borrow_with(|| {
1825 let mut http = self.get::<CargoHttpConfig>("http")?;
1826 let curl_v = curl::Version::get();
1827 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1828 Ok(http)
1829 })
1830 }
1831
1832 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1833 self.future_incompat_config
1834 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1835 }
1836
1837 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1838 self.net_config
1839 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1840 }
1841
1842 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1843 self.build_config
1844 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1845 }
1846
1847 pub fn progress_config(&self) -> &ProgressConfig {
1848 &self.progress_config
1849 }
1850
1851 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1854 let env_config = self.env_config.try_borrow_with(|| {
1855 CargoResult::Ok(Arc::new({
1856 let env_config = self.get::<EnvConfig>("env")?;
1857 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1873 if env_config.contains_key(*disallowed) {
1874 bail!(
1875 "setting the `{disallowed}` environment variable is not supported \
1876 in the `[env]` configuration table"
1877 );
1878 }
1879 }
1880 env_config
1881 .into_iter()
1882 .filter_map(|(k, v)| {
1883 if v.is_force() || self.get_env_os(&k).is_none() {
1884 Some((k, v.resolve(self).to_os_string()))
1885 } else {
1886 None
1887 }
1888 })
1889 .collect()
1890 }))
1891 })?;
1892
1893 Ok(env_config)
1894 }
1895
1896 pub fn validate_term_config(&self) -> CargoResult<()> {
1902 drop(self.get::<TermConfig>("term")?);
1903 Ok(())
1904 }
1905
1906 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1910 self.target_cfgs
1911 .try_borrow_with(|| target::load_target_cfgs(self))
1912 }
1913
1914 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1915 self.doc_extern_map
1919 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1920 }
1921
1922 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1924 target::get_target_applies_to_host(self)
1925 }
1926
1927 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1929 target::load_host_triple(self, target)
1930 }
1931
1932 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1934 target::load_target_triple(self, target)
1935 }
1936
1937 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
1942 let source_id = self.crates_io_source_id.try_borrow_with(|| {
1943 self.check_registry_index_not_set()?;
1944 let url = CRATES_IO_INDEX.into_url().unwrap();
1945 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
1946 })?;
1947 Ok(*source_id)
1948 }
1949
1950 pub fn creation_time(&self) -> Instant {
1951 self.creation_time
1952 }
1953
1954 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
1969 let d = Deserializer {
1970 gctx: self,
1971 key: ConfigKey::from_str(key),
1972 env_prefix_ok: true,
1973 };
1974 T::deserialize(d).map_err(|e| e.into())
1975 }
1976
1977 #[track_caller]
1983 #[tracing::instrument(skip_all)]
1984 pub fn assert_package_cache_locked<'a>(
1985 &self,
1986 mode: CacheLockMode,
1987 f: &'a Filesystem,
1988 ) -> &'a Path {
1989 let ret = f.as_path_unlocked();
1990 assert!(
1991 self.package_cache_lock.is_locked(mode),
1992 "package cache lock is not currently held, Cargo forgot to call \
1993 `acquire_package_cache_lock` before we got to this stack frame",
1994 );
1995 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
1996 ret
1997 }
1998
1999 #[tracing::instrument(skip_all)]
2005 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2006 self.package_cache_lock.lock(self, mode)
2007 }
2008
2009 #[tracing::instrument(skip_all)]
2015 pub fn try_acquire_package_cache_lock(
2016 &self,
2017 mode: CacheLockMode,
2018 ) -> CargoResult<Option<CacheLock<'_>>> {
2019 self.package_cache_lock.try_lock(self, mode)
2020 }
2021
2022 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2027 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2028 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2029 })?;
2030 Ok(tracker.lock().unwrap())
2031 }
2032
2033 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2035 let deferred = self
2036 .deferred_global_last_use
2037 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2038 Ok(deferred.lock().unwrap())
2039 }
2040
2041 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2043 if self.unstable_flags.warnings {
2044 Ok(self.build_config()?.warnings.unwrap_or_default())
2045 } else {
2046 Ok(WarningHandling::default())
2047 }
2048 }
2049
2050 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2051 self.ws_roots.lock().unwrap()
2052 }
2053}
2054
2055#[derive(Debug)]
2057pub struct ConfigError {
2058 error: anyhow::Error,
2059 definition: Option<Definition>,
2060}
2061
2062impl ConfigError {
2063 fn new(message: String, definition: Definition) -> ConfigError {
2064 ConfigError {
2065 error: anyhow::Error::msg(message),
2066 definition: Some(definition),
2067 }
2068 }
2069
2070 fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
2071 ConfigError {
2072 error: anyhow!(
2073 "`{}` expected {}, but found a {}",
2074 key,
2075 expected,
2076 found.desc()
2077 ),
2078 definition: Some(found.definition().clone()),
2079 }
2080 }
2081
2082 fn is_missing_field(&self) -> bool {
2083 self.error.downcast_ref::<MissingFieldError>().is_some()
2084 }
2085
2086 fn missing(key: &ConfigKey) -> ConfigError {
2087 ConfigError {
2088 error: anyhow!("missing config key `{}`", key),
2089 definition: None,
2090 }
2091 }
2092
2093 fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
2094 ConfigError {
2095 error: anyhow::Error::from(self)
2096 .context(format!("could not load config key `{}`", key)),
2097 definition: definition,
2098 }
2099 }
2100}
2101
2102impl std::error::Error for ConfigError {
2103 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2104 self.error.source()
2105 }
2106}
2107
2108impl fmt::Display for ConfigError {
2109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2110 if let Some(definition) = &self.definition {
2111 write!(f, "error in {}: {}", definition, self.error)
2112 } else {
2113 self.error.fmt(f)
2114 }
2115 }
2116}
2117
2118#[derive(Debug)]
2119struct MissingFieldError(String);
2120
2121impl fmt::Display for MissingFieldError {
2122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2123 write!(f, "missing field `{}`", self.0)
2124 }
2125}
2126
2127impl std::error::Error for MissingFieldError {}
2128
2129impl serde::de::Error for ConfigError {
2130 fn custom<T: fmt::Display>(msg: T) -> Self {
2131 ConfigError {
2132 error: anyhow::Error::msg(msg.to_string()),
2133 definition: None,
2134 }
2135 }
2136
2137 fn missing_field(field: &'static str) -> Self {
2138 ConfigError {
2139 error: anyhow::Error::new(MissingFieldError(field.to_string())),
2140 definition: None,
2141 }
2142 }
2143}
2144
2145impl From<anyhow::Error> for ConfigError {
2146 fn from(error: anyhow::Error) -> Self {
2147 ConfigError {
2148 error,
2149 definition: None,
2150 }
2151 }
2152}
2153
2154#[derive(Debug)]
2155enum KeyOrIdx {
2156 Key(String),
2157 Idx(usize),
2158}
2159
2160#[derive(Eq, PartialEq, Clone)]
2161pub enum ConfigValue {
2162 Integer(i64, Definition),
2163 String(String, Definition),
2164 List(Vec<ConfigValue>, Definition),
2165 Table(HashMap<String, ConfigValue>, Definition),
2166 Boolean(bool, Definition),
2167}
2168
2169impl fmt::Debug for ConfigValue {
2170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2171 match self {
2172 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2173 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2174 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2175 CV::List(list, def) => {
2176 write!(f, "[")?;
2177 for (i, item) in list.iter().enumerate() {
2178 if i > 0 {
2179 write!(f, ", ")?;
2180 }
2181 write!(f, "{item:?}")?;
2182 }
2183 write!(f, "] (from {})", def)
2184 }
2185 CV::Table(table, _) => write!(f, "{:?}", table),
2186 }
2187 }
2188}
2189
2190impl ConfigValue {
2191 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2192 let mut error_path = Vec::new();
2193 Self::from_toml_inner(def, toml, &mut error_path).with_context(|| {
2194 let mut it = error_path.iter().rev().peekable();
2195 let mut key_path = String::with_capacity(error_path.len() * 3);
2196 while let Some(k) = it.next() {
2197 match k {
2198 KeyOrIdx::Key(s) => key_path.push_str(&key::escape_key_part(&s)),
2199 KeyOrIdx::Idx(i) => key_path.push_str(&format!("[{i}]")),
2200 }
2201 if matches!(it.peek(), Some(KeyOrIdx::Key(_))) {
2202 key_path.push('.');
2203 }
2204 }
2205 format!("failed to parse config at `{key_path}`")
2206 })
2207 }
2208
2209 fn from_toml_inner(
2210 def: Definition,
2211 toml: toml::Value,
2212 path: &mut Vec<KeyOrIdx>,
2213 ) -> CargoResult<ConfigValue> {
2214 match toml {
2215 toml::Value::String(val) => Ok(CV::String(val, def)),
2216 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2217 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2218 toml::Value::Array(val) => Ok(CV::List(
2219 val.into_iter()
2220 .enumerate()
2221 .map(|(i, toml)| match toml {
2222 toml::Value::String(val) => Ok(CV::String(val, def.clone())),
2223 v => {
2224 path.push(KeyOrIdx::Idx(i));
2225 bail!("expected string but found {} at index {i}", v.type_str())
2226 }
2227 })
2228 .collect::<CargoResult<_>>()?,
2229 def,
2230 )),
2231 toml::Value::Table(val) => Ok(CV::Table(
2232 val.into_iter()
2233 .map(
2234 |(key, value)| match CV::from_toml_inner(def.clone(), value, path) {
2235 Ok(value) => Ok((key, value)),
2236 Err(e) => {
2237 path.push(KeyOrIdx::Key(key));
2238 Err(e)
2239 }
2240 },
2241 )
2242 .collect::<CargoResult<_>>()?,
2243 def,
2244 )),
2245 v => bail!(
2246 "found TOML configuration value of unknown type `{}`",
2247 v.type_str()
2248 ),
2249 }
2250 }
2251
2252 fn into_toml(self) -> toml::Value {
2253 match self {
2254 CV::Boolean(s, _) => toml::Value::Boolean(s),
2255 CV::String(s, _) => toml::Value::String(s),
2256 CV::Integer(i, _) => toml::Value::Integer(i),
2257 CV::List(l, _) => toml::Value::Array(l.into_iter().map(|cv| cv.into_toml()).collect()),
2258 CV::Table(l, _) => {
2259 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2260 }
2261 }
2262 }
2263
2264 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2273 self.merge_helper(from, force, &mut ConfigKey::new())
2274 }
2275
2276 fn merge_helper(
2277 &mut self,
2278 from: ConfigValue,
2279 force: bool,
2280 parts: &mut ConfigKey,
2281 ) -> CargoResult<()> {
2282 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2283 match (self, from) {
2284 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2285 if is_nonmergable_list(&parts) {
2286 if force || is_higher_priority {
2288 mem::swap(new, old);
2289 }
2290 } else {
2291 if force {
2293 old.append(new);
2294 } else {
2295 new.append(old);
2296 mem::swap(new, old);
2297 }
2298 }
2299 old.sort_by(|a, b| a.definition().cmp(b.definition()));
2300 }
2301 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2302 for (key, value) in mem::take(new) {
2303 match old.entry(key.clone()) {
2304 Occupied(mut entry) => {
2305 let new_def = value.definition().clone();
2306 let entry = entry.get_mut();
2307 parts.push(&key);
2308 entry.merge_helper(value, force, parts).with_context(|| {
2309 format!(
2310 "failed to merge key `{}` between \
2311 {} and {}",
2312 key,
2313 entry.definition(),
2314 new_def,
2315 )
2316 })?;
2317 }
2318 Vacant(entry) => {
2319 entry.insert(value);
2320 }
2321 };
2322 }
2323 }
2324 (expected @ &mut CV::List(_, _), found)
2326 | (expected @ &mut CV::Table(_, _), found)
2327 | (expected, found @ CV::List(_, _))
2328 | (expected, found @ CV::Table(_, _)) => {
2329 return Err(anyhow!(
2330 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2331 found.definition(),
2332 expected.definition(),
2333 expected.desc(),
2334 found.desc()
2335 ));
2336 }
2337 (old, mut new) => {
2338 if force || is_higher_priority {
2339 mem::swap(old, &mut new);
2340 }
2341 }
2342 }
2343
2344 Ok(())
2345 }
2346
2347 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2348 match self {
2349 CV::Integer(i, def) => Ok((*i, def)),
2350 _ => self.expected("integer", key),
2351 }
2352 }
2353
2354 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2355 match self {
2356 CV::String(s, def) => Ok((s, def)),
2357 _ => self.expected("string", key),
2358 }
2359 }
2360
2361 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2362 match self {
2363 CV::Table(table, def) => Ok((table, def)),
2364 _ => self.expected("table", key),
2365 }
2366 }
2367
2368 pub fn string_list(&self, key: &str) -> CargoResult<Vec<(String, Definition)>> {
2369 match self {
2370 CV::List(list, _) => list
2371 .iter()
2372 .map(|cv| match cv {
2373 CV::String(s, def) => Ok((s.clone(), def.clone())),
2374 _ => self.expected("string", key),
2375 })
2376 .collect::<CargoResult<_>>(),
2377 _ => self.expected("list", key),
2378 }
2379 }
2380
2381 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2382 match self {
2383 CV::Boolean(b, def) => Ok((*b, def)),
2384 _ => self.expected("bool", key),
2385 }
2386 }
2387
2388 pub fn desc(&self) -> &'static str {
2389 match *self {
2390 CV::Table(..) => "table",
2391 CV::List(..) => "array",
2392 CV::String(..) => "string",
2393 CV::Boolean(..) => "boolean",
2394 CV::Integer(..) => "integer",
2395 }
2396 }
2397
2398 pub fn definition(&self) -> &Definition {
2399 match self {
2400 CV::Boolean(_, def)
2401 | CV::Integer(_, def)
2402 | CV::String(_, def)
2403 | CV::List(_, def)
2404 | CV::Table(_, def) => def,
2405 }
2406 }
2407
2408 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2409 bail!(
2410 "expected a {}, but found a {} for `{}` in {}",
2411 wanted,
2412 self.desc(),
2413 key,
2414 self.definition()
2415 )
2416 }
2417}
2418
2419fn is_nonmergable_list(key: &ConfigKey) -> bool {
2422 key.matches("registry.credential-provider")
2423 || key.matches("registries.*.credential-provider")
2424 || key.matches("target.*.runner")
2425 || key.matches("host.runner")
2426 || key.matches("credential-alias.*")
2427 || key.matches("doc.browser")
2428}
2429
2430pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2431 ::home::cargo_home_with_cwd(cwd).ok()
2432}
2433
2434pub fn save_credentials(
2435 gctx: &GlobalContext,
2436 token: Option<RegistryCredentialConfig>,
2437 registry: &SourceId,
2438) -> CargoResult<()> {
2439 let registry = if registry.is_crates_io() {
2440 None
2441 } else {
2442 let name = registry
2443 .alt_registry_key()
2444 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2445 Some(name)
2446 };
2447
2448 let home_path = gctx.home_path.clone().into_path_unlocked();
2452 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2453 Some(path) => match path.file_name() {
2454 Some(filename) => Path::new(filename).to_owned(),
2455 None => Path::new("credentials.toml").to_owned(),
2456 },
2457 None => Path::new("credentials.toml").to_owned(),
2458 };
2459
2460 let mut file = {
2461 gctx.home_path.create_dir()?;
2462 gctx.home_path
2463 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2464 };
2465
2466 let mut contents = String::new();
2467 file.read_to_string(&mut contents).with_context(|| {
2468 format!(
2469 "failed to read configuration file `{}`",
2470 file.path().display()
2471 )
2472 })?;
2473
2474 let mut toml = parse_document(&contents, file.path(), gctx)?;
2475
2476 if let Some(token) = toml.remove("token") {
2478 let map = HashMap::from([("token".to_string(), token)]);
2479 toml.insert("registry".into(), map.into());
2480 }
2481
2482 if let Some(token) = token {
2483 let path_def = Definition::Path(file.path().to_path_buf());
2486 let (key, mut value) = match token {
2487 RegistryCredentialConfig::Token(token) => {
2488 let key = "token".to_string();
2491 let value = ConfigValue::String(token.expose(), path_def.clone());
2492 let map = HashMap::from([(key, value)]);
2493 let table = CV::Table(map, path_def.clone());
2494
2495 if let Some(registry) = registry {
2496 let map = HashMap::from([(registry.to_string(), table)]);
2497 ("registries".into(), CV::Table(map, path_def.clone()))
2498 } else {
2499 ("registry".into(), table)
2500 }
2501 }
2502 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2503 let key = "secret-key".to_string();
2506 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2507 let mut map = HashMap::from([(key, value)]);
2508 if let Some(key_subject) = key_subject {
2509 let key = "secret-key-subject".to_string();
2510 let value = ConfigValue::String(key_subject, path_def.clone());
2511 map.insert(key, value);
2512 }
2513 let table = CV::Table(map, path_def.clone());
2514
2515 if let Some(registry) = registry {
2516 let map = HashMap::from([(registry.to_string(), table)]);
2517 ("registries".into(), CV::Table(map, path_def.clone()))
2518 } else {
2519 ("registry".into(), table)
2520 }
2521 }
2522 _ => unreachable!(),
2523 };
2524
2525 if registry.is_some() {
2526 if let Some(table) = toml.remove("registries") {
2527 let v = CV::from_toml(path_def, table)?;
2528 value.merge(v, false)?;
2529 }
2530 }
2531 toml.insert(key, value.into_toml());
2532 } else {
2533 if let Some(registry) = registry {
2535 if let Some(registries) = toml.get_mut("registries") {
2536 if let Some(reg) = registries.get_mut(registry) {
2537 let rtable = reg.as_table_mut().ok_or_else(|| {
2538 format_err!("expected `[registries.{}]` to be a table", registry)
2539 })?;
2540 rtable.remove("token");
2541 rtable.remove("secret-key");
2542 rtable.remove("secret-key-subject");
2543 }
2544 }
2545 } else if let Some(registry) = toml.get_mut("registry") {
2546 let reg_table = registry
2547 .as_table_mut()
2548 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2549 reg_table.remove("token");
2550 reg_table.remove("secret-key");
2551 reg_table.remove("secret-key-subject");
2552 }
2553 }
2554
2555 let contents = toml.to_string();
2556 file.seek(SeekFrom::Start(0))?;
2557 file.write_all(contents.as_bytes())
2558 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2559 file.file().set_len(contents.len() as u64)?;
2560 set_permissions(file.file(), 0o600)
2561 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2562
2563 return Ok(());
2564
2565 #[cfg(unix)]
2566 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2567 use std::os::unix::fs::PermissionsExt;
2568
2569 let mut perms = file.metadata()?.permissions();
2570 perms.set_mode(mode);
2571 file.set_permissions(perms)?;
2572 Ok(())
2573 }
2574
2575 #[cfg(not(unix))]
2576 #[allow(unused)]
2577 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2578 Ok(())
2579 }
2580}
2581
2582#[derive(Debug, Default, Deserialize, PartialEq)]
2583#[serde(rename_all = "kebab-case")]
2584pub struct CargoHttpConfig {
2585 pub proxy: Option<String>,
2586 pub low_speed_limit: Option<u32>,
2587 pub timeout: Option<u64>,
2588 pub cainfo: Option<ConfigRelativePath>,
2589 pub proxy_cainfo: Option<ConfigRelativePath>,
2590 pub check_revoke: Option<bool>,
2591 pub user_agent: Option<String>,
2592 pub debug: Option<bool>,
2593 pub multiplexing: Option<bool>,
2594 pub ssl_version: Option<SslVersionConfig>,
2595}
2596
2597#[derive(Debug, Default, Deserialize, PartialEq)]
2598#[serde(rename_all = "kebab-case")]
2599pub struct CargoFutureIncompatConfig {
2600 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2601}
2602
2603#[derive(Debug, Default, Deserialize, PartialEq)]
2604#[serde(rename_all = "kebab-case")]
2605pub enum CargoFutureIncompatFrequencyConfig {
2606 #[default]
2607 Always,
2608 Never,
2609}
2610
2611impl CargoFutureIncompatConfig {
2612 pub fn should_display_message(&self) -> bool {
2613 use CargoFutureIncompatFrequencyConfig::*;
2614
2615 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2616 match frequency {
2617 Always => true,
2618 Never => false,
2619 }
2620 }
2621}
2622
2623#[derive(Clone, Debug, PartialEq)]
2637pub enum SslVersionConfig {
2638 Single(String),
2639 Range(SslVersionConfigRange),
2640}
2641
2642impl<'de> Deserialize<'de> for SslVersionConfig {
2643 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2644 where
2645 D: serde::Deserializer<'de>,
2646 {
2647 UntaggedEnumVisitor::new()
2648 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2649 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2650 .deserialize(deserializer)
2651 }
2652}
2653
2654#[derive(Clone, Debug, Deserialize, PartialEq)]
2655#[serde(rename_all = "kebab-case")]
2656pub struct SslVersionConfigRange {
2657 pub min: Option<String>,
2658 pub max: Option<String>,
2659}
2660
2661#[derive(Debug, Deserialize)]
2662#[serde(rename_all = "kebab-case")]
2663pub struct CargoNetConfig {
2664 pub retry: Option<u32>,
2665 pub offline: Option<bool>,
2666 pub git_fetch_with_cli: Option<bool>,
2667 pub ssh: Option<CargoSshConfig>,
2668}
2669
2670#[derive(Debug, Deserialize)]
2671#[serde(rename_all = "kebab-case")]
2672pub struct CargoSshConfig {
2673 pub known_hosts: Option<Vec<Value<String>>>,
2674}
2675
2676#[derive(Debug, Clone)]
2689pub enum JobsConfig {
2690 Integer(i32),
2691 String(String),
2692}
2693
2694impl<'de> Deserialize<'de> for JobsConfig {
2695 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2696 where
2697 D: serde::Deserializer<'de>,
2698 {
2699 UntaggedEnumVisitor::new()
2700 .i32(|int| Ok(JobsConfig::Integer(int)))
2701 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2702 .deserialize(deserializer)
2703 }
2704}
2705
2706#[derive(Debug, Deserialize)]
2707#[serde(rename_all = "kebab-case")]
2708pub struct CargoBuildConfig {
2709 pub pipelining: Option<bool>,
2711 pub dep_info_basedir: Option<ConfigRelativePath>,
2712 pub target_dir: Option<ConfigRelativePath>,
2713 pub build_dir: Option<ConfigRelativePath>,
2714 pub incremental: Option<bool>,
2715 pub target: Option<BuildTargetConfig>,
2716 pub jobs: Option<JobsConfig>,
2717 pub rustflags: Option<StringList>,
2718 pub rustdocflags: Option<StringList>,
2719 pub rustc_wrapper: Option<ConfigRelativePath>,
2720 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2721 pub rustc: Option<ConfigRelativePath>,
2722 pub rustdoc: Option<ConfigRelativePath>,
2723 pub out_dir: Option<ConfigRelativePath>,
2725 pub artifact_dir: Option<ConfigRelativePath>,
2726 pub warnings: Option<WarningHandling>,
2727 pub sbom: Option<bool>,
2729 pub analysis: Option<CargoBuildAnalysis>,
2731}
2732
2733#[derive(Debug, Deserialize, Default)]
2735#[serde(rename_all = "kebab-case")]
2736pub struct CargoBuildAnalysis {
2737 pub enabled: bool,
2738}
2739
2740#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2742#[serde(rename_all = "kebab-case")]
2743pub enum WarningHandling {
2744 #[default]
2745 Warn,
2747 Allow,
2749 Deny,
2751}
2752
2753#[derive(Debug, Deserialize)]
2763#[serde(transparent)]
2764pub struct BuildTargetConfig {
2765 inner: Value<BuildTargetConfigInner>,
2766}
2767
2768#[derive(Debug)]
2769enum BuildTargetConfigInner {
2770 One(String),
2771 Many(Vec<String>),
2772}
2773
2774impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2775 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2776 where
2777 D: serde::Deserializer<'de>,
2778 {
2779 UntaggedEnumVisitor::new()
2780 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2781 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2782 .deserialize(deserializer)
2783 }
2784}
2785
2786impl BuildTargetConfig {
2787 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2789 let map = |s: &String| {
2790 if s.ends_with(".json") {
2791 self.inner
2794 .definition
2795 .root(gctx)
2796 .join(s)
2797 .to_str()
2798 .expect("must be utf-8 in toml")
2799 .to_string()
2800 } else {
2801 s.to_string()
2803 }
2804 };
2805 let values = match &self.inner.val {
2806 BuildTargetConfigInner::One(s) => vec![map(s)],
2807 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2808 };
2809 Ok(values)
2810 }
2811}
2812
2813#[derive(Debug, Deserialize)]
2814#[serde(rename_all = "kebab-case")]
2815pub struct CargoResolverConfig {
2816 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2817 pub feature_unification: Option<FeatureUnification>,
2818}
2819
2820#[derive(Debug, Deserialize, PartialEq, Eq)]
2821#[serde(rename_all = "kebab-case")]
2822pub enum IncompatibleRustVersions {
2823 Allow,
2824 Fallback,
2825}
2826
2827#[derive(Copy, Clone, Debug, Deserialize)]
2828#[serde(rename_all = "kebab-case")]
2829pub enum FeatureUnification {
2830 Package,
2831 Selected,
2832 Workspace,
2833}
2834
2835#[derive(Deserialize, Default)]
2836#[serde(rename_all = "kebab-case")]
2837pub struct TermConfig {
2838 pub verbose: Option<bool>,
2839 pub quiet: Option<bool>,
2840 pub color: Option<String>,
2841 pub hyperlinks: Option<bool>,
2842 pub unicode: Option<bool>,
2843 #[serde(default)]
2844 #[serde(deserialize_with = "progress_or_string")]
2845 pub progress: Option<ProgressConfig>,
2846}
2847
2848#[derive(Debug, Default, Deserialize)]
2849#[serde(rename_all = "kebab-case")]
2850pub struct ProgressConfig {
2851 #[serde(default)]
2852 pub when: ProgressWhen,
2853 pub width: Option<usize>,
2854 pub term_integration: Option<bool>,
2856}
2857
2858#[derive(Debug, Default, Deserialize)]
2859#[serde(rename_all = "kebab-case")]
2860pub enum ProgressWhen {
2861 #[default]
2862 Auto,
2863 Never,
2864 Always,
2865}
2866
2867fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2868where
2869 D: serde::de::Deserializer<'de>,
2870{
2871 struct ProgressVisitor;
2872
2873 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2874 type Value = Option<ProgressConfig>;
2875
2876 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2877 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2878 }
2879
2880 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2881 where
2882 E: serde::de::Error,
2883 {
2884 match s {
2885 "auto" => Ok(Some(ProgressConfig {
2886 when: ProgressWhen::Auto,
2887 width: None,
2888 term_integration: None,
2889 })),
2890 "never" => Ok(Some(ProgressConfig {
2891 when: ProgressWhen::Never,
2892 width: None,
2893 term_integration: None,
2894 })),
2895 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2896 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2897 }
2898 }
2899
2900 fn visit_none<E>(self) -> Result<Self::Value, E>
2901 where
2902 E: serde::de::Error,
2903 {
2904 Ok(None)
2905 }
2906
2907 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2908 where
2909 D: serde::de::Deserializer<'de>,
2910 {
2911 let pc = ProgressConfig::deserialize(deserializer)?;
2912 if let ProgressConfig {
2913 when: ProgressWhen::Always,
2914 width: None,
2915 ..
2916 } = pc
2917 {
2918 return Err(serde::de::Error::custom(
2919 "\"always\" progress requires a `width` key",
2920 ));
2921 }
2922 Ok(Some(pc))
2923 }
2924 }
2925
2926 deserializer.deserialize_option(ProgressVisitor)
2927}
2928
2929#[derive(Debug)]
2930enum EnvConfigValueInner {
2931 Simple(String),
2932 WithOptions {
2933 value: String,
2934 force: bool,
2935 relative: bool,
2936 },
2937}
2938
2939impl<'de> Deserialize<'de> for EnvConfigValueInner {
2940 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2941 where
2942 D: serde::Deserializer<'de>,
2943 {
2944 #[derive(Deserialize)]
2945 struct WithOptions {
2946 value: String,
2947 #[serde(default)]
2948 force: bool,
2949 #[serde(default)]
2950 relative: bool,
2951 }
2952
2953 UntaggedEnumVisitor::new()
2954 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2955 .map(|map| {
2956 let with_options: WithOptions = map.deserialize()?;
2957 Ok(EnvConfigValueInner::WithOptions {
2958 value: with_options.value,
2959 force: with_options.force,
2960 relative: with_options.relative,
2961 })
2962 })
2963 .deserialize(deserializer)
2964 }
2965}
2966
2967#[derive(Debug, Deserialize)]
2968#[serde(transparent)]
2969pub struct EnvConfigValue {
2970 inner: Value<EnvConfigValueInner>,
2971}
2972
2973impl EnvConfigValue {
2974 pub fn is_force(&self) -> bool {
2975 match self.inner.val {
2976 EnvConfigValueInner::Simple(_) => false,
2977 EnvConfigValueInner::WithOptions { force, .. } => force,
2978 }
2979 }
2980
2981 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
2982 match self.inner.val {
2983 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
2984 EnvConfigValueInner::WithOptions {
2985 ref value,
2986 relative,
2987 ..
2988 } => {
2989 if relative {
2990 let p = self.inner.definition.root(gctx).join(&value);
2991 Cow::Owned(p.into_os_string())
2992 } else {
2993 Cow::Borrowed(OsStr::new(value.as_str()))
2994 }
2995 }
2996 }
2997 }
2998}
2999
3000pub type EnvConfig = HashMap<String, EnvConfigValue>;
3001
3002fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
3003 toml.parse().map_err(Into::into)
3005}
3006
3007fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
3008 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
3014 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
3015 })?;
3016 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
3017 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
3018 }
3019 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
3020 non_empty(d.prefix()) || non_empty(d.suffix())
3021 }
3022 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
3023 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
3024 }
3025 let ok = {
3026 let mut got_to_value = false;
3027 let mut table = doc.as_table();
3028 let mut is_root = true;
3029 while table.is_dotted() || is_root {
3030 is_root = false;
3031 if table.len() != 1 {
3032 break;
3033 }
3034 let (k, n) = table.iter().next().expect("len() == 1 above");
3035 match n {
3036 Item::Table(nt) => {
3037 if table.key(k).map_or(false, non_empty_key_decor)
3038 || non_empty_decor(nt.decor())
3039 {
3040 bail!(
3041 "--config argument `{arg}` \
3042 includes non-whitespace decoration"
3043 )
3044 }
3045 table = nt;
3046 }
3047 Item::Value(v) if v.is_inline_table() => {
3048 bail!(
3049 "--config argument `{arg}` \
3050 sets a value to an inline table, which is not accepted"
3051 );
3052 }
3053 Item::Value(v) => {
3054 if table
3055 .key(k)
3056 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
3057 || non_empty_decor(v.decor())
3058 {
3059 bail!(
3060 "--config argument `{arg}` \
3061 includes non-whitespace decoration"
3062 )
3063 }
3064 got_to_value = true;
3065 break;
3066 }
3067 Item::ArrayOfTables(_) => {
3068 bail!(
3069 "--config argument `{arg}` \
3070 sets a value to an array of tables, which is not accepted"
3071 );
3072 }
3073
3074 Item::None => {
3075 bail!("--config argument `{arg}` doesn't provide a value")
3076 }
3077 }
3078 }
3079 got_to_value
3080 };
3081 if !ok {
3082 bail!(
3083 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
3084 );
3085 }
3086 Ok(doc)
3087}
3088
3089#[derive(Debug, Deserialize, Clone)]
3100pub struct StringList(Vec<String>);
3101
3102impl StringList {
3103 pub fn as_slice(&self) -> &[String] {
3104 &self.0
3105 }
3106}
3107
3108#[macro_export]
3109macro_rules! __shell_print {
3110 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
3111 let mut shell = $config.shell();
3112 let out = shell.$which();
3113 drop(out.write_fmt(format_args!($($arg)*)));
3114 if $newline {
3115 drop(out.write_all(b"\n"));
3116 }
3117 });
3118}
3119
3120#[macro_export]
3121macro_rules! drop_println {
3122 ($config:expr) => ( $crate::drop_print!($config, "\n") );
3123 ($config:expr, $($arg:tt)*) => (
3124 $crate::__shell_print!($config, out, true, $($arg)*)
3125 );
3126}
3127
3128#[macro_export]
3129macro_rules! drop_eprintln {
3130 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
3131 ($config:expr, $($arg:tt)*) => (
3132 $crate::__shell_print!($config, err, true, $($arg)*)
3133 );
3134}
3135
3136#[macro_export]
3137macro_rules! drop_print {
3138 ($config:expr, $($arg:tt)*) => (
3139 $crate::__shell_print!($config, out, false, $($arg)*)
3140 );
3141}
3142
3143#[macro_export]
3144macro_rules! drop_eprint {
3145 ($config:expr, $($arg:tt)*) => (
3146 $crate::__shell_print!($config, err, false, $($arg)*)
3147 );
3148}
3149
3150enum Tool {
3151 Rustc,
3152 Rustdoc,
3153}
3154
3155impl Tool {
3156 fn as_str(&self) -> &str {
3157 match self {
3158 Tool::Rustc => "rustc",
3159 Tool::Rustdoc => "rustdoc",
3160 }
3161 }
3162}
3163
3164fn disables_multiplexing_for_bad_curl(
3174 curl_version: &str,
3175 http: &mut CargoHttpConfig,
3176 gctx: &GlobalContext,
3177) {
3178 use crate::util::network;
3179
3180 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3181 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3182 if bad_curl_versions
3183 .iter()
3184 .any(|v| curl_version.starts_with(v))
3185 {
3186 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3187 http.multiplexing = Some(false);
3188 }
3189 }
3190}
3191
3192#[cfg(test)]
3193mod tests {
3194 use super::CargoHttpConfig;
3195 use super::GlobalContext;
3196 use super::Shell;
3197 use super::disables_multiplexing_for_bad_curl;
3198
3199 #[test]
3200 fn disables_multiplexing() {
3201 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3202 gctx.set_search_stop_path(std::path::PathBuf::new());
3203 gctx.set_env(Default::default());
3204
3205 let mut http = CargoHttpConfig::default();
3206 http.proxy = Some("127.0.0.1:3128".into());
3207 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3208 assert_eq!(http.multiplexing, Some(false));
3209
3210 let cases = [
3211 (None, None, "7.87.0", None),
3212 (None, None, "7.88.0", None),
3213 (None, None, "7.88.1", None),
3214 (None, None, "8.0.0", None),
3215 (Some("".into()), None, "7.87.0", Some(false)),
3216 (Some("".into()), None, "7.88.0", Some(false)),
3217 (Some("".into()), None, "7.88.1", Some(false)),
3218 (Some("".into()), None, "8.0.0", None),
3219 (Some("".into()), Some(false), "7.87.0", Some(false)),
3220 (Some("".into()), Some(false), "7.88.0", Some(false)),
3221 (Some("".into()), Some(false), "7.88.1", Some(false)),
3222 (Some("".into()), Some(false), "8.0.0", Some(false)),
3223 ];
3224
3225 for (proxy, multiplexing, curl_v, result) in cases {
3226 let mut http = CargoHttpConfig {
3227 multiplexing,
3228 proxy,
3229 ..Default::default()
3230 };
3231 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3232 assert_eq!(http.multiplexing, result);
3233 }
3234 }
3235
3236 #[test]
3237 fn sync_context() {
3238 fn assert_sync<S: Sync>() {}
3239 assert_sync::<GlobalContext>();
3240 }
3241}