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 contents = fs::read_to_string(path)
1397 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1398 let toml = parse_document(&contents, path, self).with_context(|| {
1399 format!("could not parse TOML configuration in `{}`", path.display())
1400 })?;
1401 let def = match why_load {
1402 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1403 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1404 };
1405 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1406 format!(
1407 "failed to load TOML configuration from `{}`",
1408 path.display()
1409 )
1410 })?;
1411 if includes {
1412 self.load_includes(value, seen, why_load)
1413 } else {
1414 Ok(value)
1415 }
1416 }
1417
1418 fn load_includes(
1425 &self,
1426 mut value: CV,
1427 seen: &mut HashSet<PathBuf>,
1428 why_load: WhyLoad,
1429 ) -> CargoResult<CV> {
1430 let includes = self.include_paths(&mut value, true)?;
1432
1433 let mut root = CV::Table(HashMap::default(), value.definition().clone());
1435 for include in includes {
1436 let Some(abs_path) = include.resolve_path(self) else {
1437 continue;
1438 };
1439
1440 self._load_file(&abs_path, seen, true, why_load)
1441 .and_then(|include| root.merge(include, true))
1442 .with_context(|| {
1443 format!(
1444 "failed to load config include `{}` from `{}`",
1445 include.path.display(),
1446 include.def
1447 )
1448 })?;
1449 }
1450 root.merge(value, true)?;
1451 Ok(root)
1452 }
1453
1454 fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
1456 let CV::Table(table, _def) = cv else {
1457 unreachable!()
1458 };
1459 let include = if remove {
1460 table.remove("include").map(Cow::Owned)
1461 } else {
1462 table.get("include").map(Cow::Borrowed)
1463 };
1464 let includes = match include.map(|c| c.into_owned()) {
1465 Some(CV::List(list, _def)) => list
1466 .into_iter()
1467 .enumerate()
1468 .map(|(idx, cv)| match cv {
1469 CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
1470 CV::Table(mut table, def) => {
1471 let s = match table.remove("path") {
1473 Some(CV::String(s, _)) => s,
1474 Some(other) => bail!(
1475 "expected a string, but found {} at `include[{idx}].path` in `{def}`",
1476 other.desc()
1477 ),
1478 None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
1479 };
1480
1481 let optional = match table.remove("optional") {
1483 Some(CV::Boolean(b, _)) => b,
1484 Some(other) => bail!(
1485 "expected a boolean, but found {} at `include[{idx}].optional` in `{def}`",
1486 other.desc()
1487 ),
1488 None => false,
1489 };
1490
1491 let mut include = ConfigInclude::new(s, def);
1492 include.optional = optional;
1493 Ok(include)
1494 }
1495 other => bail!(
1496 "expected a string or table, but found {} at `include[{idx}]` in {}",
1497 other.desc(),
1498 other.definition(),
1499 ),
1500 })
1501 .collect::<CargoResult<Vec<_>>>()?,
1502 Some(other) => bail!(
1503 "expected a list of strings or a list of tables, but found {} at `include` in `{}",
1504 other.desc(),
1505 other.definition()
1506 ),
1507 None => {
1508 return Ok(Vec::new());
1509 }
1510 };
1511
1512 for include in &includes {
1513 if include.path.extension() != Some(OsStr::new("toml")) {
1514 bail!(
1515 "expected a config include path ending with `.toml`, \
1516 but found `{}` from `{}`",
1517 include.path.display(),
1518 include.def,
1519 )
1520 }
1521
1522 if let Some(path) = include.path.to_str() {
1523 if is_glob_pattern(path) {
1525 bail!(
1526 "expected a config include path without glob patterns, \
1527 but found `{}` from `{}`",
1528 include.path.display(),
1529 include.def,
1530 )
1531 }
1532 if path.contains(&['{', '}']) {
1533 bail!(
1534 "expected a config include path without template braces, \
1535 but found `{}` from `{}`",
1536 include.path.display(),
1537 include.def,
1538 )
1539 }
1540 }
1541 }
1542
1543 Ok(includes)
1544 }
1545
1546 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1548 let mut loaded_args = CV::Table(HashMap::default(), Definition::Cli(None));
1549 let Some(cli_args) = &self.cli_config else {
1550 return Ok(loaded_args);
1551 };
1552 let mut seen = HashSet::default();
1553 for arg in cli_args {
1554 let arg_as_path = self.cwd.join(arg);
1555 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1556 self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
1558 .with_context(|| {
1559 format!("failed to load config from `{}`", arg_as_path.display())
1560 })?
1561 } else {
1562 let doc = toml_dotted_keys(arg)?;
1563 let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1564 .with_context(|| {
1565 format!("failed to parse value from --config argument `{arg}`")
1566 })?;
1567
1568 if doc
1569 .get("registry")
1570 .and_then(|v| v.as_table())
1571 .and_then(|t| t.get("token"))
1572 .is_some()
1573 {
1574 bail!("registry.token cannot be set through --config for security reasons");
1575 } else if let Some((k, _)) = doc
1576 .get("registries")
1577 .and_then(|v| v.as_table())
1578 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1579 {
1580 bail!(
1581 "registries.{}.token cannot be set through --config for security reasons",
1582 k
1583 );
1584 }
1585
1586 CV::from_toml(Definition::Cli(None), doc)
1587 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1588 };
1589 let tmp_table = self
1590 .load_includes(tmp_table, &mut HashSet::default(), WhyLoad::Cli)
1591 .context("failed to load --config include".to_string())?;
1592 loaded_args
1593 .merge(tmp_table, true)
1594 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1595 }
1596 Ok(loaded_args)
1597 }
1598
1599 fn merge_cli_args(&mut self) -> CargoResult<()> {
1601 let cv_from_cli = self.cli_args_as_table()?;
1602 assert!(cv_from_cli.is_table(), "cv from CLI must be a table");
1603
1604 let root_cv = mem::take(self.values_mut()?);
1605 let mut root_cv = CV::Table(root_cv, Definition::BuiltIn);
1608 root_cv.merge(cv_from_cli, true)?;
1609
1610 mem::swap(self.values_mut()?, root_cv.table_mut("<root>")?.0);
1612
1613 Ok(())
1614 }
1615
1616 fn get_file_path(
1622 &self,
1623 dir: &Path,
1624 filename_without_extension: &str,
1625 warn: bool,
1626 ) -> CargoResult<Option<PathBuf>> {
1627 let possible = dir.join(filename_without_extension);
1628 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1629
1630 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1631 if warn {
1632 if let Ok(possible_with_extension_handle) =
1633 same_file::Handle::from_path(&possible_with_extension)
1634 {
1635 if possible_handle != possible_with_extension_handle {
1641 self.shell().warn(format!(
1642 "both `{}` and `{}` exist. Using `{}`",
1643 possible.display(),
1644 possible_with_extension.display(),
1645 possible.display()
1646 ))?;
1647 }
1648 } else {
1649 self.shell().print_report(&[
1650 Level::WARNING.secondary_title(
1651 format!(
1652 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1653 possible.display(),
1654 )).element(Level::HELP.message(
1655 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`")))
1656 ], false)?;
1657 }
1658 }
1659
1660 Ok(Some(possible))
1661 } else if possible_with_extension.exists() {
1662 Ok(Some(possible_with_extension))
1663 } else {
1664 Ok(None)
1665 }
1666 }
1667
1668 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1669 where
1670 F: FnMut(&Path) -> CargoResult<()>,
1671 {
1672 let mut seen_dir = HashSet::default();
1673
1674 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1675 let config_root = current.join(".cargo");
1676 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1677 walk(&path)?;
1678 }
1679
1680 let canonical_root = config_root.canonicalize().unwrap_or(config_root);
1681 seen_dir.insert(canonical_root);
1682 }
1683
1684 let canonical_home = home.canonicalize().unwrap_or(home.to_path_buf());
1685
1686 if !seen_dir.contains(&canonical_home) && !seen_dir.contains(home) {
1690 if let Some(path) = self.get_file_path(home, "config", true)? {
1691 walk(&path)?;
1692 }
1693 }
1694
1695 Ok(())
1696 }
1697
1698 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1700 RegistryName::new(registry)?;
1701 if let Some(index) = self.get_string(["registries", registry, "index"])? {
1702 self.resolve_registry_index(&index).with_context(|| {
1703 format!(
1704 "invalid index URL for registry `{}` defined in {}",
1705 registry, index.definition
1706 )
1707 })
1708 } else {
1709 bail!(
1710 "registry index was not found in any configuration: `{}`",
1711 registry
1712 );
1713 }
1714 }
1715
1716 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1718 if self.get_string(["registry", "index"])?.is_some() {
1719 bail!(
1720 "the `registry.index` config value is no longer supported\n\
1721 Use `[source]` replacement to alter the default index for crates.io."
1722 );
1723 }
1724 Ok(())
1725 }
1726
1727 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1728 let base = index
1730 .definition
1731 .root(self.cwd())
1732 .join("truncated-by-url_with_base");
1733 let _parsed = index.val.into_url()?;
1735 let url = index.val.into_url_with_base(Some(&*base))?;
1736 if url.password().is_some() {
1737 bail!("registry URLs may not contain passwords");
1738 }
1739 Ok(url)
1740 }
1741
1742 pub fn load_credentials(&self) -> CargoResult<()> {
1750 if self.credential_values.filled() {
1751 return Ok(());
1752 }
1753
1754 let home_path = self.home_path.clone().into_path_unlocked();
1755 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1756 return Ok(());
1757 };
1758
1759 let mut value = self.load_file(&credentials)?;
1760 {
1762 let (value_map, def) = value.table_mut("<root>")?;
1763
1764 if let Some(token) = value_map.remove("token") {
1765 value_map.entry("registry".into()).or_insert_with(|| {
1766 let map = HashMap::from_iter([("token".into(), token)]);
1767 CV::Table(map, def.clone())
1768 });
1769 }
1770 }
1771
1772 let mut credential_values = HashMap::default();
1773 if let CV::Table(map, _) = value {
1774 let base_map = self.values()?;
1775 for (k, v) in map {
1776 let entry = match base_map.get(&k) {
1777 Some(base_entry) => {
1778 let mut entry = base_entry.clone();
1779 entry.merge(v, true)?;
1780 entry
1781 }
1782 None => v,
1783 };
1784 credential_values.insert(k, entry);
1785 }
1786 }
1787 self.credential_values
1788 .set(credential_values)
1789 .expect("was not filled at beginning of the function");
1790 Ok(())
1791 }
1792
1793 fn maybe_get_tool(
1796 &self,
1797 tool: &str,
1798 from_config: &Option<ConfigRelativePath>,
1799 ) -> Option<PathBuf> {
1800 let var = tool.to_uppercase();
1801
1802 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1803 Some(tool_path) => {
1804 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1805 let path = if maybe_relative {
1806 self.cwd.join(tool_path)
1807 } else {
1808 PathBuf::from(tool_path)
1809 };
1810 Some(path)
1811 }
1812
1813 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1814 }
1815 }
1816
1817 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1828 let tool_str = tool.as_str();
1829 self.maybe_get_tool(tool_str, from_config)
1830 .or_else(|| {
1831 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1845 if toolchain.to_str()?.contains(&['/', '\\']) {
1848 return None;
1849 }
1850 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1853 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1854 let tool_meta = tool_resolved.metadata().ok()?;
1855 let rustup_meta = rustup_resolved.metadata().ok()?;
1856 if tool_meta.len() != rustup_meta.len() {
1861 return None;
1862 }
1863 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1865 let toolchain_exe = home::rustup_home()
1866 .ok()?
1867 .join("toolchains")
1868 .join(&toolchain)
1869 .join("bin")
1870 .join(&tool_exe);
1871 toolchain_exe.exists().then_some(toolchain_exe)
1872 })
1873 .unwrap_or_else(|| PathBuf::from(tool_str))
1874 }
1875
1876 pub fn paths_overrides(&self) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
1878 let key = ConfigKey::from_parts(["paths"]);
1879 match self.get_cv(&key)? {
1881 Some(CV::List(val, definition)) => {
1882 let val = val
1883 .into_iter()
1884 .map(|cv| match cv {
1885 CV::String(s, def) => Ok((s, def)),
1886 other => self.expected("string", &key, &other),
1887 })
1888 .collect::<CargoResult<Vec<_>>>()?;
1889 Ok(Some(Value { val, definition }))
1890 }
1891 Some(val) => self.expected("list", &key, &val),
1892 None => Ok(None),
1893 }
1894 }
1895
1896 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1897 self.jobserver
1898 }
1899
1900 pub fn http(&self) -> CargoResult<&Mutex<Easy>> {
1901 let http = self
1902 .easy
1903 .try_borrow_with(|| http_handle(self).map(Into::into))?;
1904 {
1905 let mut http = http.lock().unwrap();
1906 http.reset();
1907 let timeout = configure_http_handle(self, &mut http)?;
1908 timeout.configure(&mut http)?;
1909 }
1910 Ok(http)
1911 }
1912
1913 pub fn http_async(&self) -> CargoResult<&http_async::Client> {
1914 self.http_async.try_borrow_with(|| {
1915 let handle_config = HandleConfiguration::new(&self)?;
1916 Ok(http_async::Client::new(handle_config))
1917 })
1918 }
1919
1920 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1921 self.http_config.try_borrow_with(|| {
1922 let mut http = self.get::<CargoHttpConfig>(["http"])?;
1923 let curl_v = curl::Version::get();
1924 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1925 Ok(http)
1926 })
1927 }
1928
1929 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1930 self.future_incompat_config
1931 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>(["future-incompat-report"]))
1932 }
1933
1934 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1935 self.net_config
1936 .try_borrow_with(|| self.get::<CargoNetConfig>(["net"]))
1937 }
1938
1939 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1940 self.build_config
1941 .try_borrow_with(|| self.get::<CargoBuildConfig>(["build"]))
1942 }
1943
1944 pub fn progress_config(&self) -> &ProgressConfig {
1945 &self.progress_config
1946 }
1947
1948 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1951 let env_config = self.env_config.try_borrow_with(|| {
1952 CargoResult::Ok(Arc::new({
1953 let env_config = self.get::<EnvConfig>(["env"])?;
1954 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1970 if env_config.contains_key(*disallowed) {
1971 bail!(
1972 "setting the `{disallowed}` environment variable is not supported \
1973 in the `[env]` configuration table"
1974 );
1975 }
1976 }
1977 env_config
1978 .into_iter()
1979 .filter_map(|(k, v)| {
1980 if v.is_force() || self.get_env_os(&k).is_none() {
1981 Some((k, v.resolve(self.cwd()).to_os_string()))
1982 } else {
1983 None
1984 }
1985 })
1986 .collect()
1987 }))
1988 })?;
1989
1990 Ok(env_config)
1991 }
1992
1993 pub fn validate_term_config(&self) -> CargoResult<()> {
1999 drop(self.get::<TermConfig>(["term"])?);
2000 Ok(())
2001 }
2002
2003 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
2007 self.target_cfgs
2008 .try_borrow_with(|| target::load_target_cfgs(self))
2009 }
2010
2011 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
2012 self.doc_extern_map
2016 .try_borrow_with(|| self.get::<RustdocExternMap>(["doc", "extern-map"]))
2017 }
2018
2019 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2021 target::get_target_applies_to_host(self)
2022 }
2023
2024 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2026 target::load_host_triple(self, target)
2027 }
2028
2029 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2031 target::load_target_triple(self, target)
2032 }
2033
2034 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2039 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2040 self.check_registry_index_not_set()?;
2041 let url = CRATES_IO_INDEX.into_url().unwrap();
2042 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2043 })?;
2044 Ok(*source_id)
2045 }
2046
2047 pub fn invocation_instant(&self) -> Instant {
2048 self.invocation_instant
2049 }
2050
2051 pub fn invocation_time(&self) -> jiff::Timestamp {
2057 self.invocation_time
2058 }
2059
2060 pub fn get<'de, T: serde::de::Deserialize<'de>>(
2075 &self,
2076
2077 key: impl IntoIterator<Item = impl AsRef<str>>,
2078 ) -> CargoResult<T> {
2079 let d = Deserializer {
2080 gctx: self,
2081 key: ConfigKey::from_parts(key),
2082 env_prefix_ok: true,
2083 };
2084 T::deserialize(d).map_err(|e| e.into())
2085 }
2086
2087 #[track_caller]
2093 #[tracing::instrument(skip_all)]
2094 pub fn assert_package_cache_locked<'a>(
2095 &self,
2096 mode: CacheLockMode,
2097 f: &'a Filesystem,
2098 ) -> &'a Path {
2099 let ret = f.as_path_unlocked();
2100 assert!(
2101 self.package_cache_lock.is_locked(mode),
2102 "package cache lock is not currently held, Cargo forgot to call \
2103 `acquire_package_cache_lock` before we got to this stack frame",
2104 );
2105 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2106 ret
2107 }
2108
2109 #[tracing::instrument(skip_all)]
2115 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2116 self.package_cache_lock.lock(self, mode)
2117 }
2118
2119 #[tracing::instrument(skip_all)]
2125 pub fn try_acquire_package_cache_lock(
2126 &self,
2127 mode: CacheLockMode,
2128 ) -> CargoResult<Option<CacheLock<'_>>> {
2129 self.package_cache_lock.try_lock(self, mode)
2130 }
2131
2132 pub fn global_cache_tracker(&self) -> CargoResult<MutexGuard<'_, GlobalCacheTracker>> {
2137 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2138 Ok::<_, anyhow::Error>(Mutex::new(GlobalCacheTracker::new(self)?))
2139 })?;
2140 Ok(tracker.lock().unwrap())
2141 }
2142
2143 pub fn deferred_global_last_use(&self) -> CargoResult<MutexGuard<'_, DeferredGlobalLastUse>> {
2145 let deferred = self
2146 .deferred_global_last_use
2147 .try_borrow_with(|| Ok::<_, anyhow::Error>(Mutex::new(DeferredGlobalLastUse::new())))?;
2148 Ok(deferred.lock().unwrap())
2149 }
2150
2151 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2153 Ok(self.build_config()?.warnings.unwrap_or_default())
2154 }
2155
2156 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2157 self.ws_roots.lock().unwrap()
2158 }
2159}
2160
2161pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2162 ::home::cargo_home_with_cwd(cwd)
2163 .ok()
2164 .map(|home| paths::normalize_path(&home))
2168}
2169
2170pub fn save_credentials(
2171 gctx: &GlobalContext,
2172 token: Option<RegistryCredentialConfig>,
2173 registry: &SourceId,
2174) -> CargoResult<()> {
2175 let registry = if registry.is_crates_io() {
2176 None
2177 } else {
2178 let name = registry
2179 .alt_registry_key()
2180 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2181 Some(name)
2182 };
2183
2184 let home_path = gctx.home_path.clone().into_path_unlocked();
2188 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2189 Some(path) => match path.file_name() {
2190 Some(filename) => Path::new(filename).to_owned(),
2191 None => Path::new("credentials.toml").to_owned(),
2192 },
2193 None => Path::new("credentials.toml").to_owned(),
2194 };
2195
2196 let mut file = {
2197 gctx.home_path.create_dir()?;
2198 gctx.home_path
2199 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2200 };
2201
2202 let mut contents = String::new();
2203 file.read_to_string(&mut contents).with_context(|| {
2204 format!(
2205 "failed to read configuration file `{}`",
2206 file.path().display()
2207 )
2208 })?;
2209
2210 let mut toml = parse_document(&contents, file.path(), gctx)?;
2211
2212 if let Some(token) = toml.remove("token") {
2214 #[expect(
2215 clippy::disallowed_types,
2216 reason = "need stdlib's HashMap because of TOML compatibility"
2217 )]
2218 let map = std::collections::HashMap::from([("token".to_string(), token)]);
2219 toml.insert("registry".into(), map.into());
2220 }
2221
2222 if let Some(token) = token {
2223 let path_def = Definition::Path(file.path().to_path_buf());
2226 let (key, mut value) = match token {
2227 RegistryCredentialConfig::Token(token) => {
2228 let key = "token".to_string();
2231 let value = ConfigValue::String(token.expose(), path_def.clone());
2232 let map = HashMap::from_iter([(key, value)]);
2233 let table = CV::Table(map, path_def.clone());
2234
2235 if let Some(registry) = registry {
2236 let map = HashMap::from_iter([(registry.to_string(), table)]);
2237 ("registries".into(), CV::Table(map, path_def.clone()))
2238 } else {
2239 ("registry".into(), table)
2240 }
2241 }
2242 _ => unreachable!(),
2243 };
2244
2245 if registry.is_some() {
2246 if let Some(table) = toml.remove("registries") {
2247 let v = CV::from_toml(path_def, table)?;
2248 value.merge(v, false)?;
2249 }
2250 }
2251 toml.insert(key, value.into_toml());
2252 } else {
2253 if let Some(registry) = registry {
2255 if let Some(registries) = toml.get_mut("registries") {
2256 if let Some(reg) = registries.get_mut(registry) {
2257 let rtable = reg.as_table_mut().ok_or_else(|| {
2258 format_err!("expected `[registries.{}]` to be a table", registry)
2259 })?;
2260 rtable.remove("token");
2261 }
2262 }
2263 } else if let Some(registry) = toml.get_mut("registry") {
2264 let reg_table = registry
2265 .as_table_mut()
2266 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2267 reg_table.remove("token");
2268 }
2269 }
2270
2271 let contents = toml.to_string();
2272 file.seek(SeekFrom::Start(0))?;
2273 file.write_all(contents.as_bytes())
2274 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2275 file.file().set_len(contents.len() as u64)?;
2276 set_permissions(file.file(), 0o600)
2277 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2278
2279 return Ok(());
2280
2281 #[cfg(unix)]
2282 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2283 use std::os::unix::fs::PermissionsExt;
2284
2285 let mut perms = file.metadata()?.permissions();
2286 perms.set_mode(mode);
2287 file.set_permissions(perms)?;
2288 Ok(())
2289 }
2290
2291 #[cfg(not(unix))]
2292 fn set_permissions(_file: &File, _mode: u32) -> CargoResult<()> {
2293 Ok(())
2294 }
2295}
2296
2297struct ConfigInclude {
2303 path: PathBuf,
2306 def: Definition,
2307 optional: bool,
2309}
2310
2311impl ConfigInclude {
2312 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2313 Self {
2314 path: p.into(),
2315 def,
2316 optional: false,
2317 }
2318 }
2319
2320 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2333 let abs_path = match &self.def {
2334 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2335 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2336 }
2337 .join(&self.path);
2338 let abs_path = paths::normalize_path(&abs_path);
2339
2340 if self.optional && !abs_path.exists() {
2341 tracing::info!(
2342 "skipping optional include `{}` in `{}`: file not found at `{}`",
2343 self.path.display(),
2344 self.def,
2345 abs_path.display(),
2346 );
2347 None
2348 } else {
2349 Some(abs_path)
2350 }
2351 }
2352}
2353
2354fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2355 toml.parse().map_err(Into::into)
2357}
2358
2359fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2360 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2366 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2367 })?;
2368 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2369 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2370 }
2371 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2372 non_empty(d.prefix()) || non_empty(d.suffix())
2373 }
2374 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2375 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2376 }
2377 let ok = {
2378 let mut got_to_value = false;
2379 let mut table = doc.as_table();
2380 let mut is_root = true;
2381 while table.is_dotted() || is_root {
2382 is_root = false;
2383 if table.len() != 1 {
2384 break;
2385 }
2386 let (k, n) = table.iter().next().expect("len() == 1 above");
2387 match n {
2388 Item::Table(nt) => {
2389 if table.key(k).map_or(false, non_empty_key_decor)
2390 || non_empty_decor(nt.decor())
2391 {
2392 bail!(
2393 "--config argument `{arg}` \
2394 includes non-whitespace decoration"
2395 )
2396 }
2397 table = nt;
2398 }
2399 Item::Value(v) if v.is_inline_table() => {
2400 bail!(
2401 "--config argument `{arg}` \
2402 sets a value to an inline table, which is not accepted"
2403 );
2404 }
2405 Item::Value(v) => {
2406 if table
2407 .key(k)
2408 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2409 || non_empty_decor(v.decor())
2410 {
2411 bail!(
2412 "--config argument `{arg}` \
2413 includes non-whitespace decoration"
2414 )
2415 }
2416 got_to_value = true;
2417 break;
2418 }
2419 Item::ArrayOfTables(_) => {
2420 bail!(
2421 "--config argument `{arg}` \
2422 sets a value to an array of tables, which is not accepted"
2423 );
2424 }
2425
2426 Item::None => {
2427 bail!("--config argument `{arg}` doesn't provide a value")
2428 }
2429 }
2430 }
2431 got_to_value
2432 };
2433 if !ok {
2434 bail!(
2435 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2436 );
2437 }
2438 Ok(doc)
2439}
2440
2441#[derive(Debug, Deserialize, Clone)]
2452pub struct StringList(Vec<String>);
2453
2454impl StringList {
2455 pub fn as_slice(&self) -> &[String] {
2456 &self.0
2457 }
2458}
2459
2460#[macro_export]
2461macro_rules! __shell_print {
2462 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2463 let mut shell = $config.shell();
2464 let out = shell.$which();
2465 drop(out.write_fmt(format_args!($($arg)*)));
2466 if $newline {
2467 drop(out.write_all(b"\n"));
2468 }
2469 });
2470}
2471
2472#[macro_export]
2473macro_rules! drop_println {
2474 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2475 ($config:expr, $($arg:tt)*) => (
2476 $crate::__shell_print!($config, out, true, $($arg)*)
2477 );
2478}
2479
2480#[macro_export]
2481macro_rules! drop_eprintln {
2482 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2483 ($config:expr, $($arg:tt)*) => (
2484 $crate::__shell_print!($config, err, true, $($arg)*)
2485 );
2486}
2487
2488#[macro_export]
2489macro_rules! drop_print {
2490 ($config:expr, $($arg:tt)*) => (
2491 $crate::__shell_print!($config, out, false, $($arg)*)
2492 );
2493}
2494
2495#[macro_export]
2496macro_rules! drop_eprint {
2497 ($config:expr, $($arg:tt)*) => (
2498 $crate::__shell_print!($config, err, false, $($arg)*)
2499 );
2500}
2501
2502enum Tool {
2503 Rustc,
2504 Rustdoc,
2505}
2506
2507impl Tool {
2508 fn as_str(&self) -> &str {
2509 match self {
2510 Tool::Rustc => "rustc",
2511 Tool::Rustdoc => "rustdoc",
2512 }
2513 }
2514}
2515
2516fn disables_multiplexing_for_bad_curl(
2526 curl_version: &str,
2527 http: &mut CargoHttpConfig,
2528 gctx: &GlobalContext,
2529) {
2530 use crate::util::network;
2531
2532 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2533 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2534 if bad_curl_versions
2535 .iter()
2536 .any(|v| curl_version.starts_with(v))
2537 {
2538 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2539 http.multiplexing = Some(false);
2540 }
2541 }
2542}
2543
2544#[cfg(test)]
2545mod tests {
2546 use super::CargoHttpConfig;
2547 use super::GlobalContext;
2548 use super::Shell;
2549 use super::disables_multiplexing_for_bad_curl;
2550
2551 #[test]
2552 fn disables_multiplexing() {
2553 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2554 gctx.set_search_stop_path(std::path::PathBuf::new());
2555 gctx.set_env(Default::default());
2556
2557 let mut http = CargoHttpConfig::default();
2558 http.proxy = Some("127.0.0.1:3128".into());
2559 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2560 assert_eq!(http.multiplexing, Some(false));
2561
2562 let cases = [
2563 (None, None, "7.87.0", None),
2564 (None, None, "7.88.0", None),
2565 (None, None, "7.88.1", None),
2566 (None, None, "8.0.0", None),
2567 (Some("".into()), None, "7.87.0", Some(false)),
2568 (Some("".into()), None, "7.88.0", Some(false)),
2569 (Some("".into()), None, "7.88.1", Some(false)),
2570 (Some("".into()), None, "8.0.0", None),
2571 (Some("".into()), Some(false), "7.87.0", Some(false)),
2572 (Some("".into()), Some(false), "7.88.0", Some(false)),
2573 (Some("".into()), Some(false), "7.88.1", Some(false)),
2574 (Some("".into()), Some(false), "8.0.0", Some(false)),
2575 ];
2576
2577 for (proxy, multiplexing, curl_v, result) in cases {
2578 let mut http = CargoHttpConfig {
2579 multiplexing,
2580 proxy,
2581 ..Default::default()
2582 };
2583 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2584 assert_eq!(http.multiplexing, result);
2585 }
2586 }
2587
2588 #[test]
2589 fn sync_context() {
2590 fn assert_sync<S: Sync>() {}
2591 assert_sync::<GlobalContext>();
2592 }
2593}