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;
115use key::KeyOrIdx;
116
117mod path;
118pub use path::{ConfigRelativePath, PathAndArgs};
119
120mod target;
121pub use target::{TargetCfgConfig, TargetConfig};
122
123mod environment;
124use environment::Env;
125
126use super::auth::RegistryConfig;
127
128macro_rules! get_value_typed {
130 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
131 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
133 let cv = self.get_cv(key)?;
134 let env = self.get_config_env::<$ty>(key)?;
135 match (cv, env) {
136 (Some(CV::$variant(val, definition)), Some(env)) => {
137 if definition.is_higher_priority(&env.definition) {
138 Ok(Some(Value { val, definition }))
139 } else {
140 Ok(Some(env))
141 }
142 }
143 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
144 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
145 (None, Some(env)) => Ok(Some(env)),
146 (None, None) => Ok(None),
147 }
148 }
149 };
150}
151
152#[derive(Clone, Copy, Debug)]
154enum WhyLoad {
155 Cli,
162 FileDiscovery,
164}
165
166#[derive(Debug)]
168pub struct CredentialCacheValue {
169 pub token_value: Secret<String>,
170 pub expiration: Option<OffsetDateTime>,
171 pub operation_independent: bool,
172}
173
174#[derive(Debug)]
177pub struct GlobalContext {
178 home_path: Filesystem,
180 shell: Mutex<Shell>,
182 values: OnceLock<HashMap<String, ConfigValue>>,
184 credential_values: OnceLock<HashMap<String, ConfigValue>>,
186 cli_config: Option<Vec<String>>,
188 cwd: PathBuf,
190 search_stop_path: Option<PathBuf>,
192 cargo_exe: OnceLock<PathBuf>,
194 rustdoc: OnceLock<PathBuf>,
196 extra_verbose: bool,
198 frozen: bool,
201 locked: bool,
204 offline: bool,
207 jobserver: Option<jobserver::Client>,
209 unstable_flags: CliUnstable,
211 unstable_flags_cli: Option<Vec<String>>,
213 easy: OnceLock<Mutex<Easy>>,
215 crates_io_source_id: OnceLock<SourceId>,
217 cache_rustc_info: bool,
219 creation_time: Instant,
221 target_dir: Option<Filesystem>,
223 env: Env,
225 updated_sources: Mutex<HashSet<SourceId>>,
227 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
230 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
232 package_cache_lock: CacheLocker,
234 http_config: OnceLock<CargoHttpConfig>,
236 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
237 net_config: OnceLock<CargoNetConfig>,
238 build_config: OnceLock<CargoBuildConfig>,
239 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
240 doc_extern_map: OnceLock<RustdocExternMap>,
241 progress_config: ProgressConfig,
242 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
243 pub nightly_features_allowed: bool,
259 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
261 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
263 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
266}
267
268impl GlobalContext {
269 pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
277 static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
278 static INIT: Once = Once::new();
279
280 INIT.call_once(|| unsafe {
283 if let Some(client) = jobserver::Client::from_env() {
284 GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
285 }
286 });
287
288 let env = Env::new();
289
290 let cache_key = "CARGO_CACHE_RUSTC_INFO";
291 let cache_rustc_info = match env.get_env_os(cache_key) {
292 Some(cache) => cache != "0",
293 _ => true,
294 };
295
296 GlobalContext {
297 home_path: Filesystem::new(homedir),
298 shell: Mutex::new(shell),
299 cwd,
300 search_stop_path: None,
301 values: Default::default(),
302 credential_values: Default::default(),
303 cli_config: None,
304 cargo_exe: Default::default(),
305 rustdoc: Default::default(),
306 extra_verbose: false,
307 frozen: false,
308 locked: false,
309 offline: false,
310 jobserver: unsafe {
311 if GLOBAL_JOBSERVER.is_null() {
312 None
313 } else {
314 Some((*GLOBAL_JOBSERVER).clone())
315 }
316 },
317 unstable_flags: CliUnstable::default(),
318 unstable_flags_cli: None,
319 easy: Default::default(),
320 crates_io_source_id: Default::default(),
321 cache_rustc_info,
322 creation_time: Instant::now(),
323 target_dir: None,
324 env,
325 updated_sources: Default::default(),
326 credential_cache: Default::default(),
327 registry_config: Default::default(),
328 package_cache_lock: CacheLocker::new(),
329 http_config: Default::default(),
330 future_incompat_config: Default::default(),
331 net_config: Default::default(),
332 build_config: Default::default(),
333 target_cfgs: Default::default(),
334 doc_extern_map: Default::default(),
335 progress_config: ProgressConfig::default(),
336 env_config: Default::default(),
337 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
338 ws_roots: Default::default(),
339 global_cache_tracker: Default::default(),
340 deferred_global_last_use: Default::default(),
341 }
342 }
343
344 pub fn default() -> CargoResult<GlobalContext> {
349 let shell = Shell::new();
350 let cwd =
351 env::current_dir().context("couldn't get the current directory of the process")?;
352 let homedir = homedir(&cwd).ok_or_else(|| {
353 anyhow!(
354 "Cargo couldn't find your home directory. \
355 This probably means that $HOME was not set."
356 )
357 })?;
358 Ok(GlobalContext::new(shell, cwd, homedir))
359 }
360
361 pub fn home(&self) -> &Filesystem {
363 &self.home_path
364 }
365
366 pub fn diagnostic_home_config(&self) -> String {
370 let home = self.home_path.as_path_unlocked();
371 let path = match self.get_file_path(home, "config", false) {
372 Ok(Some(existing_path)) => existing_path,
373 _ => home.join("config.toml"),
374 };
375 path.to_string_lossy().to_string()
376 }
377
378 pub fn git_path(&self) -> Filesystem {
380 self.home_path.join("git")
381 }
382
383 pub fn git_checkouts_path(&self) -> Filesystem {
386 self.git_path().join("checkouts")
387 }
388
389 pub fn git_db_path(&self) -> Filesystem {
392 self.git_path().join("db")
393 }
394
395 pub fn registry_base_path(&self) -> Filesystem {
397 self.home_path.join("registry")
398 }
399
400 pub fn registry_index_path(&self) -> Filesystem {
402 self.registry_base_path().join("index")
403 }
404
405 pub fn registry_cache_path(&self) -> Filesystem {
407 self.registry_base_path().join("cache")
408 }
409
410 pub fn registry_source_path(&self) -> Filesystem {
412 self.registry_base_path().join("src")
413 }
414
415 pub fn default_registry(&self) -> CargoResult<Option<String>> {
417 Ok(self
418 .get_string("registry.default")?
419 .map(|registry| registry.val))
420 }
421
422 pub fn shell(&self) -> MutexGuard<'_, Shell> {
424 self.shell.lock().unwrap()
425 }
426
427 pub fn debug_assert_shell_not_borrowed(&self) {
433 if cfg!(debug_assertions) {
434 match self.shell.try_lock() {
435 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
436 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
437 }
438 }
439 }
440
441 pub fn rustdoc(&self) -> CargoResult<&Path> {
443 self.rustdoc
444 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
445 .map(AsRef::as_ref)
446 }
447
448 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
450 let cache_location =
451 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
452 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
453 let rustc_workspace_wrapper = self.maybe_get_tool(
454 "rustc_workspace_wrapper",
455 &self.build_config()?.rustc_workspace_wrapper,
456 );
457
458 Rustc::new(
459 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
460 wrapper,
461 rustc_workspace_wrapper,
462 &self
463 .home()
464 .join("bin")
465 .join("rustc")
466 .into_path_unlocked()
467 .with_extension(env::consts::EXE_EXTENSION),
468 if self.cache_rustc_info {
469 cache_location
470 } else {
471 None
472 },
473 self,
474 )
475 }
476
477 pub fn cargo_exe(&self) -> CargoResult<&Path> {
479 self.cargo_exe
480 .try_borrow_with(|| {
481 let from_env = || -> CargoResult<PathBuf> {
482 let exe = self
487 .get_env_os(crate::CARGO_ENV)
488 .map(PathBuf::from)
489 .ok_or_else(|| anyhow!("$CARGO not set"))?;
490 Ok(exe)
491 };
492
493 fn from_current_exe() -> CargoResult<PathBuf> {
494 let exe = env::current_exe()?;
499 Ok(exe)
500 }
501
502 fn from_argv() -> CargoResult<PathBuf> {
503 let argv0 = env::args_os()
510 .map(PathBuf::from)
511 .next()
512 .ok_or_else(|| anyhow!("no argv[0]"))?;
513 paths::resolve_executable(&argv0)
514 }
515
516 fn is_cargo(path: &Path) -> bool {
519 path.file_stem() == Some(OsStr::new("cargo"))
520 }
521
522 let from_current_exe = from_current_exe();
523 if from_current_exe.as_deref().is_ok_and(is_cargo) {
524 return from_current_exe;
525 }
526
527 let from_argv = from_argv();
528 if from_argv.as_deref().is_ok_and(is_cargo) {
529 return from_argv;
530 }
531
532 let exe = from_env()
533 .or(from_current_exe)
534 .or(from_argv)
535 .context("couldn't get the path to cargo executable")?;
536 Ok(exe)
537 })
538 .map(AsRef::as_ref)
539 }
540
541 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
543 self.updated_sources.lock().unwrap()
544 }
545
546 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
548 self.credential_cache.lock().unwrap()
549 }
550
551 pub(crate) fn registry_config(
553 &self,
554 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
555 self.registry_config.lock().unwrap()
556 }
557
558 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
564 self.values.try_borrow_with(|| self.load_values())
565 }
566
567 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
574 let _ = self.values()?;
575 Ok(self.values.get_mut().expect("already loaded config values"))
576 }
577
578 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
580 if self.values.get().is_some() {
581 bail!("config values already found")
582 }
583 match self.values.set(values.into()) {
584 Ok(()) => Ok(()),
585 Err(_) => bail!("could not fill values"),
586 }
587 }
588
589 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
592 let path = path.into();
593 debug_assert!(self.cwd.starts_with(&path));
594 self.search_stop_path = Some(path);
595 }
596
597 pub fn reload_cwd(&mut self) -> CargoResult<()> {
601 let cwd =
602 env::current_dir().context("couldn't get the current directory of the process")?;
603 let homedir = homedir(&cwd).ok_or_else(|| {
604 anyhow!(
605 "Cargo couldn't find your home directory. \
606 This probably means that $HOME was not set."
607 )
608 })?;
609
610 self.cwd = cwd;
611 self.home_path = Filesystem::new(homedir);
612 self.reload_rooted_at(self.cwd.clone())?;
613 Ok(())
614 }
615
616 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
619 let values = self.load_values_from(path.as_ref())?;
620 self.values.replace(values);
621 self.merge_cli_args()?;
622 self.load_unstable_flags_from_config()?;
623 Ok(())
624 }
625
626 pub fn cwd(&self) -> &Path {
628 &self.cwd
629 }
630
631 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
637 if let Some(dir) = &self.target_dir {
638 Ok(Some(dir.clone()))
639 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
640 if dir.is_empty() {
642 bail!(
643 "the target directory is set to an empty string in the \
644 `CARGO_TARGET_DIR` environment variable"
645 )
646 }
647
648 Ok(Some(Filesystem::new(self.cwd.join(dir))))
649 } else if let Some(val) = &self.build_config()?.target_dir {
650 let path = val.resolve_path(self);
651
652 if val.raw_value().is_empty() {
654 bail!(
655 "the target directory is set to an empty string in {}",
656 val.value().definition
657 )
658 }
659
660 Ok(Some(Filesystem::new(path)))
661 } else {
662 Ok(None)
663 }
664 }
665
666 pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
670 let Some(val) = &self.build_config()?.build_dir else {
671 return Ok(None);
672 };
673 self.custom_build_dir(val, workspace_manifest_path)
674 .map(Some)
675 }
676
677 pub fn custom_build_dir(
681 &self,
682 val: &ConfigRelativePath,
683 workspace_manifest_path: &Path,
684 ) -> CargoResult<Filesystem> {
685 let replacements = [
686 (
687 "{workspace-root}",
688 workspace_manifest_path
689 .parent()
690 .unwrap()
691 .to_str()
692 .context("workspace root was not valid utf-8")?
693 .to_string(),
694 ),
695 (
696 "{cargo-cache-home}",
697 self.home()
698 .as_path_unlocked()
699 .to_str()
700 .context("cargo home was not valid utf-8")?
701 .to_string(),
702 ),
703 ("{workspace-path-hash}", {
704 let real_path = std::fs::canonicalize(workspace_manifest_path)?;
705 let hash = crate::util::hex::short_hash(&real_path);
706 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
707 }),
708 ];
709
710 let template_variables = replacements
711 .iter()
712 .map(|(key, _)| key[1..key.len() - 1].to_string())
713 .collect_vec();
714
715 let path = val
716 .resolve_templated_path(self, replacements)
717 .map_err(|e| match e {
718 path::ResolveTemplateError::UnexpectedVariable {
719 variable,
720 raw_template,
721 } => {
722 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
723 if suggestion == "" {
724 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
725 suggestion = format!("\n\nhelp: available template variables are {variables}");
726 }
727 anyhow!(
728 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
729 )
730 },
731 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
732 let (btype, literal) = match bracket_type {
733 path::BracketType::Opening => ("opening", "{"),
734 path::BracketType::Closing => ("closing", "}"),
735 };
736
737 anyhow!(
738 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
739 )
740 }
741 })?;
742
743 if val.raw_value().is_empty() {
745 bail!(
746 "the build directory is set to an empty string in {}",
747 val.value().definition
748 )
749 }
750
751 Ok(Filesystem::new(path))
752 }
753
754 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
759 if let Some(vals) = self.credential_values.get() {
760 let val = self.get_cv_helper(key, vals)?;
761 if val.is_some() {
762 return Ok(val);
763 }
764 }
765 self.get_cv_helper(key, &*self.values()?)
766 }
767
768 fn get_cv_helper(
769 &self,
770 key: &ConfigKey,
771 vals: &HashMap<String, ConfigValue>,
772 ) -> CargoResult<Option<ConfigValue>> {
773 tracing::trace!("get cv {:?}", key);
774 if key.is_root() {
775 return Ok(Some(CV::Table(
778 vals.clone(),
779 Definition::Path(PathBuf::new()),
780 )));
781 }
782 let mut parts = key.parts().enumerate();
783 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
784 return Ok(None);
785 };
786 for (i, part) in parts {
787 match val {
788 CV::Table(map, _) => {
789 val = match map.get(part) {
790 Some(val) => val,
791 None => return Ok(None),
792 }
793 }
794 CV::Integer(_, def)
795 | CV::String(_, def)
796 | CV::List(_, def)
797 | CV::Boolean(_, def) => {
798 let mut key_so_far = ConfigKey::new();
799 for part in key.parts().take(i) {
800 key_so_far.push(part);
801 }
802 bail!(
803 "expected table for configuration key `{}`, \
804 but found {} in {}",
805 key_so_far,
806 val.desc(),
807 def
808 )
809 }
810 }
811 }
812 Ok(Some(val.clone()))
813 }
814
815 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
817 let cv = self.get_cv(key)?;
820 if key.is_root() {
821 return Ok(cv);
823 }
824 let env = self.env.get_str(key.as_env_key());
825 let env_def = Definition::Environment(key.as_env_key().to_string());
826 let use_env = match (&cv, env) {
827 (Some(CV::List(..)), Some(_)) => true,
829 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
830 (None, Some(_)) => true,
831 _ => false,
832 };
833
834 if !use_env {
835 return Ok(cv);
836 }
837
838 let env = env.unwrap();
842 if env == "true" {
843 Ok(Some(CV::Boolean(true, env_def)))
844 } else if env == "false" {
845 Ok(Some(CV::Boolean(false, env_def)))
846 } else if let Ok(i) = env.parse::<i64>() {
847 Ok(Some(CV::Integer(i, env_def)))
848 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
849 match cv {
850 Some(CV::List(mut cv_list, cv_def)) => {
851 self.get_env_list(key, &mut cv_list)?;
853 Ok(Some(CV::List(cv_list, cv_def)))
854 }
855 Some(cv) => {
856 bail!(
860 "unable to merge array env for config `{}`\n\
861 file: {:?}\n\
862 env: {}",
863 key,
864 cv,
865 env
866 );
867 }
868 None => {
869 let mut cv_list = Vec::new();
870 self.get_env_list(key, &mut cv_list)?;
871 Ok(Some(CV::List(cv_list, env_def)))
872 }
873 }
874 } else {
875 match cv {
877 Some(CV::List(mut cv_list, cv_def)) => {
878 self.get_env_list(key, &mut cv_list)?;
880 Ok(Some(CV::List(cv_list, cv_def)))
881 }
882 _ => {
883 Ok(Some(CV::String(env.to_string(), env_def)))
888 }
889 }
890 }
891 }
892
893 pub fn set_env(&mut self, env: HashMap<String, String>) {
895 self.env = Env::from_map(env);
896 }
897
898 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
901 self.env.iter_str()
902 }
903
904 fn env_keys(&self) -> impl Iterator<Item = &str> {
906 self.env.keys_str()
907 }
908
909 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
910 where
911 T: FromStr,
912 <T as FromStr>::Err: fmt::Display,
913 {
914 match self.env.get_str(key.as_env_key()) {
915 Some(value) => {
916 let definition = Definition::Environment(key.as_env_key().to_string());
917 Ok(Some(Value {
918 val: value
919 .parse()
920 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
921 definition,
922 }))
923 }
924 None => {
925 self.check_environment_key_case_mismatch(key);
926 Ok(None)
927 }
928 }
929 }
930
931 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
936 self.env.get_env(key)
937 }
938
939 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
944 self.env.get_env_os(key)
945 }
946
947 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
951 if self.env.contains_key(key.as_env_key()) {
952 return Ok(true);
953 }
954 if env_prefix_ok {
955 let env_prefix = format!("{}_", key.as_env_key());
956 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
957 return Ok(true);
958 }
959 }
960 if self.get_cv(key)?.is_some() {
961 return Ok(true);
962 }
963 self.check_environment_key_case_mismatch(key);
964
965 Ok(false)
966 }
967
968 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
969 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
970 let _ = self.shell().warn(format!(
971 "environment variables are expected to use uppercase letters and underscores, \
972 the variable `{}` will be ignored and have no effect",
973 env_key
974 ));
975 }
976 }
977
978 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
982 self.get::<OptValue<String>>(key)
983 }
984
985 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
991 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
992 v.map(|v| Value {
993 val: v.val.resolve_program(self),
994 definition: v.definition,
995 })
996 })
997 }
998
999 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
1000 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1001 if is_path {
1002 definition.root(self).join(value)
1003 } else {
1004 PathBuf::from(value)
1006 }
1007 }
1008
1009 fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1012 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1013 self.check_environment_key_case_mismatch(key);
1014 return Ok(());
1015 };
1016
1017 if is_nonmergable_list(&key) {
1018 output.clear();
1019 }
1020
1021 let def = Definition::Environment(key.as_env_key().to_string());
1022 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1023 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1025 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
1026 })?;
1027 let values = toml_v.as_array().expect("env var was not array");
1028 for value in values {
1029 let s = value.as_str().ok_or_else(|| {
1032 ConfigError::new(
1033 format!("expected string, found {}", value.type_str()),
1034 def.clone(),
1035 )
1036 })?;
1037 output.push(CV::String(s.to_string(), def.clone()))
1038 }
1039 } else {
1040 output.extend(
1041 env_val
1042 .split_whitespace()
1043 .map(|s| CV::String(s.to_string(), def.clone())),
1044 );
1045 }
1046 output.sort_by(|a, b| a.definition().cmp(b.definition()));
1047 Ok(())
1048 }
1049
1050 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1054 match self.get_cv(key)? {
1055 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1056 Some(val) => self.expected("table", key, &val),
1057 None => Ok(None),
1058 }
1059 }
1060
1061 get_value_typed! {get_integer, i64, Integer, "an integer"}
1062 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1063 get_value_typed! {get_string_priv, String, String, "a string"}
1064
1065 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1067 val.expected(ty, &key.to_string())
1068 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1069 }
1070
1071 pub fn configure(
1077 &mut self,
1078 verbose: u32,
1079 quiet: bool,
1080 color: Option<&str>,
1081 frozen: bool,
1082 locked: bool,
1083 offline: bool,
1084 target_dir: &Option<PathBuf>,
1085 unstable_flags: &[String],
1086 cli_config: &[String],
1087 ) -> CargoResult<()> {
1088 for warning in self
1089 .unstable_flags
1090 .parse(unstable_flags, self.nightly_features_allowed)?
1091 {
1092 self.shell().warn(warning)?;
1093 }
1094 if !unstable_flags.is_empty() {
1095 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1098 }
1099 if !cli_config.is_empty() {
1100 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1101 self.merge_cli_args()?;
1102 }
1103
1104 self.load_unstable_flags_from_config()?;
1108 if self.unstable_flags.config_include {
1109 self.reload_rooted_at(self.cwd.clone())?;
1116 }
1117
1118 let term = self.get::<TermConfig>("term").unwrap_or_default();
1122
1123 let extra_verbose = verbose >= 2;
1125 let verbose = verbose != 0;
1126 let verbosity = match (verbose, quiet) {
1127 (true, true) => bail!("cannot set both --verbose and --quiet"),
1128 (true, false) => Verbosity::Verbose,
1129 (false, true) => Verbosity::Quiet,
1130 (false, false) => match (term.verbose, term.quiet) {
1131 (Some(true), Some(true)) => {
1132 bail!("cannot set both `term.verbose` and `term.quiet`")
1133 }
1134 (Some(true), _) => Verbosity::Verbose,
1135 (_, Some(true)) => Verbosity::Quiet,
1136 _ => Verbosity::Normal,
1137 },
1138 };
1139 self.shell().set_verbosity(verbosity);
1140 self.extra_verbose = extra_verbose;
1141
1142 let color = color.or_else(|| term.color.as_deref());
1143 self.shell().set_color_choice(color)?;
1144 if let Some(hyperlinks) = term.hyperlinks {
1145 self.shell().set_hyperlinks(hyperlinks)?;
1146 }
1147 if let Some(unicode) = term.unicode {
1148 self.shell().set_unicode(unicode)?;
1149 }
1150
1151 self.progress_config = term.progress.unwrap_or_default();
1152
1153 self.frozen = frozen;
1154 self.locked = locked;
1155 self.offline = offline
1156 || self
1157 .net_config()
1158 .ok()
1159 .and_then(|n| n.offline)
1160 .unwrap_or(false);
1161 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1162 self.target_dir = cli_target_dir;
1163
1164 Ok(())
1165 }
1166
1167 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1168 if self.nightly_features_allowed {
1171 self.unstable_flags = self
1172 .get::<Option<CliUnstable>>("unstable")?
1173 .unwrap_or_default();
1174 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1175 self.unstable_flags.parse(unstable_flags_cli, true)?;
1180 }
1181 }
1182
1183 Ok(())
1184 }
1185
1186 pub fn cli_unstable(&self) -> &CliUnstable {
1187 &self.unstable_flags
1188 }
1189
1190 pub fn extra_verbose(&self) -> bool {
1191 self.extra_verbose
1192 }
1193
1194 pub fn network_allowed(&self) -> bool {
1195 !self.offline_flag().is_some()
1196 }
1197
1198 pub fn offline_flag(&self) -> Option<&'static str> {
1199 if self.frozen {
1200 Some("--frozen")
1201 } else if self.offline {
1202 Some("--offline")
1203 } else {
1204 None
1205 }
1206 }
1207
1208 pub fn set_locked(&mut self, locked: bool) {
1209 self.locked = locked;
1210 }
1211
1212 pub fn lock_update_allowed(&self) -> bool {
1213 !self.locked_flag().is_some()
1214 }
1215
1216 pub fn locked_flag(&self) -> Option<&'static str> {
1217 if self.frozen {
1218 Some("--frozen")
1219 } else if self.locked {
1220 Some("--locked")
1221 } else {
1222 None
1223 }
1224 }
1225
1226 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1228 self.load_values_from(&self.cwd)
1229 }
1230
1231 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1235 let mut result = Vec::new();
1236 let mut seen = HashSet::new();
1237 let home = self.home_path.clone().into_path_unlocked();
1238 self.walk_tree(&self.cwd, &home, |path| {
1239 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1240 if self.cli_unstable().config_include {
1241 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1242 }
1243 result.push(cv);
1244 Ok(())
1245 })
1246 .context("could not load Cargo configuration")?;
1247 Ok(result)
1248 }
1249
1250 fn load_unmerged_include(
1254 &self,
1255 cv: &mut CV,
1256 seen: &mut HashSet<PathBuf>,
1257 output: &mut Vec<CV>,
1258 ) -> CargoResult<()> {
1259 let includes = self.include_paths(cv, false)?;
1260 for include in includes {
1261 let Some(abs_path) = include.resolve_path(self) else {
1262 continue;
1263 };
1264
1265 let mut cv = self
1266 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1267 .with_context(|| {
1268 format!(
1269 "failed to load config include `{}` from `{}`",
1270 include.path.display(),
1271 include.def
1272 )
1273 })?;
1274 self.load_unmerged_include(&mut cv, seen, output)?;
1275 output.push(cv);
1276 }
1277 Ok(())
1278 }
1279
1280 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1282 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1285 let home = self.home_path.clone().into_path_unlocked();
1286
1287 self.walk_tree(path, &home, |path| {
1288 let value = self.load_file(path)?;
1289 cfg.merge(value, false).with_context(|| {
1290 format!("failed to merge configuration at `{}`", path.display())
1291 })?;
1292 Ok(())
1293 })
1294 .context("could not load Cargo configuration")?;
1295
1296 match cfg {
1297 CV::Table(map, _) => Ok(map),
1298 _ => unreachable!(),
1299 }
1300 }
1301
1302 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1306 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1307 }
1308
1309 fn _load_file(
1319 &self,
1320 path: &Path,
1321 seen: &mut HashSet<PathBuf>,
1322 includes: bool,
1323 why_load: WhyLoad,
1324 ) -> CargoResult<ConfigValue> {
1325 if !seen.insert(path.to_path_buf()) {
1326 bail!(
1327 "config `include` cycle detected with path `{}`",
1328 path.display()
1329 );
1330 }
1331 tracing::debug!(?path, ?why_load, includes, "load config from file");
1332
1333 let contents = fs::read_to_string(path)
1334 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1335 let toml = parse_document(&contents, path, self).with_context(|| {
1336 format!("could not parse TOML configuration in `{}`", path.display())
1337 })?;
1338 let def = match why_load {
1339 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1340 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1341 };
1342 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1343 format!(
1344 "failed to load TOML configuration from `{}`",
1345 path.display()
1346 )
1347 })?;
1348 if includes {
1349 self.load_includes(value, seen, why_load)
1350 } else {
1351 Ok(value)
1352 }
1353 }
1354
1355 fn load_includes(
1362 &self,
1363 mut value: CV,
1364 seen: &mut HashSet<PathBuf>,
1365 why_load: WhyLoad,
1366 ) -> CargoResult<CV> {
1367 let includes = self.include_paths(&mut value, true)?;
1369 if !self.cli_unstable().config_include {
1371 return Ok(value);
1372 }
1373 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1375 for include in includes {
1376 let Some(abs_path) = include.resolve_path(self) else {
1377 continue;
1378 };
1379
1380 self._load_file(&abs_path, seen, true, why_load)
1381 .and_then(|include| root.merge(include, true))
1382 .with_context(|| {
1383 format!(
1384 "failed to load config include `{}` from `{}`",
1385 include.path.display(),
1386 include.def
1387 )
1388 })?;
1389 }
1390 root.merge(value, true)?;
1391 Ok(root)
1392 }
1393
1394 fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1396 let CV::Table(table, _def) = cv else {
1397 unreachable!()
1398 };
1399 let include = if remove {
1400 table.remove("include").map(Cow::Owned)
1401 } else {
1402 table.get("include").map(Cow::Borrowed)
1403 };
1404 let includes = match include.map(|c| c.into_owned()) {
1405 Some(CV::String(s, def)) => vec![ConfigInclude::new(s, def)],
1406 Some(CV::List(list, _def)) => list
1407 .into_iter()
1408 .enumerate()
1409 .map(|(idx, cv)| match cv {
1410 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1411 CV::Table(mut table, def) => {
1412 let s = match table.remove("path") {
1414 Some(CV::String(s, _)) => s,
1415 Some(other) => bail!(
1416 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1417 other.desc()
1418 ),
1419 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1420 };
1421
1422 let optional = match table.remove("optional") {
1424 Some(CV::Boolean(b, _)) => b,
1425 Some(other) => bail!(
1426 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1427 other.desc()
1428 ),
1429 None => false,
1430 };
1431
1432 let mut include = ConfigInclude::new(s, def);
1433 include.optional = optional;
1434 Ok(include)
1435 }
1436 other => bail!(
1437 "expected a string or table, but found {} at `include[{idx}]` in {}",
1438 other.desc(),
1439 other.definition(),
1440 ),
1441 })
1442 .collect::<CargoResult<Vec<_>>>()?,
1443 Some(other) => bail!(
1444 "expected a string or list of strings, but found {} at `include` in `{}",
1445 other.desc(),
1446 other.definition()
1447 ),
1448 None => {
1449 return Ok(Vec::new());
1450 }
1451 };
1452
1453 for include in &includes {
1454 if include.path.extension() != Some(OsStr::new("toml")) {
1455 bail!(
1456 "expected a config include path ending with `.toml`, \
1457 but found `{}` from `{}`",
1458 include.path.display(),
1459 include.def,
1460 )
1461 }
1462 }
1463
1464 Ok(includes)
1465 }
1466
1467 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1469 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1470 let Some(cli_args) = &self.cli_config else {
1471 return Ok(loaded_args);
1472 };
1473 let mut seen = HashSet::new();
1474 for arg in cli_args {
1475 let arg_as_path = self.cwd.join(arg);
1476 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1477 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1479 .with_context(|| {
1480 format!("failed to load config from `{}`", arg_as_path.display())
1481 })?
1482 } else {
1483 let doc = toml_dotted_keys(arg)?;
1484 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1485 .with_context(|| {
1486 format!("failed to parse value from --config argument `{arg}`")
1487 })?;
1488
1489 if doc
1490 .get("registry")
1491 .and_then(|v| v.as_table())
1492 .and_then(|t| t.get("token"))
1493 .is_some()
1494 {
1495 bail!("registry.token cannot be set through --config for security reasons");
1496 } else if let Some((k, _)) = doc
1497 .get("registries")
1498 .and_then(|v| v.as_table())
1499 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1500 {
1501 bail!(
1502 "registries.{}.token cannot be set through --config for security reasons",
1503 k
1504 );
1505 }
1506
1507 if doc
1508 .get("registry")
1509 .and_then(|v| v.as_table())
1510 .and_then(|t| t.get("secret-key"))
1511 .is_some()
1512 {
1513 bail!(
1514 "registry.secret-key cannot be set through --config for security reasons"
1515 );
1516 } else if let Some((k, _)) = doc
1517 .get("registries")
1518 .and_then(|v| v.as_table())
1519 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1520 {
1521 bail!(
1522 "registries.{}.secret-key cannot be set through --config for security reasons",
1523 k
1524 );
1525 }
1526
1527 CV::from_toml(Definition::Cli(None), doc)
1528 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1529 };
1530 let tmp_table = self
1531 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1532 .context("failed to load --config include".to_string())?;
1533 loaded_args
1534 .merge(tmp_table, true)
1535 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1536 }
1537 Ok(loaded_args)
1538 }
1539
1540 fn merge_cli_args(&mut self) -> CargoResult<()> {
1542 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1543 unreachable!()
1544 };
1545 let values = self.values_mut()?;
1546 for (key, value) in loaded_map.into_iter() {
1547 match values.entry(key) {
1548 Vacant(entry) => {
1549 entry.insert(value);
1550 }
1551 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1552 format!(
1553 "failed to merge --config key `{}` into `{}`",
1554 entry.key(),
1555 entry.get().definition(),
1556 )
1557 })?,
1558 };
1559 }
1560 Ok(())
1561 }
1562
1563 fn get_file_path(
1569 &self,
1570 dir: &Path,
1571 filename_without_extension: &str,
1572 warn: bool,
1573 ) -> CargoResult<Option<PathBuf>> {
1574 let possible = dir.join(filename_without_extension);
1575 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1576
1577 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1578 if warn {
1579 if let Ok(possible_with_extension_handle) =
1580 same_file::Handle::from_path(&possible_with_extension)
1581 {
1582 if possible_handle != possible_with_extension_handle {
1588 self.shell().warn(format!(
1589 "both `{}` and `{}` exist. Using `{}`",
1590 possible.display(),
1591 possible_with_extension.display(),
1592 possible.display()
1593 ))?;
1594 }
1595 } else {
1596 self.shell().print_report(&[
1597 Level::WARNING.secondary_title(
1598 format!(
1599 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1600 possible.display(),
1601 )).element(Level::HELP.message(
1602 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1603
1604 ], false)?;
1605 }
1606 }
1607
1608 Ok(Some(possible))
1609 } else if possible_with_extension.exists() {
1610 Ok(Some(possible_with_extension))
1611 } else {
1612 Ok(None)
1613 }
1614 }
1615
1616 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1617 where
1618 F: FnMut(&Path) -> CargoResult<()>,
1619 {
1620 let mut seen_dir = HashSet::new();
1621
1622 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1623 let config_root = current.join(".cargo");
1624 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1625 walk(&path)?;
1626 }
1627 seen_dir.insert(config_root);
1628 }
1629
1630 if !seen_dir.contains(home) {
1634 if let Some(path) = self.get_file_path(home, "config", true)? {
1635 walk(&path)?;
1636 }
1637 }
1638
1639 Ok(())
1640 }
1641
1642 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1644 RegistryName::new(registry)?;
1645 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1646 self.resolve_registry_index(&index).with_context(|| {
1647 format!(
1648 "invalid index URL for registry `{}` defined in {}",
1649 registry, index.definition
1650 )
1651 })
1652 } else {
1653 bail!(
1654 "registry index was not found in any configuration: `{}`",
1655 registry
1656 );
1657 }
1658 }
1659
1660 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1662 if self.get_string("registry.index")?.is_some() {
1663 bail!(
1664 "the `registry.index` config value is no longer supported\n\
1665 Use `[source]` replacement to alter the default index for crates.io."
1666 );
1667 }
1668 Ok(())
1669 }
1670
1671 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1672 let base = index
1674 .definition
1675 .root(self)
1676 .join("truncated-by-url_with_base");
1677 let _parsed = index.val.into_url()?;
1679 let url = index.val.into_url_with_base(Some(&*base))?;
1680 if url.password().is_some() {
1681 bail!("registry URLs may not contain passwords");
1682 }
1683 Ok(url)
1684 }
1685
1686 pub fn load_credentials(&self) -> CargoResult<()> {
1694 if self.credential_values.filled() {
1695 return Ok(());
1696 }
1697
1698 let home_path = self.home_path.clone().into_path_unlocked();
1699 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1700 return Ok(());
1701 };
1702
1703 let mut value = self.load_file(&credentials)?;
1704 {
1706 let CV::Table(ref mut value_map, ref def) = value else {
1707 unreachable!();
1708 };
1709
1710 if let Some(token) = value_map.remove("token") {
1711 if let Vacant(entry) = value_map.entry("registry".into()) {
1712 let map = HashMap::from([("token".into(), token)]);
1713 let table = CV::Table(map, def.clone());
1714 entry.insert(table);
1715 }
1716 }
1717 }
1718
1719 let mut credential_values = HashMap::new();
1720 if let CV::Table(map, _) = value {
1721 let base_map = self.values()?;
1722 for (k, v) in map {
1723 let entry = match base_map.get(&k) {
1724 Some(base_entry) => {
1725 let mut entry = base_entry.clone();
1726 entry.merge(v, true)?;
1727 entry
1728 }
1729 None => v,
1730 };
1731 credential_values.insert(k, entry);
1732 }
1733 }
1734 self.credential_values
1735 .set(credential_values)
1736 .expect("was not filled at beginning of the function");
1737 Ok(())
1738 }
1739
1740 fn maybe_get_tool(
1743 &self,
1744 tool: &str,
1745 from_config: &Option<ConfigRelativePath>,
1746 ) -> Option<PathBuf> {
1747 let var = tool.to_uppercase();
1748
1749 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1750 Some(tool_path) => {
1751 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1752 let path = if maybe_relative {
1753 self.cwd.join(tool_path)
1754 } else {
1755 PathBuf::from(tool_path)
1756 };
1757 Some(path)
1758 }
1759
1760 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1761 }
1762 }
1763
1764 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1775 let tool_str = tool.as_str();
1776 self.maybe_get_tool(tool_str, from_config)
1777 .or_else(|| {
1778 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1792 if toolchain.to_str()?.contains(&['/', '\\']) {
1795 return None;
1796 }
1797 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1800 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1801 let tool_meta = tool_resolved.metadata().ok()?;
1802 let rustup_meta = rustup_resolved.metadata().ok()?;
1803 if tool_meta.len() != rustup_meta.len() {
1808 return None;
1809 }
1810 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1812 let toolchain_exe = home::rustup_home()
1813 .ok()?
1814 .join("toolchains")
1815 .join(&toolchain)
1816 .join("bin")
1817 .join(&tool_exe);
1818 toolchain_exe.exists().then_some(toolchain_exe)
1819 })
1820 .unwrap_or_else(|| PathBuf::from(tool_str))
1821 }
1822
1823 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1825 let key = ConfigKey::from_str("paths");
1826 match self.get_cv(&key)? {
1828 Some(CV::List(val, definition)) => {
1829 let val = val
1830 .into_iter()
1831 .map(|cv| match cv {
1832 CV::String(s, def) => Ok((s, def)),
1833 other => self.expected("string", &key, &other),
1834 })
1835 .collect::<CargoResult<Vec<_>>>()?;
1836 Ok(Some(Value { val, definition }))
1837 }
1838 Some(val) => self.expected("list", &key, &val),
1839 None => Ok(None),
1840 }
1841 }
1842
1843 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1844 self.jobserver.as_ref()
1845 }
1846
1847 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1848 let http = self
1849 .easy
1850 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1851 {
1852 let mut http = http.lock().unwrap();
1853 http.reset();
1854 let timeout = configure_http_handle(self, &mut http)?;
1855 timeout.configure(&mut http)?;
1856 }
1857 Ok(http)
1858 }
1859
1860 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1861 self.http_config.try_borrow_with(|| {
1862 let mut http = self.get::<CargoHttpConfig>("http")?;
1863 let curl_v = curl::Version::get();
1864 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1865 Ok(http)
1866 })
1867 }
1868
1869 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1870 self.future_incompat_config
1871 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1872 }
1873
1874 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1875 self.net_config
1876 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1877 }
1878
1879 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1880 self.build_config
1881 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1882 }
1883
1884 pub fn progress_config(&self) -> &ProgressConfig {
1885 &self.progress_config
1886 }
1887
1888 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1891 let env_config = self.env_config.try_borrow_with(|| {
1892 CargoResult::Ok(Arc::new({
1893 let env_config = self.get::<EnvConfig>("env")?;
1894 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1910 if env_config.contains_key(*disallowed) {
1911 bail!(
1912 "setting the `{disallowed}` environment variable is not supported \
1913 in the `[env]` configuration table"
1914 );
1915 }
1916 }
1917 env_config
1918 .into_iter()
1919 .filter_map(|(k, v)| {
1920 if v.is_force() || self.get_env_os(&k).is_none() {
1921 Some((k, v.resolve(self).to_os_string()))
1922 } else {
1923 None
1924 }
1925 })
1926 .collect()
1927 }))
1928 })?;
1929
1930 Ok(env_config)
1931 }
1932
1933 pub fn validate_term_config(&self) -> CargoResult<()> {
1939 drop(self.get::<TermConfig>("term")?);
1940 Ok(())
1941 }
1942
1943 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1947 self.target_cfgs
1948 .try_borrow_with(|| target::load_target_cfgs(self))
1949 }
1950
1951 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1952 self.doc_extern_map
1956 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1957 }
1958
1959 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1961 target::get_target_applies_to_host(self)
1962 }
1963
1964 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1966 target::load_host_triple(self, target)
1967 }
1968
1969 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1971 target::load_target_triple(self, target)
1972 }
1973
1974 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
1979 let source_id = self.crates_io_source_id.try_borrow_with(|| {
1980 self.check_registry_index_not_set()?;
1981 let url = CRATES_IO_INDEX.into_url().unwrap();
1982 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
1983 })?;
1984 Ok(*source_id)
1985 }
1986
1987 pub fn creation_time(&self) -> Instant {
1988 self.creation_time
1989 }
1990
1991 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2006 let d = Deserializer {
2007 gctx: self,
2008 key: ConfigKey::from_str(key),
2009 env_prefix_ok: true,
2010 };
2011 T::deserialize(d).map_err(|e| e.into())
2012 }
2013
2014 #[track_caller]
2020 #[tracing::instrument(skip_all)]
2021 pub fn assert_package_cache_locked<'a>(
2022 &self,
2023 mode: CacheLockMode,
2024 f: &'a Filesystem,
2025 ) -> &'a Path {
2026 let ret = f.as_path_unlocked();
2027 assert!(
2028 self.package_cache_lock.is_locked(mode),
2029 "package cache lock is not currently held, Cargo forgot to call \
2030 `acquire_package_cache_lock` before we got to this stack frame",
2031 );
2032 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2033 ret
2034 }
2035
2036 #[tracing::instrument(skip_all)]
2042 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2043 self.package_cache_lock.lock(self, mode)
2044 }
2045
2046 #[tracing::instrument(skip_all)]
2052 pub fn try_acquire_package_cache_lock(
2053 &self,
2054 mode: CacheLockMode,
2055 ) -> CargoResult<Option<CacheLock<'_>>> {
2056 self.package_cache_lock.try_lock(self, mode)
2057 }
2058
2059 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2064 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2065 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2066 })?;
2067 Ok(tracker.lock().unwrap())
2068 }
2069
2070 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2072 let deferred = self
2073 .deferred_global_last_use
2074 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2075 Ok(deferred.lock().unwrap())
2076 }
2077
2078 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2080 if self.unstable_flags.warnings {
2081 Ok(self.build_config()?.warnings.unwrap_or_default())
2082 } else {
2083 Ok(WarningHandling::default())
2084 }
2085 }
2086
2087 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2088 self.ws_roots.lock().unwrap()
2089 }
2090}
2091
2092#[derive(Eq, PartialEq, Clone)]
2094pub enum ConfigValue {
2095 Integer(i64, Definition),
2096 String(String, Definition),
2097 List(Vec<ConfigValue>, Definition),
2098 Table(HashMap<String, ConfigValue>, Definition),
2099 Boolean(bool, Definition),
2100}
2101
2102impl fmt::Debug for ConfigValue {
2103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2104 match self {
2105 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2106 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2107 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2108 CV::List(list, def) => {
2109 write!(f, "[")?;
2110 for (i, item) in list.iter().enumerate() {
2111 if i > 0 {
2112 write!(f, ", ")?;
2113 }
2114 write!(f, "{item:?}")?;
2115 }
2116 write!(f, "] (from {})", def)
2117 }
2118 CV::Table(table, _) => write!(f, "{:?}", table),
2119 }
2120 }
2121}
2122
2123impl ConfigValue {
2124 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2125 let mut error_path = Vec::new();
2126 Self::from_toml_inner(def, toml, &mut error_path).with_context(|| {
2127 let mut it = error_path.iter().rev().peekable();
2128 let mut key_path = String::with_capacity(error_path.len() * 3);
2129 while let Some(k) = it.next() {
2130 match k {
2131 KeyOrIdx::Key(s) => key_path.push_str(&key::escape_key_part(&s)),
2132 KeyOrIdx::Idx(i) => key_path.push_str(&format!("[{i}]")),
2133 }
2134 if matches!(it.peek(), Some(KeyOrIdx::Key(_))) {
2135 key_path.push('.');
2136 }
2137 }
2138 format!("failed to parse config at `{key_path}`")
2139 })
2140 }
2141
2142 fn from_toml_inner(
2143 def: Definition,
2144 toml: toml::Value,
2145 path: &mut Vec<KeyOrIdx>,
2146 ) -> CargoResult<ConfigValue> {
2147 match toml {
2148 toml::Value::String(val) => Ok(CV::String(val, def)),
2149 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2150 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2151 toml::Value::Array(val) => Ok(CV::List(
2152 val.into_iter()
2153 .enumerate()
2154 .map(|(i, toml)| {
2155 CV::from_toml_inner(def.clone(), toml, path)
2156 .inspect_err(|_| path.push(KeyOrIdx::Idx(i)))
2157 })
2158 .collect::<CargoResult<_>>()?,
2159 def,
2160 )),
2161 toml::Value::Table(val) => Ok(CV::Table(
2162 val.into_iter()
2163 .map(
2164 |(key, value)| match CV::from_toml_inner(def.clone(), value, path) {
2165 Ok(value) => Ok((key, value)),
2166 Err(e) => {
2167 path.push(KeyOrIdx::Key(key));
2168 Err(e)
2169 }
2170 },
2171 )
2172 .collect::<CargoResult<_>>()?,
2173 def,
2174 )),
2175 v => bail!("unsupported TOML configuration type `{}`", v.type_str()),
2176 }
2177 }
2178
2179 fn into_toml(self) -> toml::Value {
2180 match self {
2181 CV::Boolean(s, _) => toml::Value::Boolean(s),
2182 CV::String(s, _) => toml::Value::String(s),
2183 CV::Integer(i, _) => toml::Value::Integer(i),
2184 CV::List(l, _) => toml::Value::Array(l.into_iter().map(|cv| cv.into_toml()).collect()),
2185 CV::Table(l, _) => {
2186 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2187 }
2188 }
2189 }
2190
2191 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2200 self.merge_helper(from, force, &mut ConfigKey::new())
2201 }
2202
2203 fn merge_helper(
2204 &mut self,
2205 from: ConfigValue,
2206 force: bool,
2207 parts: &mut ConfigKey,
2208 ) -> CargoResult<()> {
2209 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2210 match (self, from) {
2211 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2212 if is_nonmergable_list(&parts) {
2213 if force || is_higher_priority {
2215 mem::swap(new, old);
2216 }
2217 } else {
2218 if force {
2220 old.append(new);
2221 } else {
2222 new.append(old);
2223 mem::swap(new, old);
2224 }
2225 }
2226 old.sort_by(|a, b| a.definition().cmp(b.definition()));
2227 }
2228 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2229 for (key, value) in mem::take(new) {
2230 match old.entry(key.clone()) {
2231 Occupied(mut entry) => {
2232 let new_def = value.definition().clone();
2233 let entry = entry.get_mut();
2234 parts.push(&key);
2235 entry.merge_helper(value, force, parts).with_context(|| {
2236 format!(
2237 "failed to merge key `{}` between \
2238 {} and {}",
2239 key,
2240 entry.definition(),
2241 new_def,
2242 )
2243 })?;
2244 }
2245 Vacant(entry) => {
2246 entry.insert(value);
2247 }
2248 };
2249 }
2250 }
2251 (expected @ &mut CV::List(_, _), found)
2253 | (expected @ &mut CV::Table(_, _), found)
2254 | (expected, found @ CV::List(_, _))
2255 | (expected, found @ CV::Table(_, _)) => {
2256 return Err(anyhow!(
2257 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2258 found.definition(),
2259 expected.definition(),
2260 expected.desc(),
2261 found.desc()
2262 ));
2263 }
2264 (old, mut new) => {
2265 if force || is_higher_priority {
2266 mem::swap(old, &mut new);
2267 }
2268 }
2269 }
2270
2271 Ok(())
2272 }
2273
2274 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2275 match self {
2276 CV::Integer(i, def) => Ok((*i, def)),
2277 _ => self.expected("integer", key),
2278 }
2279 }
2280
2281 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2282 match self {
2283 CV::String(s, def) => Ok((s, def)),
2284 _ => self.expected("string", key),
2285 }
2286 }
2287
2288 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2289 match self {
2290 CV::Table(table, def) => Ok((table, def)),
2291 _ => self.expected("table", key),
2292 }
2293 }
2294
2295 pub fn string_list(&self, key: &str) -> CargoResult<Vec<(String, Definition)>> {
2296 match self {
2297 CV::List(list, _) => list
2298 .iter()
2299 .map(|cv| match cv {
2300 CV::String(s, def) => Ok((s.clone(), def.clone())),
2301 _ => self.expected("string", key),
2302 })
2303 .collect::<CargoResult<_>>(),
2304 _ => self.expected("list", key),
2305 }
2306 }
2307
2308 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2309 match self {
2310 CV::Boolean(b, def) => Ok((*b, def)),
2311 _ => self.expected("bool", key),
2312 }
2313 }
2314
2315 pub fn desc(&self) -> &'static str {
2316 match *self {
2317 CV::Table(..) => "table",
2318 CV::List(..) => "array",
2319 CV::String(..) => "string",
2320 CV::Boolean(..) => "boolean",
2321 CV::Integer(..) => "integer",
2322 }
2323 }
2324
2325 pub fn definition(&self) -> &Definition {
2326 match self {
2327 CV::Boolean(_, def)
2328 | CV::Integer(_, def)
2329 | CV::String(_, def)
2330 | CV::List(_, def)
2331 | CV::Table(_, def) => def,
2332 }
2333 }
2334
2335 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2336 bail!(
2337 "expected a {}, but found a {} for `{}` in {}",
2338 wanted,
2339 self.desc(),
2340 key,
2341 self.definition()
2342 )
2343 }
2344}
2345
2346fn is_nonmergable_list(key: &ConfigKey) -> bool {
2349 key.matches("registry.credential-provider")
2350 || key.matches("registries.*.credential-provider")
2351 || key.matches("target.*.runner")
2352 || key.matches("host.runner")
2353 || key.matches("credential-alias.*")
2354 || key.matches("doc.browser")
2355}
2356
2357pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2358 ::home::cargo_home_with_cwd(cwd).ok()
2359}
2360
2361pub fn save_credentials(
2362 gctx: &GlobalContext,
2363 token: Option<RegistryCredentialConfig>,
2364 registry: &SourceId,
2365) -> CargoResult<()> {
2366 let registry = if registry.is_crates_io() {
2367 None
2368 } else {
2369 let name = registry
2370 .alt_registry_key()
2371 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2372 Some(name)
2373 };
2374
2375 let home_path = gctx.home_path.clone().into_path_unlocked();
2379 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2380 Some(path) => match path.file_name() {
2381 Some(filename) => Path::new(filename).to_owned(),
2382 None => Path::new("credentials.toml").to_owned(),
2383 },
2384 None => Path::new("credentials.toml").to_owned(),
2385 };
2386
2387 let mut file = {
2388 gctx.home_path.create_dir()?;
2389 gctx.home_path
2390 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2391 };
2392
2393 let mut contents = String::new();
2394 file.read_to_string(&mut contents).with_context(|| {
2395 format!(
2396 "failed to read configuration file `{}`",
2397 file.path().display()
2398 )
2399 })?;
2400
2401 let mut toml = parse_document(&contents, file.path(), gctx)?;
2402
2403 if let Some(token) = toml.remove("token") {
2405 let map = HashMap::from([("token".to_string(), token)]);
2406 toml.insert("registry".into(), map.into());
2407 }
2408
2409 if let Some(token) = token {
2410 let path_def = Definition::Path(file.path().to_path_buf());
2413 let (key, mut value) = match token {
2414 RegistryCredentialConfig::Token(token) => {
2415 let key = "token".to_string();
2418 let value = ConfigValue::String(token.expose(), path_def.clone());
2419 let map = HashMap::from([(key, value)]);
2420 let table = CV::Table(map, path_def.clone());
2421
2422 if let Some(registry) = registry {
2423 let map = HashMap::from([(registry.to_string(), table)]);
2424 ("registries".into(), CV::Table(map, path_def.clone()))
2425 } else {
2426 ("registry".into(), table)
2427 }
2428 }
2429 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2430 let key = "secret-key".to_string();
2433 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2434 let mut map = HashMap::from([(key, value)]);
2435 if let Some(key_subject) = key_subject {
2436 let key = "secret-key-subject".to_string();
2437 let value = ConfigValue::String(key_subject, path_def.clone());
2438 map.insert(key, value);
2439 }
2440 let table = CV::Table(map, path_def.clone());
2441
2442 if let Some(registry) = registry {
2443 let map = HashMap::from([(registry.to_string(), table)]);
2444 ("registries".into(), CV::Table(map, path_def.clone()))
2445 } else {
2446 ("registry".into(), table)
2447 }
2448 }
2449 _ => unreachable!(),
2450 };
2451
2452 if registry.is_some() {
2453 if let Some(table) = toml.remove("registries") {
2454 let v = CV::from_toml(path_def, table)?;
2455 value.merge(v, false)?;
2456 }
2457 }
2458 toml.insert(key, value.into_toml());
2459 } else {
2460 if let Some(registry) = registry {
2462 if let Some(registries) = toml.get_mut("registries") {
2463 if let Some(reg) = registries.get_mut(registry) {
2464 let rtable = reg.as_table_mut().ok_or_else(|| {
2465 format_err!("expected `[registries.{}]` to be a table", registry)
2466 })?;
2467 rtable.remove("token");
2468 rtable.remove("secret-key");
2469 rtable.remove("secret-key-subject");
2470 }
2471 }
2472 } else if let Some(registry) = toml.get_mut("registry") {
2473 let reg_table = registry
2474 .as_table_mut()
2475 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2476 reg_table.remove("token");
2477 reg_table.remove("secret-key");
2478 reg_table.remove("secret-key-subject");
2479 }
2480 }
2481
2482 let contents = toml.to_string();
2483 file.seek(SeekFrom::Start(0))?;
2484 file.write_all(contents.as_bytes())
2485 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2486 file.file().set_len(contents.len() as u64)?;
2487 set_permissions(file.file(), 0o600)
2488 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2489
2490 return Ok(());
2491
2492 #[cfg(unix)]
2493 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2494 use std::os::unix::fs::PermissionsExt;
2495
2496 let mut perms = file.metadata()?.permissions();
2497 perms.set_mode(mode);
2498 file.set_permissions(perms)?;
2499 Ok(())
2500 }
2501
2502 #[cfg(not(unix))]
2503 #[allow(unused)]
2504 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2505 Ok(())
2506 }
2507}
2508
2509struct ConfigInclude {
2515 path: PathBuf,
2518 def: Definition,
2519 optional: bool,
2521}
2522
2523impl ConfigInclude {
2524 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2525 Self {
2526 path: p.into(),
2527 def,
2528 optional: false,
2529 }
2530 }
2531
2532 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2545 let abs_path = match &self.def {
2546 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2547 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2548 }
2549 .join(&self.path);
2550
2551 if self.optional && !abs_path.exists() {
2552 tracing::info!(
2553 "skipping optional include `{}` in `{}`: file not found at `{}`",
2554 self.path.display(),
2555 self.def,
2556 abs_path.display(),
2557 );
2558 None
2559 } else {
2560 Some(abs_path)
2561 }
2562 }
2563}
2564
2565#[derive(Debug, Default, Deserialize, PartialEq)]
2566#[serde(rename_all = "kebab-case")]
2567pub struct CargoHttpConfig {
2568 pub proxy: Option<String>,
2569 pub low_speed_limit: Option<u32>,
2570 pub timeout: Option<u64>,
2571 pub cainfo: Option<ConfigRelativePath>,
2572 pub proxy_cainfo: Option<ConfigRelativePath>,
2573 pub check_revoke: Option<bool>,
2574 pub user_agent: Option<String>,
2575 pub debug: Option<bool>,
2576 pub multiplexing: Option<bool>,
2577 pub ssl_version: Option<SslVersionConfig>,
2578}
2579
2580#[derive(Debug, Default, Deserialize, PartialEq)]
2581#[serde(rename_all = "kebab-case")]
2582pub struct CargoFutureIncompatConfig {
2583 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2584}
2585
2586#[derive(Debug, Default, Deserialize, PartialEq)]
2587#[serde(rename_all = "kebab-case")]
2588pub enum CargoFutureIncompatFrequencyConfig {
2589 #[default]
2590 Always,
2591 Never,
2592}
2593
2594impl CargoFutureIncompatConfig {
2595 pub fn should_display_message(&self) -> bool {
2596 use CargoFutureIncompatFrequencyConfig::*;
2597
2598 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2599 match frequency {
2600 Always => true,
2601 Never => false,
2602 }
2603 }
2604}
2605
2606#[derive(Clone, Debug, PartialEq)]
2620pub enum SslVersionConfig {
2621 Single(String),
2622 Range(SslVersionConfigRange),
2623}
2624
2625impl<'de> Deserialize<'de> for SslVersionConfig {
2626 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2627 where
2628 D: serde::Deserializer<'de>,
2629 {
2630 UntaggedEnumVisitor::new()
2631 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2632 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2633 .deserialize(deserializer)
2634 }
2635}
2636
2637#[derive(Clone, Debug, Deserialize, PartialEq)]
2638#[serde(rename_all = "kebab-case")]
2639pub struct SslVersionConfigRange {
2640 pub min: Option<String>,
2641 pub max: Option<String>,
2642}
2643
2644#[derive(Debug, Deserialize)]
2645#[serde(rename_all = "kebab-case")]
2646pub struct CargoNetConfig {
2647 pub retry: Option<u32>,
2648 pub offline: Option<bool>,
2649 pub git_fetch_with_cli: Option<bool>,
2650 pub ssh: Option<CargoSshConfig>,
2651}
2652
2653#[derive(Debug, Deserialize)]
2654#[serde(rename_all = "kebab-case")]
2655pub struct CargoSshConfig {
2656 pub known_hosts: Option<Vec<Value<String>>>,
2657}
2658
2659#[derive(Debug, Clone)]
2672pub enum JobsConfig {
2673 Integer(i32),
2674 String(String),
2675}
2676
2677impl<'de> Deserialize<'de> for JobsConfig {
2678 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2679 where
2680 D: serde::Deserializer<'de>,
2681 {
2682 UntaggedEnumVisitor::new()
2683 .i32(|int| Ok(JobsConfig::Integer(int)))
2684 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2685 .deserialize(deserializer)
2686 }
2687}
2688
2689#[derive(Debug, Deserialize)]
2690#[serde(rename_all = "kebab-case")]
2691pub struct CargoBuildConfig {
2692 pub pipelining: Option<bool>,
2694 pub dep_info_basedir: Option<ConfigRelativePath>,
2695 pub target_dir: Option<ConfigRelativePath>,
2696 pub build_dir: Option<ConfigRelativePath>,
2697 pub incremental: Option<bool>,
2698 pub target: Option<BuildTargetConfig>,
2699 pub jobs: Option<JobsConfig>,
2700 pub rustflags: Option<StringList>,
2701 pub rustdocflags: Option<StringList>,
2702 pub rustc_wrapper: Option<ConfigRelativePath>,
2703 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2704 pub rustc: Option<ConfigRelativePath>,
2705 pub rustdoc: Option<ConfigRelativePath>,
2706 pub out_dir: Option<ConfigRelativePath>,
2708 pub artifact_dir: Option<ConfigRelativePath>,
2709 pub warnings: Option<WarningHandling>,
2710 pub sbom: Option<bool>,
2712 pub analysis: Option<CargoBuildAnalysis>,
2714}
2715
2716#[derive(Debug, Deserialize, Default)]
2718#[serde(rename_all = "kebab-case")]
2719pub struct CargoBuildAnalysis {
2720 pub enabled: bool,
2721}
2722
2723#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2725#[serde(rename_all = "kebab-case")]
2726pub enum WarningHandling {
2727 #[default]
2728 Warn,
2730 Allow,
2732 Deny,
2734}
2735
2736#[derive(Debug, Deserialize)]
2746#[serde(transparent)]
2747pub struct BuildTargetConfig {
2748 inner: Value<BuildTargetConfigInner>,
2749}
2750
2751#[derive(Debug)]
2752enum BuildTargetConfigInner {
2753 One(String),
2754 Many(Vec<String>),
2755}
2756
2757impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2758 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2759 where
2760 D: serde::Deserializer<'de>,
2761 {
2762 UntaggedEnumVisitor::new()
2763 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2764 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2765 .deserialize(deserializer)
2766 }
2767}
2768
2769impl BuildTargetConfig {
2770 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2772 let map = |s: &String| {
2773 if s.ends_with(".json") {
2774 self.inner
2777 .definition
2778 .root(gctx)
2779 .join(s)
2780 .to_str()
2781 .expect("must be utf-8 in toml")
2782 .to_string()
2783 } else {
2784 s.to_string()
2786 }
2787 };
2788 let values = match &self.inner.val {
2789 BuildTargetConfigInner::One(s) => vec![map(s)],
2790 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2791 };
2792 Ok(values)
2793 }
2794}
2795
2796#[derive(Debug, Deserialize)]
2797#[serde(rename_all = "kebab-case")]
2798pub struct CargoResolverConfig {
2799 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2800 pub feature_unification: Option<FeatureUnification>,
2801}
2802
2803#[derive(Debug, Deserialize, PartialEq, Eq)]
2804#[serde(rename_all = "kebab-case")]
2805pub enum IncompatibleRustVersions {
2806 Allow,
2807 Fallback,
2808}
2809
2810#[derive(Copy, Clone, Debug, Deserialize)]
2811#[serde(rename_all = "kebab-case")]
2812pub enum FeatureUnification {
2813 Package,
2814 Selected,
2815 Workspace,
2816}
2817
2818#[derive(Deserialize, Default)]
2819#[serde(rename_all = "kebab-case")]
2820pub struct TermConfig {
2821 pub verbose: Option<bool>,
2822 pub quiet: Option<bool>,
2823 pub color: Option<String>,
2824 pub hyperlinks: Option<bool>,
2825 pub unicode: Option<bool>,
2826 #[serde(default)]
2827 #[serde(deserialize_with = "progress_or_string")]
2828 pub progress: Option<ProgressConfig>,
2829}
2830
2831#[derive(Debug, Default, Deserialize)]
2832#[serde(rename_all = "kebab-case")]
2833pub struct ProgressConfig {
2834 #[serde(default)]
2835 pub when: ProgressWhen,
2836 pub width: Option<usize>,
2837 pub term_integration: Option<bool>,
2839}
2840
2841#[derive(Debug, Default, Deserialize)]
2842#[serde(rename_all = "kebab-case")]
2843pub enum ProgressWhen {
2844 #[default]
2845 Auto,
2846 Never,
2847 Always,
2848}
2849
2850fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2851where
2852 D: serde::de::Deserializer<'de>,
2853{
2854 struct ProgressVisitor;
2855
2856 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2857 type Value = Option<ProgressConfig>;
2858
2859 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2860 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2861 }
2862
2863 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2864 where
2865 E: serde::de::Error,
2866 {
2867 match s {
2868 "auto" => Ok(Some(ProgressConfig {
2869 when: ProgressWhen::Auto,
2870 width: None,
2871 term_integration: None,
2872 })),
2873 "never" => Ok(Some(ProgressConfig {
2874 when: ProgressWhen::Never,
2875 width: None,
2876 term_integration: None,
2877 })),
2878 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2879 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2880 }
2881 }
2882
2883 fn visit_none<E>(self) -> Result<Self::Value, E>
2884 where
2885 E: serde::de::Error,
2886 {
2887 Ok(None)
2888 }
2889
2890 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2891 where
2892 D: serde::de::Deserializer<'de>,
2893 {
2894 let pc = ProgressConfig::deserialize(deserializer)?;
2895 if let ProgressConfig {
2896 when: ProgressWhen::Always,
2897 width: None,
2898 ..
2899 } = pc
2900 {
2901 return Err(serde::de::Error::custom(
2902 "\"always\" progress requires a `width` key",
2903 ));
2904 }
2905 Ok(Some(pc))
2906 }
2907 }
2908
2909 deserializer.deserialize_option(ProgressVisitor)
2910}
2911
2912#[derive(Debug)]
2913enum EnvConfigValueInner {
2914 Simple(String),
2915 WithOptions {
2916 value: String,
2917 force: bool,
2918 relative: bool,
2919 },
2920}
2921
2922impl<'de> Deserialize<'de> for EnvConfigValueInner {
2923 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2924 where
2925 D: serde::Deserializer<'de>,
2926 {
2927 #[derive(Deserialize)]
2928 struct WithOptions {
2929 value: String,
2930 #[serde(default)]
2931 force: bool,
2932 #[serde(default)]
2933 relative: bool,
2934 }
2935
2936 UntaggedEnumVisitor::new()
2937 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2938 .map(|map| {
2939 let with_options: WithOptions = map.deserialize()?;
2940 Ok(EnvConfigValueInner::WithOptions {
2941 value: with_options.value,
2942 force: with_options.force,
2943 relative: with_options.relative,
2944 })
2945 })
2946 .deserialize(deserializer)
2947 }
2948}
2949
2950#[derive(Debug, Deserialize)]
2951#[serde(transparent)]
2952pub struct EnvConfigValue {
2953 inner: Value<EnvConfigValueInner>,
2954}
2955
2956impl EnvConfigValue {
2957 pub fn is_force(&self) -> bool {
2958 match self.inner.val {
2959 EnvConfigValueInner::Simple(_) => false,
2960 EnvConfigValueInner::WithOptions { force, .. } => force,
2961 }
2962 }
2963
2964 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
2965 match self.inner.val {
2966 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
2967 EnvConfigValueInner::WithOptions {
2968 ref value,
2969 relative,
2970 ..
2971 } => {
2972 if relative {
2973 let p = self.inner.definition.root(gctx).join(&value);
2974 Cow::Owned(p.into_os_string())
2975 } else {
2976 Cow::Borrowed(OsStr::new(value.as_str()))
2977 }
2978 }
2979 }
2980 }
2981}
2982
2983pub type EnvConfig = HashMap<String, EnvConfigValue>;
2984
2985fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2986 toml.parse().map_err(Into::into)
2988}
2989
2990fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2991 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2997 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2998 })?;
2999 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
3000 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
3001 }
3002 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
3003 non_empty(d.prefix()) || non_empty(d.suffix())
3004 }
3005 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
3006 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
3007 }
3008 let ok = {
3009 let mut got_to_value = false;
3010 let mut table = doc.as_table();
3011 let mut is_root = true;
3012 while table.is_dotted() || is_root {
3013 is_root = false;
3014 if table.len() != 1 {
3015 break;
3016 }
3017 let (k, n) = table.iter().next().expect("len() == 1 above");
3018 match n {
3019 Item::Table(nt) => {
3020 if table.key(k).map_or(false, non_empty_key_decor)
3021 || non_empty_decor(nt.decor())
3022 {
3023 bail!(
3024 "--config argument `{arg}` \
3025 includes non-whitespace decoration"
3026 )
3027 }
3028 table = nt;
3029 }
3030 Item::Value(v) if v.is_inline_table() => {
3031 bail!(
3032 "--config argument `{arg}` \
3033 sets a value to an inline table, which is not accepted"
3034 );
3035 }
3036 Item::Value(v) => {
3037 if table
3038 .key(k)
3039 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
3040 || non_empty_decor(v.decor())
3041 {
3042 bail!(
3043 "--config argument `{arg}` \
3044 includes non-whitespace decoration"
3045 )
3046 }
3047 got_to_value = true;
3048 break;
3049 }
3050 Item::ArrayOfTables(_) => {
3051 bail!(
3052 "--config argument `{arg}` \
3053 sets a value to an array of tables, which is not accepted"
3054 );
3055 }
3056
3057 Item::None => {
3058 bail!("--config argument `{arg}` doesn't provide a value")
3059 }
3060 }
3061 }
3062 got_to_value
3063 };
3064 if !ok {
3065 bail!(
3066 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
3067 );
3068 }
3069 Ok(doc)
3070}
3071
3072#[derive(Debug, Deserialize, Clone)]
3083pub struct StringList(Vec<String>);
3084
3085impl StringList {
3086 pub fn as_slice(&self) -> &[String] {
3087 &self.0
3088 }
3089}
3090
3091#[macro_export]
3092macro_rules! __shell_print {
3093 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
3094 let mut shell = $config.shell();
3095 let out = shell.$which();
3096 drop(out.write_fmt(format_args!($($arg)*)));
3097 if $newline {
3098 drop(out.write_all(b"\n"));
3099 }
3100 });
3101}
3102
3103#[macro_export]
3104macro_rules! drop_println {
3105 ($config:expr) => ( $crate::drop_print!($config, "\n") );
3106 ($config:expr, $($arg:tt)*) => (
3107 $crate::__shell_print!($config, out, true, $($arg)*)
3108 );
3109}
3110
3111#[macro_export]
3112macro_rules! drop_eprintln {
3113 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
3114 ($config:expr, $($arg:tt)*) => (
3115 $crate::__shell_print!($config, err, true, $($arg)*)
3116 );
3117}
3118
3119#[macro_export]
3120macro_rules! drop_print {
3121 ($config:expr, $($arg:tt)*) => (
3122 $crate::__shell_print!($config, out, false, $($arg)*)
3123 );
3124}
3125
3126#[macro_export]
3127macro_rules! drop_eprint {
3128 ($config:expr, $($arg:tt)*) => (
3129 $crate::__shell_print!($config, err, false, $($arg)*)
3130 );
3131}
3132
3133enum Tool {
3134 Rustc,
3135 Rustdoc,
3136}
3137
3138impl Tool {
3139 fn as_str(&self) -> &str {
3140 match self {
3141 Tool::Rustc => "rustc",
3142 Tool::Rustdoc => "rustdoc",
3143 }
3144 }
3145}
3146
3147fn disables_multiplexing_for_bad_curl(
3157 curl_version: &str,
3158 http: &mut CargoHttpConfig,
3159 gctx: &GlobalContext,
3160) {
3161 use crate::util::network;
3162
3163 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3164 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3165 if bad_curl_versions
3166 .iter()
3167 .any(|v| curl_version.starts_with(v))
3168 {
3169 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3170 http.multiplexing = Some(false);
3171 }
3172 }
3173}
3174
3175#[cfg(test)]
3176mod tests {
3177 use super::CargoHttpConfig;
3178 use super::GlobalContext;
3179 use super::Shell;
3180 use super::disables_multiplexing_for_bad_curl;
3181
3182 #[test]
3183 fn disables_multiplexing() {
3184 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3185 gctx.set_search_stop_path(std::path::PathBuf::new());
3186 gctx.set_env(Default::default());
3187
3188 let mut http = CargoHttpConfig::default();
3189 http.proxy = Some("127.0.0.1:3128".into());
3190 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3191 assert_eq!(http.multiplexing, Some(false));
3192
3193 let cases = [
3194 (None, None, "7.87.0", None),
3195 (None, None, "7.88.0", None),
3196 (None, None, "7.88.1", None),
3197 (None, None, "8.0.0", None),
3198 (Some("".into()), None, "7.87.0", Some(false)),
3199 (Some("".into()), None, "7.88.0", Some(false)),
3200 (Some("".into()), None, "7.88.1", Some(false)),
3201 (Some("".into()), None, "8.0.0", None),
3202 (Some("".into()), Some(false), "7.87.0", Some(false)),
3203 (Some("".into()), Some(false), "7.88.0", Some(false)),
3204 (Some("".into()), Some(false), "7.88.1", Some(false)),
3205 (Some("".into()), Some(false), "8.0.0", Some(false)),
3206 ];
3207
3208 for (proxy, multiplexing, curl_v, result) in cases {
3209 let mut http = CargoHttpConfig {
3210 multiplexing,
3211 proxy,
3212 ..Default::default()
3213 };
3214 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3215 assert_eq!(http.multiplexing, result);
3216 }
3217 }
3218
3219 #[test]
3220 fn sync_context() {
3221 fn assert_sync<S: Sync>() {}
3222 assert_sync::<GlobalContext>();
3223 }
3224}