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 =
425 ws.map(|ws| ws.build_dir().join(".rustc_info.json").into_path_unlocked());
426 let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
427 let rustc_workspace_wrapper = self.maybe_get_tool(
428 "rustc_workspace_wrapper",
429 &self.build_config()?.rustc_workspace_wrapper,
430 );
431
432 Rustc::new(
433 self.get_tool(Tool::Rustc, &self.build_config()?.rustc),
434 wrapper,
435 rustc_workspace_wrapper,
436 &self
437 .home()
438 .join("bin")
439 .join("rustc")
440 .into_path_unlocked()
441 .with_extension(env::consts::EXE_EXTENSION),
442 if self.cache_rustc_info {
443 cache_location
444 } else {
445 None
446 },
447 self,
448 )
449 }
450
451 pub fn cargo_exe(&self) -> CargoResult<&Path> {
453 self.cargo_exe
454 .try_borrow_with(|| {
455 let from_env = || -> CargoResult<PathBuf> {
456 let exe = try_canonicalize(
461 self.get_env_os(crate::CARGO_ENV)
462 .map(PathBuf::from)
463 .ok_or_else(|| anyhow!("$CARGO not set"))?,
464 )?;
465 Ok(exe)
466 };
467
468 fn from_current_exe() -> CargoResult<PathBuf> {
469 let exe = try_canonicalize(env::current_exe()?)?;
474 Ok(exe)
475 }
476
477 fn from_argv() -> CargoResult<PathBuf> {
478 let argv0 = env::args_os()
487 .map(PathBuf::from)
488 .next()
489 .ok_or_else(|| anyhow!("no argv[0]"))?;
490 paths::resolve_executable(&argv0)
491 }
492
493 fn is_cargo(path: &Path) -> bool {
496 path.file_stem() == Some(OsStr::new("cargo"))
497 }
498
499 let from_current_exe = from_current_exe();
500 if from_current_exe.as_deref().is_ok_and(is_cargo) {
501 return from_current_exe;
502 }
503
504 let from_argv = from_argv();
505 if from_argv.as_deref().is_ok_and(is_cargo) {
506 return from_argv;
507 }
508
509 let exe = from_env()
510 .or(from_current_exe)
511 .or(from_argv)
512 .context("couldn't get the path to cargo executable")?;
513 Ok(exe)
514 })
515 .map(AsRef::as_ref)
516 }
517
518 pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> {
520 self.updated_sources
521 .borrow_with(|| RefCell::new(HashSet::new()))
522 .borrow_mut()
523 }
524
525 pub fn credential_cache(&self) -> RefMut<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
527 self.credential_cache
528 .borrow_with(|| RefCell::new(HashMap::new()))
529 .borrow_mut()
530 }
531
532 pub(crate) fn registry_config(&self) -> RefMut<'_, HashMap<SourceId, Option<RegistryConfig>>> {
534 self.registry_config
535 .borrow_with(|| RefCell::new(HashMap::new()))
536 .borrow_mut()
537 }
538
539 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
545 self.values.try_borrow_with(|| self.load_values())
546 }
547
548 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
555 let _ = self.values()?;
556 Ok(self
557 .values
558 .borrow_mut()
559 .expect("already loaded config values"))
560 }
561
562 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
564 if self.values.borrow().is_some() {
565 bail!("config values already found")
566 }
567 match self.values.fill(values) {
568 Ok(()) => Ok(()),
569 Err(_) => bail!("could not fill values"),
570 }
571 }
572
573 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
576 let path = path.into();
577 debug_assert!(self.cwd.starts_with(&path));
578 self.search_stop_path = Some(path);
579 }
580
581 pub fn reload_cwd(&mut self) -> CargoResult<()> {
585 let cwd =
586 env::current_dir().context("couldn't get the current directory of the process")?;
587 let homedir = homedir(&cwd).ok_or_else(|| {
588 anyhow!(
589 "Cargo couldn't find your home directory. \
590 This probably means that $HOME was not set."
591 )
592 })?;
593
594 self.cwd = cwd;
595 self.home_path = Filesystem::new(homedir);
596 self.reload_rooted_at(self.cwd.clone())?;
597 Ok(())
598 }
599
600 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
603 let values = self.load_values_from(path.as_ref())?;
604 self.values.replace(values);
605 self.merge_cli_args()?;
606 self.load_unstable_flags_from_config()?;
607 Ok(())
608 }
609
610 pub fn cwd(&self) -> &Path {
612 &self.cwd
613 }
614
615 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
621 if let Some(dir) = &self.target_dir {
622 Ok(Some(dir.clone()))
623 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
624 if dir.is_empty() {
626 bail!(
627 "the target directory is set to an empty string in the \
628 `CARGO_TARGET_DIR` environment variable"
629 )
630 }
631
632 Ok(Some(Filesystem::new(self.cwd.join(dir))))
633 } else if let Some(val) = &self.build_config()?.target_dir {
634 let path = val.resolve_path(self);
635
636 if val.raw_value().is_empty() {
638 bail!(
639 "the target directory is set to an empty string in {}",
640 val.value().definition
641 )
642 }
643
644 Ok(Some(Filesystem::new(path)))
645 } else {
646 Ok(None)
647 }
648 }
649
650 pub fn build_dir(&self, workspace_manifest_path: &PathBuf) -> CargoResult<Option<Filesystem>> {
656 if !self.cli_unstable().build_dir {
657 return self.target_dir();
658 }
659 if let Some(val) = &self.build_config()?.build_dir {
660 let replacements = vec![
661 (
662 "{workspace-root}",
663 workspace_manifest_path
664 .parent()
665 .unwrap()
666 .to_str()
667 .context("workspace root was not valid utf-8")?
668 .to_string(),
669 ),
670 (
671 "{cargo-cache-home}",
672 self.home()
673 .as_path_unlocked()
674 .to_str()
675 .context("cargo home was not valid utf-8")?
676 .to_string(),
677 ),
678 ("{workspace-path-hash}", {
679 let hash = crate::util::hex::short_hash(&workspace_manifest_path);
680 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
681 }),
682 ];
683
684 let path = val
685 .resolve_templated_path(self, replacements)
686 .map_err(|e| match e {
687 path::ResolveTemplateError::UnexpectedVariable {
688 variable,
689 raw_template,
690 } => anyhow!(
691 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`"
692 ),
693 })?;
694
695 if val.raw_value().is_empty() {
697 bail!(
698 "the build directory is set to an empty string in {}",
699 val.value().definition
700 )
701 }
702
703 Ok(Some(Filesystem::new(path)))
704 } else {
705 return self.target_dir();
708 }
709 }
710
711 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
716 if let Some(vals) = self.credential_values.borrow() {
717 let val = self.get_cv_helper(key, vals)?;
718 if val.is_some() {
719 return Ok(val);
720 }
721 }
722 self.get_cv_helper(key, self.values()?)
723 }
724
725 fn get_cv_helper(
726 &self,
727 key: &ConfigKey,
728 vals: &HashMap<String, ConfigValue>,
729 ) -> CargoResult<Option<ConfigValue>> {
730 tracing::trace!("get cv {:?}", key);
731 if key.is_root() {
732 return Ok(Some(CV::Table(
735 vals.clone(),
736 Definition::Path(PathBuf::new()),
737 )));
738 }
739 let mut parts = key.parts().enumerate();
740 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
741 return Ok(None);
742 };
743 for (i, part) in parts {
744 match val {
745 CV::Table(map, _) => {
746 val = match map.get(part) {
747 Some(val) => val,
748 None => return Ok(None),
749 }
750 }
751 CV::Integer(_, def)
752 | CV::String(_, def)
753 | CV::List(_, def)
754 | CV::Boolean(_, def) => {
755 let mut key_so_far = ConfigKey::new();
756 for part in key.parts().take(i) {
757 key_so_far.push(part);
758 }
759 bail!(
760 "expected table for configuration key `{}`, \
761 but found {} in {}",
762 key_so_far,
763 val.desc(),
764 def
765 )
766 }
767 }
768 }
769 Ok(Some(val.clone()))
770 }
771
772 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
774 let cv = self.get_cv(key)?;
777 if key.is_root() {
778 return Ok(cv);
780 }
781 let env = self.env.get_str(key.as_env_key());
782 let env_def = Definition::Environment(key.as_env_key().to_string());
783 let use_env = match (&cv, env) {
784 (Some(CV::List(..)), Some(_)) => true,
786 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
787 (None, Some(_)) => true,
788 _ => false,
789 };
790
791 if !use_env {
792 return Ok(cv);
793 }
794
795 let env = env.unwrap();
799 if env == "true" {
800 Ok(Some(CV::Boolean(true, env_def)))
801 } else if env == "false" {
802 Ok(Some(CV::Boolean(false, env_def)))
803 } else if let Ok(i) = env.parse::<i64>() {
804 Ok(Some(CV::Integer(i, env_def)))
805 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
806 match cv {
807 Some(CV::List(mut cv_list, cv_def)) => {
808 self.get_env_list(key, &mut cv_list)?;
810 Ok(Some(CV::List(cv_list, cv_def)))
811 }
812 Some(cv) => {
813 bail!(
817 "unable to merge array env for config `{}`\n\
818 file: {:?}\n\
819 env: {}",
820 key,
821 cv,
822 env
823 );
824 }
825 None => {
826 let mut cv_list = Vec::new();
827 self.get_env_list(key, &mut cv_list)?;
828 Ok(Some(CV::List(cv_list, env_def)))
829 }
830 }
831 } else {
832 match cv {
834 Some(CV::List(mut cv_list, cv_def)) => {
835 self.get_env_list(key, &mut cv_list)?;
837 Ok(Some(CV::List(cv_list, cv_def)))
838 }
839 _ => {
840 Ok(Some(CV::String(env.to_string(), env_def)))
845 }
846 }
847 }
848 }
849
850 pub fn set_env(&mut self, env: HashMap<String, String>) {
852 self.env = Env::from_map(env);
853 }
854
855 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
858 self.env.iter_str()
859 }
860
861 fn env_keys(&self) -> impl Iterator<Item = &str> {
863 self.env.keys_str()
864 }
865
866 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
867 where
868 T: FromStr,
869 <T as FromStr>::Err: fmt::Display,
870 {
871 match self.env.get_str(key.as_env_key()) {
872 Some(value) => {
873 let definition = Definition::Environment(key.as_env_key().to_string());
874 Ok(Some(Value {
875 val: value
876 .parse()
877 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
878 definition,
879 }))
880 }
881 None => {
882 self.check_environment_key_case_mismatch(key);
883 Ok(None)
884 }
885 }
886 }
887
888 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
893 self.env.get_env(key)
894 }
895
896 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
901 self.env.get_env_os(key)
902 }
903
904 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
908 if self.env.contains_key(key.as_env_key()) {
909 return Ok(true);
910 }
911 if env_prefix_ok {
912 let env_prefix = format!("{}_", key.as_env_key());
913 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
914 return Ok(true);
915 }
916 }
917 if self.get_cv(key)?.is_some() {
918 return Ok(true);
919 }
920 self.check_environment_key_case_mismatch(key);
921
922 Ok(false)
923 }
924
925 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
926 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
927 let _ = self.shell().warn(format!(
928 "environment variables are expected to use uppercase letters and underscores, \
929 the variable `{}` will be ignored and have no effect",
930 env_key
931 ));
932 }
933 }
934
935 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
939 self.get::<OptValue<String>>(key)
940 }
941
942 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
948 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
949 v.map(|v| Value {
950 val: v.val.resolve_program(self),
951 definition: v.definition,
952 })
953 })
954 }
955
956 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
957 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
958 if is_path {
959 definition.root(self).join(value)
960 } else {
961 PathBuf::from(value)
963 }
964 }
965
966 pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
974 let key = ConfigKey::from_str(key);
975 self._get_list(&key)
976 }
977
978 fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
979 match self.get_cv(key)? {
980 Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
981 Some(val) => self.expected("list", key, &val),
982 None => Ok(None),
983 }
984 }
985
986 fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
988 let mut res = Vec::new();
989
990 match self.get_cv(key)? {
991 Some(CV::List(val, _def)) => res.extend(val),
992 Some(CV::String(val, def)) => {
993 let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
994 res.extend(split_vs);
995 }
996 Some(val) => {
997 return self.expected("string or array of strings", key, &val);
998 }
999 None => {}
1000 }
1001
1002 self.get_env_list(key, &mut res)?;
1003
1004 Ok(res)
1005 }
1006
1007 fn get_env_list(
1010 &self,
1011 key: &ConfigKey,
1012 output: &mut Vec<(String, Definition)>,
1013 ) -> CargoResult<()> {
1014 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1015 self.check_environment_key_case_mismatch(key);
1016 return Ok(());
1017 };
1018
1019 if is_nonmergable_list(&key) {
1020 output.clear();
1021 }
1022
1023 let def = Definition::Environment(key.as_env_key().to_string());
1024 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1025 let toml_v = toml::Value::deserialize(toml::de::ValueDeserializer::new(&env_val))
1027 .map_err(|e| {
1028 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
1029 })?;
1030 let values = toml_v.as_array().expect("env var was not array");
1031 for value in values {
1032 let s = value.as_str().ok_or_else(|| {
1034 ConfigError::new(
1035 format!("expected string, found {}", value.type_str()),
1036 def.clone(),
1037 )
1038 })?;
1039 output.push((s.to_string(), def.clone()));
1040 }
1041 } else {
1042 output.extend(
1043 env_val
1044 .split_whitespace()
1045 .map(|s| (s.to_string(), def.clone())),
1046 );
1047 }
1048 output.sort_by(|a, b| a.1.cmp(&b.1));
1049 Ok(())
1050 }
1051
1052 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1056 match self.get_cv(key)? {
1057 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1058 Some(val) => self.expected("table", key, &val),
1059 None => Ok(None),
1060 }
1061 }
1062
1063 get_value_typed! {get_integer, i64, Integer, "an integer"}
1064 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1065 get_value_typed! {get_string_priv, String, String, "a string"}
1066
1067 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1069 val.expected(ty, &key.to_string())
1070 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1071 }
1072
1073 pub fn configure(
1079 &mut self,
1080 verbose: u32,
1081 quiet: bool,
1082 color: Option<&str>,
1083 frozen: bool,
1084 locked: bool,
1085 offline: bool,
1086 target_dir: &Option<PathBuf>,
1087 unstable_flags: &[String],
1088 cli_config: &[String],
1089 ) -> CargoResult<()> {
1090 for warning in self
1091 .unstable_flags
1092 .parse(unstable_flags, self.nightly_features_allowed)?
1093 {
1094 self.shell().warn(warning)?;
1095 }
1096 if !unstable_flags.is_empty() {
1097 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1100 }
1101 if !cli_config.is_empty() {
1102 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1103 self.merge_cli_args()?;
1104 }
1105
1106 self.load_unstable_flags_from_config()?;
1110 if self.unstable_flags.config_include {
1111 self.reload_rooted_at(self.cwd.clone())?;
1118 }
1119
1120 let term = self.get::<TermConfig>("term").unwrap_or_default();
1124
1125 let extra_verbose = verbose >= 2;
1127 let verbose = verbose != 0;
1128 let verbosity = match (verbose, quiet) {
1129 (true, true) => bail!("cannot set both --verbose and --quiet"),
1130 (true, false) => Verbosity::Verbose,
1131 (false, true) => Verbosity::Quiet,
1132 (false, false) => match (term.verbose, term.quiet) {
1133 (Some(true), Some(true)) => {
1134 bail!("cannot set both `term.verbose` and `term.quiet`")
1135 }
1136 (Some(true), _) => Verbosity::Verbose,
1137 (_, Some(true)) => Verbosity::Quiet,
1138 _ => Verbosity::Normal,
1139 },
1140 };
1141 self.shell().set_verbosity(verbosity);
1142 self.extra_verbose = extra_verbose;
1143
1144 let color = color.or_else(|| term.color.as_deref());
1145 self.shell().set_color_choice(color)?;
1146 if let Some(hyperlinks) = term.hyperlinks {
1147 self.shell().set_hyperlinks(hyperlinks)?;
1148 }
1149 if let Some(unicode) = term.unicode {
1150 self.shell().set_unicode(unicode)?;
1151 }
1152
1153 self.progress_config = term.progress.unwrap_or_default();
1154
1155 self.frozen = frozen;
1156 self.locked = locked;
1157 self.offline = offline
1158 || self
1159 .net_config()
1160 .ok()
1161 .and_then(|n| n.offline)
1162 .unwrap_or(false);
1163 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1164 self.target_dir = cli_target_dir;
1165
1166 Ok(())
1167 }
1168
1169 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1170 if self.nightly_features_allowed {
1173 self.unstable_flags = self
1174 .get::<Option<CliUnstable>>("unstable")?
1175 .unwrap_or_default();
1176 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1177 self.unstable_flags.parse(unstable_flags_cli, true)?;
1182 }
1183 }
1184
1185 Ok(())
1186 }
1187
1188 pub fn cli_unstable(&self) -> &CliUnstable {
1189 &self.unstable_flags
1190 }
1191
1192 pub fn extra_verbose(&self) -> bool {
1193 self.extra_verbose
1194 }
1195
1196 pub fn network_allowed(&self) -> bool {
1197 !self.offline_flag().is_some()
1198 }
1199
1200 pub fn offline_flag(&self) -> Option<&'static str> {
1201 if self.frozen {
1202 Some("--frozen")
1203 } else if self.offline {
1204 Some("--offline")
1205 } else {
1206 None
1207 }
1208 }
1209
1210 pub fn set_locked(&mut self, locked: bool) {
1211 self.locked = locked;
1212 }
1213
1214 pub fn lock_update_allowed(&self) -> bool {
1215 !self.locked_flag().is_some()
1216 }
1217
1218 pub fn locked_flag(&self) -> Option<&'static str> {
1219 if self.frozen {
1220 Some("--frozen")
1221 } else if self.locked {
1222 Some("--locked")
1223 } else {
1224 None
1225 }
1226 }
1227
1228 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1230 self.load_values_from(&self.cwd)
1231 }
1232
1233 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1237 let mut result = Vec::new();
1238 let mut seen = HashSet::new();
1239 let home = self.home_path.clone().into_path_unlocked();
1240 self.walk_tree(&self.cwd, &home, |path| {
1241 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1242 if self.cli_unstable().config_include {
1243 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1244 }
1245 result.push(cv);
1246 Ok(())
1247 })
1248 .context("could not load Cargo configuration")?;
1249 Ok(result)
1250 }
1251
1252 fn load_unmerged_include(
1256 &self,
1257 cv: &mut CV,
1258 seen: &mut HashSet<PathBuf>,
1259 output: &mut Vec<CV>,
1260 ) -> CargoResult<()> {
1261 let includes = self.include_paths(cv, false)?;
1262 for (path, abs_path, def) in includes {
1263 let mut cv = self
1264 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1265 .with_context(|| {
1266 format!("failed to load config include `{}` from `{}`", path, def)
1267 })?;
1268 self.load_unmerged_include(&mut cv, seen, output)?;
1269 output.push(cv);
1270 }
1271 Ok(())
1272 }
1273
1274 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1276 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1279 let home = self.home_path.clone().into_path_unlocked();
1280
1281 self.walk_tree(path, &home, |path| {
1282 let value = self.load_file(path)?;
1283 cfg.merge(value, false).with_context(|| {
1284 format!("failed to merge configuration at `{}`", path.display())
1285 })?;
1286 Ok(())
1287 })
1288 .context("could not load Cargo configuration")?;
1289
1290 match cfg {
1291 CV::Table(map, _) => Ok(map),
1292 _ => unreachable!(),
1293 }
1294 }
1295
1296 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1300 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1301 }
1302
1303 fn _load_file(
1313 &self,
1314 path: &Path,
1315 seen: &mut HashSet<PathBuf>,
1316 includes: bool,
1317 why_load: WhyLoad,
1318 ) -> CargoResult<ConfigValue> {
1319 if !seen.insert(path.to_path_buf()) {
1320 bail!(
1321 "config `include` cycle detected with path `{}`",
1322 path.display()
1323 );
1324 }
1325 tracing::debug!(?path, ?why_load, includes, "load config from file");
1326
1327 let contents = fs::read_to_string(path)
1328 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1329 let toml = parse_document(&contents, path, self).with_context(|| {
1330 format!("could not parse TOML configuration in `{}`", path.display())
1331 })?;
1332 let def = match why_load {
1333 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1334 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1335 };
1336 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1337 format!(
1338 "failed to load TOML configuration from `{}`",
1339 path.display()
1340 )
1341 })?;
1342 if includes {
1343 self.load_includes(value, seen, why_load)
1344 } else {
1345 Ok(value)
1346 }
1347 }
1348
1349 fn load_includes(
1356 &self,
1357 mut value: CV,
1358 seen: &mut HashSet<PathBuf>,
1359 why_load: WhyLoad,
1360 ) -> CargoResult<CV> {
1361 let includes = self.include_paths(&mut value, true)?;
1363 if !self.cli_unstable().config_include {
1365 return Ok(value);
1366 }
1367 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1369 for (path, abs_path, def) in includes {
1370 self._load_file(&abs_path, seen, true, why_load)
1371 .and_then(|include| root.merge(include, true))
1372 .with_context(|| {
1373 format!("failed to load config include `{}` from `{}`", path, def)
1374 })?;
1375 }
1376 root.merge(value, true)?;
1377 Ok(root)
1378 }
1379
1380 fn include_paths(
1382 &self,
1383 cv: &mut CV,
1384 remove: bool,
1385 ) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
1386 let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
1387 let abs_path = match def {
1388 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1389 Definition::Environment(_) | Definition::Cli(None) => self.cwd().join(&path),
1390 };
1391 (path.to_string(), abs_path, def.clone())
1392 };
1393 let CV::Table(table, _def) = cv else {
1394 unreachable!()
1395 };
1396 let owned;
1397 let include = if remove {
1398 owned = table.remove("include");
1399 owned.as_ref()
1400 } else {
1401 table.get("include")
1402 };
1403 let includes = match include {
1404 Some(CV::String(s, def)) => {
1405 vec![abs(s, def)]
1406 }
1407 Some(CV::List(list, _def)) => list.iter().map(|(s, def)| abs(s, def)).collect(),
1408 Some(other) => bail!(
1409 "`include` expected a string or list, but found {} in `{}`",
1410 other.desc(),
1411 other.definition()
1412 ),
1413 None => {
1414 return Ok(Vec::new());
1415 }
1416 };
1417
1418 for (path, abs_path, def) in &includes {
1419 if abs_path.extension() != Some(OsStr::new("toml")) {
1420 bail!(
1421 "expected a config include path ending with `.toml`, \
1422 but found `{path}` from `{def}`",
1423 )
1424 }
1425 }
1426
1427 Ok(includes)
1428 }
1429
1430 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1432 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1433 let Some(cli_args) = &self.cli_config else {
1434 return Ok(loaded_args);
1435 };
1436 let mut seen = HashSet::new();
1437 for arg in cli_args {
1438 let arg_as_path = self.cwd.join(arg);
1439 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1440 let str_path = arg_as_path
1442 .to_str()
1443 .ok_or_else(|| {
1444 anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
1445 })?
1446 .to_string();
1447 self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
1448 .with_context(|| format!("failed to load config from `{}`", str_path))?
1449 } else {
1450 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
1456 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
1457 })?;
1458 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
1459 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
1460 }
1461 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
1462 non_empty(d.prefix()) || non_empty(d.suffix())
1463 }
1464 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
1465 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
1466 }
1467 let ok = {
1468 let mut got_to_value = false;
1469 let mut table = doc.as_table();
1470 let mut is_root = true;
1471 while table.is_dotted() || is_root {
1472 is_root = false;
1473 if table.len() != 1 {
1474 break;
1475 }
1476 let (k, n) = table.iter().next().expect("len() == 1 above");
1477 match n {
1478 Item::Table(nt) => {
1479 if table.key(k).map_or(false, non_empty_key_decor)
1480 || non_empty_decor(nt.decor())
1481 {
1482 bail!(
1483 "--config argument `{arg}` \
1484 includes non-whitespace decoration"
1485 )
1486 }
1487 table = nt;
1488 }
1489 Item::Value(v) if v.is_inline_table() => {
1490 bail!(
1491 "--config argument `{arg}` \
1492 sets a value to an inline table, which is not accepted"
1493 );
1494 }
1495 Item::Value(v) => {
1496 if table
1497 .key(k)
1498 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
1499 || non_empty_decor(v.decor())
1500 {
1501 bail!(
1502 "--config argument `{arg}` \
1503 includes non-whitespace decoration"
1504 )
1505 }
1506 got_to_value = true;
1507 break;
1508 }
1509 Item::ArrayOfTables(_) => {
1510 bail!(
1511 "--config argument `{arg}` \
1512 sets a value to an array of tables, which is not accepted"
1513 );
1514 }
1515
1516 Item::None => {
1517 bail!("--config argument `{arg}` doesn't provide a value")
1518 }
1519 }
1520 }
1521 got_to_value
1522 };
1523 if !ok {
1524 bail!(
1525 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
1526 );
1527 }
1528
1529 let toml_v: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1530 .with_context(|| {
1531 format!("failed to parse value from --config argument `{arg}`")
1532 })?;
1533
1534 if toml_v
1535 .get("registry")
1536 .and_then(|v| v.as_table())
1537 .and_then(|t| t.get("token"))
1538 .is_some()
1539 {
1540 bail!("registry.token cannot be set through --config for security reasons");
1541 } else if let Some((k, _)) = toml_v
1542 .get("registries")
1543 .and_then(|v| v.as_table())
1544 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1545 {
1546 bail!(
1547 "registries.{}.token cannot be set through --config for security reasons",
1548 k
1549 );
1550 }
1551
1552 if toml_v
1553 .get("registry")
1554 .and_then(|v| v.as_table())
1555 .and_then(|t| t.get("secret-key"))
1556 .is_some()
1557 {
1558 bail!(
1559 "registry.secret-key cannot be set through --config for security reasons"
1560 );
1561 } else if let Some((k, _)) = toml_v
1562 .get("registries")
1563 .and_then(|v| v.as_table())
1564 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1565 {
1566 bail!(
1567 "registries.{}.secret-key cannot be set through --config for security reasons",
1568 k
1569 );
1570 }
1571
1572 CV::from_toml(Definition::Cli(None), toml_v)
1573 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1574 };
1575 let tmp_table = self
1576 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1577 .context("failed to load --config include".to_string())?;
1578 loaded_args
1579 .merge(tmp_table, true)
1580 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1581 }
1582 Ok(loaded_args)
1583 }
1584
1585 fn merge_cli_args(&mut self) -> CargoResult<()> {
1587 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1588 unreachable!()
1589 };
1590 let values = self.values_mut()?;
1591 for (key, value) in loaded_map.into_iter() {
1592 match values.entry(key) {
1593 Vacant(entry) => {
1594 entry.insert(value);
1595 }
1596 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1597 format!(
1598 "failed to merge --config key `{}` into `{}`",
1599 entry.key(),
1600 entry.get().definition(),
1601 )
1602 })?,
1603 };
1604 }
1605 Ok(())
1606 }
1607
1608 fn get_file_path(
1614 &self,
1615 dir: &Path,
1616 filename_without_extension: &str,
1617 warn: bool,
1618 ) -> CargoResult<Option<PathBuf>> {
1619 let possible = dir.join(filename_without_extension);
1620 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1621
1622 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1623 if warn {
1624 if let Ok(possible_with_extension_handle) =
1625 same_file::Handle::from_path(&possible_with_extension)
1626 {
1627 if possible_handle != possible_with_extension_handle {
1633 self.shell().warn(format!(
1634 "both `{}` and `{}` exist. Using `{}`",
1635 possible.display(),
1636 possible_with_extension.display(),
1637 possible.display()
1638 ))?;
1639 }
1640 } else {
1641 self.shell().warn(format!(
1642 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1643 possible.display(),
1644 ))?;
1645 self.shell().note(
1646 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`"),
1647 )?;
1648 }
1649 }
1650
1651 Ok(Some(possible))
1652 } else if possible_with_extension.exists() {
1653 Ok(Some(possible_with_extension))
1654 } else {
1655 Ok(None)
1656 }
1657 }
1658
1659 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1660 where
1661 F: FnMut(&Path) -> CargoResult<()>,
1662 {
1663 let mut seen_dir = HashSet::new();
1664
1665 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1666 let config_root = current.join(".cargo");
1667 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1668 walk(&path)?;
1669 }
1670 seen_dir.insert(config_root);
1671 }
1672
1673 if !seen_dir.contains(home) {
1677 if let Some(path) = self.get_file_path(home, "config", true)? {
1678 walk(&path)?;
1679 }
1680 }
1681
1682 Ok(())
1683 }
1684
1685 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1687 RegistryName::new(registry)?;
1688 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1689 self.resolve_registry_index(&index).with_context(|| {
1690 format!(
1691 "invalid index URL for registry `{}` defined in {}",
1692 registry, index.definition
1693 )
1694 })
1695 } else {
1696 bail!(
1697 "registry index was not found in any configuration: `{}`",
1698 registry
1699 );
1700 }
1701 }
1702
1703 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1705 if self.get_string("registry.index")?.is_some() {
1706 bail!(
1707 "the `registry.index` config value is no longer supported\n\
1708 Use `[source]` replacement to alter the default index for crates.io."
1709 );
1710 }
1711 Ok(())
1712 }
1713
1714 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1715 let base = index
1717 .definition
1718 .root(self)
1719 .join("truncated-by-url_with_base");
1720 let _parsed = index.val.into_url()?;
1722 let url = index.val.into_url_with_base(Some(&*base))?;
1723 if url.password().is_some() {
1724 bail!("registry URLs may not contain passwords");
1725 }
1726 Ok(url)
1727 }
1728
1729 pub fn load_credentials(&self) -> CargoResult<()> {
1737 if self.credential_values.filled() {
1738 return Ok(());
1739 }
1740
1741 let home_path = self.home_path.clone().into_path_unlocked();
1742 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1743 return Ok(());
1744 };
1745
1746 let mut value = self.load_file(&credentials)?;
1747 {
1749 let CV::Table(ref mut value_map, ref def) = value else {
1750 unreachable!();
1751 };
1752
1753 if let Some(token) = value_map.remove("token") {
1754 if let Vacant(entry) = value_map.entry("registry".into()) {
1755 let map = HashMap::from([("token".into(), token)]);
1756 let table = CV::Table(map, def.clone());
1757 entry.insert(table);
1758 }
1759 }
1760 }
1761
1762 let mut credential_values = HashMap::new();
1763 if let CV::Table(map, _) = value {
1764 let base_map = self.values()?;
1765 for (k, v) in map {
1766 let entry = match base_map.get(&k) {
1767 Some(base_entry) => {
1768 let mut entry = base_entry.clone();
1769 entry.merge(v, true)?;
1770 entry
1771 }
1772 None => v,
1773 };
1774 credential_values.insert(k, entry);
1775 }
1776 }
1777 self.credential_values
1778 .fill(credential_values)
1779 .expect("was not filled at beginning of the function");
1780 Ok(())
1781 }
1782
1783 fn maybe_get_tool(
1786 &self,
1787 tool: &str,
1788 from_config: &Option<ConfigRelativePath>,
1789 ) -> Option<PathBuf> {
1790 let var = tool.to_uppercase();
1791
1792 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1793 Some(tool_path) => {
1794 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1795 let path = if maybe_relative {
1796 self.cwd.join(tool_path)
1797 } else {
1798 PathBuf::from(tool_path)
1799 };
1800 Some(path)
1801 }
1802
1803 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1804 }
1805 }
1806
1807 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1818 let tool_str = tool.as_str();
1819 self.maybe_get_tool(tool_str, from_config)
1820 .or_else(|| {
1821 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1835 if toolchain.to_str()?.contains(&['/', '\\']) {
1838 return None;
1839 }
1840 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1843 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1844 let tool_meta = tool_resolved.metadata().ok()?;
1845 let rustup_meta = rustup_resolved.metadata().ok()?;
1846 if tool_meta.len() != rustup_meta.len() {
1851 return None;
1852 }
1853 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1855 let toolchain_exe = home::rustup_home()
1856 .ok()?
1857 .join("toolchains")
1858 .join(&toolchain)
1859 .join("bin")
1860 .join(&tool_exe);
1861 toolchain_exe.exists().then_some(toolchain_exe)
1862 })
1863 .unwrap_or_else(|| PathBuf::from(tool_str))
1864 }
1865
1866 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1867 self.jobserver.as_ref()
1868 }
1869
1870 pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
1871 let http = self
1872 .easy
1873 .try_borrow_with(|| http_handle(self).map(RefCell::new))?;
1874 {
1875 let mut http = http.borrow_mut();
1876 http.reset();
1877 let timeout = configure_http_handle(self, &mut http)?;
1878 timeout.configure(&mut http)?;
1879 }
1880 Ok(http)
1881 }
1882
1883 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1884 self.http_config.try_borrow_with(|| {
1885 let mut http = self.get::<CargoHttpConfig>("http")?;
1886 let curl_v = curl::Version::get();
1887 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1888 Ok(http)
1889 })
1890 }
1891
1892 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1893 self.future_incompat_config
1894 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1895 }
1896
1897 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1898 self.net_config
1899 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1900 }
1901
1902 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1903 self.build_config
1904 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1905 }
1906
1907 pub fn progress_config(&self) -> &ProgressConfig {
1908 &self.progress_config
1909 }
1910
1911 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1914 let env_config = self.env_config.try_borrow_with(|| {
1915 CargoResult::Ok(Arc::new({
1916 let env_config = self.get::<EnvConfig>("env")?;
1917 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1933 if env_config.contains_key(*disallowed) {
1934 bail!(
1935 "setting the `{disallowed}` environment variable is not supported \
1936 in the `[env]` configuration table"
1937 );
1938 }
1939 }
1940 env_config
1941 .into_iter()
1942 .filter_map(|(k, v)| {
1943 if v.is_force() || self.get_env_os(&k).is_none() {
1944 Some((k, v.resolve(self).to_os_string()))
1945 } else {
1946 None
1947 }
1948 })
1949 .collect()
1950 }))
1951 })?;
1952
1953 Ok(env_config)
1954 }
1955
1956 pub fn validate_term_config(&self) -> CargoResult<()> {
1962 drop(self.get::<TermConfig>("term")?);
1963 Ok(())
1964 }
1965
1966 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1970 self.target_cfgs
1971 .try_borrow_with(|| target::load_target_cfgs(self))
1972 }
1973
1974 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1975 self.doc_extern_map
1979 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1980 }
1981
1982 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
1984 target::get_target_applies_to_host(self)
1985 }
1986
1987 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1989 target::load_host_triple(self, target)
1990 }
1991
1992 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
1994 target::load_target_triple(self, target)
1995 }
1996
1997 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2002 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2003 self.check_registry_index_not_set()?;
2004 let url = CRATES_IO_INDEX.into_url().unwrap();
2005 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2006 })?;
2007 Ok(*source_id)
2008 }
2009
2010 pub fn creation_time(&self) -> Instant {
2011 self.creation_time
2012 }
2013
2014 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2029 let d = Deserializer {
2030 gctx: self,
2031 key: ConfigKey::from_str(key),
2032 env_prefix_ok: true,
2033 };
2034 T::deserialize(d).map_err(|e| e.into())
2035 }
2036
2037 #[track_caller]
2043 #[tracing::instrument(skip_all)]
2044 pub fn assert_package_cache_locked<'a>(
2045 &self,
2046 mode: CacheLockMode,
2047 f: &'a Filesystem,
2048 ) -> &'a Path {
2049 let ret = f.as_path_unlocked();
2050 assert!(
2051 self.package_cache_lock.is_locked(mode),
2052 "package cache lock is not currently held, Cargo forgot to call \
2053 `acquire_package_cache_lock` before we got to this stack frame",
2054 );
2055 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2056 ret
2057 }
2058
2059 #[tracing::instrument(skip_all)]
2065 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2066 self.package_cache_lock.lock(self, mode)
2067 }
2068
2069 #[tracing::instrument(skip_all)]
2075 pub fn try_acquire_package_cache_lock(
2076 &self,
2077 mode: CacheLockMode,
2078 ) -> CargoResult<Option<CacheLock<'_>>> {
2079 self.package_cache_lock.try_lock(self, mode)
2080 }
2081
2082 pub fn global_cache_tracker(&self) -> CargoResult<RefMut<'_, GlobalCacheTracker>> {
2087 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2088 Ok::<_, anyhow::Error>(RefCell::new(GlobalCacheTracker::new(self)?))
2089 })?;
2090 Ok(tracker.borrow_mut())
2091 }
2092
2093 pub fn deferred_global_last_use(&self) -> CargoResult<RefMut<'_, DeferredGlobalLastUse>> {
2095 let deferred = self.deferred_global_last_use.try_borrow_with(|| {
2096 Ok::<_, anyhow::Error>(RefCell::new(DeferredGlobalLastUse::new()))
2097 })?;
2098 Ok(deferred.borrow_mut())
2099 }
2100
2101 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2103 if self.unstable_flags.warnings {
2104 Ok(self.build_config()?.warnings.unwrap_or_default())
2105 } else {
2106 Ok(WarningHandling::default())
2107 }
2108 }
2109}
2110
2111#[derive(Debug)]
2113pub struct ConfigError {
2114 error: anyhow::Error,
2115 definition: Option<Definition>,
2116}
2117
2118impl ConfigError {
2119 fn new(message: String, definition: Definition) -> ConfigError {
2120 ConfigError {
2121 error: anyhow::Error::msg(message),
2122 definition: Some(definition),
2123 }
2124 }
2125
2126 fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
2127 ConfigError {
2128 error: anyhow!(
2129 "`{}` expected {}, but found a {}",
2130 key,
2131 expected,
2132 found.desc()
2133 ),
2134 definition: Some(found.definition().clone()),
2135 }
2136 }
2137
2138 fn is_missing_field(&self) -> bool {
2139 self.error.downcast_ref::<MissingFieldError>().is_some()
2140 }
2141
2142 fn missing(key: &ConfigKey) -> ConfigError {
2143 ConfigError {
2144 error: anyhow!("missing config key `{}`", key),
2145 definition: None,
2146 }
2147 }
2148
2149 fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
2150 ConfigError {
2151 error: anyhow::Error::from(self)
2152 .context(format!("could not load config key `{}`", key)),
2153 definition: definition,
2154 }
2155 }
2156}
2157
2158impl std::error::Error for ConfigError {
2159 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2160 self.error.source()
2161 }
2162}
2163
2164impl fmt::Display for ConfigError {
2165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2166 if let Some(definition) = &self.definition {
2167 write!(f, "error in {}: {}", definition, self.error)
2168 } else {
2169 self.error.fmt(f)
2170 }
2171 }
2172}
2173
2174#[derive(Debug)]
2175struct MissingFieldError(String);
2176
2177impl fmt::Display for MissingFieldError {
2178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2179 write!(f, "missing field `{}`", self.0)
2180 }
2181}
2182
2183impl std::error::Error for MissingFieldError {}
2184
2185impl serde::de::Error for ConfigError {
2186 fn custom<T: fmt::Display>(msg: T) -> Self {
2187 ConfigError {
2188 error: anyhow::Error::msg(msg.to_string()),
2189 definition: None,
2190 }
2191 }
2192
2193 fn missing_field(field: &'static str) -> Self {
2194 ConfigError {
2195 error: anyhow::Error::new(MissingFieldError(field.to_string())),
2196 definition: None,
2197 }
2198 }
2199}
2200
2201impl From<anyhow::Error> for ConfigError {
2202 fn from(error: anyhow::Error) -> Self {
2203 ConfigError {
2204 error,
2205 definition: None,
2206 }
2207 }
2208}
2209
2210#[derive(Eq, PartialEq, Clone)]
2211pub enum ConfigValue {
2212 Integer(i64, Definition),
2213 String(String, Definition),
2214 List(Vec<(String, Definition)>, Definition),
2215 Table(HashMap<String, ConfigValue>, Definition),
2216 Boolean(bool, Definition),
2217}
2218
2219impl fmt::Debug for ConfigValue {
2220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2221 match self {
2222 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2223 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2224 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2225 CV::List(list, def) => {
2226 write!(f, "[")?;
2227 for (i, (s, def)) in list.iter().enumerate() {
2228 if i > 0 {
2229 write!(f, ", ")?;
2230 }
2231 write!(f, "{} (from {})", s, def)?;
2232 }
2233 write!(f, "] (from {})", def)
2234 }
2235 CV::Table(table, _) => write!(f, "{:?}", table),
2236 }
2237 }
2238}
2239
2240impl ConfigValue {
2241 fn get_definition(&self) -> &Definition {
2242 match self {
2243 CV::Boolean(_, def)
2244 | CV::Integer(_, def)
2245 | CV::String(_, def)
2246 | CV::List(_, def)
2247 | CV::Table(_, def) => def,
2248 }
2249 }
2250
2251 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2252 match toml {
2253 toml::Value::String(val) => Ok(CV::String(val, def)),
2254 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2255 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2256 toml::Value::Array(val) => Ok(CV::List(
2257 val.into_iter()
2258 .map(|toml| match toml {
2259 toml::Value::String(val) => Ok((val, def.clone())),
2260 v => bail!("expected string but found {} in list", v.type_str()),
2261 })
2262 .collect::<CargoResult<_>>()?,
2263 def,
2264 )),
2265 toml::Value::Table(val) => Ok(CV::Table(
2266 val.into_iter()
2267 .map(|(key, value)| {
2268 let value = CV::from_toml(def.clone(), value)
2269 .with_context(|| format!("failed to parse key `{}`", key))?;
2270 Ok((key, value))
2271 })
2272 .collect::<CargoResult<_>>()?,
2273 def,
2274 )),
2275 v => bail!(
2276 "found TOML configuration value of unknown type `{}`",
2277 v.type_str()
2278 ),
2279 }
2280 }
2281
2282 fn into_toml(self) -> toml::Value {
2283 match self {
2284 CV::Boolean(s, _) => toml::Value::Boolean(s),
2285 CV::String(s, _) => toml::Value::String(s),
2286 CV::Integer(i, _) => toml::Value::Integer(i),
2287 CV::List(l, _) => {
2288 toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect())
2289 }
2290 CV::Table(l, _) => {
2291 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2292 }
2293 }
2294 }
2295
2296 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2305 self.merge_helper(from, force, &mut ConfigKey::new())
2306 }
2307
2308 fn merge_helper(
2309 &mut self,
2310 from: ConfigValue,
2311 force: bool,
2312 parts: &mut ConfigKey,
2313 ) -> CargoResult<()> {
2314 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2315 match (self, from) {
2316 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2317 if is_nonmergable_list(&parts) {
2318 if force || is_higher_priority {
2320 mem::swap(new, old);
2321 }
2322 } else {
2323 if force {
2325 old.append(new);
2326 } else {
2327 new.append(old);
2328 mem::swap(new, old);
2329 }
2330 }
2331 old.sort_by(|a, b| a.1.cmp(&b.1));
2332 }
2333 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2334 for (key, value) in mem::take(new) {
2335 match old.entry(key.clone()) {
2336 Occupied(mut entry) => {
2337 let new_def = value.definition().clone();
2338 let entry = entry.get_mut();
2339 parts.push(&key);
2340 entry.merge_helper(value, force, parts).with_context(|| {
2341 format!(
2342 "failed to merge key `{}` between \
2343 {} and {}",
2344 key,
2345 entry.definition(),
2346 new_def,
2347 )
2348 })?;
2349 }
2350 Vacant(entry) => {
2351 entry.insert(value);
2352 }
2353 };
2354 }
2355 }
2356 (expected @ &mut CV::List(_, _), found)
2358 | (expected @ &mut CV::Table(_, _), found)
2359 | (expected, found @ CV::List(_, _))
2360 | (expected, found @ CV::Table(_, _)) => {
2361 return Err(anyhow!(
2362 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2363 found.definition(),
2364 expected.definition(),
2365 expected.desc(),
2366 found.desc()
2367 ));
2368 }
2369 (old, mut new) => {
2370 if force || is_higher_priority {
2371 mem::swap(old, &mut new);
2372 }
2373 }
2374 }
2375
2376 Ok(())
2377 }
2378
2379 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2380 match self {
2381 CV::Integer(i, def) => Ok((*i, def)),
2382 _ => self.expected("integer", key),
2383 }
2384 }
2385
2386 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2387 match self {
2388 CV::String(s, def) => Ok((s, def)),
2389 _ => self.expected("string", key),
2390 }
2391 }
2392
2393 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2394 match self {
2395 CV::Table(table, def) => Ok((table, def)),
2396 _ => self.expected("table", key),
2397 }
2398 }
2399
2400 pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
2401 match self {
2402 CV::List(list, _) => Ok(list),
2403 _ => self.expected("list", key),
2404 }
2405 }
2406
2407 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2408 match self {
2409 CV::Boolean(b, def) => Ok((*b, def)),
2410 _ => self.expected("bool", key),
2411 }
2412 }
2413
2414 pub fn desc(&self) -> &'static str {
2415 match *self {
2416 CV::Table(..) => "table",
2417 CV::List(..) => "array",
2418 CV::String(..) => "string",
2419 CV::Boolean(..) => "boolean",
2420 CV::Integer(..) => "integer",
2421 }
2422 }
2423
2424 pub fn definition(&self) -> &Definition {
2425 match self {
2426 CV::Boolean(_, def)
2427 | CV::Integer(_, def)
2428 | CV::String(_, def)
2429 | CV::List(_, def)
2430 | CV::Table(_, def) => def,
2431 }
2432 }
2433
2434 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2435 bail!(
2436 "expected a {}, but found a {} for `{}` in {}",
2437 wanted,
2438 self.desc(),
2439 key,
2440 self.definition()
2441 )
2442 }
2443}
2444
2445fn is_nonmergable_list(key: &ConfigKey) -> bool {
2448 key.matches("registry.credential-provider")
2449 || key.matches("registries.*.credential-provider")
2450 || key.matches("target.*.runner")
2451 || key.matches("host.runner")
2452 || key.matches("credential-alias.*")
2453 || key.matches("doc.browser")
2454}
2455
2456pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2457 ::home::cargo_home_with_cwd(cwd).ok()
2458}
2459
2460pub fn save_credentials(
2461 gctx: &GlobalContext,
2462 token: Option<RegistryCredentialConfig>,
2463 registry: &SourceId,
2464) -> CargoResult<()> {
2465 let registry = if registry.is_crates_io() {
2466 None
2467 } else {
2468 let name = registry
2469 .alt_registry_key()
2470 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2471 Some(name)
2472 };
2473
2474 let home_path = gctx.home_path.clone().into_path_unlocked();
2478 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2479 Some(path) => match path.file_name() {
2480 Some(filename) => Path::new(filename).to_owned(),
2481 None => Path::new("credentials.toml").to_owned(),
2482 },
2483 None => Path::new("credentials.toml").to_owned(),
2484 };
2485
2486 let mut file = {
2487 gctx.home_path.create_dir()?;
2488 gctx.home_path
2489 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2490 };
2491
2492 let mut contents = String::new();
2493 file.read_to_string(&mut contents).with_context(|| {
2494 format!(
2495 "failed to read configuration file `{}`",
2496 file.path().display()
2497 )
2498 })?;
2499
2500 let mut toml = parse_document(&contents, file.path(), gctx)?;
2501
2502 if let Some(token) = toml.remove("token") {
2504 let map = HashMap::from([("token".to_string(), token)]);
2505 toml.insert("registry".into(), map.into());
2506 }
2507
2508 if let Some(token) = token {
2509 let path_def = Definition::Path(file.path().to_path_buf());
2512 let (key, mut value) = match token {
2513 RegistryCredentialConfig::Token(token) => {
2514 let key = "token".to_string();
2517 let value = ConfigValue::String(token.expose(), path_def.clone());
2518 let map = HashMap::from([(key, value)]);
2519 let table = CV::Table(map, path_def.clone());
2520
2521 if let Some(registry) = registry {
2522 let map = HashMap::from([(registry.to_string(), table)]);
2523 ("registries".into(), CV::Table(map, path_def.clone()))
2524 } else {
2525 ("registry".into(), table)
2526 }
2527 }
2528 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2529 let key = "secret-key".to_string();
2532 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2533 let mut map = HashMap::from([(key, value)]);
2534 if let Some(key_subject) = key_subject {
2535 let key = "secret-key-subject".to_string();
2536 let value = ConfigValue::String(key_subject, path_def.clone());
2537 map.insert(key, value);
2538 }
2539 let table = CV::Table(map, path_def.clone());
2540
2541 if let Some(registry) = registry {
2542 let map = HashMap::from([(registry.to_string(), table)]);
2543 ("registries".into(), CV::Table(map, path_def.clone()))
2544 } else {
2545 ("registry".into(), table)
2546 }
2547 }
2548 _ => unreachable!(),
2549 };
2550
2551 if registry.is_some() {
2552 if let Some(table) = toml.remove("registries") {
2553 let v = CV::from_toml(path_def, table)?;
2554 value.merge(v, false)?;
2555 }
2556 }
2557 toml.insert(key, value.into_toml());
2558 } else {
2559 if let Some(registry) = registry {
2561 if let Some(registries) = toml.get_mut("registries") {
2562 if let Some(reg) = registries.get_mut(registry) {
2563 let rtable = reg.as_table_mut().ok_or_else(|| {
2564 format_err!("expected `[registries.{}]` to be a table", registry)
2565 })?;
2566 rtable.remove("token");
2567 rtable.remove("secret-key");
2568 rtable.remove("secret-key-subject");
2569 }
2570 }
2571 } else if let Some(registry) = toml.get_mut("registry") {
2572 let reg_table = registry
2573 .as_table_mut()
2574 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2575 reg_table.remove("token");
2576 reg_table.remove("secret-key");
2577 reg_table.remove("secret-key-subject");
2578 }
2579 }
2580
2581 let contents = toml.to_string();
2582 file.seek(SeekFrom::Start(0))?;
2583 file.write_all(contents.as_bytes())
2584 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2585 file.file().set_len(contents.len() as u64)?;
2586 set_permissions(file.file(), 0o600)
2587 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2588
2589 return Ok(());
2590
2591 #[cfg(unix)]
2592 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2593 use std::os::unix::fs::PermissionsExt;
2594
2595 let mut perms = file.metadata()?.permissions();
2596 perms.set_mode(mode);
2597 file.set_permissions(perms)?;
2598 Ok(())
2599 }
2600
2601 #[cfg(not(unix))]
2602 #[allow(unused)]
2603 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2604 Ok(())
2605 }
2606}
2607
2608#[derive(Debug, Default, Deserialize, PartialEq)]
2609#[serde(rename_all = "kebab-case")]
2610pub struct CargoHttpConfig {
2611 pub proxy: Option<String>,
2612 pub low_speed_limit: Option<u32>,
2613 pub timeout: Option<u64>,
2614 pub cainfo: Option<ConfigRelativePath>,
2615 pub check_revoke: Option<bool>,
2616 pub user_agent: Option<String>,
2617 pub debug: Option<bool>,
2618 pub multiplexing: Option<bool>,
2619 pub ssl_version: Option<SslVersionConfig>,
2620}
2621
2622#[derive(Debug, Default, Deserialize, PartialEq)]
2623#[serde(rename_all = "kebab-case")]
2624pub struct CargoFutureIncompatConfig {
2625 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2626}
2627
2628#[derive(Debug, Default, Deserialize, PartialEq)]
2629#[serde(rename_all = "kebab-case")]
2630pub enum CargoFutureIncompatFrequencyConfig {
2631 #[default]
2632 Always,
2633 Never,
2634}
2635
2636impl CargoFutureIncompatConfig {
2637 pub fn should_display_message(&self) -> bool {
2638 use CargoFutureIncompatFrequencyConfig::*;
2639
2640 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2641 match frequency {
2642 Always => true,
2643 Never => false,
2644 }
2645 }
2646}
2647
2648#[derive(Clone, Debug, PartialEq)]
2662pub enum SslVersionConfig {
2663 Single(String),
2664 Range(SslVersionConfigRange),
2665}
2666
2667impl<'de> Deserialize<'de> for SslVersionConfig {
2668 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2669 where
2670 D: serde::Deserializer<'de>,
2671 {
2672 UntaggedEnumVisitor::new()
2673 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2674 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2675 .deserialize(deserializer)
2676 }
2677}
2678
2679#[derive(Clone, Debug, Deserialize, PartialEq)]
2680#[serde(rename_all = "kebab-case")]
2681pub struct SslVersionConfigRange {
2682 pub min: Option<String>,
2683 pub max: Option<String>,
2684}
2685
2686#[derive(Debug, Deserialize)]
2687#[serde(rename_all = "kebab-case")]
2688pub struct CargoNetConfig {
2689 pub retry: Option<u32>,
2690 pub offline: Option<bool>,
2691 pub git_fetch_with_cli: Option<bool>,
2692 pub ssh: Option<CargoSshConfig>,
2693}
2694
2695#[derive(Debug, Deserialize)]
2696#[serde(rename_all = "kebab-case")]
2697pub struct CargoSshConfig {
2698 pub known_hosts: Option<Vec<Value<String>>>,
2699}
2700
2701#[derive(Debug, Clone)]
2714pub enum JobsConfig {
2715 Integer(i32),
2716 String(String),
2717}
2718
2719impl<'de> Deserialize<'de> for JobsConfig {
2720 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2721 where
2722 D: serde::Deserializer<'de>,
2723 {
2724 UntaggedEnumVisitor::new()
2725 .i32(|int| Ok(JobsConfig::Integer(int)))
2726 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2727 .deserialize(deserializer)
2728 }
2729}
2730
2731#[derive(Debug, Deserialize)]
2732#[serde(rename_all = "kebab-case")]
2733pub struct CargoBuildConfig {
2734 pub pipelining: Option<bool>,
2736 pub dep_info_basedir: Option<ConfigRelativePath>,
2737 pub target_dir: Option<ConfigRelativePath>,
2738 pub build_dir: Option<ConfigRelativePath>,
2739 pub incremental: Option<bool>,
2740 pub target: Option<BuildTargetConfig>,
2741 pub jobs: Option<JobsConfig>,
2742 pub rustflags: Option<StringList>,
2743 pub rustdocflags: Option<StringList>,
2744 pub rustc_wrapper: Option<ConfigRelativePath>,
2745 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2746 pub rustc: Option<ConfigRelativePath>,
2747 pub rustdoc: Option<ConfigRelativePath>,
2748 pub out_dir: Option<ConfigRelativePath>,
2750 pub artifact_dir: Option<ConfigRelativePath>,
2751 pub warnings: Option<WarningHandling>,
2752 pub sbom: Option<bool>,
2754}
2755
2756#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2758#[serde(rename_all = "kebab-case")]
2759pub enum WarningHandling {
2760 #[default]
2761 Warn,
2763 Allow,
2765 Deny,
2767}
2768
2769#[derive(Debug, Deserialize)]
2779#[serde(transparent)]
2780pub struct BuildTargetConfig {
2781 inner: Value<BuildTargetConfigInner>,
2782}
2783
2784#[derive(Debug)]
2785enum BuildTargetConfigInner {
2786 One(String),
2787 Many(Vec<String>),
2788}
2789
2790impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2791 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2792 where
2793 D: serde::Deserializer<'de>,
2794 {
2795 UntaggedEnumVisitor::new()
2796 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2797 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2798 .deserialize(deserializer)
2799 }
2800}
2801
2802impl BuildTargetConfig {
2803 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2805 let map = |s: &String| {
2806 if s.ends_with(".json") {
2807 self.inner
2810 .definition
2811 .root(gctx)
2812 .join(s)
2813 .to_str()
2814 .expect("must be utf-8 in toml")
2815 .to_string()
2816 } else {
2817 s.to_string()
2819 }
2820 };
2821 let values = match &self.inner.val {
2822 BuildTargetConfigInner::One(s) => vec![map(s)],
2823 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2824 };
2825 Ok(values)
2826 }
2827}
2828
2829#[derive(Debug, Deserialize)]
2830#[serde(rename_all = "kebab-case")]
2831pub struct CargoResolverConfig {
2832 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2833 pub feature_unification: Option<FeatureUnification>,
2834}
2835
2836#[derive(Debug, Deserialize, PartialEq, Eq)]
2837#[serde(rename_all = "kebab-case")]
2838pub enum IncompatibleRustVersions {
2839 Allow,
2840 Fallback,
2841}
2842
2843#[derive(Copy, Clone, Debug, Deserialize)]
2844#[serde(rename_all = "kebab-case")]
2845pub enum FeatureUnification {
2846 Selected,
2847 Workspace,
2848}
2849
2850#[derive(Deserialize, Default)]
2851#[serde(rename_all = "kebab-case")]
2852pub struct TermConfig {
2853 pub verbose: Option<bool>,
2854 pub quiet: Option<bool>,
2855 pub color: Option<String>,
2856 pub hyperlinks: Option<bool>,
2857 pub unicode: Option<bool>,
2858 #[serde(default)]
2859 #[serde(deserialize_with = "progress_or_string")]
2860 pub progress: Option<ProgressConfig>,
2861}
2862
2863#[derive(Debug, Default, Deserialize)]
2864#[serde(rename_all = "kebab-case")]
2865pub struct ProgressConfig {
2866 #[serde(default)]
2867 pub when: ProgressWhen,
2868 pub width: Option<usize>,
2869 pub term_integration: Option<bool>,
2871}
2872
2873#[derive(Debug, Default, Deserialize)]
2874#[serde(rename_all = "kebab-case")]
2875pub enum ProgressWhen {
2876 #[default]
2877 Auto,
2878 Never,
2879 Always,
2880}
2881
2882fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2883where
2884 D: serde::de::Deserializer<'de>,
2885{
2886 struct ProgressVisitor;
2887
2888 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2889 type Value = Option<ProgressConfig>;
2890
2891 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2892 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2893 }
2894
2895 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2896 where
2897 E: serde::de::Error,
2898 {
2899 match s {
2900 "auto" => Ok(Some(ProgressConfig {
2901 when: ProgressWhen::Auto,
2902 width: None,
2903 term_integration: None,
2904 })),
2905 "never" => Ok(Some(ProgressConfig {
2906 when: ProgressWhen::Never,
2907 width: None,
2908 term_integration: None,
2909 })),
2910 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2911 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2912 }
2913 }
2914
2915 fn visit_none<E>(self) -> Result<Self::Value, E>
2916 where
2917 E: serde::de::Error,
2918 {
2919 Ok(None)
2920 }
2921
2922 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2923 where
2924 D: serde::de::Deserializer<'de>,
2925 {
2926 let pc = ProgressConfig::deserialize(deserializer)?;
2927 if let ProgressConfig {
2928 when: ProgressWhen::Always,
2929 width: None,
2930 ..
2931 } = pc
2932 {
2933 return Err(serde::de::Error::custom(
2934 "\"always\" progress requires a `width` key",
2935 ));
2936 }
2937 Ok(Some(pc))
2938 }
2939 }
2940
2941 deserializer.deserialize_option(ProgressVisitor)
2942}
2943
2944#[derive(Debug)]
2945enum EnvConfigValueInner {
2946 Simple(String),
2947 WithOptions {
2948 value: String,
2949 force: bool,
2950 relative: bool,
2951 },
2952}
2953
2954impl<'de> Deserialize<'de> for EnvConfigValueInner {
2955 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2956 where
2957 D: serde::Deserializer<'de>,
2958 {
2959 #[derive(Deserialize)]
2960 struct WithOptions {
2961 value: String,
2962 #[serde(default)]
2963 force: bool,
2964 #[serde(default)]
2965 relative: bool,
2966 }
2967
2968 UntaggedEnumVisitor::new()
2969 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2970 .map(|map| {
2971 let with_options: WithOptions = map.deserialize()?;
2972 Ok(EnvConfigValueInner::WithOptions {
2973 value: with_options.value,
2974 force: with_options.force,
2975 relative: with_options.relative,
2976 })
2977 })
2978 .deserialize(deserializer)
2979 }
2980}
2981
2982#[derive(Debug, Deserialize)]
2983#[serde(transparent)]
2984pub struct EnvConfigValue {
2985 inner: Value<EnvConfigValueInner>,
2986}
2987
2988impl EnvConfigValue {
2989 pub fn is_force(&self) -> bool {
2990 match self.inner.val {
2991 EnvConfigValueInner::Simple(_) => false,
2992 EnvConfigValueInner::WithOptions { force, .. } => force,
2993 }
2994 }
2995
2996 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
2997 match self.inner.val {
2998 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
2999 EnvConfigValueInner::WithOptions {
3000 ref value,
3001 relative,
3002 ..
3003 } => {
3004 if relative {
3005 let p = self.inner.definition.root(gctx).join(&value);
3006 Cow::Owned(p.into_os_string())
3007 } else {
3008 Cow::Borrowed(OsStr::new(value.as_str()))
3009 }
3010 }
3011 }
3012 }
3013}
3014
3015pub type EnvConfig = HashMap<String, EnvConfigValue>;
3016
3017fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
3018 toml.parse().map_err(Into::into)
3020}
3021
3022#[derive(Debug, Deserialize, Clone)]
3033pub struct StringList(Vec<String>);
3034
3035impl StringList {
3036 pub fn as_slice(&self) -> &[String] {
3037 &self.0
3038 }
3039}
3040
3041#[macro_export]
3042macro_rules! __shell_print {
3043 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
3044 let mut shell = $config.shell();
3045 let out = shell.$which();
3046 drop(out.write_fmt(format_args!($($arg)*)));
3047 if $newline {
3048 drop(out.write_all(b"\n"));
3049 }
3050 });
3051}
3052
3053#[macro_export]
3054macro_rules! drop_println {
3055 ($config:expr) => ( $crate::drop_print!($config, "\n") );
3056 ($config:expr, $($arg:tt)*) => (
3057 $crate::__shell_print!($config, out, true, $($arg)*)
3058 );
3059}
3060
3061#[macro_export]
3062macro_rules! drop_eprintln {
3063 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
3064 ($config:expr, $($arg:tt)*) => (
3065 $crate::__shell_print!($config, err, true, $($arg)*)
3066 );
3067}
3068
3069#[macro_export]
3070macro_rules! drop_print {
3071 ($config:expr, $($arg:tt)*) => (
3072 $crate::__shell_print!($config, out, false, $($arg)*)
3073 );
3074}
3075
3076#[macro_export]
3077macro_rules! drop_eprint {
3078 ($config:expr, $($arg:tt)*) => (
3079 $crate::__shell_print!($config, err, false, $($arg)*)
3080 );
3081}
3082
3083enum Tool {
3084 Rustc,
3085 Rustdoc,
3086}
3087
3088impl Tool {
3089 fn as_str(&self) -> &str {
3090 match self {
3091 Tool::Rustc => "rustc",
3092 Tool::Rustdoc => "rustdoc",
3093 }
3094 }
3095}
3096
3097fn disables_multiplexing_for_bad_curl(
3107 curl_version: &str,
3108 http: &mut CargoHttpConfig,
3109 gctx: &GlobalContext,
3110) {
3111 use crate::util::network;
3112
3113 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3114 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3115 if bad_curl_versions
3116 .iter()
3117 .any(|v| curl_version.starts_with(v))
3118 {
3119 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3120 http.multiplexing = Some(false);
3121 }
3122 }
3123}
3124
3125#[cfg(test)]
3126mod tests {
3127 use super::disables_multiplexing_for_bad_curl;
3128 use super::CargoHttpConfig;
3129 use super::GlobalContext;
3130 use super::Shell;
3131
3132 #[test]
3133 fn disables_multiplexing() {
3134 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3135 gctx.set_search_stop_path(std::path::PathBuf::new());
3136 gctx.set_env(Default::default());
3137
3138 let mut http = CargoHttpConfig::default();
3139 http.proxy = Some("127.0.0.1:3128".into());
3140 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3141 assert_eq!(http.multiplexing, Some(false));
3142
3143 let cases = [
3144 (None, None, "7.87.0", None),
3145 (None, None, "7.88.0", None),
3146 (None, None, "7.88.1", None),
3147 (None, None, "8.0.0", None),
3148 (Some("".into()), None, "7.87.0", Some(false)),
3149 (Some("".into()), None, "7.88.0", Some(false)),
3150 (Some("".into()), None, "7.88.1", Some(false)),
3151 (Some("".into()), None, "8.0.0", None),
3152 (Some("".into()), Some(false), "7.87.0", Some(false)),
3153 (Some("".into()), Some(false), "7.88.0", Some(false)),
3154 (Some("".into()), Some(false), "7.88.1", Some(false)),
3155 (Some("".into()), Some(false), "8.0.0", Some(false)),
3156 ];
3157
3158 for (proxy, multiplexing, curl_v, result) in cases {
3159 let mut http = CargoHttpConfig {
3160 multiplexing,
3161 proxy,
3162 ..Default::default()
3163 };
3164 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3165 assert_eq!(http.multiplexing, result);
3166 }
3167 }
3168}