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 if self.unstable_flags.warnings {
2151 Ok(self.build_config()?.warnings.unwrap_or_default())
2152 } else {
2153 Ok(WarningHandling::default())
2154 }
2155 }
2156
2157 pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
2158 self.ws_roots.lock().unwrap()
2159 }
2160}
2161
2162pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2163 ::home::cargo_home_with_cwd(cwd).ok()
2164}
2165
2166pub fn save_credentials(
2167 gctx: &GlobalContext,
2168 token: Option<RegistryCredentialConfig>,
2169 registry: &SourceId,
2170) -> CargoResult<()> {
2171 let registry = if registry.is_crates_io() {
2172 None
2173 } else {
2174 let name = registry
2175 .alt_registry_key()
2176 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2177 Some(name)
2178 };
2179
2180 let home_path = gctx.home_path.clone().into_path_unlocked();
2184 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2185 Some(path) => match path.file_name() {
2186 Some(filename) => Path::new(filename).to_owned(),
2187 None => Path::new("credentials.toml").to_owned(),
2188 },
2189 None => Path::new("credentials.toml").to_owned(),
2190 };
2191
2192 let mut file = {
2193 gctx.home_path.create_dir()?;
2194 gctx.home_path
2195 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2196 };
2197
2198 let mut contents = String::new();
2199 file.read_to_string(&mut contents).with_context(|| {
2200 format!(
2201 "failed to read configuration file `{}`",
2202 file.path().display()
2203 )
2204 })?;
2205
2206 let mut toml = parse_document(&contents, file.path(), gctx)?;
2207
2208 if let Some(token) = toml.remove("token") {
2210 let map = HashMap::from([("token".to_string(), token)]);
2211 toml.insert("registry".into(), map.into());
2212 }
2213
2214 if let Some(token) = token {
2215 let path_def = Definition::Path(file.path().to_path_buf());
2218 let (key, mut value) = match token {
2219 RegistryCredentialConfig::Token(token) => {
2220 let key = "token".to_string();
2223 let value = ConfigValue::String(token.expose(), path_def.clone());
2224 let map = HashMap::from([(key, value)]);
2225 let table = CV::Table(map, path_def.clone());
2226
2227 if let Some(registry) = registry {
2228 let map = HashMap::from([(registry.to_string(), table)]);
2229 ("registries".into(), CV::Table(map, path_def.clone()))
2230 } else {
2231 ("registry".into(), table)
2232 }
2233 }
2234 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2235 let key = "secret-key".to_string();
2238 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2239 let mut map = HashMap::from([(key, value)]);
2240 if let Some(key_subject) = key_subject {
2241 let key = "secret-key-subject".to_string();
2242 let value = ConfigValue::String(key_subject, path_def.clone());
2243 map.insert(key, value);
2244 }
2245 let table = CV::Table(map, path_def.clone());
2246
2247 if let Some(registry) = registry {
2248 let map = HashMap::from([(registry.to_string(), table)]);
2249 ("registries".into(), CV::Table(map, path_def.clone()))
2250 } else {
2251 ("registry".into(), table)
2252 }
2253 }
2254 _ => unreachable!(),
2255 };
2256
2257 if registry.is_some() {
2258 if let Some(table) = toml.remove("registries") {
2259 let v = CV::from_toml(path_def, table)?;
2260 value.merge(v, false)?;
2261 }
2262 }
2263 toml.insert(key, value.into_toml());
2264 } else {
2265 if let Some(registry) = registry {
2267 if let Some(registries) = toml.get_mut("registries") {
2268 if let Some(reg) = registries.get_mut(registry) {
2269 let rtable = reg.as_table_mut().ok_or_else(|| {
2270 format_err!("expected `[registries.{}]` to be a table", registry)
2271 })?;
2272 rtable.remove("token");
2273 rtable.remove("secret-key");
2274 rtable.remove("secret-key-subject");
2275 }
2276 }
2277 } else if let Some(registry) = toml.get_mut("registry") {
2278 let reg_table = registry
2279 .as_table_mut()
2280 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2281 reg_table.remove("token");
2282 reg_table.remove("secret-key");
2283 reg_table.remove("secret-key-subject");
2284 }
2285 }
2286
2287 let contents = toml.to_string();
2288 file.seek(SeekFrom::Start(0))?;
2289 file.write_all(contents.as_bytes())
2290 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2291 file.file().set_len(contents.len() as u64)?;
2292 set_permissions(file.file(), 0o600)
2293 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2294
2295 return Ok(());
2296
2297 #[cfg(unix)]
2298 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2299 use std::os::unix::fs::PermissionsExt;
2300
2301 let mut perms = file.metadata()?.permissions();
2302 perms.set_mode(mode);
2303 file.set_permissions(perms)?;
2304 Ok(())
2305 }
2306
2307 #[cfg(not(unix))]
2308 fn set_permissions(_file: &File, _mode: u32) -> CargoResult<()> {
2309 Ok(())
2310 }
2311}
2312
2313struct ConfigInclude {
2319 path: PathBuf,
2322 def: Definition,
2323 optional: bool,
2325}
2326
2327impl ConfigInclude {
2328 fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
2329 Self {
2330 path: p.into(),
2331 def,
2332 optional: false,
2333 }
2334 }
2335
2336 fn resolve_path(&self, gctx: &GlobalContext) -> Option<PathBuf> {
2349 let abs_path = match &self.def {
2350 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
2351 Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
2352 }
2353 .join(&self.path);
2354
2355 if self.optional && !abs_path.exists() {
2356 tracing::info!(
2357 "skipping optional include `{}` in `{}`: file not found at `{}`",
2358 self.path.display(),
2359 self.def,
2360 abs_path.display(),
2361 );
2362 None
2363 } else {
2364 Some(abs_path)
2365 }
2366 }
2367}
2368
2369fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2370 toml.parse().map_err(Into::into)
2372}
2373
2374fn toml_dotted_keys(arg: &str) -> CargoResult<toml_edit::DocumentMut> {
2375 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
2381 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
2382 })?;
2383 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
2384 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
2385 }
2386 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
2387 non_empty(d.prefix()) || non_empty(d.suffix())
2388 }
2389 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
2390 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
2391 }
2392 let ok = {
2393 let mut got_to_value = false;
2394 let mut table = doc.as_table();
2395 let mut is_root = true;
2396 while table.is_dotted() || is_root {
2397 is_root = false;
2398 if table.len() != 1 {
2399 break;
2400 }
2401 let (k, n) = table.iter().next().expect("len() == 1 above");
2402 match n {
2403 Item::Table(nt) => {
2404 if table.key(k).map_or(false, non_empty_key_decor)
2405 || non_empty_decor(nt.decor())
2406 {
2407 bail!(
2408 "--config argument `{arg}` \
2409 includes non-whitespace decoration"
2410 )
2411 }
2412 table = nt;
2413 }
2414 Item::Value(v) if v.is_inline_table() => {
2415 bail!(
2416 "--config argument `{arg}` \
2417 sets a value to an inline table, which is not accepted"
2418 );
2419 }
2420 Item::Value(v) => {
2421 if table
2422 .key(k)
2423 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
2424 || non_empty_decor(v.decor())
2425 {
2426 bail!(
2427 "--config argument `{arg}` \
2428 includes non-whitespace decoration"
2429 )
2430 }
2431 got_to_value = true;
2432 break;
2433 }
2434 Item::ArrayOfTables(_) => {
2435 bail!(
2436 "--config argument `{arg}` \
2437 sets a value to an array of tables, which is not accepted"
2438 );
2439 }
2440
2441 Item::None => {
2442 bail!("--config argument `{arg}` doesn't provide a value")
2443 }
2444 }
2445 }
2446 got_to_value
2447 };
2448 if !ok {
2449 bail!(
2450 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
2451 );
2452 }
2453 Ok(doc)
2454}
2455
2456#[derive(Debug, Deserialize, Clone)]
2467pub struct StringList(Vec<String>);
2468
2469impl StringList {
2470 pub fn as_slice(&self) -> &[String] {
2471 &self.0
2472 }
2473}
2474
2475#[macro_export]
2476macro_rules! __shell_print {
2477 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2478 let mut shell = $config.shell();
2479 let out = shell.$which();
2480 drop(out.write_fmt(format_args!($($arg)*)));
2481 if $newline {
2482 drop(out.write_all(b"\n"));
2483 }
2484 });
2485}
2486
2487#[macro_export]
2488macro_rules! drop_println {
2489 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2490 ($config:expr, $($arg:tt)*) => (
2491 $crate::__shell_print!($config, out, true, $($arg)*)
2492 );
2493}
2494
2495#[macro_export]
2496macro_rules! drop_eprintln {
2497 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2498 ($config:expr, $($arg:tt)*) => (
2499 $crate::__shell_print!($config, err, true, $($arg)*)
2500 );
2501}
2502
2503#[macro_export]
2504macro_rules! drop_print {
2505 ($config:expr, $($arg:tt)*) => (
2506 $crate::__shell_print!($config, out, false, $($arg)*)
2507 );
2508}
2509
2510#[macro_export]
2511macro_rules! drop_eprint {
2512 ($config:expr, $($arg:tt)*) => (
2513 $crate::__shell_print!($config, err, false, $($arg)*)
2514 );
2515}
2516
2517enum Tool {
2518 Rustc,
2519 Rustdoc,
2520}
2521
2522impl Tool {
2523 fn as_str(&self) -> &str {
2524 match self {
2525 Tool::Rustc => "rustc",
2526 Tool::Rustdoc => "rustdoc",
2527 }
2528 }
2529}
2530
2531fn disables_multiplexing_for_bad_curl(
2541 curl_version: &str,
2542 http: &mut CargoHttpConfig,
2543 gctx: &GlobalContext,
2544) {
2545 use crate::util::network;
2546
2547 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
2548 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
2549 if bad_curl_versions
2550 .iter()
2551 .any(|v| curl_version.starts_with(v))
2552 {
2553 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
2554 http.multiplexing = Some(false);
2555 }
2556 }
2557}
2558
2559#[cfg(test)]
2560mod tests {
2561 use super::CargoHttpConfig;
2562 use super::GlobalContext;
2563 use super::Shell;
2564 use super::disables_multiplexing_for_bad_curl;
2565
2566 #[test]
2567 fn disables_multiplexing() {
2568 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
2569 gctx.set_search_stop_path(std::path::PathBuf::new());
2570 gctx.set_env(Default::default());
2571
2572 let mut http = CargoHttpConfig::default();
2573 http.proxy = Some("127.0.0.1:3128".into());
2574 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
2575 assert_eq!(http.multiplexing, Some(false));
2576
2577 let cases = [
2578 (None, None, "7.87.0", None),
2579 (None, None, "7.88.0", None),
2580 (None, None, "7.88.1", None),
2581 (None, None, "8.0.0", None),
2582 (Some("".into()), None, "7.87.0", Some(false)),
2583 (Some("".into()), None, "7.88.0", Some(false)),
2584 (Some("".into()), None, "7.88.1", Some(false)),
2585 (Some("".into()), None, "8.0.0", None),
2586 (Some("".into()), Some(false), "7.87.0", Some(false)),
2587 (Some("".into()), Some(false), "7.88.0", Some(false)),
2588 (Some("".into()), Some(false), "7.88.1", Some(false)),
2589 (Some("".into()), Some(false), "8.0.0", Some(false)),
2590 ];
2591
2592 for (proxy, multiplexing, curl_v, result) in cases {
2593 let mut http = CargoHttpConfig {
2594 multiplexing,
2595 proxy,
2596 ..Default::default()
2597 };
2598 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
2599 assert_eq!(http.multiplexing, result);
2600 }
2601 }
2602
2603 #[test]
2604 fn sync_context() {
2605 fn assert_sync<S: Sync>() {}
2606 assert_sync::<GlobalContext>();
2607 }
2608}