1use crate::util::cache_lock::{CacheLock, CacheLockMode, CacheLocker};
53use std::borrow::Cow;
54use std::cell::{RefCell, RefMut};
55use std::collections::hash_map::Entry::{Occupied, Vacant};
56use std::collections::{HashMap, HashSet};
57use std::env;
58use std::ffi::{OsStr, OsString};
59use std::fmt;
60use std::fs::{self, File};
61use std::io::prelude::*;
62use std::io::SeekFrom;
63use std::mem;
64use std::path::{Path, PathBuf};
65use std::str::FromStr;
66use std::sync::{Arc, Once};
67use std::time::Instant;
68
69use self::ConfigValue as CV;
70use crate::core::compiler::rustdoc::RustdocExternMap;
71use crate::core::global_cache_tracker::{DeferredGlobalLastUse, GlobalCacheTracker};
72use crate::core::shell::Verbosity;
73use crate::core::{features, CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig};
74use crate::ops::RegistryCredentialConfig;
75use crate::sources::CRATES_IO_INDEX;
76use crate::sources::CRATES_IO_REGISTRY;
77use crate::util::errors::CargoResult;
78use crate::util::network::http::configure_http_handle;
79use crate::util::network::http::http_handle;
80use crate::util::try_canonicalize;
81use crate::util::{internal, CanonicalUrl};
82use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
83use anyhow::{anyhow, bail, format_err, Context as _};
84use cargo_credential::Secret;
85use cargo_util::paths;
86use cargo_util_schemas::manifest::RegistryName;
87use curl::easy::Easy;
88use lazycell::LazyCell;
89use serde::de::IntoDeserializer as _;
90use serde::Deserialize;
91use serde_untagged::UntaggedEnumVisitor;
92use time::OffsetDateTime;
93use toml_edit::Item;
94use url::Url;
95
96mod de;
97use de::Deserializer;
98
99mod value;
100pub use value::{Definition, OptValue, Value};
101
102mod key;
103pub use key::ConfigKey;
104
105mod path;
106pub use path::{ConfigRelativePath, PathAndArgs};
107
108mod target;
109pub use target::{TargetCfgConfig, TargetConfig};
110
111mod environment;
112use environment::Env;
113
114use super::auth::RegistryConfig;
115
116macro_rules! get_value_typed {
118 ($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
119 fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
121 let cv = self.get_cv(key)?;
122 let env = self.get_config_env::<$ty>(key)?;
123 match (cv, env) {
124 (Some(CV::$variant(val, definition)), Some(env)) => {
125 if definition.is_higher_priority(&env.definition) {
126 Ok(Some(Value { val, definition }))
127 } else {
128 Ok(Some(env))
129 }
130 }
131 (Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
132 (Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
133 (None, Some(env)) => Ok(Some(env)),
134 (None, None) => Ok(None),
135 }
136 }
137 };
138}
139
140#[derive(Clone, Copy, Debug)]
142enum WhyLoad {
143 Cli,
150 FileDiscovery,
152}
153
154#[derive(Debug)]
156pub struct CredentialCacheValue {
157 pub token_value: Secret<String>,
158 pub expiration: Option<OffsetDateTime>,
159 pub operation_independent: bool,
160}
161
162#[derive(Debug)]
165pub struct GlobalContext {
166 home_path: Filesystem,
168 shell: RefCell<Shell>,
170 values: LazyCell<HashMap<String, ConfigValue>>,
172 credential_values: LazyCell<HashMap<String, ConfigValue>>,
174 cli_config: Option<Vec<String>>,
176 cwd: PathBuf,
178 search_stop_path: Option<PathBuf>,
180 cargo_exe: LazyCell<PathBuf>,
182 rustdoc: LazyCell<PathBuf>,
184 extra_verbose: bool,
186 frozen: bool,
189 locked: bool,
192 offline: bool,
195 jobserver: Option<jobserver::Client>,
197 unstable_flags: CliUnstable,
199 unstable_flags_cli: Option<Vec<String>>,
201 easy: LazyCell<RefCell<Easy>>,
203 crates_io_source_id: LazyCell<SourceId>,
205 cache_rustc_info: bool,
207 creation_time: Instant,
209 target_dir: Option<Filesystem>,
211 env: Env,
213 updated_sources: LazyCell<RefCell<HashSet<SourceId>>>,
215 credential_cache: LazyCell<RefCell<HashMap<CanonicalUrl, CredentialCacheValue>>>,
218 registry_config: LazyCell<RefCell<HashMap<SourceId, Option<RegistryConfig>>>>,
220 package_cache_lock: CacheLocker,
222 http_config: LazyCell<CargoHttpConfig>,
224 future_incompat_config: LazyCell<CargoFutureIncompatConfig>,
225 net_config: LazyCell<CargoNetConfig>,
226 build_config: LazyCell<CargoBuildConfig>,
227 target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
228 doc_extern_map: LazyCell<RustdocExternMap>,
229 progress_config: ProgressConfig,
230 env_config: LazyCell<Arc<HashMap<String, OsString>>>,
231 pub nightly_features_allowed: bool,
247 pub ws_roots: RefCell<HashMap<PathBuf, WorkspaceRootConfig>>,
249 global_cache_tracker: LazyCell<RefCell<GlobalCacheTracker>>,
251 deferred_global_last_use: LazyCell<RefCell<DeferredGlobalLastUse>>,
254}
255
256impl GlobalContext {
257 pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> GlobalContext {
265 static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
266 static INIT: Once = Once::new();
267
268 INIT.call_once(|| unsafe {
271 if let Some(client) = jobserver::Client::from_env() {
272 GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
273 }
274 });
275
276 let env = Env::new();
277
278 let cache_key = "CARGO_CACHE_RUSTC_INFO";
279 let cache_rustc_info = match env.get_env_os(cache_key) {
280 Some(cache) => cache != "0",
281 _ => true,
282 };
283
284 GlobalContext {
285 home_path: Filesystem::new(homedir),
286 shell: RefCell::new(shell),
287 cwd,
288 search_stop_path: None,
289 values: LazyCell::new(),
290 credential_values: LazyCell::new(),
291 cli_config: None,
292 cargo_exe: LazyCell::new(),
293 rustdoc: LazyCell::new(),
294 extra_verbose: false,
295 frozen: false,
296 locked: false,
297 offline: false,
298 jobserver: unsafe {
299 if GLOBAL_JOBSERVER.is_null() {
300 None
301 } else {
302 Some((*GLOBAL_JOBSERVER).clone())
303 }
304 },
305 unstable_flags: CliUnstable::default(),
306 unstable_flags_cli: None,
307 easy: LazyCell::new(),
308 crates_io_source_id: LazyCell::new(),
309 cache_rustc_info,
310 creation_time: Instant::now(),
311 target_dir: None,
312 env,
313 updated_sources: LazyCell::new(),
314 credential_cache: LazyCell::new(),
315 registry_config: LazyCell::new(),
316 package_cache_lock: CacheLocker::new(),
317 http_config: LazyCell::new(),
318 future_incompat_config: LazyCell::new(),
319 net_config: LazyCell::new(),
320 build_config: LazyCell::new(),
321 target_cfgs: LazyCell::new(),
322 doc_extern_map: LazyCell::new(),
323 progress_config: ProgressConfig::default(),
324 env_config: LazyCell::new(),
325 nightly_features_allowed: matches!(&*features::channel(), "nightly" | "dev"),
326 ws_roots: RefCell::new(HashMap::new()),
327 global_cache_tracker: LazyCell::new(),
328 deferred_global_last_use: LazyCell::new(),
329 }
330 }
331
332 pub fn default() -> CargoResult<GlobalContext> {
337 let shell = Shell::new();
338 let cwd =
339 env::current_dir().context("couldn't get the current directory of the process")?;
340 let homedir = homedir(&cwd).ok_or_else(|| {
341 anyhow!(
342 "Cargo couldn't find your home directory. \
343 This probably means that $HOME was not set."
344 )
345 })?;
346 Ok(GlobalContext::new(shell, cwd, homedir))
347 }
348
349 pub fn home(&self) -> &Filesystem {
351 &self.home_path
352 }
353
354 pub fn diagnostic_home_config(&self) -> String {
358 let home = self.home_path.as_path_unlocked();
359 let path = match self.get_file_path(home, "config", false) {
360 Ok(Some(existing_path)) => existing_path,
361 _ => home.join("config.toml"),
362 };
363 path.to_string_lossy().to_string()
364 }
365
366 pub fn git_path(&self) -> Filesystem {
368 self.home_path.join("git")
369 }
370
371 pub fn git_checkouts_path(&self) -> Filesystem {
374 self.git_path().join("checkouts")
375 }
376
377 pub fn git_db_path(&self) -> Filesystem {
380 self.git_path().join("db")
381 }
382
383 pub fn registry_base_path(&self) -> Filesystem {
385 self.home_path.join("registry")
386 }
387
388 pub fn registry_index_path(&self) -> Filesystem {
390 self.registry_base_path().join("index")
391 }
392
393 pub fn registry_cache_path(&self) -> Filesystem {
395 self.registry_base_path().join("cache")
396 }
397
398 pub fn registry_source_path(&self) -> Filesystem {
400 self.registry_base_path().join("src")
401 }
402
403 pub fn default_registry(&self) -> CargoResult<Option<String>> {
405 Ok(self
406 .get_string("registry.default")?
407 .map(|registry| registry.val))
408 }
409
410 pub fn shell(&self) -> RefMut<'_, Shell> {
412 self.shell.borrow_mut()
413 }
414
415 pub fn rustdoc(&self) -> CargoResult<&Path> {
417 self.rustdoc
418 .try_borrow_with(|| Ok(self.get_tool(Tool::Rustdoc, &self.build_config()?.rustdoc)))
419 .map(AsRef::as_ref)
420 }
421
422 pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
424 let cache_location = ws.map(|ws| {
425 ws.target_dir()
426 .join(".rustc_info.json")
427 .into_path_unlocked()
428 });
429 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
430 let rustc_workspace_wrapper = self.maybe_get_tool(
431 "rustc_workspace_wrapper",
432 &self.build_config()?.rustc_workspace_wrapper,
433 );
434
435 Rustc::new(
436 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
437 wrapper,
438 rustc_workspace_wrapper,
439 &self
440 .home()
441 .join("bin")
442 .join("rustc")
443 .into_path_unlocked()
444 .with_extension(env::consts::EXE_EXTENSION),
445 if self.cache_rustc_info {
446 cache_location
447 } else {
448 None
449 },
450 self,
451 )
452 }
453
454 pub fn cargo_exe(&self) -> CargoResult<&Path> {
456 self.cargo_exe
457 .try_borrow_with(|| {
458 let from_env = || -> CargoResult<PathBuf> {
459 let exe = try_canonicalize(
464 self.get_env_os(crate::CARGO_ENV)
465 .map(PathBuf::from)
466 .ok_or_else(|| anyhow!("$CARGO not set"))?,
467 )?;
468 Ok(exe)
469 };
470
471 fn from_current_exe() -> CargoResult<PathBuf> {
472 let exe = try_canonicalize(env::current_exe()?)?;
477 Ok(exe)
478 }
479
480 fn from_argv() -> CargoResult<PathBuf> {
481 let argv0 = env::args_os()
490 .map(PathBuf::from)
491 .next()
492 .ok_or_else(|| anyhow!("no argv[0]"))?;
493 paths::resolve_executable(&argv0)
494 }
495
496 let exe = from_env()
497 .or_else(|_| from_current_exe())
498 .or_else(|_| from_argv())
499 .context("couldn't get the path to cargo executable")?;
500 Ok(exe)
501 })
502 .map(AsRef::as_ref)
503 }
504
505 pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> {
507 self.updated_sources
508 .borrow_with(|| RefCell::new(HashSet::new()))
509 .borrow_mut()
510 }
511
512 pub fn credential_cache(&self) -> RefMut<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
514 self.credential_cache
515 .borrow_with(|| RefCell::new(HashMap::new()))
516 .borrow_mut()
517 }
518
519 pub(crate) fn registry_config(&self) -> RefMut<'_, HashMap<SourceId, Option<RegistryConfig>>> {
521 self.registry_config
522 .borrow_with(|| RefCell::new(HashMap::new()))
523 .borrow_mut()
524 }
525
526 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
532 self.values.try_borrow_with(|| self.load_values())
533 }
534
535 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
542 let _ = self.values()?;
543 Ok(self
544 .values
545 .borrow_mut()
546 .expect("already loaded config values"))
547 }
548
549 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
551 if self.values.borrow().is_some() {
552 bail!("config values already found")
553 }
554 match self.values.fill(values) {
555 Ok(()) => Ok(()),
556 Err(_) => bail!("could not fill values"),
557 }
558 }
559
560 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
563 let path = path.into();
564 debug_assert!(self.cwd.starts_with(&path));
565 self.search_stop_path = Some(path);
566 }
567
568 pub fn reload_cwd(&mut self) -> CargoResult<()> {
572 let cwd =
573 env::current_dir().context("couldn't get the current directory of the process")?;
574 let homedir = homedir(&cwd).ok_or_else(|| {
575 anyhow!(
576 "Cargo couldn't find your home directory. \
577 This probably means that $HOME was not set."
578 )
579 })?;
580
581 self.cwd = cwd;
582 self.home_path = Filesystem::new(homedir);
583 self.reload_rooted_at(self.cwd.clone())?;
584 Ok(())
585 }
586
587 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
590 let values = self.load_values_from(path.as_ref())?;
591 self.values.replace(values);
592 self.merge_cli_args()?;
593 self.load_unstable_flags_from_config()?;
594 Ok(())
595 }
596
597 pub fn cwd(&self) -> &Path {
599 &self.cwd
600 }
601
602 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
608 if let Some(dir) = &self.target_dir {
609 Ok(Some(dir.clone()))
610 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
611 if dir.is_empty() {
613 bail!(
614 "the target directory is set to an empty string in the \
615 `CARGO_TARGET_DIR` environment variable"
616 )
617 }
618
619 Ok(Some(Filesystem::new(self.cwd.join(dir))))
620 } else if let Some(val) = &self.build_config()?.target_dir {
621 let path = val.resolve_path(self);
622
623 if val.raw_value().is_empty() {
625 bail!(
626 "the target directory is set to an empty string in {}",
627 val.value().definition
628 )
629 }
630
631 Ok(Some(Filesystem::new(path)))
632 } else {
633 Ok(None)
634 }
635 }
636
637 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
642 if let Some(vals) = self.credential_values.borrow() {
643 let val = self.get_cv_helper(key, vals)?;
644 if val.is_some() {
645 return Ok(val);
646 }
647 }
648 self.get_cv_helper(key, self.values()?)
649 }
650
651 fn get_cv_helper(
652 &self,
653 key: &ConfigKey,
654 vals: &HashMap<String, ConfigValue>,
655 ) -> CargoResult<Option<ConfigValue>> {
656 tracing::trace!("get cv {:?}", key);
657 if key.is_root() {
658 return Ok(Some(CV::Table(
661 vals.clone(),
662 Definition::Path(PathBuf::new()),
663 )));
664 }
665 let mut parts = key.parts().enumerate();
666 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
667 return Ok(None);
668 };
669 for (i, part) in parts {
670 match val {
671 CV::Table(map, _) => {
672 val = match map.get(part) {
673 Some(val) => val,
674 None => return Ok(None),
675 }
676 }
677 CV::Integer(_, def)
678 | CV::String(_, def)
679 | CV::List(_, def)
680 | CV::Boolean(_, def) => {
681 let mut key_so_far = ConfigKey::new();
682 for part in key.parts().take(i) {
683 key_so_far.push(part);
684 }
685 bail!(
686 "expected table for configuration key `{}`, \
687 but found {} in {}",
688 key_so_far,
689 val.desc(),
690 def
691 )
692 }
693 }
694 }
695 Ok(Some(val.clone()))
696 }
697
698 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
700 let cv = self.get_cv(key)?;
703 if key.is_root() {
704 return Ok(cv);
706 }
707 let env = self.env.get_str(key.as_env_key());
708 let env_def = Definition::Environment(key.as_env_key().to_string());
709 let use_env = match (&cv, env) {
710 (Some(CV::List(..)), Some(_)) => true,
712 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
713 (None, Some(_)) => true,
714 _ => false,
715 };
716
717 if !use_env {
718 return Ok(cv);
719 }
720
721 let env = env.unwrap();
725 if env == "true" {
726 Ok(Some(CV::Boolean(true, env_def)))
727 } else if env == "false" {
728 Ok(Some(CV::Boolean(false, env_def)))
729 } else if let Ok(i) = env.parse::<i64>() {
730 Ok(Some(CV::Integer(i, env_def)))
731 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
732 match cv {
733 Some(CV::List(mut cv_list, cv_def)) => {
734 self.get_env_list(key, &mut cv_list)?;
736 Ok(Some(CV::List(cv_list, cv_def)))
737 }
738 Some(cv) => {
739 bail!(
743 "unable to merge array env for config `{}`\n\
744 file: {:?}\n\
745 env: {}",
746 key,
747 cv,
748 env
749 );
750 }
751 None => {
752 let mut cv_list = Vec::new();
753 self.get_env_list(key, &mut cv_list)?;
754 Ok(Some(CV::List(cv_list, env_def)))
755 }
756 }
757 } else {
758 match cv {
760 Some(CV::List(mut cv_list, cv_def)) => {
761 self.get_env_list(key, &mut cv_list)?;
763 Ok(Some(CV::List(cv_list, cv_def)))
764 }
765 _ => {
766 Ok(Some(CV::String(env.to_string(), env_def)))
771 }
772 }
773 }
774 }
775
776 pub fn set_env(&mut self, env: HashMap<String, String>) {
778 self.env = Env::from_map(env);
779 }
780
781 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
784 self.env.iter_str()
785 }
786
787 fn env_keys(&self) -> impl Iterator<Item = &str> {
789 self.env.keys_str()
790 }
791
792 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
793 where
794 T: FromStr,
795 <T as FromStr>::Err: fmt::Display,
796 {
797 match self.env.get_str(key.as_env_key()) {
798 Some(value) => {
799 let definition = Definition::Environment(key.as_env_key().to_string());
800 Ok(Some(Value {
801 val: value
802 .parse()
803 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
804 definition,
805 }))
806 }
807 None => {
808 self.check_environment_key_case_mismatch(key);
809 Ok(None)
810 }
811 }
812 }
813
814 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
819 self.env.get_env(key)
820 }
821
822 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
827 self.env.get_env_os(key)
828 }
829
830 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
834 if self.env.contains_key(key.as_env_key()) {
835 return Ok(true);
836 }
837 if env_prefix_ok {
838 let env_prefix = format!("{}_", key.as_env_key());
839 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
840 return Ok(true);
841 }
842 }
843 if self.get_cv(key)?.is_some() {
844 return Ok(true);
845 }
846 self.check_environment_key_case_mismatch(key);
847
848 Ok(false)
849 }
850
851 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
852 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
853 let _ = self.shell().warn(format!(
854 "environment variables are expected to use uppercase letters and underscores, \
855 the variable `{}` will be ignored and have no effect",
856 env_key
857 ));
858 }
859 }
860
861 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
865 self.get::<OptValue<String>>(key)
866 }
867
868 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
874 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
875 v.map(|v| Value {
876 val: v.val.resolve_program(self),
877 definition: v.definition,
878 })
879 })
880 }
881
882 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
883 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
884 if is_path {
885 definition.root(self).join(value)
886 } else {
887 PathBuf::from(value)
889 }
890 }
891
892 pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
900 let key = ConfigKey::from_str(key);
901 self._get_list(&key)
902 }
903
904 fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
905 match self.get_cv(key)? {
906 Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
907 Some(val) => self.expected("list", key, &val),
908 None => Ok(None),
909 }
910 }
911
912 fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
914 let mut res = Vec::new();
915
916 match self.get_cv(key)? {
917 Some(CV::List(val, _def)) => res.extend(val),
918 Some(CV::String(val, def)) => {
919 let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
920 res.extend(split_vs);
921 }
922 Some(val) => {
923 return self.expected("string or array of strings", key, &val);
924 }
925 None => {}
926 }
927
928 self.get_env_list(key, &mut res)?;
929
930 Ok(res)
931 }
932
933 fn get_env_list(
936 &self,
937 key: &ConfigKey,
938 output: &mut Vec<(String, Definition)>,
939 ) -> CargoResult<()> {
940 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
941 self.check_environment_key_case_mismatch(key);
942 return Ok(());
943 };
944
945 if is_nonmergable_list(&key) {
946 output.clear();
947 }
948
949 let def = Definition::Environment(key.as_env_key().to_string());
950 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
951 let toml_v = toml::Value::deserialize(toml::de::ValueDeserializer::new(&env_val))
953 .map_err(|e| {
954 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
955 })?;
956 let values = toml_v.as_array().expect("env var was not array");
957 for value in values {
958 let s = value.as_str().ok_or_else(|| {
960 ConfigError::new(
961 format!("expected string, found {}", value.type_str()),
962 def.clone(),
963 )
964 })?;
965 output.push((s.to_string(), def.clone()));
966 }
967 } else {
968 output.extend(
969 env_val
970 .split_whitespace()
971 .map(|s| (s.to_string(), def.clone())),
972 );
973 }
974 output.sort_by(|a, b| a.1.cmp(&b.1));
975 Ok(())
976 }
977
978 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
982 match self.get_cv(key)? {
983 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
984 Some(val) => self.expected("table", key, &val),
985 None => Ok(None),
986 }
987 }
988
989 get_value_typed! {get_integer, i64, Integer, "an integer"}
990 get_value_typed! {get_bool, bool, Boolean, "true/false"}
991 get_value_typed! {get_string_priv, String, String, "a string"}
992
993 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
995 val.expected(ty, &key.to_string())
996 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
997 }
998
999 pub fn configure(
1005 &mut self,
1006 verbose: u32,
1007 quiet: bool,
1008 color: Option<&str>,
1009 frozen: bool,
1010 locked: bool,
1011 offline: bool,
1012 target_dir: &Option<PathBuf>,
1013 unstable_flags: &[String],
1014 cli_config: &[String],
1015 ) -> CargoResult<()> {
1016 for warning in self
1017 .unstable_flags
1018 .parse(unstable_flags, self.nightly_features_allowed)?
1019 {
1020 self.shell().warn(warning)?;
1021 }
1022 if !unstable_flags.is_empty() {
1023 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1026 }
1027 if !cli_config.is_empty() {
1028 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1029 self.merge_cli_args()?;
1030 }
1031
1032 self.load_unstable_flags_from_config()?;
1036 if self.unstable_flags.config_include {
1037 self.reload_rooted_at(self.cwd.clone())?;
1044 }
1045
1046 let term = self.get::<TermConfig>("term").unwrap_or_default();
1050
1051 let extra_verbose = verbose >= 2;
1053 let verbose = verbose != 0;
1054 let verbosity = match (verbose, quiet) {
1055 (true, true) => bail!("cannot set both --verbose and --quiet"),
1056 (true, false) => Verbosity::Verbose,
1057 (false, true) => Verbosity::Quiet,
1058 (false, false) => match (term.verbose, term.quiet) {
1059 (Some(true), Some(true)) => {
1060 bail!("cannot set both `term.verbose` and `term.quiet`")
1061 }
1062 (Some(true), _) => Verbosity::Verbose,
1063 (_, Some(true)) => Verbosity::Quiet,
1064 _ => Verbosity::Normal,
1065 },
1066 };
1067 self.shell().set_verbosity(verbosity);
1068 self.extra_verbose = extra_verbose;
1069
1070 let color = color.or_else(|| term.color.as_deref());
1071 self.shell().set_color_choice(color)?;
1072 if let Some(hyperlinks) = term.hyperlinks {
1073 self.shell().set_hyperlinks(hyperlinks)?;
1074 }
1075 if let Some(unicode) = term.unicode {
1076 self.shell().set_unicode(unicode)?;
1077 }
1078
1079 self.progress_config = term.progress.unwrap_or_default();
1080
1081 self.frozen = frozen;
1082 self.locked = locked;
1083 self.offline = offline
1084 || self
1085 .net_config()
1086 .ok()
1087 .and_then(|n| n.offline)
1088 .unwrap_or(false);
1089 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1090 self.target_dir = cli_target_dir;
1091
1092 Ok(())
1093 }
1094
1095 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1096 if self.nightly_features_allowed {
1099 self.unstable_flags = self
1100 .get::<Option<CliUnstable>>("unstable")?
1101 .unwrap_or_default();
1102 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1103 self.unstable_flags.parse(unstable_flags_cli, true)?;
1108 }
1109 }
1110
1111 Ok(())
1112 }
1113
1114 pub fn cli_unstable(&self) -> &CliUnstable {
1115 &self.unstable_flags
1116 }
1117
1118 pub fn extra_verbose(&self) -> bool {
1119 self.extra_verbose
1120 }
1121
1122 pub fn network_allowed(&self) -> bool {
1123 !self.frozen() && !self.offline()
1124 }
1125
1126 pub fn offline(&self) -> bool {
1127 self.offline
1128 }
1129
1130 pub fn frozen(&self) -> bool {
1131 self.frozen
1132 }
1133
1134 pub fn locked(&self) -> bool {
1135 self.locked
1136 }
1137
1138 pub fn set_locked(&mut self, locked: bool) {
1139 self.locked = locked;
1140 }
1141
1142 pub fn lock_update_allowed(&self) -> bool {
1143 !self.frozen && !self.locked
1144 }
1145
1146 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1148 self.load_values_from(&self.cwd)
1149 }
1150
1151 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1155 let mut result = Vec::new();
1156 let mut seen = HashSet::new();
1157 let home = self.home_path.clone().into_path_unlocked();
1158 self.walk_tree(&self.cwd, &home, |path| {
1159 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1160 if self.cli_unstable().config_include {
1161 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1162 }
1163 result.push(cv);
1164 Ok(())
1165 })
1166 .context("could not load Cargo configuration")?;
1167 Ok(result)
1168 }
1169
1170 fn load_unmerged_include(
1174 &self,
1175 cv: &mut CV,
1176 seen: &mut HashSet<PathBuf>,
1177 output: &mut Vec<CV>,
1178 ) -> CargoResult<()> {
1179 let includes = self.include_paths(cv, false)?;
1180 for (path, abs_path, def) in includes {
1181 let mut cv = self
1182 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1183 .with_context(|| {
1184 format!("failed to load config include `{}` from `{}`", path, def)
1185 })?;
1186 self.load_unmerged_include(&mut cv, seen, output)?;
1187 output.push(cv);
1188 }
1189 Ok(())
1190 }
1191
1192 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1194 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1197 let home = self.home_path.clone().into_path_unlocked();
1198
1199 self.walk_tree(path, &home, |path| {
1200 let value = self.load_file(path)?;
1201 cfg.merge(value, false).with_context(|| {
1202 format!("failed to merge configuration at `{}`", path.display())
1203 })?;
1204 Ok(())
1205 })
1206 .context("could not load Cargo configuration")?;
1207
1208 match cfg {
1209 CV::Table(map, _) => Ok(map),
1210 _ => unreachable!(),
1211 }
1212 }
1213
1214 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1218 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1219 }
1220
1221 fn _load_file(
1231 &self,
1232 path: &Path,
1233 seen: &mut HashSet<PathBuf>,
1234 includes: bool,
1235 why_load: WhyLoad,
1236 ) -> CargoResult<ConfigValue> {
1237 if !seen.insert(path.to_path_buf()) {
1238 bail!(
1239 "config `include` cycle detected with path `{}`",
1240 path.display()
1241 );
1242 }
1243 tracing::debug!(?path, ?why_load, includes, "load config from file");
1244
1245 let contents = fs::read_to_string(path)
1246 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1247 let toml = parse_document(&contents, path, self).with_context(|| {
1248 format!("could not parse TOML configuration in `{}`", path.display())
1249 })?;
1250 let def = match why_load {
1251 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1252 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1253 };
1254 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1255 format!(
1256 "failed to load TOML configuration from `{}`",
1257 path.display()
1258 )
1259 })?;
1260 if includes {
1261 self.load_includes(value, seen, why_load)
1262 } else {
1263 Ok(value)
1264 }
1265 }
1266
1267 fn load_includes(
1274 &self,
1275 mut value: CV,
1276 seen: &mut HashSet<PathBuf>,
1277 why_load: WhyLoad,
1278 ) -> CargoResult<CV> {
1279 let includes = self.include_paths(&mut value, true)?;
1281 if !self.cli_unstable().config_include {
1283 return Ok(value);
1284 }
1285 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1287 for (path, abs_path, def) in includes {
1288 self._load_file(&abs_path, seen, true, why_load)
1289 .and_then(|include| root.merge(include, true))
1290 .with_context(|| {
1291 format!("failed to load config include `{}` from `{}`", path, def)
1292 })?;
1293 }
1294 root.merge(value, true)?;
1295 Ok(root)
1296 }
1297
1298 fn include_paths(
1300 &self,
1301 cv: &mut CV,
1302 remove: bool,
1303 ) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
1304 let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
1305 let abs_path = match def {
1306 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1307 Definition::Environment(_) | Definition::Cli(None) => self.cwd().join(&path),
1308 };
1309 (path.to_string(), abs_path, def.clone())
1310 };
1311 let CV::Table(table, _def) = cv else {
1312 unreachable!()
1313 };
1314 let owned;
1315 let include = if remove {
1316 owned = table.remove("include");
1317 owned.as_ref()
1318 } else {
1319 table.get("include")
1320 };
1321 let includes = match include {
1322 Some(CV::String(s, def)) => {
1323 vec![abs(s, def)]
1324 }
1325 Some(CV::List(list, _def)) => list.iter().map(|(s, def)| abs(s, def)).collect(),
1326 Some(other) => bail!(
1327 "`include` expected a string or list, but found {} in `{}`",
1328 other.desc(),
1329 other.definition()
1330 ),
1331 None => {
1332 return Ok(Vec::new());
1333 }
1334 };
1335
1336 for (path, abs_path, def) in &includes {
1337 if abs_path.extension() != Some(OsStr::new("toml")) {
1338 bail!(
1339 "expected a config include path ending with `.toml`, \
1340 but found `{path}` from `{def}`",
1341 )
1342 }
1343 }
1344
1345 Ok(includes)
1346 }
1347
1348 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1350 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1351 let Some(cli_args) = &self.cli_config else {
1352 return Ok(loaded_args);
1353 };
1354 let mut seen = HashSet::new();
1355 for arg in cli_args {
1356 let arg_as_path = self.cwd.join(arg);
1357 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1358 let str_path = arg_as_path
1360 .to_str()
1361 .ok_or_else(|| {
1362 anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
1363 })?
1364 .to_string();
1365 self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
1366 .with_context(|| format!("failed to load config from `{}`", str_path))?
1367 } else {
1368 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
1374 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
1375 })?;
1376 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
1377 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
1378 }
1379 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
1380 non_empty(d.prefix()) || non_empty(d.suffix())
1381 }
1382 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
1383 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
1384 }
1385 let ok = {
1386 let mut got_to_value = false;
1387 let mut table = doc.as_table();
1388 let mut is_root = true;
1389 while table.is_dotted() || is_root {
1390 is_root = false;
1391 if table.len() != 1 {
1392 break;
1393 }
1394 let (k, n) = table.iter().next().expect("len() == 1 above");
1395 match n {
1396 Item::Table(nt) => {
1397 if table.key(k).map_or(false, non_empty_key_decor)
1398 || non_empty_decor(nt.decor())
1399 {
1400 bail!(
1401 "--config argument `{arg}` \
1402 includes non-whitespace decoration"
1403 )
1404 }
1405 table = nt;
1406 }
1407 Item::Value(v) if v.is_inline_table() => {
1408 bail!(
1409 "--config argument `{arg}` \
1410 sets a value to an inline table, which is not accepted"
1411 );
1412 }
1413 Item::Value(v) => {
1414 if table
1415 .key(k)
1416 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
1417 || non_empty_decor(v.decor())
1418 {
1419 bail!(
1420 "--config argument `{arg}` \
1421 includes non-whitespace decoration"
1422 )
1423 }
1424 got_to_value = true;
1425 break;
1426 }
1427 Item::ArrayOfTables(_) => {
1428 bail!(
1429 "--config argument `{arg}` \
1430 sets a value to an array of tables, which is not accepted"
1431 );
1432 }
1433
1434 Item::None => {
1435 bail!("--config argument `{arg}` doesn't provide a value")
1436 }
1437 }
1438 }
1439 got_to_value
1440 };
1441 if !ok {
1442 bail!(
1443 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
1444 );
1445 }
1446
1447 let toml_v: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1448 .with_context(|| {
1449 format!("failed to parse value from --config argument `{arg}`")
1450 })?;
1451
1452 if toml_v
1453 .get("registry")
1454 .and_then(|v| v.as_table())
1455 .and_then(|t| t.get("token"))
1456 .is_some()
1457 {
1458 bail!("registry.token cannot be set through --config for security reasons");
1459 } else if let Some((k, _)) = toml_v
1460 .get("registries")
1461 .and_then(|v| v.as_table())
1462 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1463 {
1464 bail!(
1465 "registries.{}.token cannot be set through --config for security reasons",
1466 k
1467 );
1468 }
1469
1470 if toml_v
1471 .get("registry")
1472 .and_then(|v| v.as_table())
1473 .and_then(|t| t.get("secret-key"))
1474 .is_some()
1475 {
1476 bail!(
1477 "registry.secret-key cannot be set through --config for security reasons"
1478 );
1479 } else if let Some((k, _)) = toml_v
1480 .get("registries")
1481 .and_then(|v| v.as_table())
1482 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1483 {
1484 bail!(
1485 "registries.{}.secret-key cannot be set through --config for security reasons",
1486 k
1487 );
1488 }
1489
1490 CV::from_toml(Definition::Cli(None), toml_v)
1491 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1492 };
1493 let tmp_table = self
1494 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1495 .context("failed to load --config include".to_string())?;
1496 loaded_args
1497 .merge(tmp_table, true)
1498 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1499 }
1500 Ok(loaded_args)
1501 }
1502
1503 fn merge_cli_args(&mut self) -> CargoResult<()> {
1505 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1506 unreachable!()
1507 };
1508 let values = self.values_mut()?;
1509 for (key, value) in loaded_map.into_iter() {
1510 match values.entry(key) {
1511 Vacant(entry) => {
1512 entry.insert(value);
1513 }
1514 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1515 format!(
1516 "failed to merge --config key `{}` into `{}`",
1517 entry.key(),
1518 entry.get().definition(),
1519 )
1520 })?,
1521 };
1522 }
1523 Ok(())
1524 }
1525
1526 fn get_file_path(
1532 &self,
1533 dir: &Path,
1534 filename_without_extension: &str,
1535 warn: bool,
1536 ) -> CargoResult<Option<PathBuf>> {
1537 let possible = dir.join(filename_without_extension);
1538 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1539
1540 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1541 if warn {
1542 if let Ok(possible_with_extension_handle) =
1543 same_file::Handle::from_path(&possible_with_extension)
1544 {
1545 if possible_handle != possible_with_extension_handle {
1551 self.shell().warn(format!(
1552 "both `{}` and `{}` exist. Using `{}`",
1553 possible.display(),
1554 possible_with_extension.display(),
1555 possible.display()
1556 ))?;
1557 }
1558 } else {
1559 self.shell().warn(format!(
1560 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1561 possible.display(),
1562 ))?;
1563 self.shell().note(
1564 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`"),
1565 )?;
1566 }
1567 }
1568
1569 Ok(Some(possible))
1570 } else if possible_with_extension.exists() {
1571 Ok(Some(possible_with_extension))
1572 } else {
1573 Ok(None)
1574 }
1575 }
1576
1577 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1578 where
1579 F: FnMut(&Path) -> CargoResult<()>,
1580 {
1581 let mut seen_dir = HashSet::new();
1582
1583 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1584 let config_root = current.join(".cargo");
1585 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1586 walk(&path)?;
1587 }
1588 seen_dir.insert(config_root);
1589 }
1590
1591 if !seen_dir.contains(home) {
1595 if let Some(path) = self.get_file_path(home, "config", true)? {
1596 walk(&path)?;
1597 }
1598 }
1599
1600 Ok(())
1601 }
1602
1603 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1605 RegistryName::new(registry)?;
1606 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1607 self.resolve_registry_index(&index).with_context(|| {
1608 format!(
1609 "invalid index URL for registry `{}` defined in {}",
1610 registry, index.definition
1611 )
1612 })
1613 } else {
1614 bail!(
1615 "registry index was not found in any configuration: `{}`",
1616 registry
1617 );
1618 }
1619 }
1620
1621 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1623 if self.get_string("registry.index")?.is_some() {
1624 bail!(
1625 "the `registry.index` config value is no longer supported\n\
1626 Use `[source]` replacement to alter the default index for crates.io."
1627 );
1628 }
1629 Ok(())
1630 }
1631
1632 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1633 let base = index
1635 .definition
1636 .root(self)
1637 .join("truncated-by-url_with_base");
1638 let _parsed = index.val.into_url()?;
1640 let url = index.val.into_url_with_base(Some(&*base))?;
1641 if url.password().is_some() {
1642 bail!("registry URLs may not contain passwords");
1643 }
1644 Ok(url)
1645 }
1646
1647 pub fn load_credentials(&self) -> CargoResult<()> {
1655 if self.credential_values.filled() {
1656 return Ok(());
1657 }
1658
1659 let home_path = self.home_path.clone().into_path_unlocked();
1660 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1661 return Ok(());
1662 };
1663
1664 let mut value = self.load_file(&credentials)?;
1665 {
1667 let CV::Table(ref mut value_map, ref def) = value else {
1668 unreachable!();
1669 };
1670
1671 if let Some(token) = value_map.remove("token") {
1672 if let Vacant(entry) = value_map.entry("registry".into()) {
1673 let map = HashMap::from([("token".into(), token)]);
1674 let table = CV::Table(map, def.clone());
1675 entry.insert(table);
1676 }
1677 }
1678 }
1679
1680 let mut credential_values = HashMap::new();
1681 if let CV::Table(map, _) = value {
1682 let base_map = self.values()?;
1683 for (k, v) in map {
1684 let entry = match base_map.get(&k) {
1685 Some(base_entry) => {
1686 let mut entry = base_entry.clone();
1687 entry.merge(v, true)?;
1688 entry
1689 }
1690 None => v,
1691 };
1692 credential_values.insert(k, entry);
1693 }
1694 }
1695 self.credential_values
1696 .fill(credential_values)
1697 .expect("was not filled at beginning of the function");
1698 Ok(())
1699 }
1700
1701 fn maybe_get_tool(
1704 &self,
1705 tool: &str,
1706 from_config: &Option<ConfigRelativePath>,
1707 ) -> Option<PathBuf> {
1708 let var = tool.to_uppercase();
1709
1710 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1711 Some(tool_path) => {
1712 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1713 let path = if maybe_relative {
1714 self.cwd.join(tool_path)
1715 } else {
1716 PathBuf::from(tool_path)
1717 };
1718 Some(path)
1719 }
1720
1721 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1722 }
1723 }
1724
1725 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1736 let tool_str = tool.as_str();
1737 self.maybe_get_tool(tool_str, from_config)
1738 .or_else(|| {
1739 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1753 if toolchain.to_str()?.contains(&['/', '\\']) {
1756 return None;
1757 }
1758 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1761 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1762 let tool_meta = tool_resolved.metadata().ok()?;
1763 let rustup_meta = rustup_resolved.metadata().ok()?;
1764 if tool_meta.len() != rustup_meta.len() {
1769 return None;
1770 }
1771 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1773 let toolchain_exe = home::rustup_home()
1774 .ok()?
1775 .join("toolchains")
1776 .join(&toolchain)
1777 .join("bin")
1778 .join(&tool_exe);
1779 toolchain_exe.exists().then_some(toolchain_exe)
1780 })
1781 .unwrap_or_else(|| PathBuf::from(tool_str))
1782 }
1783
1784 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1785 self.jobserver.as_ref()
1786 }
1787
1788 pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
1789 let http = self
1790 .easy
1791 .try_borrow_with(|| http_handle(self).map(RefCell::new))?;
1792 {
1793 let mut http = http.borrow_mut();
1794 http.reset();
1795 let timeout = configure_http_handle(self, &mut http)?;
1796 timeout.configure(&mut http)?;
1797 }
1798 Ok(http)
1799 }
1800
1801 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1802 self.http_config.try_borrow_with(|| {
1803 let mut http = self.get::<CargoHttpConfig>("http")?;
1804 let curl_v = curl::Version::get();
1805 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1806 Ok(http)
1807 })
1808 }
1809
1810 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1811 self.future_incompat_config
1812 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1813 }
1814
1815 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1816 self.net_config
1817 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1818 }
1819
1820 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1821 self.build_config
1822 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1823 }
1824
1825 pub fn progress_config(&self) -> &ProgressConfig {
1826 &self.progress_config
1827 }
1828
1829 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1832 let env_config = self.env_config.try_borrow_with(|| {
1833 CargoResult::Ok(Arc::new({
1834 let env_config = self.get::<EnvConfig>("env")?;
1835 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1851 if env_config.contains_key(*disallowed) {
1852 bail!(
1853 "setting the `{disallowed}` environment variable is not supported \
1854 in the `[env]` configuration table"
1855 );
1856 }
1857 }
1858 env_config
1859 .into_iter()
1860 .filter_map(|(k, v)| {
1861 if v.is_force() || self.get_env_os(&k).is_none() {
1862 Some((k, v.resolve(self).to_os_string()))
1863 } else {
1864 None
1865 }
1866 })
1867 .collect()
1868 }))
1869 })?;
1870
1871 Ok(env_config)
1872 }
1873
1874 pub fn validate_term_config(&self) -> CargoResult<()> {
1880 drop(self.get::<TermConfig>("term")?);
1881 Ok(())
1882 }
1883
1884 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1888 self.target_cfgs
1889 .try_borrow_with(|| target::load_target_cfgs(self))
1890 }
1891
1892 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1893 self.doc_extern_map
1897 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1898 }
1899
1900 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1902 target::get_target_applies_to_host(self)
1903 }
1904
1905 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1907 target::load_host_triple(self, target)
1908 }
1909
1910 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1912 target::load_target_triple(self, target)
1913 }
1914
1915 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
1920 let source_id = self.crates_io_source_id.try_borrow_with(|| {
1921 self.check_registry_index_not_set()?;
1922 let url = CRATES_IO_INDEX.into_url().unwrap();
1923 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
1924 })?;
1925 Ok(*source_id)
1926 }
1927
1928 pub fn creation_time(&self) -> Instant {
1929 self.creation_time
1930 }
1931
1932 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
1947 let d = Deserializer {
1948 gctx: self,
1949 key: ConfigKey::from_str(key),
1950 env_prefix_ok: true,
1951 };
1952 T::deserialize(d).map_err(|e| e.into())
1953 }
1954
1955 #[track_caller]
1961 #[tracing::instrument(skip_all)]
1962 pub fn assert_package_cache_locked<'a>(
1963 &self,
1964 mode: CacheLockMode,
1965 f: &'a Filesystem,
1966 ) -> &'a Path {
1967 let ret = f.as_path_unlocked();
1968 assert!(
1969 self.package_cache_lock.is_locked(mode),
1970 "package cache lock is not currently held, Cargo forgot to call \
1971 `acquire_package_cache_lock` before we got to this stack frame",
1972 );
1973 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
1974 ret
1975 }
1976
1977 #[tracing::instrument(skip_all)]
1983 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
1984 self.package_cache_lock.lock(self, mode)
1985 }
1986
1987 #[tracing::instrument(skip_all)]
1993 pub fn try_acquire_package_cache_lock(
1994 &self,
1995 mode: CacheLockMode,
1996 ) -> CargoResult<Option<CacheLock<'_>>> {
1997 self.package_cache_lock.try_lock(self, mode)
1998 }
1999
2000 pub fn global_cache_tracker(&self) -> CargoResult<RefMut<'_, GlobalCacheTracker>> {
2005 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2006 Ok::<_, anyhow::Error>(RefCell::new(GlobalCacheTracker::new(self)?))
2007 })?;
2008 Ok(tracker.borrow_mut())
2009 }
2010
2011 pub fn deferred_global_last_use(&self) -> CargoResult<RefMut<'_, DeferredGlobalLastUse>> {
2013 let deferred = self.deferred_global_last_use.try_borrow_with(|| {
2014 Ok::<_, anyhow::Error>(RefCell::new(DeferredGlobalLastUse::new()))
2015 })?;
2016 Ok(deferred.borrow_mut())
2017 }
2018
2019 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2021 if self.unstable_flags.warnings {
2022 Ok(self.build_config()?.warnings.unwrap_or_default())
2023 } else {
2024 Ok(WarningHandling::default())
2025 }
2026 }
2027}
2028
2029#[derive(Debug)]
2031pub struct ConfigError {
2032 error: anyhow::Error,
2033 definition: Option<Definition>,
2034}
2035
2036impl ConfigError {
2037 fn new(message: String, definition: Definition) -> ConfigError {
2038 ConfigError {
2039 error: anyhow::Error::msg(message),
2040 definition: Some(definition),
2041 }
2042 }
2043
2044 fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
2045 ConfigError {
2046 error: anyhow!(
2047 "`{}` expected {}, but found a {}",
2048 key,
2049 expected,
2050 found.desc()
2051 ),
2052 definition: Some(found.definition().clone()),
2053 }
2054 }
2055
2056 fn is_missing_field(&self) -> bool {
2057 self.error.downcast_ref::<MissingFieldError>().is_some()
2058 }
2059
2060 fn missing(key: &ConfigKey) -> ConfigError {
2061 ConfigError {
2062 error: anyhow!("missing config key `{}`", key),
2063 definition: None,
2064 }
2065 }
2066
2067 fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
2068 ConfigError {
2069 error: anyhow::Error::from(self)
2070 .context(format!("could not load config key `{}`", key)),
2071 definition: definition,
2072 }
2073 }
2074}
2075
2076impl std::error::Error for ConfigError {
2077 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2078 self.error.source()
2079 }
2080}
2081
2082impl fmt::Display for ConfigError {
2083 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2084 if let Some(definition) = &self.definition {
2085 write!(f, "error in {}: {}", definition, self.error)
2086 } else {
2087 self.error.fmt(f)
2088 }
2089 }
2090}
2091
2092#[derive(Debug)]
2093struct MissingFieldError(String);
2094
2095impl fmt::Display for MissingFieldError {
2096 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2097 write!(f, "missing field `{}`", self.0)
2098 }
2099}
2100
2101impl std::error::Error for MissingFieldError {}
2102
2103impl serde::de::Error for ConfigError {
2104 fn custom<T: fmt::Display>(msg: T) -> Self {
2105 ConfigError {
2106 error: anyhow::Error::msg(msg.to_string()),
2107 definition: None,
2108 }
2109 }
2110
2111 fn missing_field(field: &'static str) -> Self {
2112 ConfigError {
2113 error: anyhow::Error::new(MissingFieldError(field.to_string())),
2114 definition: None,
2115 }
2116 }
2117}
2118
2119impl From<anyhow::Error> for ConfigError {
2120 fn from(error: anyhow::Error) -> Self {
2121 ConfigError {
2122 error,
2123 definition: None,
2124 }
2125 }
2126}
2127
2128#[derive(Eq, PartialEq, Clone)]
2129pub enum ConfigValue {
2130 Integer(i64, Definition),
2131 String(String, Definition),
2132 List(Vec<(String, Definition)>, Definition),
2133 Table(HashMap<String, ConfigValue>, Definition),
2134 Boolean(bool, Definition),
2135}
2136
2137impl fmt::Debug for ConfigValue {
2138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2139 match self {
2140 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2141 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2142 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2143 CV::List(list, def) => {
2144 write!(f, "[")?;
2145 for (i, (s, def)) in list.iter().enumerate() {
2146 if i > 0 {
2147 write!(f, ", ")?;
2148 }
2149 write!(f, "{} (from {})", s, def)?;
2150 }
2151 write!(f, "] (from {})", def)
2152 }
2153 CV::Table(table, _) => write!(f, "{:?}", table),
2154 }
2155 }
2156}
2157
2158impl ConfigValue {
2159 fn get_definition(&self) -> &Definition {
2160 match self {
2161 CV::Boolean(_, def)
2162 | CV::Integer(_, def)
2163 | CV::String(_, def)
2164 | CV::List(_, def)
2165 | CV::Table(_, def) => def,
2166 }
2167 }
2168
2169 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2170 match toml {
2171 toml::Value::String(val) => Ok(CV::String(val, def)),
2172 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2173 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2174 toml::Value::Array(val) => Ok(CV::List(
2175 val.into_iter()
2176 .map(|toml| match toml {
2177 toml::Value::String(val) => Ok((val, def.clone())),
2178 v => bail!("expected string but found {} in list", v.type_str()),
2179 })
2180 .collect::<CargoResult<_>>()?,
2181 def,
2182 )),
2183 toml::Value::Table(val) => Ok(CV::Table(
2184 val.into_iter()
2185 .map(|(key, value)| {
2186 let value = CV::from_toml(def.clone(), value)
2187 .with_context(|| format!("failed to parse key `{}`", key))?;
2188 Ok((key, value))
2189 })
2190 .collect::<CargoResult<_>>()?,
2191 def,
2192 )),
2193 v => bail!(
2194 "found TOML configuration value of unknown type `{}`",
2195 v.type_str()
2196 ),
2197 }
2198 }
2199
2200 fn into_toml(self) -> toml::Value {
2201 match self {
2202 CV::Boolean(s, _) => toml::Value::Boolean(s),
2203 CV::String(s, _) => toml::Value::String(s),
2204 CV::Integer(i, _) => toml::Value::Integer(i),
2205 CV::List(l, _) => {
2206 toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect())
2207 }
2208 CV::Table(l, _) => {
2209 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2210 }
2211 }
2212 }
2213
2214 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2223 self.merge_helper(from, force, &mut ConfigKey::new())
2224 }
2225
2226 fn merge_helper(
2227 &mut self,
2228 from: ConfigValue,
2229 force: bool,
2230 parts: &mut ConfigKey,
2231 ) -> CargoResult<()> {
2232 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2233 match (self, from) {
2234 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2235 if is_nonmergable_list(&parts) {
2236 if force || is_higher_priority {
2238 mem::swap(new, old);
2239 }
2240 } else {
2241 if force {
2243 old.append(new);
2244 } else {
2245 new.append(old);
2246 mem::swap(new, old);
2247 }
2248 }
2249 old.sort_by(|a, b| a.1.cmp(&b.1));
2250 }
2251 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2252 for (key, value) in mem::take(new) {
2253 match old.entry(key.clone()) {
2254 Occupied(mut entry) => {
2255 let new_def = value.definition().clone();
2256 let entry = entry.get_mut();
2257 parts.push(&key);
2258 entry.merge_helper(value, force, parts).with_context(|| {
2259 format!(
2260 "failed to merge key `{}` between \
2261 {} and {}",
2262 key,
2263 entry.definition(),
2264 new_def,
2265 )
2266 })?;
2267 }
2268 Vacant(entry) => {
2269 entry.insert(value);
2270 }
2271 };
2272 }
2273 }
2274 (expected @ &mut CV::List(_, _), found)
2276 | (expected @ &mut CV::Table(_, _), found)
2277 | (expected, found @ CV::List(_, _))
2278 | (expected, found @ CV::Table(_, _)) => {
2279 return Err(anyhow!(
2280 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2281 found.definition(),
2282 expected.definition(),
2283 expected.desc(),
2284 found.desc()
2285 ));
2286 }
2287 (old, mut new) => {
2288 if force || is_higher_priority {
2289 mem::swap(old, &mut new);
2290 }
2291 }
2292 }
2293
2294 Ok(())
2295 }
2296
2297 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2298 match self {
2299 CV::Integer(i, def) => Ok((*i, def)),
2300 _ => self.expected("integer", key),
2301 }
2302 }
2303
2304 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2305 match self {
2306 CV::String(s, def) => Ok((s, def)),
2307 _ => self.expected("string", key),
2308 }
2309 }
2310
2311 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2312 match self {
2313 CV::Table(table, def) => Ok((table, def)),
2314 _ => self.expected("table", key),
2315 }
2316 }
2317
2318 pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
2319 match self {
2320 CV::List(list, _) => Ok(list),
2321 _ => self.expected("list", key),
2322 }
2323 }
2324
2325 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2326 match self {
2327 CV::Boolean(b, def) => Ok((*b, def)),
2328 _ => self.expected("bool", key),
2329 }
2330 }
2331
2332 pub fn desc(&self) -> &'static str {
2333 match *self {
2334 CV::Table(..) => "table",
2335 CV::List(..) => "array",
2336 CV::String(..) => "string",
2337 CV::Boolean(..) => "boolean",
2338 CV::Integer(..) => "integer",
2339 }
2340 }
2341
2342 pub fn definition(&self) -> &Definition {
2343 match self {
2344 CV::Boolean(_, def)
2345 | CV::Integer(_, def)
2346 | CV::String(_, def)
2347 | CV::List(_, def)
2348 | CV::Table(_, def) => def,
2349 }
2350 }
2351
2352 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2353 bail!(
2354 "expected a {}, but found a {} for `{}` in {}",
2355 wanted,
2356 self.desc(),
2357 key,
2358 self.definition()
2359 )
2360 }
2361}
2362
2363fn is_nonmergable_list(key: &ConfigKey) -> bool {
2366 key.matches("registry.credential-provider")
2367 || key.matches("registries.*.credential-provider")
2368 || key.matches("target.*.runner")
2369 || key.matches("host.runner")
2370 || key.matches("credential-alias.*")
2371 || key.matches("doc.browser")
2372}
2373
2374pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2375 ::home::cargo_home_with_cwd(cwd).ok()
2376}
2377
2378pub fn save_credentials(
2379 gctx: &GlobalContext,
2380 token: Option<RegistryCredentialConfig>,
2381 registry: &SourceId,
2382) -> CargoResult<()> {
2383 let registry = if registry.is_crates_io() {
2384 None
2385 } else {
2386 let name = registry
2387 .alt_registry_key()
2388 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2389 Some(name)
2390 };
2391
2392 let home_path = gctx.home_path.clone().into_path_unlocked();
2396 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2397 Some(path) => match path.file_name() {
2398 Some(filename) => Path::new(filename).to_owned(),
2399 None => Path::new("credentials.toml").to_owned(),
2400 },
2401 None => Path::new("credentials.toml").to_owned(),
2402 };
2403
2404 let mut file = {
2405 gctx.home_path.create_dir()?;
2406 gctx.home_path
2407 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2408 };
2409
2410 let mut contents = String::new();
2411 file.read_to_string(&mut contents).with_context(|| {
2412 format!(
2413 "failed to read configuration file `{}`",
2414 file.path().display()
2415 )
2416 })?;
2417
2418 let mut toml = parse_document(&contents, file.path(), gctx)?;
2419
2420 if let Some(token) = toml.remove("token") {
2422 let map = HashMap::from([("token".to_string(), token)]);
2423 toml.insert("registry".into(), map.into());
2424 }
2425
2426 if let Some(token) = token {
2427 let path_def = Definition::Path(file.path().to_path_buf());
2430 let (key, mut value) = match token {
2431 RegistryCredentialConfig::Token(token) => {
2432 let key = "token".to_string();
2435 let value = ConfigValue::String(token.expose(), path_def.clone());
2436 let map = HashMap::from([(key, value)]);
2437 let table = CV::Table(map, path_def.clone());
2438
2439 if let Some(registry) = registry {
2440 let map = HashMap::from([(registry.to_string(), table)]);
2441 ("registries".into(), CV::Table(map, path_def.clone()))
2442 } else {
2443 ("registry".into(), table)
2444 }
2445 }
2446 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2447 let key = "secret-key".to_string();
2450 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2451 let mut map = HashMap::from([(key, value)]);
2452 if let Some(key_subject) = key_subject {
2453 let key = "secret-key-subject".to_string();
2454 let value = ConfigValue::String(key_subject, path_def.clone());
2455 map.insert(key, value);
2456 }
2457 let table = CV::Table(map, path_def.clone());
2458
2459 if let Some(registry) = registry {
2460 let map = HashMap::from([(registry.to_string(), table)]);
2461 ("registries".into(), CV::Table(map, path_def.clone()))
2462 } else {
2463 ("registry".into(), table)
2464 }
2465 }
2466 _ => unreachable!(),
2467 };
2468
2469 if registry.is_some() {
2470 if let Some(table) = toml.remove("registries") {
2471 let v = CV::from_toml(path_def, table)?;
2472 value.merge(v, false)?;
2473 }
2474 }
2475 toml.insert(key, value.into_toml());
2476 } else {
2477 if let Some(registry) = registry {
2479 if let Some(registries) = toml.get_mut("registries") {
2480 if let Some(reg) = registries.get_mut(registry) {
2481 let rtable = reg.as_table_mut().ok_or_else(|| {
2482 format_err!("expected `[registries.{}]` to be a table", registry)
2483 })?;
2484 rtable.remove("token");
2485 rtable.remove("secret-key");
2486 rtable.remove("secret-key-subject");
2487 }
2488 }
2489 } else if let Some(registry) = toml.get_mut("registry") {
2490 let reg_table = registry
2491 .as_table_mut()
2492 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2493 reg_table.remove("token");
2494 reg_table.remove("secret-key");
2495 reg_table.remove("secret-key-subject");
2496 }
2497 }
2498
2499 let contents = toml.to_string();
2500 file.seek(SeekFrom::Start(0))?;
2501 file.write_all(contents.as_bytes())
2502 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2503 file.file().set_len(contents.len() as u64)?;
2504 set_permissions(file.file(), 0o600)
2505 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2506
2507 return Ok(());
2508
2509 #[cfg(unix)]
2510 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2511 use std::os::unix::fs::PermissionsExt;
2512
2513 let mut perms = file.metadata()?.permissions();
2514 perms.set_mode(mode);
2515 file.set_permissions(perms)?;
2516 Ok(())
2517 }
2518
2519 #[cfg(not(unix))]
2520 #[allow(unused)]
2521 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2522 Ok(())
2523 }
2524}
2525
2526#[derive(Debug, Default, Deserialize, PartialEq)]
2527#[serde(rename_all = "kebab-case")]
2528pub struct CargoHttpConfig {
2529 pub proxy: Option<String>,
2530 pub low_speed_limit: Option<u32>,
2531 pub timeout: Option<u64>,
2532 pub cainfo: Option<ConfigRelativePath>,
2533 pub check_revoke: Option<bool>,
2534 pub user_agent: Option<String>,
2535 pub debug: Option<bool>,
2536 pub multiplexing: Option<bool>,
2537 pub ssl_version: Option<SslVersionConfig>,
2538}
2539
2540#[derive(Debug, Default, Deserialize, PartialEq)]
2541#[serde(rename_all = "kebab-case")]
2542pub struct CargoFutureIncompatConfig {
2543 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2544}
2545
2546#[derive(Debug, Default, Deserialize, PartialEq)]
2547#[serde(rename_all = "kebab-case")]
2548pub enum CargoFutureIncompatFrequencyConfig {
2549 #[default]
2550 Always,
2551 Never,
2552}
2553
2554impl CargoFutureIncompatConfig {
2555 pub fn should_display_message(&self) -> bool {
2556 use CargoFutureIncompatFrequencyConfig::*;
2557
2558 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2559 match frequency {
2560 Always => true,
2561 Never => false,
2562 }
2563 }
2564}
2565
2566#[derive(Clone, Debug, PartialEq)]
2580pub enum SslVersionConfig {
2581 Single(String),
2582 Range(SslVersionConfigRange),
2583}
2584
2585impl<'de> Deserialize<'de> for SslVersionConfig {
2586 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2587 where
2588 D: serde::Deserializer<'de>,
2589 {
2590 UntaggedEnumVisitor::new()
2591 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2592 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2593 .deserialize(deserializer)
2594 }
2595}
2596
2597#[derive(Clone, Debug, Deserialize, PartialEq)]
2598#[serde(rename_all = "kebab-case")]
2599pub struct SslVersionConfigRange {
2600 pub min: Option<String>,
2601 pub max: Option<String>,
2602}
2603
2604#[derive(Debug, Deserialize)]
2605#[serde(rename_all = "kebab-case")]
2606pub struct CargoNetConfig {
2607 pub retry: Option<u32>,
2608 pub offline: Option<bool>,
2609 pub git_fetch_with_cli: Option<bool>,
2610 pub ssh: Option<CargoSshConfig>,
2611}
2612
2613#[derive(Debug, Deserialize)]
2614#[serde(rename_all = "kebab-case")]
2615pub struct CargoSshConfig {
2616 pub known_hosts: Option<Vec<Value<String>>>,
2617}
2618
2619#[derive(Debug, Clone)]
2632pub enum JobsConfig {
2633 Integer(i32),
2634 String(String),
2635}
2636
2637impl<'de> Deserialize<'de> for JobsConfig {
2638 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2639 where
2640 D: serde::Deserializer<'de>,
2641 {
2642 UntaggedEnumVisitor::new()
2643 .i32(|int| Ok(JobsConfig::Integer(int)))
2644 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2645 .deserialize(deserializer)
2646 }
2647}
2648
2649#[derive(Debug, Deserialize)]
2650#[serde(rename_all = "kebab-case")]
2651pub struct CargoBuildConfig {
2652 pub pipelining: Option<bool>,
2654 pub dep_info_basedir: Option<ConfigRelativePath>,
2655 pub target_dir: Option<ConfigRelativePath>,
2656 pub incremental: Option<bool>,
2657 pub target: Option<BuildTargetConfig>,
2658 pub jobs: Option<JobsConfig>,
2659 pub rustflags: Option<StringList>,
2660 pub rustdocflags: Option<StringList>,
2661 pub rustc_wrapper: Option<ConfigRelativePath>,
2662 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2663 pub rustc: Option<ConfigRelativePath>,
2664 pub rustdoc: Option<ConfigRelativePath>,
2665 pub out_dir: Option<ConfigRelativePath>,
2667 pub artifact_dir: Option<ConfigRelativePath>,
2668 pub warnings: Option<WarningHandling>,
2669}
2670
2671#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2673#[serde(rename_all = "kebab-case")]
2674pub enum WarningHandling {
2675 #[default]
2676 Warn,
2678 Allow,
2680 Deny,
2682}
2683
2684#[derive(Debug, Deserialize)]
2694#[serde(transparent)]
2695pub struct BuildTargetConfig {
2696 inner: Value<BuildTargetConfigInner>,
2697}
2698
2699#[derive(Debug)]
2700enum BuildTargetConfigInner {
2701 One(String),
2702 Many(Vec<String>),
2703}
2704
2705impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2706 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2707 where
2708 D: serde::Deserializer<'de>,
2709 {
2710 UntaggedEnumVisitor::new()
2711 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2712 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2713 .deserialize(deserializer)
2714 }
2715}
2716
2717impl BuildTargetConfig {
2718 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2720 let map = |s: &String| {
2721 if s.ends_with(".json") {
2722 self.inner
2725 .definition
2726 .root(gctx)
2727 .join(s)
2728 .to_str()
2729 .expect("must be utf-8 in toml")
2730 .to_string()
2731 } else {
2732 s.to_string()
2734 }
2735 };
2736 let values = match &self.inner.val {
2737 BuildTargetConfigInner::One(s) => vec![map(s)],
2738 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2739 };
2740 Ok(values)
2741 }
2742}
2743
2744#[derive(Debug, Deserialize)]
2745#[serde(rename_all = "kebab-case")]
2746pub struct CargoResolverConfig {
2747 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2748 pub feature_unification: Option<FeatureUnification>,
2749}
2750
2751#[derive(Debug, Deserialize, PartialEq, Eq)]
2752#[serde(rename_all = "kebab-case")]
2753pub enum IncompatibleRustVersions {
2754 Allow,
2755 Fallback,
2756}
2757
2758#[derive(Copy, Clone, Debug, Deserialize)]
2759#[serde(rename_all = "kebab-case")]
2760pub enum FeatureUnification {
2761 Selected,
2762 Workspace,
2763}
2764
2765#[derive(Deserialize, Default)]
2766#[serde(rename_all = "kebab-case")]
2767pub struct TermConfig {
2768 pub verbose: Option<bool>,
2769 pub quiet: Option<bool>,
2770 pub color: Option<String>,
2771 pub hyperlinks: Option<bool>,
2772 pub unicode: Option<bool>,
2773 #[serde(default)]
2774 #[serde(deserialize_with = "progress_or_string")]
2775 pub progress: Option<ProgressConfig>,
2776}
2777
2778#[derive(Debug, Default, Deserialize)]
2779#[serde(rename_all = "kebab-case")]
2780pub struct ProgressConfig {
2781 pub when: ProgressWhen,
2782 pub width: Option<usize>,
2783}
2784
2785#[derive(Debug, Default, Deserialize)]
2786#[serde(rename_all = "kebab-case")]
2787pub enum ProgressWhen {
2788 #[default]
2789 Auto,
2790 Never,
2791 Always,
2792}
2793
2794fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2795where
2796 D: serde::de::Deserializer<'de>,
2797{
2798 struct ProgressVisitor;
2799
2800 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2801 type Value = Option<ProgressConfig>;
2802
2803 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2804 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2805 }
2806
2807 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2808 where
2809 E: serde::de::Error,
2810 {
2811 match s {
2812 "auto" => Ok(Some(ProgressConfig {
2813 when: ProgressWhen::Auto,
2814 width: None,
2815 })),
2816 "never" => Ok(Some(ProgressConfig {
2817 when: ProgressWhen::Never,
2818 width: None,
2819 })),
2820 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2821 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2822 }
2823 }
2824
2825 fn visit_none<E>(self) -> Result<Self::Value, E>
2826 where
2827 E: serde::de::Error,
2828 {
2829 Ok(None)
2830 }
2831
2832 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2833 where
2834 D: serde::de::Deserializer<'de>,
2835 {
2836 let pc = ProgressConfig::deserialize(deserializer)?;
2837 if let ProgressConfig {
2838 when: ProgressWhen::Always,
2839 width: None,
2840 } = pc
2841 {
2842 return Err(serde::de::Error::custom(
2843 "\"always\" progress requires a `width` key",
2844 ));
2845 }
2846 Ok(Some(pc))
2847 }
2848 }
2849
2850 deserializer.deserialize_option(ProgressVisitor)
2851}
2852
2853#[derive(Debug)]
2854enum EnvConfigValueInner {
2855 Simple(String),
2856 WithOptions {
2857 value: String,
2858 force: bool,
2859 relative: bool,
2860 },
2861}
2862
2863impl<'de> Deserialize<'de> for EnvConfigValueInner {
2864 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2865 where
2866 D: serde::Deserializer<'de>,
2867 {
2868 #[derive(Deserialize)]
2869 struct WithOptions {
2870 value: String,
2871 #[serde(default)]
2872 force: bool,
2873 #[serde(default)]
2874 relative: bool,
2875 }
2876
2877 UntaggedEnumVisitor::new()
2878 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2879 .map(|map| {
2880 let with_options: WithOptions = map.deserialize()?;
2881 Ok(EnvConfigValueInner::WithOptions {
2882 value: with_options.value,
2883 force: with_options.force,
2884 relative: with_options.relative,
2885 })
2886 })
2887 .deserialize(deserializer)
2888 }
2889}
2890
2891#[derive(Debug, Deserialize)]
2892#[serde(transparent)]
2893pub struct EnvConfigValue {
2894 inner: Value<EnvConfigValueInner>,
2895}
2896
2897impl EnvConfigValue {
2898 pub fn is_force(&self) -> bool {
2899 match self.inner.val {
2900 EnvConfigValueInner::Simple(_) => false,
2901 EnvConfigValueInner::WithOptions { force, .. } => force,
2902 }
2903 }
2904
2905 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
2906 match self.inner.val {
2907 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
2908 EnvConfigValueInner::WithOptions {
2909 ref value,
2910 relative,
2911 ..
2912 } => {
2913 if relative {
2914 let p = self.inner.definition.root(gctx).join(&value);
2915 Cow::Owned(p.into_os_string())
2916 } else {
2917 Cow::Borrowed(OsStr::new(value.as_str()))
2918 }
2919 }
2920 }
2921 }
2922}
2923
2924pub type EnvConfig = HashMap<String, EnvConfigValue>;
2925
2926fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
2927 toml.parse().map_err(Into::into)
2929}
2930
2931#[derive(Debug, Deserialize, Clone)]
2942pub struct StringList(Vec<String>);
2943
2944impl StringList {
2945 pub fn as_slice(&self) -> &[String] {
2946 &self.0
2947 }
2948}
2949
2950#[macro_export]
2951macro_rules! __shell_print {
2952 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
2953 let mut shell = $config.shell();
2954 let out = shell.$which();
2955 drop(out.write_fmt(format_args!($($arg)*)));
2956 if $newline {
2957 drop(out.write_all(b"\n"));
2958 }
2959 });
2960}
2961
2962#[macro_export]
2963macro_rules! drop_println {
2964 ($config:expr) => ( $crate::drop_print!($config, "\n") );
2965 ($config:expr, $($arg:tt)*) => (
2966 $crate::__shell_print!($config, out, true, $($arg)*)
2967 );
2968}
2969
2970#[macro_export]
2971macro_rules! drop_eprintln {
2972 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
2973 ($config:expr, $($arg:tt)*) => (
2974 $crate::__shell_print!($config, err, true, $($arg)*)
2975 );
2976}
2977
2978#[macro_export]
2979macro_rules! drop_print {
2980 ($config:expr, $($arg:tt)*) => (
2981 $crate::__shell_print!($config, out, false, $($arg)*)
2982 );
2983}
2984
2985#[macro_export]
2986macro_rules! drop_eprint {
2987 ($config:expr, $($arg:tt)*) => (
2988 $crate::__shell_print!($config, err, false, $($arg)*)
2989 );
2990}
2991
2992enum Tool {
2993 Rustc,
2994 Rustdoc,
2995}
2996
2997impl Tool {
2998 fn as_str(&self) -> &str {
2999 match self {
3000 Tool::Rustc => "rustc",
3001 Tool::Rustdoc => "rustdoc",
3002 }
3003 }
3004}
3005
3006fn disables_multiplexing_for_bad_curl(
3016 curl_version: &str,
3017 http: &mut CargoHttpConfig,
3018 gctx: &GlobalContext,
3019) {
3020 use crate::util::network;
3021
3022 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3023 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3024 if bad_curl_versions
3025 .iter()
3026 .any(|v| curl_version.starts_with(v))
3027 {
3028 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3029 http.multiplexing = Some(false);
3030 }
3031 }
3032}
3033
3034#[cfg(test)]
3035mod tests {
3036 use super::disables_multiplexing_for_bad_curl;
3037 use super::CargoHttpConfig;
3038 use super::GlobalContext;
3039 use super::Shell;
3040
3041 #[test]
3042 fn disables_multiplexing() {
3043 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3044 gctx.set_search_stop_path(std::path::PathBuf::new());
3045 gctx.set_env(Default::default());
3046
3047 let mut http = CargoHttpConfig::default();
3048 http.proxy = Some("127.0.0.1:3128".into());
3049 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3050 assert_eq!(http.multiplexing, Some(false));
3051
3052 let cases = [
3053 (None, None, "7.87.0", None),
3054 (None, None, "7.88.0", None),
3055 (None, None, "7.88.1", None),
3056 (None, None, "8.0.0", None),
3057 (Some("".into()), None, "7.87.0", Some(false)),
3058 (Some("".into()), None, "7.88.0", Some(false)),
3059 (Some("".into()), None, "7.88.1", Some(false)),
3060 (Some("".into()), None, "8.0.0", None),
3061 (Some("".into()), Some(false), "7.87.0", Some(false)),
3062 (Some("".into()), Some(false), "7.88.0", Some(false)),
3063 (Some("".into()), Some(false), "7.88.1", Some(false)),
3064 (Some("".into()), Some(false), "8.0.0", Some(false)),
3065 ];
3066
3067 for (proxy, multiplexing, curl_v, result) in cases {
3068 let mut http = CargoHttpConfig {
3069 multiplexing,
3070 proxy,
3071 ..Default::default()
3072 };
3073 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3074 assert_eq!(http.multiplexing, result);
3075 }
3076 }
3077}