1use crate::util::data_structures::{HashMap, HashSet};
65use std::borrow::Cow;
66use std::env;
67use std::ffi::{OsStr, OsString};
68use std::fmt;
69use std::fs::{self, File};
70use std::io::SeekFrom;
71use std::io::prelude::*;
72use std::mem;
73use std::path::{Path, PathBuf};
74use std::str::FromStr;
75use std::sync::{Arc, LazyLock, Mutex, MutexGuard, OnceLock};
76use std::time::Instant;
77
78use self::ConfigValue as CV;
79use crate::ops::RegistryCredentialConfig;
80use crate::sources::CRATES_IO_INDEX;
81use crate::sources::CRATES_IO_REGISTRY;
82use crate::util::OnceExt as _;
83use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
84use crate::util::errors::CargoResult;
85use crate::util::network::http::{HandleConfiguration, configure_http_handle, http_handle};
86use crate::util::network::http_async;
87use crate::util::restricted_names::is_glob_pattern;
88use crate::util::{CanonicalUrl, closest_msg, internal};
89use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
90use crate::workspace::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
91use crate::workspace::{CliUnstable, SourceId, Workspace, WorkspaceRootConfig, features};
92
93use anyhow::{Context as _, anyhow, bail, format_err};
94use cargo_credential::Secret;
95use cargo_util::paths;
96use cargo_util_schemas::manifest::RegistryName;
97use cargo_util_terminal::report::Level;
98use cargo_util_terminal::{Shell, Verbosity};
99use curl::easy::Easy;
100use itertools::Itertools;
101use serde::Deserialize;
102use serde::de::IntoDeserializer as _;
103use time::OffsetDateTime;
104use toml_edit::Item;
105use url::Url;
106
107mod de;
108use de::Deserializer;
109
110mod error;
111pub use error::ConfigError;
112
113mod value;
114pub use value::{Definition, OptValue, Value};
115
116mod key;
117pub use key::ConfigKey;
118
119mod config_value;
120pub use config_value::ConfigValue;
121use config_value::is_nonmergeable_list;
122
123mod path;
124pub use path::BracketType;
125pub use path::ConfigRelativePath;
126pub use path::PathAndArgs;
127pub use path::ResolveTemplateError;
128
129mod target;
130pub use target::{TargetCfgConfig, TargetConfig};
131
132mod environment;
133use environment::Env;
134
135mod schema;
136pub use schema::*;
137
138macro_rules! get_value_typed {
140 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
141 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
143 let cv = self.get_cv(key)?;
144 let env = self.get_config_env::<$ty>(key)?;
145 match (cv, env) {
146 (Some(CV::$variant(val, definition)), Some(env)) => {
147 if definition.is_higher_priority(&env.definition) {
148 Ok(Some(Value { val, definition }))
149 } else {
150 Ok(Some(env))
151 }
152 }
153 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
154 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
155 (None, Some(env)) => Ok(Some(env)),
156 (None, None) => Ok(None),
157 }
158 }
159 };
160}
161
162pub const TOP_LEVEL_CONFIG_KEYS: &[&str] = &[
163 "paths",
164 "alias",
165 "build",
166 "credential-alias",
167 "doc",
168 "env",
169 "future-incompat-report",
170 "cache",
171 "cargo-new",
172 "http",
173 "install",
174 "net",
175 "patch",
176 "profile",
177 "resolver",
178 "registries",
179 "registry",
180 "source",
181 "target",
182 "term",
183];
184
185#[derive(Clone, Copy, Debug)]
187enum WhyLoad {
188 Cli,
193 FileDiscovery,
195}
196
197#[derive(Debug)]
199pub struct CredentialCacheValue {
200 pub token_value: Secret<String>,
201 pub expiration: Option<OffsetDateTime>,
202 pub operation_independent: bool,
203}
204
205#[derive(Debug)]
208pub struct GlobalContext {
209 home_path: Filesystem,
211 shell: Mutex<Shell>,
213 values: OnceLock<HashMap<String, ConfigValue>>,
215 credential_values: OnceLock<HashMap<String, ConfigValue>>,
217 cli_config: Option<Vec<String>>,
219 cwd: PathBuf,
221 search_stop_path: Option<PathBuf>,
223 cargo_exe: OnceLock<PathBuf>,
225 rustdoc: OnceLock<PathBuf>,
227 extra_verbose: bool,
229 frozen: bool,
232 locked: bool,
235 offline: bool,
238 jobserver: Option<&'static jobserver::Client>,
240 unstable_flags: CliUnstable,
242 unstable_flags_cli: Option<Vec<String>>,
244 easy: OnceLock<Mutex<Easy>>,
246 crates_io_source_id: OnceLock<SourceId>,
248 cache_rustc_info: bool,
250 invocation_instant: Instant,
252 invocation_time: jiff::Timestamp,
256 target_dir: Option<Filesystem>,
258 env: Env,
260 updated_sources: Mutex<HashSet<SourceId>>,
262 credential_cache: Mutex<HashMap<CanonicalUrl, CredentialCacheValue>>,
265 registry_config: Mutex<HashMap<SourceId, Option<RegistryConfig>>>,
267 package_cache_lock: CacheLocker,
269 http_config: OnceLock<CargoHttpConfig>,
271 http_async: OnceLock<http_async::Client>,
272 future_incompat_config: OnceLock<CargoFutureIncompatConfig>,
273 net_config: OnceLock<CargoNetConfig>,
274 build_config: OnceLock<CargoBuildConfig>,
275 target_cfgs: OnceLock<Vec<(String, TargetCfgConfig)>>,
276 doc_extern_map: OnceLock<RustdocExternMap>,
277 progress_config: ProgressConfig,
278 env_config: OnceLock<Arc<HashMap<String, OsString>>>,
279 pub nightly_features_allowed: bool,
295 ws_roots: Mutex<HashMap<PathBuf, WorkspaceRootConfig>>,
297 global_cache_tracker: OnceLock<Mutex<GlobalCacheTracker>>,
299 deferred_global_last_use: OnceLock<Mutex<DeferredGlobalLastUse>>,
302}
303
304impl GlobalContext {
305 pub fn new(mut shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
313 static GLOBAL_JOBSERVER: LazyLock<CargoResult<Option<jobserver::Client>>> = LazyLock::new(
314 || {
315 use jobserver::FromEnvErrorKind;
316 let jobserver::FromEnv { client, var } =
322 unsafe { jobserver::Client::from_env_ext(true) };
323
324 match client {
325 Ok(client) => return Ok(Some(client)),
326 Err(e)
327 if matches!(
328 e.kind(),
329 FromEnvErrorKind::NoEnvVar
330 | FromEnvErrorKind::NoJobserver
331 | FromEnvErrorKind::NegativeFd
332 | FromEnvErrorKind::Unsupported
333 ) =>
334 {
335 Ok(None)
336 }
337 Err(e) => {
338 let (name, value) = var.unwrap();
339 Err(anyhow::anyhow!(
340 "failed to connect to jobserver from environment variable `{name}={value:?}`: {e}"
341 ))
342 }
343 }
344 },
345 );
346 let jobserver = match &*GLOBAL_JOBSERVER {
347 Ok(jobserver) => jobserver.as_ref(),
348 Err(e) => {
349 let _ = shell.warn(e);
350 None
351 }
352 };
353
354 let env = Env::new();
355
356 let cache_key = "CARGO_CACHE_RUSTC_INFO";
357 let cache_rustc_info = match env.get_env_os(cache_key) {
358 Some(cache) => cache != "0",
359 _ => true,
360 };
361
362 #[expect(
363 clippy::disallowed_methods,
364 reason = "testing only, no reason for config support"
365 )]
366 let invocation_time = match env::var("__CARGO_TEST_INVOCATION_TIME") {
367 Ok(now) => now.parse().unwrap(),
368 Err(_) => jiff::Timestamp::now(),
369 };
370
371 GlobalContext {
372 home_path: Filesystem::new(homedir),
373 shell: Mutex::new(shell),
374 cwd,
375 search_stop_path: None,
376 values: Default::default(),
377 credential_values: Default::default(),
378 cli_config: None,
379 cargo_exe: Default::default(),
380 rustdoc: Default::default(),
381 extra_verbose: false,
382 frozen: false,
383 locked: false,
384 offline: false,
385 jobserver,
386 unstable_flags: CliUnstable::default(),
387 unstable_flags_cli: None,
388 easy: Default::default(),
389 crates_io_source_id: Default::default(),
390 cache_rustc_info,
391 invocation_instant: Instant::now(),
392 invocation_time,
393 target_dir: None,
394 env,
395 updated_sources: Default::default(),
396 credential_cache: Default::default(),
397 registry_config: Default::default(),
398 package_cache_lock: CacheLocker::new(),
399 http_config: Default::default(),
400 http_async: Default::default(),
401 future_incompat_config: Default::default(),
402 net_config: Default::default(),
403 build_config: Default::default(),
404 target_cfgs: Default::default(),
405 doc_extern_map: Default::default(),
406 progress_config: ProgressConfig::default(),
407 env_config: Default::default(),
408 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
409 ws_roots: Default::default(),
410 global_cache_tracker: Default::default(),
411 deferred_global_last_use: Default::default(),
412 }
413 }
414
415 pub fn default() -> CargoResult<GlobalContext> {
420 let shell = Shell::new();
421 let cwd =
422 env::current_dir().context("couldn't get the current directory of the process")?;
423 let homedir = homedir(&cwd).ok_or_else(|| {
424 anyhow!(
425 "Cargo couldn't find your home directory. \
426 This probably means that $HOME was not set."
427 )
428 })?;
429 Ok(GlobalContext::new(shell, cwd, homedir))
430 }
431
432 pub fn home(&self) -> &Filesystem {
434 &self.home_path
435 }
436
437 pub fn diagnostic_home_config(&self) -> String {
441 let home = self.home_path.as_path_unlocked();
442 let path = match self.get_file_path(home, "config", false) {
443 Ok(Some(existing_path)) => existing_path,
444 _ => home.join("config.toml"),
445 };
446 path.to_string_lossy().to_string()
447 }
448
449 pub fn git_path(&self) -> Filesystem {
451 self.home_path.join("git")
452 }
453
454 pub fn git_checkouts_path(&self) -> Filesystem {
457 self.git_path().join("checkouts")
458 }
459
460 pub fn git_db_path(&self) -> Filesystem {
463 self.git_path().join("db")
464 }
465
466 pub fn registry_base_path(&self) -> Filesystem {
468 self.home_path.join("registry")
469 }
470
471 pub fn registry_index_path(&self) -> Filesystem {
473 self.registry_base_path().join("index")
474 }
475
476 pub fn registry_cache_path(&self) -> Filesystem {
478 self.registry_base_path().join("cache")
479 }
480
481 pub fn registry_source_path(&self) -> Filesystem {
483 self.registry_base_path().join("src")
484 }
485
486 pub fn default_registry(&self) -> CargoResult<Option<String>> {
488 Ok(self
489 .get_string(["registry", "default"])?
490 .map(|registry| registry.val))
491 }
492
493 pub fn shell(&self) -> MutexGuard<'_, Shell> {
495 self.shell.lock().unwrap()
496 }
497
498 pub fn debug_assert_shell_not_borrowed(&self) {
504 if cfg!(debug_assertions) {
505 match self.shell.try_lock() {
506 Ok(_) | Err(std::sync::TryLockError::Poisoned(_)) => (),
507 Err(std::sync::TryLockError::WouldBlock) => panic!("shell is borrowed!"),
508 }
509 }
510 }
511
512 pub fn rustdoc(&self) -> CargoResult<&Path> {
514 self.rustdoc
515 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
516 .map(AsRef::as_ref)
517 }
518
519 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
521 let cache_location =
522 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
523 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
524 let rustc_workspace_wrapper = self.maybe_get_tool(
525 "rustc_workspace_wrapper",
526 &self.build_config()?.rustc_workspace_wrapper,
527 );
528
529 Rustc::new(
530 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
531 wrapper,
532 rustc_workspace_wrapper,
533 &self
534 .home()
535 .join("bin")
536 .join("rustc")
537 .into_path_unlocked()
538 .with_extension(env::consts::EXE_EXTENSION),
539 if self.cache_rustc_info {
540 cache_location
541 } else {
542 None
543 },
544 self,
545 )
546 }
547
548 pub fn cargo_exe(&self) -> CargoResult<&Path> {
550 self.cargo_exe
551 .try_borrow_with(|| {
552 let from_env = || -> CargoResult<PathBuf> {
553 let exe = self
558 .get_env_os(crate::CARGO_ENV)
559 .map(PathBuf::from)
560 .ok_or_else(|| anyhow!("$CARGO not set"))?;
561 Ok(exe)
562 };
563
564 fn from_current_exe() -> CargoResult<PathBuf> {
565 let exe = env::current_exe()?;
570 Ok(exe)
571 }
572
573 fn from_argv() -> CargoResult<PathBuf> {
574 let argv0 = env::args_os()
581 .map(PathBuf::from)
582 .next()
583 .ok_or_else(|| anyhow!("no argv[0]"))?;
584 paths::resolve_executable(&argv0)
585 }
586
587 fn is_cargo(path: &Path) -> bool {
590 path.file_stem() == Some(OsStr::new("cargo"))
591 }
592
593 let from_current_exe = from_current_exe();
594 if from_current_exe.as_deref().is_ok_and(is_cargo) {
595 return from_current_exe;
596 }
597
598 let from_argv = from_argv();
599 if from_argv.as_deref().is_ok_and(is_cargo) {
600 return from_argv;
601 }
602
603 let exe = from_env()
604 .or(from_current_exe)
605 .or(from_argv)
606 .context("couldn't get the path to cargo executable")?;
607 Ok(exe)
608 })
609 .map(AsRef::as_ref)
610 }
611
612 pub fn updated_sources(&self) -> MutexGuard<'_, HashSet<SourceId>> {
614 self.updated_sources.lock().unwrap()
615 }
616
617 pub fn credential_cache(&self) -> MutexGuard<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
619 self.credential_cache.lock().unwrap()
620 }
621
622 pub(crate) fn registry_config(
624 &self,
625 ) -> MutexGuard<'_, HashMap<SourceId, Option<RegistryConfig>>> {
626 self.registry_config.lock().unwrap()
627 }
628
629 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
635 self.values.try_borrow_with(|| self.load_values())
636 }
637
638 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
645 let _ = self.values()?;
646 Ok(self.values.get_mut().expect("already loaded config values"))
647 }
648
649 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
651 if self.values.get().is_some() {
652 bail!("config values already found")
653 }
654 match self.values.set(values.into()) {
655 Ok(()) => Ok(()),
656 Err(_) => bail!("could not fill values"),
657 }
658 }
659
660 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
663 let path = path.into();
664 debug_assert!(self.cwd.starts_with(&path));
665 self.search_stop_path = Some(path);
666 }
667
668 pub fn reload_cwd(&mut self) -> CargoResult<()> {
672 let cwd =
673 env::current_dir().context("couldn't get the current directory of the process")?;
674 let homedir = homedir(&cwd).ok_or_else(|| {
675 anyhow!(
676 "Cargo couldn't find your home directory. \
677 This probably means that $HOME was not set."
678 )
679 })?;
680
681 self.cwd = cwd;
682 self.home_path = Filesystem::new(homedir);
683 self.reload_rooted_at(self.cwd.clone())?;
684 Ok(())
685 }
686
687 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
690 let values = self.load_values_from(path.as_ref())?;
691 self.values.replace(values);
692 self.merge_cli_args()?;
693 self.load_unstable_flags_from_config()?;
694 Ok(())
695 }
696
697 pub fn cwd(&self) -> &Path {
699 &self.cwd
700 }
701
702 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
708 if let Some(dir) = &self.target_dir {
709 Ok(Some(dir.clone()))
710 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
711 if dir.is_empty() {
713 bail!(
714 "the target directory is set to an empty string in the \
715 `CARGO_TARGET_DIR` environment variable"
716 )
717 }
718
719 Ok(Some(Filesystem::new(self.cwd.join(dir))))
720 } else if let Some(val) = &self.build_config()?.target_dir {
721 let path = val.resolve_path(self);
722
723 if val.raw_value().is_empty() {
725 bail!(
726 "the target directory is set to an empty string in {}",
727 val.value().definition
728 )
729 }
730
731 Ok(Some(Filesystem::new(path)))
732 } else {
733 Ok(None)
734 }
735 }
736
737 pub fn build_dir(&self, workspace_manifest_path: &Path) -> CargoResult<Option<Filesystem>> {
741 let Some(val) = &self.build_config()?.build_dir else {
742 return Ok(None);
743 };
744 self.custom_build_dir(val, workspace_manifest_path)
745 .map(Some)
746 }
747
748 pub fn custom_build_dir(
752 &self,
753 val: &ConfigRelativePath,
754 workspace_manifest_path: &Path,
755 ) -> CargoResult<Filesystem> {
756 let replacements = [
757 (
758 "{workspace-root}",
759 workspace_manifest_path
760 .parent()
761 .unwrap()
762 .to_str()
763 .context("workspace root was not valid utf-8")?
764 .to_string(),
765 ),
766 (
767 "{cargo-cache-home}",
768 self.home()
769 .as_path_unlocked()
770 .to_str()
771 .context("cargo home was not valid utf-8")?
772 .to_string(),
773 ),
774 ("{workspace-path-hash}", {
775 let real_path = std::fs::canonicalize(workspace_manifest_path)
776 .unwrap_or_else(|_err| workspace_manifest_path.to_owned());
777 let hash = crate::util::hex::short_hash(&real_path);
778 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
779 }),
780 ];
781
782 let template_variables = replacements
783 .iter()
784 .map(|(key, _)| key[1..key.len() - 1].to_string())
785 .collect_vec();
786
787 let path = val
788 .resolve_templated_path(self, replacements)
789 .map_err(|e| match e {
790 path::ResolveTemplateError::UnexpectedVariable {
791 variable,
792 raw_template,
793 } => {
794 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
795 if suggestion == "" {
796 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
797 suggestion = format!("\n\nhelp: available template variables are {variables}");
798 }
799 anyhow!(
800 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
801 )
802 }
803 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
804 let (btype, literal) = match bracket_type {
805 path::BracketType::Opening => ("opening", "{"),
806 path::BracketType::Closing => ("closing", "}"),
807 };
808
809 anyhow!(
810 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
811 )
812 }
813 })?;
814
815 if val.raw_value().is_empty() {
817 bail!(
818 "the build directory is set to an empty string in {}",
819 val.value().definition
820 )
821 }
822
823 Ok(Filesystem::new(path))
824 }
825
826 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
831 if let Some(vals) = self.credential_values.get() {
832 let val = self.get_cv_helper(key, vals)?;
833 if val.is_some() {
834 return Ok(val);
835 }
836 }
837 self.get_cv_helper(key, &*self.values()?)
838 }
839
840 fn get_cv_helper(
841 &self,
842 key: &ConfigKey,
843 vals: &HashMap<String, ConfigValue>,
844 ) -> CargoResult<Option<ConfigValue>> {
845 tracing::trace!("get cv {:?}", key);
846 if key.is_root() {
847 return Ok(Some(CV::Table(
850 vals.clone(),
851 Definition::Path(PathBuf::new()),
852 )));
853 }
854 let mut parts = key.parts().enumerate();
855 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
856 return Ok(None);
857 };
858 for (i, part) in parts {
859 match val {
860 CV::Table(map, _) => {
861 val = match map.get(part) {
862 Some(val) => val,
863 None => return Ok(None),
864 }
865 }
866 CV::Integer(_, def)
867 | CV::String(_, def)
868 | CV::List(_, def)
869 | CV::Boolean(_, def) => {
870 let key_so_far = ConfigKey::from_parts(key.parts().take(i));
871 bail!(
872 "expected table for configuration key `{}`, \
873 but found {} in {}",
874 key_so_far,
875 val.desc(),
876 def
877 )
878 }
879 }
880 }
881 Ok(Some(val.clone()))
882 }
883
884 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
886 let cv = self.get_cv(key)?;
889 if key.is_root() {
890 return Ok(cv);
892 }
893 let env = self.env.get_str(key.as_env_key());
894 let env_def = Definition::Environment(key.as_env_key().to_string());
895 let use_env = match (&cv, env) {
896 (Some(CV::List(..)), Some(_)) => true,
898 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
899 (None, Some(_)) => true,
900 _ => false,
901 };
902
903 if !use_env {
904 return Ok(cv);
905 }
906
907 let env = env.unwrap();
911 if env == "true" {
912 Ok(Some(CV::Boolean(true, env_def)))
913 } else if env == "false" {
914 Ok(Some(CV::Boolean(false, env_def)))
915 } else if let Ok(i) = env.parse::<i64>() {
916 Ok(Some(CV::Integer(i, env_def)))
917 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
918 match cv {
919 Some(CV::List(mut cv_list, cv_def)) => {
920 self.get_env_list(key, &mut cv_list)?;
922 Ok(Some(CV::List(cv_list, cv_def)))
923 }
924 Some(cv) => {
925 bail!(
929 "unable to merge array env for config `{}`\n\
930 file: {:?}\n\
931 env: {}",
932 key,
933 cv,
934 env
935 );
936 }
937 None => {
938 let mut cv_list = Vec::new();
939 self.get_env_list(key, &mut cv_list)?;
940 Ok(Some(CV::List(cv_list, env_def)))
941 }
942 }
943 } else {
944 match cv {
946 Some(CV::List(mut cv_list, cv_def)) => {
947 self.get_env_list(key, &mut cv_list)?;
949 Ok(Some(CV::List(cv_list, cv_def)))
950 }
951 _ => {
952 Ok(Some(CV::String(env.to_string(), env_def)))
957 }
958 }
959 }
960 }
961
962 pub fn set_env(&mut self, env: HashMap<String, String>) {
964 self.env = Env::from_map(env);
965 }
966
967 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
970 self.env.iter_str()
971 }
972
973 fn env_keys(&self) -> impl Iterator<Item = &str> {
975 self.env.keys_str()
976 }
977
978 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
979 where
980 T: FromStr,
981 <T as FromStr>::Err: fmt::Display,
982 {
983 match self.env.get_str(key.as_env_key()) {
984 Some(value) => {
985 let definition = Definition::Environment(key.as_env_key().to_string());
986 Ok(Some(Value {
987 val: value
988 .parse()
989 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
990 definition,
991 }))
992 }
993 None => {
994 self.check_environment_key_case_mismatch(key);
995 Ok(None)
996 }
997 }
998 }
999
1000 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
1005 self.env.get_env(key)
1006 }
1007
1008 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
1013 self.env.get_env_os(key)
1014 }
1015
1016 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
1020 if self.env.contains_key(key.as_env_key()) {
1021 return Ok(true);
1022 }
1023 if env_prefix_ok {
1024 let env_prefix = format!("{}_", key.as_env_key());
1025 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
1026 return Ok(true);
1027 }
1028 }
1029 if self.get_cv(key)?.is_some() {
1030 return Ok(true);
1031 }
1032 self.check_environment_key_case_mismatch(key);
1033
1034 Ok(false)
1035 }
1036
1037 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
1038 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
1039 let _ = self.shell().warn(format!(
1040 "environment variables are expected to use uppercase letters and underscores, \
1041 the variable `{}` will be ignored and have no effect",
1042 env_key
1043 ));
1044 }
1045 }
1046
1047 pub fn get_string(
1051 &self,
1052 key: impl IntoIterator<Item = impl AsRef<str>>,
1053 ) -> CargoResult<OptValue<String>> {
1054 self.get::<OptValue<String>>(key)
1055 }
1056
1057 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
1058 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
1059 if is_path {
1060 definition.root(self.cwd()).join(value)
1061 } else {
1062 PathBuf::from(value)
1064 }
1065 }
1066
1067 fn get_env_list(&self, key: &ConfigKey, output: &mut Vec<ConfigValue>) -> CargoResult<()> {
1070 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1071 self.check_environment_key_case_mismatch(key);
1072 return Ok(());
1073 };
1074
1075 let env_def = Definition::Environment(key.as_env_key().to_string());
1076
1077 if is_nonmergeable_list(&key) {
1078 assert!(
1079 output
1080 .windows(2)
1081 .all(|cvs| cvs[0].definition() == cvs[1].definition()),
1082 "non-mergeable list must have only one definition: {output:?}",
1083 );
1084
1085 if output
1088 .first()
1089 .map(|o| o.definition() > &env_def)
1090 .unwrap_or_default()
1091 {
1092 return Ok(());
1093 } else {
1094 output.clear();
1095 }
1096 }
1097
1098 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1099 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1101 ConfigError::new(format!("could not parse TOML list: {}", e), env_def.clone())
1102 })?;
1103 let values = toml_v.as_array().expect("env var was not array");
1104 for value in values {
1105 let s = value.as_str().ok_or_else(|| {
1108 ConfigError::new(
1109 format!("expected string, found {}", value.type_str()),
1110 env_def.clone(),
1111 )
1112 })?;
1113 output.push(CV::String(s.to_string(), env_def.clone()))
1114 }
1115 } else {
1116 output.extend(
1117 env_val
1118 .split_whitespace()
1119 .map(|s| CV::String(s.to_string(), env_def.clone())),
1120 );
1121 }
1122 output.sort_by(|a, b| a.definition().cmp(b.definition()));
1123 Ok(())
1124 }
1125
1126 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1130 match self.get_cv(key)? {
1131 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1132 Some(val) => self.expected("table", key, &val),
1133 None => Ok(None),
1134 }
1135 }
1136
1137 get_value_typed! {get_integer, i64, Integer, "an integer"}
1138 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1139 get_value_typed! {get_string_priv, String, String, "a string"}
1140
1141 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1143 val.expected(ty, &key.to_string())
1144 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1145 }
1146
1147 pub fn configure(
1153 &mut self,
1154 verbose: u32,
1155 quiet: bool,
1156 color: Option<&str>,
1157 frozen: bool,
1158 locked: bool,
1159 offline: bool,
1160 target_dir: &Option<PathBuf>,
1161 unstable_flags: &[String],
1162 cli_config: &[String],
1163 ) -> CargoResult<()> {
1164 for warning in self
1165 .unstable_flags
1166 .parse(unstable_flags, self.nightly_features_allowed)?
1167 {
1168 self.shell().warn(warning)?;
1169 }
1170 if !unstable_flags.is_empty() {
1171 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1174 }
1175 if !cli_config.is_empty() {
1176 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1177 self.merge_cli_args()?;
1178 }
1179
1180 self.load_unstable_flags_from_config()?;
1181
1182 let term = self.get::<TermConfig>(["term"]).unwrap_or_default();
1186
1187 let extra_verbose = verbose >= 2;
1189 let verbose = verbose != 0;
1190 let verbosity = match (verbose, quiet) {
1191 (true, true) => bail!("cannot set both --verbose and --quiet"),
1192 (true, false) => Verbosity::Verbose,
1193 (false, true) => Verbosity::Quiet,
1194 (false, false) => match (term.verbose, term.quiet) {
1195 (Some(true), Some(true)) => {
1196 bail!("cannot set both `term.verbose` and `term.quiet`")
1197 }
1198 (Some(true), _) => Verbosity::Verbose,
1199 (_, Some(true)) => Verbosity::Quiet,
1200 _ => Verbosity::Normal,
1201 },
1202 };
1203 self.shell().set_verbosity(verbosity);
1204 self.extra_verbose = extra_verbose;
1205
1206 let color = color.or_else(|| term.color.as_deref());
1207 self.shell().set_color_choice(color)?;
1208 if let Some(hyperlinks) = term.hyperlinks {
1209 self.shell().set_hyperlinks(hyperlinks)?;
1210 }
1211 if let Some(unicode) = term.unicode {
1212 self.shell().set_unicode(unicode)?;
1213 }
1214
1215 self.progress_config = term.progress.unwrap_or_default();
1216
1217 self.frozen = frozen;
1218 self.locked = locked;
1219 self.offline = offline
1220 || self
1221 .net_config()
1222 .ok()
1223 .and_then(|n| n.offline)
1224 .unwrap_or(false);
1225 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1226 self.target_dir = cli_target_dir;
1227
1228 self.shell()
1229 .set_unstable_flags_rustc_unicode(self.unstable_flags.rustc_unicode)?;
1230
1231 Ok(())
1232 }
1233
1234 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1235 if self.nightly_features_allowed {
1238 self.unstable_flags = self
1239 .get::<Option<CliUnstable>>(["unstable"])?
1240 .unwrap_or_default();
1241 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1242 self.unstable_flags.parse(unstable_flags_cli, true)?;
1247 }
1248 }
1249
1250 Ok(())
1251 }
1252
1253 pub fn cli_unstable(&self) -> &CliUnstable {
1254 &self.unstable_flags
1255 }
1256
1257 pub fn extra_verbose(&self) -> bool {
1258 self.extra_verbose
1259 }
1260
1261 pub fn network_allowed(&self) -> bool {
1262 !self.offline_flag().is_some()
1263 }
1264
1265 pub fn offline_flag(&self) -> Option<&'static str> {
1266 if self.frozen {
1267 Some("--frozen")
1268 } else if self.offline {
1269 Some("--offline")
1270 } else {
1271 None
1272 }
1273 }
1274
1275 pub fn set_locked(&mut self, locked: bool) {
1276 self.locked = locked;
1277 }
1278
1279 pub fn lock_update_allowed(&self) -> bool {
1280 !self.locked_flag().is_some()
1281 }
1282
1283 pub fn locked_flag(&self) -> Option<&'static str> {
1284 if self.frozen {
1285 Some("--frozen")
1286 } else if self.locked {
1287 Some("--locked")
1288 } else {
1289 None
1290 }
1291 }
1292
1293 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1295 self.load_values_from(&self.cwd)
1296 }
1297
1298 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1302 let mut result = Vec::new();
1303 let mut seen = HashSet::default();
1304 let home = self.home_path.clone().into_path_unlocked();
1305 self.walk_tree(&self.cwd, &home, |path| {
1306 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1307 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1308 result.push(cv);
1309 Ok(())
1310 })
1311 .context("could not load Cargo configuration")?;
1312 Ok(result)
1313 }
1314
1315 fn load_unmerged_include(
1319 &self,
1320 cv: &mut CV,
1321 seen: &mut HashSet<PathBuf>,
1322 output: &mut Vec<CV>,
1323 ) -> CargoResult<()> {
1324 let includes = self.include_paths(cv, false)?;
1325 for include in includes {
1326 let Some(abs_path) = include.resolve_path(self) else {
1327 continue;
1328 };
1329
1330 let mut cv = self
1331 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1332 .with_context(|| {
1333 format!(
1334 "failed to load config include `{}` from `{}`",
1335 include.path.display(),
1336 include.def
1337 )
1338 })?;
1339 self.load_unmerged_include(&mut cv, seen, output)?;
1340 output.push(cv);
1341 }
1342 Ok(())
1343 }
1344
1345 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1347 let mut cfg = CV::Table(HashMap::default(), Definition::BuiltIn);
1350 let home = self.home_path.clone().into_path_unlocked();
1351
1352 self.walk_tree(path, &home, |path| {
1353 let value = self.load_file(path)?;
1354 cfg.merge(value, false).with_context(|| {
1355 format!("failed to merge configuration at `{}`", path.display())
1356 })?;
1357 Ok(())
1358 })
1359 .context("could not load Cargo configuration")?;
1360
1361 match cfg {
1362 CV::Table(map, _) => Ok(map),
1363 _ => unreachable!(),
1364 }
1365 }
1366
1367 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1371 self._load_file(path, &mut HashSet::default(), true, WhyLoad::FileDiscovery)
1372 }
1373
1374 fn _load_file(
1382 &self,
1383 path: &Path,
1384 seen: &mut HashSet<PathBuf>,
1385 includes: bool,
1386 why_load: WhyLoad,
1387 ) -> CargoResult<ConfigValue> {
1388 if !seen.insert(path.to_path_buf()) {
1389 bail!(
1390 "config `include` cycle detected with path `{}`",
1391 path.display()
1392 );
1393 }
1394 tracing::debug!(?path, ?why_load, includes, "load config from file");
1395
1396 let value = self.load_layer(path, why_load)?;
1397
1398 if includes {
1399 self.load_includes(value, seen, why_load)
1400 } else {
1401 Ok(value)
1402 }
1403 }
1404
1405 fn load_layer(&self, path: &Path, why_load: WhyLoad) -> CargoResult<ConfigValue> {
1411 tracing::debug!(?path, ?why_load, "load config from file");
1412
1413 let contents = fs::read_to_string(path)
1414 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1415 let toml = contents.parse::<toml::Table>().with_context(|| {
1416 format!("could not parse TOML configuration in `{}`", path.display())
1417 })?;
1418 let def = match why_load {
1419 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1420 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1421 };
1422 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1423 format!(
1424 "failed to load TOML configuration from `{}`",
1425 path.display()
1426 )
1427 })?;
1428 Ok(value)
1429 }
1430
1431 fn load_includes(
1438 &self,
1439 mut value: CV,
1440 seen: &mut HashSet<PathBuf>,
1441 why_load: WhyLoad,
1442 ) -> CargoResult<CV> {
1443 let includes = self.include_paths(&mut value, true)?;
1445
1446 let mut root = CV::Table(HashMap::default(), value.definition().clone());
1448 for include in includes {
1449 let Some(abs_path) = include.resolve_path(self) else {
1450 continue;
1451 };
1452
1453 self._load_file(&abs_path, seen, true, why_load)
1454 .and_then(|include| root.merge(include, true))
1455 .with_context(|| {
1456 format!(
1457 "failed to load config include `{}` from `{}`",
1458 include.path.display(),
1459 include.def
1460 )
1461 })?;
1462 }
1463 root.merge(value, true)?;
1464 Ok(root)
1465 }
1466
1467 fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1469 let CV::Table(table, _def) = cv else {
1470 unreachable!()
1471 };
1472 let include = if remove {
1473 table.remove("include").map(Cow::Owned)
1474 } else {
1475 table.get("include").map(Cow::Borrowed)
1476 };
1477 let includes = match include.map(|c| c.into_owned()) {
1478 Some(CV::List(list, _def)) => list
1479 .into_iter()
1480 .enumerate()
1481 .map(|(idx, cv)| match cv {
1482 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1483 CV::Table(mut table, def) => {
1484 let s = match table.remove("path") {
1486 Some(CV::String(s, _)) => s,
1487 Some(other) => bail!(
1488 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1489 other.desc()
1490 ),
1491 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1492 };
1493
1494 let optional = match table.remove("optional") {
1496 Some(CV::Boolean(b, _)) => b,
1497 Some(other) => bail!(
1498 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1499 other.desc()
1500 ),
1501 None => false,
1502 };
1503
1504 let mut include = ConfigInclude::new(s, def);
1505 include.optional = optional;
1506 Ok(include)
1507 }
1508 other => bail!(
1509 "expected a string or table, but found {} at `include[{idx}]` in {}",
1510 other.desc(),
1511 other.definition(),
1512 ),
1513 })
1514 .collect::<CargoResult<Vec<_>>>()?,
1515 Some(other) => bail!(
1516 "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1517 other.desc(),
1518 other.definition()
1519 ),
1520 None => {
1521 return Ok(Vec::new());
1522 }
1523 };
1524
1525 for include in &includes {
1526 if include.path.extension() != Some(OsStr::new("toml")) {
1527 bail!(
1528 "expected a config include path ending with `.toml`, \
1529 but found `{}` from `{}`",
1530 include.path.display(),
1531 include.def,
1532 )
1533 }
1534
1535 if let Some(path) = include.path.to_str() {
1536 if is_glob_pattern(path) {
1538 bail!(
1539 "expected a config include path without glob patterns, \
1540 but found `{}` from `{}`",
1541 include.path.display(),
1542 include.def,
1543 )
1544 }
1545 if path.contains(&['{', '}']) {
1546 bail!(
1547 "expected a config include path without template braces, \
1548 but found `{}` from `{}`",
1549 include.path.display(),
1550 include.def,
1551 )
1552 }
1553 }
1554 }
1555
1556 Ok(includes)
1557 }
1558
1559 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1561 let mut loaded_args = CV::Table(HashMap::default(), Definition::Cli(None));
1562 let Some(cli_args) = &self.cli_config else {
1563 return Ok(loaded_args);
1564 };
1565 let mut seen = HashSet::default();
1566 for arg in cli_args {
1567 let arg_as_path = self.cwd.join(arg);
1568 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1569 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1571 .with_context(|| {
1572 format!("failed to load config from `{}`", arg_as_path.display())
1573 })?
1574 } else {
1575 let doc = toml_dotted_keys(arg)?;
1576 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1577 .with_context(|| {
1578 format!("failed to parse value from --config argument `{arg}`")
1579 })?;
1580
1581 if doc
1582 .get("registry")
1583 .and_then(|v| v.as_table())
1584 .and_then(|t| t.get("token"))
1585 .is_some()
1586 {
1587 bail!("registry.token cannot be set through --config for security reasons");
1588 } else if let Some((k, _)) = doc
1589 .get("registries")
1590 .and_then(|v| v.as_table())
1591 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1592 {
1593 bail!(
1594 "registries.{}.token cannot be set through --config for security reasons",
1595 k
1596 );
1597 }
1598
1599 CV::from_toml(Definition::Cli(None), doc)
1600 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1601 };
1602 let tmp_table = self
1603 .load_includes(tmp_table, &mut HashSet::default(), WhyLoad::Cli)
1604 .context("failed to load --config include".to_string())?;
1605 loaded_args
1606 .merge(tmp_table, true)
1607 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1608 }
1609 Ok(loaded_args)
1610 }
1611
1612 fn merge_cli_args(&mut self) -> CargoResult<()> {
1614 let cv_from_cli = self.cli_args_as_table()?;
1615 assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1616
1617 let root_cv = mem::take(self.values_mut()?);
1618 let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1621 root_cv.merge(cv_from_cli, true)?;
1622
1623 mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1625
1626 Ok(())
1627 }
1628
1629 fn get_file_path(
1635 &self,
1636 dir: &Path,
1637 filename_without_extension: &str,
1638 warn: bool,
1639 ) -> CargoResult<Option<PathBuf>> {
1640 let possible = dir.join(filename_without_extension);
1641 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1642
1643 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1644 if warn {
1645 if let Ok(possible_with_extension_handle) =
1646 same_file::Handle::from_path(&possible_with_extension)
1647 {
1648 if possible_handle != possible_with_extension_handle {
1654 self.shell().warn(format!(
1655 "both `{}` and `{}` exist. Using `{}`",
1656 possible.display(),
1657 possible_with_extension.display(),
1658 possible.display()
1659 ))?;
1660 }
1661 } else {
1662 self.shell().print_report(&[
1663 Level::WARNING.secondary_title(
1664 format!(
1665 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1666 possible.display(),
1667 )).element(Level::HELP.message(
1668 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1669 ], false)?;
1670 }
1671 }
1672
1673 Ok(Some(possible))
1674 } else if possible_with_extension.exists() {
1675 Ok(Some(possible_with_extension))
1676 } else {
1677 Ok(None)
1678 }
1679 }
1680
1681 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1682 where
1683 F: FnMut(&Path) -> CargoResult<()>,
1684 {
1685 let mut seen_dir = HashSet::default();
1686
1687 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1688 let config_root = current.join(".cargo");
1689 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1690 walk(&path)?;
1691 }
1692
1693 let canonical_root = config_root.canonicalize().unwrap_or(config_root);
1694 seen_dir.insert(canonical_root);
1695 }
1696
1697 let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf());
1698
1699 if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
1703 if let Some(path) = self.get_file_path(home, "config", true)? {
1704 walk(&path)?;
1705 }
1706 }
1707
1708 Ok(())
1709 }
1710
1711 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1713 RegistryName::new(registry)?;
1714 if let Some(index) = self.get_string(["registries", registry, "index"])? {
1715 self.resolve_registry_index(&index).with_context(|| {
1716 format!(
1717 "invalid index URL for registry `{}` defined in {}",
1718 registry, index.definition
1719 )
1720 })
1721 } else {
1722 bail!(
1723 "registry index was not found in any configuration: `{}`",
1724 registry
1725 );
1726 }
1727 }
1728
1729 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1731 if self.get_string(["registry", "index"])?.is_some() {
1732 bail!(
1733 "the `registry.index` config value is no longer supported\n\
1734 Use `[source]` replacement to alter the default index for crates.io."
1735 );
1736 }
1737 Ok(())
1738 }
1739
1740 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1741 let base = index
1743 .definition
1744 .root(self.cwd())
1745 .join("truncated-by-url_with_base");
1746 let _parsed = index.val.into_url()?;
1748 let url = index.val.into_url_with_base(Some(&*base))?;
1749 if url.password().is_some() {
1750 bail!("registry URLs may not contain passwords");
1751 }
1752 Ok(url)
1753 }
1754
1755 pub fn load_credentials(&self) -> CargoResult<()> {
1763 if self.credential_values.filled() {
1764 return Ok(());
1765 }
1766
1767 let home_path = self.home_path.clone().into_path_unlocked();
1768 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1769 return Ok(());
1770 };
1771
1772 let mut value = self.load_layer(&credentials, WhyLoad::FileDiscovery)?;
1773 {
1775 let (value_map, def) = value.table_mut("<root>")?;
1776
1777 if let Some(token) = value_map.remove("token") {
1778 value_map.entry("registry".into()).or_insert_with(|| {
1779 let map = HashMap::from_iter([("token".into(), token)]);
1780 CV::Table(map, def.clone())
1781 });
1782 }
1783 }
1784
1785 let mut credential_values = HashMap::default();
1786 if let CV::Table(map, _) = value {
1787 let base_map = self.values()?;
1788 for (k, v) in map {
1789 let entry = match base_map.get(&k) {
1790 Some(base_entry) => {
1791 let mut entry = base_entry.clone();
1792 entry.merge(v, true)?;
1793 entry
1794 }
1795 None => v,
1796 };
1797 credential_values.insert(k, entry);
1798 }
1799 }
1800 self.credential_values
1801 .set(credential_values)
1802 .expect("was not filled at beginning of the function");
1803 Ok(())
1804 }
1805
1806 fn maybe_get_tool(
1809 &self,
1810 tool: &str,
1811 from_config: &Option<ConfigRelativePath>,
1812 ) -> Option<PathBuf> {
1813 let var = tool.to_uppercase();
1814
1815 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1816 Some(tool_path) => {
1817 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1818 let path = if maybe_relative {
1819 self.cwd.join(tool_path)
1820 } else {
1821 PathBuf::from(tool_path)
1822 };
1823 Some(path)
1824 }
1825
1826 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1827 }
1828 }
1829
1830 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1841 let tool_str = tool.as_str();
1842 self.maybe_get_tool(tool_str, from_config)
1843 .or_else(|| {
1844 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1858 let toolchain = toolchain.to_str()?;
1861 let toolchain_path = Path::new(toolchain);
1862 let toolchain_dir = if toolchain_path.is_absolute() {
1863 toolchain_path.to_path_buf()
1864 } else if !toolchain.contains(&['/', '\\']) {
1865 home::rustup_home().ok()?.join("toolchains").join(toolchain)
1866 } else {
1867 return None;
1868 };
1869 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1872 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1873 let tool_meta = tool_resolved.metadata().ok()?;
1874 let rustup_meta = rustup_resolved.metadata().ok()?;
1875 if tool_meta.len() != rustup_meta.len() {
1880 return None;
1881 }
1882 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1884 let toolchain_exe = toolchain_dir.join("bin").join(&tool_exe);
1885 toolchain_exe.exists().then_some(toolchain_exe)
1886 })
1887 .unwrap_or_else(|| PathBuf::from(tool_str))
1888 }
1889
1890 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1892 let key = ConfigKey::from_parts(["paths"]);
1893 match self.get_cv(&key)? {
1895 Some(CV::List(val, definition)) => {
1896 let val = val
1897 .into_iter()
1898 .map(|cv| match cv {
1899 CV::String(s, def) => Ok((s, def)),
1900 other => self.expected("string", &key, &other),
1901 })
1902 .collect::<CargoResult<Vec<_>>>()?;
1903 Ok(Some(Value { val, definition }))
1904 }
1905 Some(val) => self.expected("list", &key, &val),
1906 None => Ok(None),
1907 }
1908 }
1909
1910 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1911 self.jobserver
1912 }
1913
1914 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1915 let http = self
1916 .easy
1917 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1918 {
1919 let mut http = http.lock().unwrap();
1920 http.reset();
1921 let timeout = configure_http_handle(self, &mut http)?;
1922 timeout.configure(&mut http)?;
1923 }
1924 Ok(http)
1925 }
1926
1927 pub fn http_async(&self) -> CargoResult<&http_async::Client> {
1928 self.http_async.try_borrow_with(|| {
1929 let handle_config = HandleConfiguration::new(&self)?;
1930 Ok(http_async::Client::new(handle_config))
1931 })
1932 }
1933
1934 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1935 self.http_config.try_borrow_with(|| {
1936 let mut http = self.get::<CargoHttpConfig>(["http"])?;
1937 let curl_v = curl::Version::get();
1938 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1939 Ok(http)
1940 })
1941 }
1942
1943 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1944 self.future_incompat_config
1945 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>(["future-incompat-report"]))
1946 }
1947
1948 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1949 self.net_config
1950 .try_borrow_with(|| self.get::<CargoNetConfig>(["net"]))
1951 }
1952
1953 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1954 self.build_config
1955 .try_borrow_with(|| self.get::<CargoBuildConfig>(["build"]))
1956 }
1957
1958 pub fn progress_config(&self) -> &ProgressConfig {
1959 &self.progress_config
1960 }
1961
1962 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1965 let env_config = self.env_config.try_borrow_with(|| {
1966 CargoResult::Ok(Arc::new({
1967 let env_config = self.get::<EnvConfig>(["env"])?;
1968 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1984 if env_config.contains_key(*disallowed) {
1985 bail!(
1986 "setting the `{disallowed}` environment variable is not supported \
1987 in the `[env]` configuration table"
1988 );
1989 }
1990 }
1991 env_config
1992 .into_iter()
1993 .filter_map(|(k, v)| {
1994 if v.is_force() || self.get_env_os(&k).is_none() {
1995 Some((k, v.resolve(self.cwd()).to_os_string()))
1996 } else {
1997 None
1998 }
1999 })
2000 .collect()
2001 }))
2002 })?;
2003
2004 Ok(env_config)
2005 }
2006
2007 pub fn validate_term_config(&self) -> CargoResult<()> {
2013 drop(self.get::<TermConfig>(["term"])?);
2014 Ok(())
2015 }
2016
2017 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
2021 self.target_cfgs
2022 .try_borrow_with(|| target::load_target_cfgs(self))
2023 }
2024
2025 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
2026 self.doc_extern_map
2030 .try_borrow_with(|| self.get::<RustdocExternMap>(["doc", "extern-map"]))
2031 }
2032
2033 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2035 target::get_target_applies_to_host(self)
2036 }
2037
2038 pub fn host_cfg_tuple(&self, target: &str) -> CargoResult<TargetConfig> {
2040 target::load_host_tuple(self, target)
2041 }
2042
2043 pub fn target_cfg_tuple(&self, target: &str) -> CargoResult<TargetConfig> {
2045 target::load_target_tuple(self, target)
2046 }
2047
2048 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2053 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2054 self.check_registry_index_not_set()?;
2055 let url = CRATES_IO_INDEX.into_url().unwrap();
2056 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2057 })?;
2058 Ok(*source_id)
2059 }
2060
2061 pub fn invocation_instant(&self) -> Instant {
2062 self.invocation_instant
2063 }
2064
2065 pub fn invocation_time(&self) -> jiff::Timestamp {
2071 self.invocation_time
2072 }
2073
2074 pub fn get<'de, T: serde::de::Deserialize<'de>>(
2089 &self,
2090
2091 key: impl IntoIterator<Item = impl AsRef<str>>,
2092 ) -> CargoResult<T> {
2093 let d = Deserializer {
2094 gctx: self,
2095 key: ConfigKey::from_parts(key),
2096 env_prefix_ok: true,
2097 };
2098 T::deserialize(d).map_err(|e| e.into())
2099 }
2100
2101 #[track_caller]
2107 #[tracing::instrument(skip_all)]
2108 pub fn assert_package_cache_locked<'a>(
2109 &self,
2110 mode: CacheLockMode,
2111 f: &'a Filesystem,
2112 ) -> &'a Path {
2113 let ret = f.as_path_unlocked();
2114 assert!(
2115 self.package_cache_lock.is_locked(mode),
2116 "package cache lock is not currently held, Cargo forgot to call \
2117 `acquire_package_cache_lock` before we got to this stack frame",
2118 );
2119 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2120 ret
2121 }
2122
2123 #[tracing::instrument(skip_all)]
2129 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2130 self.package_cache_lock.lock(self, mode)
2131 }
2132
2133 #[tracing::instrument(skip_all)]
2139 pub fn try_acquire_package_cache_lock(
2140 &self,
2141 mode: CacheLockMode,
2142 ) -> CargoResult<Option<CacheLock<'_>>> {
2143 self.package_cache_lock.try_lock(self, mode)
2144 }
2145
2146 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2151 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2152 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2153 })?;
2154 Ok(tracker.lock().unwrap())
2155 }
2156
2157 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2159 let deferred = self
2160 .deferred_global_last_use
2161 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2162 Ok(deferred.lock().unwrap())
2163 }
2164
2165 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2167 Ok(self.build_config()?.warnings.unwrap_or_default())
2168 }
2169
2170 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2171 self.ws_roots.lock().unwrap()
2172 }
2173}
2174
2175pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2176 ::home::cargo_home_with_cwd(cwd)
2177 .ok()
2178 .map(|home| paths::normalize_path(&home))
2182}
2183
2184pub fn save_credentials(
2185 gctx: &GlobalContext,
2186 token: Option<RegistryCredentialConfig>,
2187 registry: &SourceId,
2188) -> CargoResult<()> {
2189 let registry = if registry.is_crates_io() {
2190 None
2191 } else {
2192 let name = registry
2193 .alt_registry_key()
2194 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2195 Some(name)
2196 };
2197
2198 let home_path = gctx.home_path.clone().into_path_unlocked();
2202 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2203 Some(path) => match path.file_name() {
2204 Some(filename) => Path::new(filename).to_owned(),
2205 None => Path::new("credentials.toml").to_owned(),
2206 },
2207 None => Path::new("credentials.toml").to_owned(),
2208 };
2209
2210 let mut file = {
2211 gctx.home_path.create_dir()?;
2212 gctx.home_path
2213 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2214 };
2215
2216 let mut contents = String::new();
2217 file.read_to_string(&mut contents).with_context(|| {
2218 format!(
2219 "failed to read configuration file `{}`",
2220 file.path().display()
2221 )
2222 })?;
2223
2224 let mut toml = contents.parse::<toml::Table>()?;
2225
2226 if let Some(token) = toml.remove("token") {
2228 #[expect(
2229 clippy::disallowed_types,
2230 reason = "need stdlib's HashMap because of TOML compatibility"
2231 )]
2232 let map = std::collections::HashMap::from([("token".to_string(), token)]);
2233 toml.insert("registry".into(), map.into());
2234 }
2235
2236 if let Some(token) = token {
2237 let path_def = Definition::Path(file.path().to_path_buf());
2240 let (key, mut value) = match token {
2241 RegistryCredentialConfig::Token(token) => {
2242 let key = "token".to_string();
2245 let value = ConfigValue::String(token.expose(), path_def.clone());
2246 let map = HashMap::from_iter([(key, value)]);
2247 let table = CV::Table(map, path_def.clone());
2248
2249 if let Some(registry) = registry {
2250 let map = HashMap::from_iter([(registry.to_string(), table)]);
2251 ("registries".into(), CV::Table(map, path_def.clone()))
2252 } else {
2253 ("registry".into(), table)
2254 }
2255 }
2256 _ => unreachable!(),
2257 };
2258
2259 if registry.is_some() {
2260 if let Some(table) = toml.remove("registries") {
2261 let v = CV::from_toml(path_def, table)?;
2262 value.merge(v, false)?;
2263 }
2264 }
2265 toml.insert(key, value.into_toml());
2266 } else {
2267 if let Some(registry) = registry {
2269 if let Some(registries) = toml.get_mut("registries") {
2270 if let Some(reg) = registries.get_mut(registry) {
2271 let rtable = reg.as_table_mut().ok_or_else(|| {
2272 format_err!("expected `[registries.{}]` to be a table", registry)
2273 })?;
2274 rtable.remove("token");
2275 }
2276 }
2277 } else if let Some(registry) = toml.get_mut("registry") {
2278 let reg_table = registry
2279 .as_table_mut()
2280 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2281 reg_table.remove("token");
2282 }
2283 }
2284
2285 let contents = toml.to_string();
2286 file.seek(SeekFrom::Start(0))?;
2287 file.write_all(contents.as_bytes())
2288 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2289 file.file().set_len(contents.len() as u64)?;
2290 set_permissions(file.file(), 0o600)
2291 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2292
2293 return Ok(());
2294
2295 #[cfg(unix)]
2296 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2297 use std::os::unix::fs::PermissionsExt;
2298
2299 let mut perms = file.metadata()?.permissions();
2300 perms.set_mode(mode);
2301 file.set_permissions(perms)?;
2302 Ok(())
2303 }
2304
2305 #[cfg(not(unix))]
2306 fn set_permissions(_file: &File, _mode: u32) -> CargoResult<()> {
2307 Ok(())
2308 }
2309}
2310
2311struct ConfigInclude {
2317 path: PathBuf,
2320 def: Definition,
2321 optional: bool,
2323}
2324
2325impl ConfigInclude {
2326 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2327 Self {
2328 path: p.into(),
2329 def,
2330 optional: false,
2331 }
2332 }
2333
2334 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2347 let abs_path = match &self.def {
2348 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2349 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2350 }
2351 .join(&self.path);
2352 let abs_path = paths::normalize_path(&abs_path);
2353
2354 if self.optional && !abs_path.exists() {
2355 tracing::info!(
2356 "skipping optional include `{}` in `{}`: file not found at `{}`",
2357 self.path.display(),
2358 self.def,
2359 abs_path.display(),
2360 );
2361 None
2362 } else {
2363 Some(abs_path)
2364 }
2365 }
2366}
2367
2368fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2369 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2375 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2376 })?;
2377 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2378 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2379 }
2380 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2381 non_empty(d.prefix()) || non_empty(d.suffix())
2382 }
2383 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2384 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2385 }
2386 let ok = {
2387 let mut got_to_value = false;
2388 let mut table = doc.as_table();
2389 let mut is_root = true;
2390 while table.is_dotted() || is_root {
2391 is_root = false;
2392 if table.len() != 1 {
2393 break;
2394 }
2395 let (k, n) = table.iter().next().expect("len() == 1 above");
2396 match n {
2397 Item::Table(nt) => {
2398 if table.key(k).map_or(false, non_empty_key_decor)
2399 || non_empty_decor(nt.decor())
2400 {
2401 bail!(
2402 "--config argument `{arg}` \
2403 includes non-whitespace decoration"
2404 )
2405 }
2406 table = nt;
2407 }
2408 Item::Value(v) if v.is_inline_table() => {
2409 bail!(
2410 "--config argument `{arg}` \
2411 sets a value to an inline table, which is not accepted"
2412 );
2413 }
2414 Item::Value(v) => {
2415 if table
2416 .key(k)
2417 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2418 || non_empty_decor(v.decor())
2419 {
2420 bail!(
2421 "--config argument `{arg}` \
2422 includes non-whitespace decoration"
2423 )
2424 }
2425 got_to_value = true;
2426 break;
2427 }
2428 Item::ArrayOfTables(_) => {
2429 bail!(
2430 "--config argument `{arg}` \
2431 sets a value to an array of tables, which is not accepted"
2432 );
2433 }
2434
2435 Item::None => {
2436 bail!("--config argument `{arg}` doesn't provide a value")
2437 }
2438 }
2439 }
2440 got_to_value
2441 };
2442 if !ok {
2443 bail!(
2444 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2445 );
2446 }
2447 Ok(doc)
2448}
2449
2450#[derive(Debug, Deserialize, Clone)]
2461pub struct StringList(Vec<String>);
2462
2463impl StringList {
2464 pub fn as_slice(&self) -> &[String] {
2465 &self.0
2466 }
2467}
2468
2469#[macro_export]
2470macro_rules! __shell_print {
2471 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2472 let mut shell = $config.shell();
2473 let out = shell.$which();
2474 drop(out.write_fmt(format_args!($($arg)*)));
2475 if $newline {
2476 drop(out.write_all(b"\n"));
2477 }
2478 });
2479}
2480
2481#[macro_export]
2482macro_rules! drop_println {
2483 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2484 ($config:expr, $($arg:tt)*) => (
2485 $crate::__shell_print!($config, out, true, $($arg)*)
2486 );
2487}
2488
2489#[macro_export]
2490macro_rules! drop_eprintln {
2491 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2492 ($config:expr, $($arg:tt)*) => (
2493 $crate::__shell_print!($config, err, true, $($arg)*)
2494 );
2495}
2496
2497#[macro_export]
2498macro_rules! drop_print {
2499 ($config:expr, $($arg:tt)*) => (
2500 $crate::__shell_print!($config, out, false, $($arg)*)
2501 );
2502}
2503
2504#[macro_export]
2505macro_rules! drop_eprint {
2506 ($config:expr, $($arg:tt)*) => (
2507 $crate::__shell_print!($config, err, false, $($arg)*)
2508 );
2509}
2510
2511enum Tool {
2512 Rustc,
2513 Rustdoc,
2514}
2515
2516impl Tool {
2517 fn as_str(&self) -> &str {
2518 match self {
2519 Tool::Rustc => "rustc",
2520 Tool::Rustdoc => "rustdoc",
2521 }
2522 }
2523}
2524
2525fn disables_multiplexing_for_bad_curl(
2535 curl_version: &str,
2536 http: &mut CargoHttpConfig,
2537 gctx: &GlobalContext,
2538) {
2539 use crate::util::network;
2540
2541 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2542 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2543 if bad_curl_versions
2544 .iter()
2545 .any(|v| curl_version.starts_with(v))
2546 {
2547 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2548 http.multiplexing = Some(false);
2549 }
2550 }
2551}
2552
2553#[cfg(test)]
2554mod tests {
2555 use super::CargoHttpConfig;
2556 use super::GlobalContext;
2557 use super::Shell;
2558 use super::disables_multiplexing_for_bad_curl;
2559
2560 #[test]
2561 fn disables_multiplexing() {
2562 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2563 gctx.set_search_stop_path(std::path::PathBuf::new());
2564 gctx.set_env(Default::default());
2565
2566 let mut http = CargoHttpConfig::default();
2567 http.proxy = Some("127.0.0.1:3128".into());
2568 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2569 assert_eq!(http.multiplexing, Some(false));
2570
2571 let cases = [
2572 (None, None, "7.87.0", None),
2573 (None, None, "7.88.0", None),
2574 (None, None, "7.88.1", None),
2575 (None, None, "8.0.0", None),
2576 (Some("".into()), None, "7.87.0", Some(false)),
2577 (Some("".into()), None, "7.88.0", Some(false)),
2578 (Some("".into()), None, "7.88.1", Some(false)),
2579 (Some("".into()), None, "8.0.0", None),
2580 (Some("".into()), Some(false), "7.87.0", Some(false)),
2581 (Some("".into()), Some(false), "7.88.0", Some(false)),
2582 (Some("".into()), Some(false), "7.88.1", Some(false)),
2583 (Some("".into()), Some(false), "8.0.0", Some(false)),
2584 ];
2585
2586 for (proxy, multiplexing, curl_v, result) in cases {
2587 let mut http = CargoHttpConfig {
2588 multiplexing,
2589 proxy,
2590 ..Default::default()
2591 };
2592 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2593 assert_eq!(http.multiplexing, result);
2594 }
2595 }
2596
2597 #[test]
2598 fn sync_context() {
2599 fn assert_sync<S: Sync>() {}
2600 assert_sync::<GlobalContext>();
2601 }
2602}