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