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::SeekFrom;
62use std::io::prelude::*;
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::{CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig, features};
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::{CanonicalUrl, closest_msg, internal};
81use crate::util::{Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
82use anyhow::{Context as _, anyhow, bail, format_err};
83use cargo_credential::Secret;
84use cargo_util::paths;
85use cargo_util_schemas::manifest::RegistryName;
86use curl::easy::Easy;
87use itertools::Itertools;
88use lazycell::LazyCell;
89use serde::Deserialize;
90use serde::de::IntoDeserializer as _;
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 = self
461 .get_env_os(crate::CARGO_ENV)
462 .map(PathBuf::from)
463 .ok_or_else(|| anyhow!("$CARGO not set"))?;
464 Ok(exe)
465 };
466
467 fn from_current_exe() -> CargoResult<PathBuf> {
468 let exe = env::current_exe()?;
473 Ok(exe)
474 }
475
476 fn from_argv() -> CargoResult<PathBuf> {
477 let argv0 = env::args_os()
484 .map(PathBuf::from)
485 .next()
486 .ok_or_else(|| anyhow!("no argv[0]"))?;
487 paths::resolve_executable(&argv0)
488 }
489
490 fn is_cargo(path: &Path) -> bool {
493 path.file_stem() == Some(OsStr::new("cargo"))
494 }
495
496 let from_current_exe = from_current_exe();
497 if from_current_exe.as_deref().is_ok_and(is_cargo) {
498 return from_current_exe;
499 }
500
501 let from_argv = from_argv();
502 if from_argv.as_deref().is_ok_and(is_cargo) {
503 return from_argv;
504 }
505
506 let exe = from_env()
507 .or(from_current_exe)
508 .or(from_argv)
509 .context("couldn't get the path to cargo executable")?;
510 Ok(exe)
511 })
512 .map(AsRef::as_ref)
513 }
514
515 pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> {
517 self.updated_sources
518 .borrow_with(|| RefCell::new(HashSet::new()))
519 .borrow_mut()
520 }
521
522 pub fn credential_cache(&self) -> RefMut<'_, HashMap<CanonicalUrl, CredentialCacheValue>> {
524 self.credential_cache
525 .borrow_with(|| RefCell::new(HashMap::new()))
526 .borrow_mut()
527 }
528
529 pub(crate) fn registry_config(&self) -> RefMut<'_, HashMap<SourceId, Option<RegistryConfig>>> {
531 self.registry_config
532 .borrow_with(|| RefCell::new(HashMap::new()))
533 .borrow_mut()
534 }
535
536 pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
542 self.values.try_borrow_with(|| self.load_values())
543 }
544
545 pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
552 let _ = self.values()?;
553 Ok(self
554 .values
555 .borrow_mut()
556 .expect("already loaded config values"))
557 }
558
559 pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
561 if self.values.borrow().is_some() {
562 bail!("config values already found")
563 }
564 match self.values.fill(values) {
565 Ok(()) => Ok(()),
566 Err(_) => bail!("could not fill values"),
567 }
568 }
569
570 pub fn set_search_stop_path<P: Into<PathBuf>>(&mut self, path: P) {
573 let path = path.into();
574 debug_assert!(self.cwd.starts_with(&path));
575 self.search_stop_path = Some(path);
576 }
577
578 pub fn reload_cwd(&mut self) -> CargoResult<()> {
582 let cwd =
583 env::current_dir().context("couldn't get the current directory of the process")?;
584 let homedir = homedir(&cwd).ok_or_else(|| {
585 anyhow!(
586 "Cargo couldn't find your home directory. \
587 This probably means that $HOME was not set."
588 )
589 })?;
590
591 self.cwd = cwd;
592 self.home_path = Filesystem::new(homedir);
593 self.reload_rooted_at(self.cwd.clone())?;
594 Ok(())
595 }
596
597 pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
600 let values = self.load_values_from(path.as_ref())?;
601 self.values.replace(values);
602 self.merge_cli_args()?;
603 self.load_unstable_flags_from_config()?;
604 Ok(())
605 }
606
607 pub fn cwd(&self) -> &Path {
609 &self.cwd
610 }
611
612 pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
618 if let Some(dir) = &self.target_dir {
619 Ok(Some(dir.clone()))
620 } else if let Some(dir) = self.get_env_os("CARGO_TARGET_DIR") {
621 if dir.is_empty() {
623 bail!(
624 "the target directory is set to an empty string in the \
625 `CARGO_TARGET_DIR` environment variable"
626 )
627 }
628
629 Ok(Some(Filesystem::new(self.cwd.join(dir))))
630 } else if let Some(val) = &self.build_config()?.target_dir {
631 let path = val.resolve_path(self);
632
633 if val.raw_value().is_empty() {
635 bail!(
636 "the target directory is set to an empty string in {}",
637 val.value().definition
638 )
639 }
640
641 Ok(Some(Filesystem::new(path)))
642 } else {
643 Ok(None)
644 }
645 }
646
647 pub fn build_dir(&self, workspace_manifest_path: &PathBuf) -> CargoResult<Option<Filesystem>> {
653 if !self.cli_unstable().build_dir {
654 return self.target_dir();
655 }
656 if let Some(val) = &self.build_config()?.build_dir {
657 let replacements = vec![
658 (
659 "{workspace-root}",
660 workspace_manifest_path
661 .parent()
662 .unwrap()
663 .to_str()
664 .context("workspace root was not valid utf-8")?
665 .to_string(),
666 ),
667 (
668 "{cargo-cache-home}",
669 self.home()
670 .as_path_unlocked()
671 .to_str()
672 .context("cargo home was not valid utf-8")?
673 .to_string(),
674 ),
675 ("{workspace-path-hash}", {
676 let real_path = std::fs::canonicalize(workspace_manifest_path)?;
677 let hash = crate::util::hex::short_hash(&real_path);
678 format!("{}{}{}", &hash[0..2], std::path::MAIN_SEPARATOR, &hash[2..])
679 }),
680 ];
681
682 let template_variables = replacements
683 .iter()
684 .map(|(key, _)| key[1..key.len() - 1].to_string())
685 .collect_vec();
686
687 let path = val
688 .resolve_templated_path(self, replacements)
689 .map_err(|e| match e {
690 path::ResolveTemplateError::UnexpectedVariable {
691 variable,
692 raw_template,
693 } => {
694 let mut suggestion = closest_msg(&variable, template_variables.iter(), |key| key, "template variable");
695 if suggestion == "" {
696 let variables = template_variables.iter().map(|v| format!("`{{{v}}}`")).join(", ");
697 suggestion = format!("\n\nhelp: available template variables are {variables}");
698 }
699 anyhow!(
700 "unexpected variable `{variable}` in build.build-dir path `{raw_template}`{suggestion}"
701 )
702 },
703 path::ResolveTemplateError::UnexpectedBracket { bracket_type, raw_template } => {
704 let (btype, literal) = match bracket_type {
705 path::BracketType::Opening => ("opening", "{"),
706 path::BracketType::Closing => ("closing", "}"),
707 };
708
709 anyhow!(
710 "unexpected {btype} bracket `{literal}` in build.build-dir path `{raw_template}`"
711 )
712 }
713 })?;
714
715 if val.raw_value().is_empty() {
717 bail!(
718 "the build directory is set to an empty string in {}",
719 val.value().definition
720 )
721 }
722
723 Ok(Some(Filesystem::new(path)))
724 } else {
725 return self.target_dir();
728 }
729 }
730
731 fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
736 if let Some(vals) = self.credential_values.borrow() {
737 let val = self.get_cv_helper(key, vals)?;
738 if val.is_some() {
739 return Ok(val);
740 }
741 }
742 self.get_cv_helper(key, self.values()?)
743 }
744
745 fn get_cv_helper(
746 &self,
747 key: &ConfigKey,
748 vals: &HashMap<String, ConfigValue>,
749 ) -> CargoResult<Option<ConfigValue>> {
750 tracing::trace!("get cv {:?}", key);
751 if key.is_root() {
752 return Ok(Some(CV::Table(
755 vals.clone(),
756 Definition::Path(PathBuf::new()),
757 )));
758 }
759 let mut parts = key.parts().enumerate();
760 let Some(mut val) = vals.get(parts.next().unwrap().1) else {
761 return Ok(None);
762 };
763 for (i, part) in parts {
764 match val {
765 CV::Table(map, _) => {
766 val = match map.get(part) {
767 Some(val) => val,
768 None => return Ok(None),
769 }
770 }
771 CV::Integer(_, def)
772 | CV::String(_, def)
773 | CV::List(_, def)
774 | CV::Boolean(_, def) => {
775 let mut key_so_far = ConfigKey::new();
776 for part in key.parts().take(i) {
777 key_so_far.push(part);
778 }
779 bail!(
780 "expected table for configuration key `{}`, \
781 but found {} in {}",
782 key_so_far,
783 val.desc(),
784 def
785 )
786 }
787 }
788 }
789 Ok(Some(val.clone()))
790 }
791
792 pub(crate) fn get_cv_with_env(&self, key: &ConfigKey) -> CargoResult<Option<CV>> {
794 let cv = self.get_cv(key)?;
797 if key.is_root() {
798 return Ok(cv);
800 }
801 let env = self.env.get_str(key.as_env_key());
802 let env_def = Definition::Environment(key.as_env_key().to_string());
803 let use_env = match (&cv, env) {
804 (Some(CV::List(..)), Some(_)) => true,
806 (Some(cv), Some(_)) => env_def.is_higher_priority(cv.definition()),
807 (None, Some(_)) => true,
808 _ => false,
809 };
810
811 if !use_env {
812 return Ok(cv);
813 }
814
815 let env = env.unwrap();
819 if env == "true" {
820 Ok(Some(CV::Boolean(true, env_def)))
821 } else if env == "false" {
822 Ok(Some(CV::Boolean(false, env_def)))
823 } else if let Ok(i) = env.parse::<i64>() {
824 Ok(Some(CV::Integer(i, env_def)))
825 } else if self.cli_unstable().advanced_env && env.starts_with('[') && env.ends_with(']') {
826 match cv {
827 Some(CV::List(mut cv_list, cv_def)) => {
828 self.get_env_list(key, &mut cv_list)?;
830 Ok(Some(CV::List(cv_list, cv_def)))
831 }
832 Some(cv) => {
833 bail!(
837 "unable to merge array env for config `{}`\n\
838 file: {:?}\n\
839 env: {}",
840 key,
841 cv,
842 env
843 );
844 }
845 None => {
846 let mut cv_list = Vec::new();
847 self.get_env_list(key, &mut cv_list)?;
848 Ok(Some(CV::List(cv_list, env_def)))
849 }
850 }
851 } else {
852 match cv {
854 Some(CV::List(mut cv_list, cv_def)) => {
855 self.get_env_list(key, &mut cv_list)?;
857 Ok(Some(CV::List(cv_list, cv_def)))
858 }
859 _ => {
860 Ok(Some(CV::String(env.to_string(), env_def)))
865 }
866 }
867 }
868 }
869
870 pub fn set_env(&mut self, env: HashMap<String, String>) {
872 self.env = Env::from_map(env);
873 }
874
875 pub(crate) fn env(&self) -> impl Iterator<Item = (&str, &str)> {
878 self.env.iter_str()
879 }
880
881 fn env_keys(&self) -> impl Iterator<Item = &str> {
883 self.env.keys_str()
884 }
885
886 fn get_config_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
887 where
888 T: FromStr,
889 <T as FromStr>::Err: fmt::Display,
890 {
891 match self.env.get_str(key.as_env_key()) {
892 Some(value) => {
893 let definition = Definition::Environment(key.as_env_key().to_string());
894 Ok(Some(Value {
895 val: value
896 .parse()
897 .map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
898 definition,
899 }))
900 }
901 None => {
902 self.check_environment_key_case_mismatch(key);
903 Ok(None)
904 }
905 }
906 }
907
908 pub fn get_env(&self, key: impl AsRef<OsStr>) -> CargoResult<&str> {
913 self.env.get_env(key)
914 }
915
916 pub fn get_env_os(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
921 self.env.get_env_os(key)
922 }
923
924 fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> CargoResult<bool> {
928 if self.env.contains_key(key.as_env_key()) {
929 return Ok(true);
930 }
931 if env_prefix_ok {
932 let env_prefix = format!("{}_", key.as_env_key());
933 if self.env_keys().any(|k| k.starts_with(&env_prefix)) {
934 return Ok(true);
935 }
936 }
937 if self.get_cv(key)?.is_some() {
938 return Ok(true);
939 }
940 self.check_environment_key_case_mismatch(key);
941
942 Ok(false)
943 }
944
945 fn check_environment_key_case_mismatch(&self, key: &ConfigKey) {
946 if let Some(env_key) = self.env.get_normalized(key.as_env_key()) {
947 let _ = self.shell().warn(format!(
948 "environment variables are expected to use uppercase letters and underscores, \
949 the variable `{}` will be ignored and have no effect",
950 env_key
951 ));
952 }
953 }
954
955 pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
959 self.get::<OptValue<String>>(key)
960 }
961
962 pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
968 self.get::<OptValue<ConfigRelativePath>>(key).map(|v| {
969 v.map(|v| Value {
970 val: v.val.resolve_program(self),
971 definition: v.definition,
972 })
973 })
974 }
975
976 fn string_to_path(&self, value: &str, definition: &Definition) -> PathBuf {
977 let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
978 if is_path {
979 definition.root(self).join(value)
980 } else {
981 PathBuf::from(value)
983 }
984 }
985
986 pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
994 let key = ConfigKey::from_str(key);
995 self._get_list(&key)
996 }
997
998 fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
999 match self.get_cv(key)? {
1000 Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
1001 Some(val) => self.expected("list", key, &val),
1002 None => Ok(None),
1003 }
1004 }
1005
1006 fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
1008 let mut res = Vec::new();
1009
1010 match self.get_cv(key)? {
1011 Some(CV::List(val, _def)) => res.extend(val),
1012 Some(CV::String(val, def)) => {
1013 let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
1014 res.extend(split_vs);
1015 }
1016 Some(val) => {
1017 return self.expected("string or array of strings", key, &val);
1018 }
1019 None => {}
1020 }
1021
1022 self.get_env_list(key, &mut res)?;
1023
1024 Ok(res)
1025 }
1026
1027 fn get_env_list(
1030 &self,
1031 key: &ConfigKey,
1032 output: &mut Vec<(String, Definition)>,
1033 ) -> CargoResult<()> {
1034 let Some(env_val) = self.env.get_str(key.as_env_key()) else {
1035 self.check_environment_key_case_mismatch(key);
1036 return Ok(());
1037 };
1038
1039 if is_nonmergable_list(&key) {
1040 output.clear();
1041 }
1042
1043 let def = Definition::Environment(key.as_env_key().to_string());
1044 if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
1045 let toml_v = env_val.parse::<toml::Value>().map_err(|e| {
1047 ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
1048 })?;
1049 let values = toml_v.as_array().expect("env var was not array");
1050 for value in values {
1051 let s = value.as_str().ok_or_else(|| {
1053 ConfigError::new(
1054 format!("expected string, found {}", value.type_str()),
1055 def.clone(),
1056 )
1057 })?;
1058 output.push((s.to_string(), def.clone()));
1059 }
1060 } else {
1061 output.extend(
1062 env_val
1063 .split_whitespace()
1064 .map(|s| (s.to_string(), def.clone())),
1065 );
1066 }
1067 output.sort_by(|a, b| a.1.cmp(&b.1));
1068 Ok(())
1069 }
1070
1071 fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
1075 match self.get_cv(key)? {
1076 Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
1077 Some(val) => self.expected("table", key, &val),
1078 None => Ok(None),
1079 }
1080 }
1081
1082 get_value_typed! {get_integer, i64, Integer, "an integer"}
1083 get_value_typed! {get_bool, bool, Boolean, "true/false"}
1084 get_value_typed! {get_string_priv, String, String, "a string"}
1085
1086 fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
1088 val.expected(ty, &key.to_string())
1089 .map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
1090 }
1091
1092 pub fn configure(
1098 &mut self,
1099 verbose: u32,
1100 quiet: bool,
1101 color: Option<&str>,
1102 frozen: bool,
1103 locked: bool,
1104 offline: bool,
1105 target_dir: &Option<PathBuf>,
1106 unstable_flags: &[String],
1107 cli_config: &[String],
1108 ) -> CargoResult<()> {
1109 for warning in self
1110 .unstable_flags
1111 .parse(unstable_flags, self.nightly_features_allowed)?
1112 {
1113 self.shell().warn(warning)?;
1114 }
1115 if !unstable_flags.is_empty() {
1116 self.unstable_flags_cli = Some(unstable_flags.to_vec());
1119 }
1120 if !cli_config.is_empty() {
1121 self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
1122 self.merge_cli_args()?;
1123 }
1124
1125 self.load_unstable_flags_from_config()?;
1129 if self.unstable_flags.config_include {
1130 self.reload_rooted_at(self.cwd.clone())?;
1137 }
1138
1139 let term = self.get::<TermConfig>("term").unwrap_or_default();
1143
1144 let extra_verbose = verbose >= 2;
1146 let verbose = verbose != 0;
1147 let verbosity = match (verbose, quiet) {
1148 (true, true) => bail!("cannot set both --verbose and --quiet"),
1149 (true, false) => Verbosity::Verbose,
1150 (false, true) => Verbosity::Quiet,
1151 (false, false) => match (term.verbose, term.quiet) {
1152 (Some(true), Some(true)) => {
1153 bail!("cannot set both `term.verbose` and `term.quiet`")
1154 }
1155 (Some(true), _) => Verbosity::Verbose,
1156 (_, Some(true)) => Verbosity::Quiet,
1157 _ => Verbosity::Normal,
1158 },
1159 };
1160 self.shell().set_verbosity(verbosity);
1161 self.extra_verbose = extra_verbose;
1162
1163 let color = color.or_else(|| term.color.as_deref());
1164 self.shell().set_color_choice(color)?;
1165 if let Some(hyperlinks) = term.hyperlinks {
1166 self.shell().set_hyperlinks(hyperlinks)?;
1167 }
1168 if let Some(unicode) = term.unicode {
1169 self.shell().set_unicode(unicode)?;
1170 }
1171
1172 self.progress_config = term.progress.unwrap_or_default();
1173
1174 self.frozen = frozen;
1175 self.locked = locked;
1176 self.offline = offline
1177 || self
1178 .net_config()
1179 .ok()
1180 .and_then(|n| n.offline)
1181 .unwrap_or(false);
1182 let cli_target_dir = target_dir.as_ref().map(|dir| Filesystem::new(dir.clone()));
1183 self.target_dir = cli_target_dir;
1184
1185 Ok(())
1186 }
1187
1188 fn load_unstable_flags_from_config(&mut self) -> CargoResult<()> {
1189 if self.nightly_features_allowed {
1192 self.unstable_flags = self
1193 .get::<Option<CliUnstable>>("unstable")?
1194 .unwrap_or_default();
1195 if let Some(unstable_flags_cli) = &self.unstable_flags_cli {
1196 self.unstable_flags.parse(unstable_flags_cli, true)?;
1201 }
1202 }
1203
1204 Ok(())
1205 }
1206
1207 pub fn cli_unstable(&self) -> &CliUnstable {
1208 &self.unstable_flags
1209 }
1210
1211 pub fn extra_verbose(&self) -> bool {
1212 self.extra_verbose
1213 }
1214
1215 pub fn network_allowed(&self) -> bool {
1216 !self.offline_flag().is_some()
1217 }
1218
1219 pub fn offline_flag(&self) -> Option<&'static str> {
1220 if self.frozen {
1221 Some("--frozen")
1222 } else if self.offline {
1223 Some("--offline")
1224 } else {
1225 None
1226 }
1227 }
1228
1229 pub fn set_locked(&mut self, locked: bool) {
1230 self.locked = locked;
1231 }
1232
1233 pub fn lock_update_allowed(&self) -> bool {
1234 !self.locked_flag().is_some()
1235 }
1236
1237 pub fn locked_flag(&self) -> Option<&'static str> {
1238 if self.frozen {
1239 Some("--frozen")
1240 } else if self.locked {
1241 Some("--locked")
1242 } else {
1243 None
1244 }
1245 }
1246
1247 pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
1249 self.load_values_from(&self.cwd)
1250 }
1251
1252 pub(crate) fn load_values_unmerged(&self) -> CargoResult<Vec<ConfigValue>> {
1256 let mut result = Vec::new();
1257 let mut seen = HashSet::new();
1258 let home = self.home_path.clone().into_path_unlocked();
1259 self.walk_tree(&self.cwd, &home, |path| {
1260 let mut cv = self._load_file(path, &mut seen, false, WhyLoad::FileDiscovery)?;
1261 if self.cli_unstable().config_include {
1262 self.load_unmerged_include(&mut cv, &mut seen, &mut result)?;
1263 }
1264 result.push(cv);
1265 Ok(())
1266 })
1267 .context("could not load Cargo configuration")?;
1268 Ok(result)
1269 }
1270
1271 fn load_unmerged_include(
1275 &self,
1276 cv: &mut CV,
1277 seen: &mut HashSet<PathBuf>,
1278 output: &mut Vec<CV>,
1279 ) -> CargoResult<()> {
1280 let includes = self.include_paths(cv, false)?;
1281 for (path, abs_path, def) in includes {
1282 let mut cv = self
1283 ._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
1284 .with_context(|| {
1285 format!("failed to load config include `{}` from `{}`", path, def)
1286 })?;
1287 self.load_unmerged_include(&mut cv, seen, output)?;
1288 output.push(cv);
1289 }
1290 Ok(())
1291 }
1292
1293 fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
1295 let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
1298 let home = self.home_path.clone().into_path_unlocked();
1299
1300 self.walk_tree(path, &home, |path| {
1301 let value = self.load_file(path)?;
1302 cfg.merge(value, false).with_context(|| {
1303 format!("failed to merge configuration at `{}`", path.display())
1304 })?;
1305 Ok(())
1306 })
1307 .context("could not load Cargo configuration")?;
1308
1309 match cfg {
1310 CV::Table(map, _) => Ok(map),
1311 _ => unreachable!(),
1312 }
1313 }
1314
1315 fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
1319 self._load_file(path, &mut HashSet::new(), true, WhyLoad::FileDiscovery)
1320 }
1321
1322 fn _load_file(
1332 &self,
1333 path: &Path,
1334 seen: &mut HashSet<PathBuf>,
1335 includes: bool,
1336 why_load: WhyLoad,
1337 ) -> CargoResult<ConfigValue> {
1338 if !seen.insert(path.to_path_buf()) {
1339 bail!(
1340 "config `include` cycle detected with path `{}`",
1341 path.display()
1342 );
1343 }
1344 tracing::debug!(?path, ?why_load, includes, "load config from file");
1345
1346 let contents = fs::read_to_string(path)
1347 .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
1348 let toml = parse_document(&contents, path, self).with_context(|| {
1349 format!("could not parse TOML configuration in `{}`", path.display())
1350 })?;
1351 let def = match why_load {
1352 WhyLoad::Cli => Definition::Cli(Some(path.into())),
1353 WhyLoad::FileDiscovery => Definition::Path(path.into()),
1354 };
1355 let value = CV::from_toml(def, toml::Value::Table(toml)).with_context(|| {
1356 format!(
1357 "failed to load TOML configuration from `{}`",
1358 path.display()
1359 )
1360 })?;
1361 if includes {
1362 self.load_includes(value, seen, why_load)
1363 } else {
1364 Ok(value)
1365 }
1366 }
1367
1368 fn load_includes(
1375 &self,
1376 mut value: CV,
1377 seen: &mut HashSet<PathBuf>,
1378 why_load: WhyLoad,
1379 ) -> CargoResult<CV> {
1380 let includes = self.include_paths(&mut value, true)?;
1382 if !self.cli_unstable().config_include {
1384 return Ok(value);
1385 }
1386 let mut root = CV::Table(HashMap::new(), value.definition().clone());
1388 for (path, abs_path, def) in includes {
1389 self._load_file(&abs_path, seen, true, why_load)
1390 .and_then(|include| root.merge(include, true))
1391 .with_context(|| {
1392 format!("failed to load config include `{}` from `{}`", path, def)
1393 })?;
1394 }
1395 root.merge(value, true)?;
1396 Ok(root)
1397 }
1398
1399 fn include_paths(
1401 &self,
1402 cv: &mut CV,
1403 remove: bool,
1404 ) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
1405 let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
1406 let abs_path = match def {
1407 Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
1408 Definition::Environment(_) | Definition::Cli(None) => self.cwd().join(&path),
1409 };
1410 (path.to_string(), abs_path, def.clone())
1411 };
1412 let CV::Table(table, _def) = cv else {
1413 unreachable!()
1414 };
1415 let owned;
1416 let include = if remove {
1417 owned = table.remove("include");
1418 owned.as_ref()
1419 } else {
1420 table.get("include")
1421 };
1422 let includes = match include {
1423 Some(CV::String(s, def)) => {
1424 vec![abs(s, def)]
1425 }
1426 Some(CV::List(list, _def)) => list.iter().map(|(s, def)| abs(s, def)).collect(),
1427 Some(other) => bail!(
1428 "`include` expected a string or list, but found {} in `{}`",
1429 other.desc(),
1430 other.definition()
1431 ),
1432 None => {
1433 return Ok(Vec::new());
1434 }
1435 };
1436
1437 for (path, abs_path, def) in &includes {
1438 if abs_path.extension() != Some(OsStr::new("toml")) {
1439 bail!(
1440 "expected a config include path ending with `.toml`, \
1441 but found `{path}` from `{def}`",
1442 )
1443 }
1444 }
1445
1446 Ok(includes)
1447 }
1448
1449 pub(crate) fn cli_args_as_table(&self) -> CargoResult<ConfigValue> {
1451 let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli(None));
1452 let Some(cli_args) = &self.cli_config else {
1453 return Ok(loaded_args);
1454 };
1455 let mut seen = HashSet::new();
1456 for arg in cli_args {
1457 let arg_as_path = self.cwd.join(arg);
1458 let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
1459 let str_path = arg_as_path
1461 .to_str()
1462 .ok_or_else(|| {
1463 anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
1464 })?
1465 .to_string();
1466 self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
1467 .with_context(|| format!("failed to load config from `{}`", str_path))?
1468 } else {
1469 let doc: toml_edit::DocumentMut = arg.parse().with_context(|| {
1475 format!("failed to parse value from --config argument `{arg}` as a dotted key expression")
1476 })?;
1477 fn non_empty(d: Option<&toml_edit::RawString>) -> bool {
1478 d.map_or(false, |p| !p.as_str().unwrap_or_default().trim().is_empty())
1479 }
1480 fn non_empty_decor(d: &toml_edit::Decor) -> bool {
1481 non_empty(d.prefix()) || non_empty(d.suffix())
1482 }
1483 fn non_empty_key_decor(k: &toml_edit::Key) -> bool {
1484 non_empty_decor(k.leaf_decor()) || non_empty_decor(k.dotted_decor())
1485 }
1486 let ok = {
1487 let mut got_to_value = false;
1488 let mut table = doc.as_table();
1489 let mut is_root = true;
1490 while table.is_dotted() || is_root {
1491 is_root = false;
1492 if table.len() != 1 {
1493 break;
1494 }
1495 let (k, n) = table.iter().next().expect("len() == 1 above");
1496 match n {
1497 Item::Table(nt) => {
1498 if table.key(k).map_or(false, non_empty_key_decor)
1499 || non_empty_decor(nt.decor())
1500 {
1501 bail!(
1502 "--config argument `{arg}` \
1503 includes non-whitespace decoration"
1504 )
1505 }
1506 table = nt;
1507 }
1508 Item::Value(v) if v.is_inline_table() => {
1509 bail!(
1510 "--config argument `{arg}` \
1511 sets a value to an inline table, which is not accepted"
1512 );
1513 }
1514 Item::Value(v) => {
1515 if table
1516 .key(k)
1517 .map_or(false, |k| non_empty(k.leaf_decor().prefix()))
1518 || non_empty_decor(v.decor())
1519 {
1520 bail!(
1521 "--config argument `{arg}` \
1522 includes non-whitespace decoration"
1523 )
1524 }
1525 got_to_value = true;
1526 break;
1527 }
1528 Item::ArrayOfTables(_) => {
1529 bail!(
1530 "--config argument `{arg}` \
1531 sets a value to an array of tables, which is not accepted"
1532 );
1533 }
1534
1535 Item::None => {
1536 bail!("--config argument `{arg}` doesn't provide a value")
1537 }
1538 }
1539 }
1540 got_to_value
1541 };
1542 if !ok {
1543 bail!(
1544 "--config argument `{arg}` was not a TOML dotted key expression (such as `build.jobs = 2`)"
1545 );
1546 }
1547
1548 let toml_v: toml::Value = toml::Value::deserialize(doc.into_deserializer())
1549 .with_context(|| {
1550 format!("failed to parse value from --config argument `{arg}`")
1551 })?;
1552
1553 if toml_v
1554 .get("registry")
1555 .and_then(|v| v.as_table())
1556 .and_then(|t| t.get("token"))
1557 .is_some()
1558 {
1559 bail!("registry.token cannot be set through --config for security reasons");
1560 } else if let Some((k, _)) = toml_v
1561 .get("registries")
1562 .and_then(|v| v.as_table())
1563 .and_then(|t| t.iter().find(|(_, v)| v.get("token").is_some()))
1564 {
1565 bail!(
1566 "registries.{}.token cannot be set through --config for security reasons",
1567 k
1568 );
1569 }
1570
1571 if toml_v
1572 .get("registry")
1573 .and_then(|v| v.as_table())
1574 .and_then(|t| t.get("secret-key"))
1575 .is_some()
1576 {
1577 bail!(
1578 "registry.secret-key cannot be set through --config for security reasons"
1579 );
1580 } else if let Some((k, _)) = toml_v
1581 .get("registries")
1582 .and_then(|v| v.as_table())
1583 .and_then(|t| t.iter().find(|(_, v)| v.get("secret-key").is_some()))
1584 {
1585 bail!(
1586 "registries.{}.secret-key cannot be set through --config for security reasons",
1587 k
1588 );
1589 }
1590
1591 CV::from_toml(Definition::Cli(None), toml_v)
1592 .with_context(|| format!("failed to convert --config argument `{arg}`"))?
1593 };
1594 let tmp_table = self
1595 .load_includes(tmp_table, &mut HashSet::new(), WhyLoad::Cli)
1596 .context("failed to load --config include".to_string())?;
1597 loaded_args
1598 .merge(tmp_table, true)
1599 .with_context(|| format!("failed to merge --config argument `{arg}`"))?;
1600 }
1601 Ok(loaded_args)
1602 }
1603
1604 fn merge_cli_args(&mut self) -> CargoResult<()> {
1606 let CV::Table(loaded_map, _def) = self.cli_args_as_table()? else {
1607 unreachable!()
1608 };
1609 let values = self.values_mut()?;
1610 for (key, value) in loaded_map.into_iter() {
1611 match values.entry(key) {
1612 Vacant(entry) => {
1613 entry.insert(value);
1614 }
1615 Occupied(mut entry) => entry.get_mut().merge(value, true).with_context(|| {
1616 format!(
1617 "failed to merge --config key `{}` into `{}`",
1618 entry.key(),
1619 entry.get().definition(),
1620 )
1621 })?,
1622 };
1623 }
1624 Ok(())
1625 }
1626
1627 fn get_file_path(
1633 &self,
1634 dir: &Path,
1635 filename_without_extension: &str,
1636 warn: bool,
1637 ) -> CargoResult<Option<PathBuf>> {
1638 let possible = dir.join(filename_without_extension);
1639 let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
1640
1641 if let Ok(possible_handle) = same_file::Handle::from_path(&possible) {
1642 if warn {
1643 if let Ok(possible_with_extension_handle) =
1644 same_file::Handle::from_path(&possible_with_extension)
1645 {
1646 if possible_handle != possible_with_extension_handle {
1652 self.shell().warn(format!(
1653 "both `{}` and `{}` exist. Using `{}`",
1654 possible.display(),
1655 possible_with_extension.display(),
1656 possible.display()
1657 ))?;
1658 }
1659 } else {
1660 self.shell().warn(format!(
1661 "`{}` is deprecated in favor of `{filename_without_extension}.toml`",
1662 possible.display(),
1663 ))?;
1664 self.shell().note(
1665 format!("if you need to support cargo 1.38 or earlier, you can symlink `{filename_without_extension}` to `{filename_without_extension}.toml`"),
1666 )?;
1667 }
1668 }
1669
1670 Ok(Some(possible))
1671 } else if possible_with_extension.exists() {
1672 Ok(Some(possible_with_extension))
1673 } else {
1674 Ok(None)
1675 }
1676 }
1677
1678 fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
1679 where
1680 F: FnMut(&Path) -> CargoResult<()>,
1681 {
1682 let mut seen_dir = HashSet::new();
1683
1684 for current in paths::ancestors(pwd, self.search_stop_path.as_deref()) {
1685 let config_root = current.join(".cargo");
1686 if let Some(path) = self.get_file_path(&config_root, "config", true)? {
1687 walk(&path)?;
1688 }
1689 seen_dir.insert(config_root);
1690 }
1691
1692 if !seen_dir.contains(home) {
1696 if let Some(path) = self.get_file_path(home, "config", true)? {
1697 walk(&path)?;
1698 }
1699 }
1700
1701 Ok(())
1702 }
1703
1704 pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
1706 RegistryName::new(registry)?;
1707 if let Some(index) = self.get_string(&format!("registries.{}.index", registry))? {
1708 self.resolve_registry_index(&index).with_context(|| {
1709 format!(
1710 "invalid index URL for registry `{}` defined in {}",
1711 registry, index.definition
1712 )
1713 })
1714 } else {
1715 bail!(
1716 "registry index was not found in any configuration: `{}`",
1717 registry
1718 );
1719 }
1720 }
1721
1722 pub fn check_registry_index_not_set(&self) -> CargoResult<()> {
1724 if self.get_string("registry.index")?.is_some() {
1725 bail!(
1726 "the `registry.index` config value is no longer supported\n\
1727 Use `[source]` replacement to alter the default index for crates.io."
1728 );
1729 }
1730 Ok(())
1731 }
1732
1733 fn resolve_registry_index(&self, index: &Value<String>) -> CargoResult<Url> {
1734 let base = index
1736 .definition
1737 .root(self)
1738 .join("truncated-by-url_with_base");
1739 let _parsed = index.val.into_url()?;
1741 let url = index.val.into_url_with_base(Some(&*base))?;
1742 if url.password().is_some() {
1743 bail!("registry URLs may not contain passwords");
1744 }
1745 Ok(url)
1746 }
1747
1748 pub fn load_credentials(&self) -> CargoResult<()> {
1756 if self.credential_values.filled() {
1757 return Ok(());
1758 }
1759
1760 let home_path = self.home_path.clone().into_path_unlocked();
1761 let Some(credentials) = self.get_file_path(&home_path, "credentials", true)? else {
1762 return Ok(());
1763 };
1764
1765 let mut value = self.load_file(&credentials)?;
1766 {
1768 let CV::Table(ref mut value_map, ref def) = value else {
1769 unreachable!();
1770 };
1771
1772 if let Some(token) = value_map.remove("token") {
1773 if let Vacant(entry) = value_map.entry("registry".into()) {
1774 let map = HashMap::from([("token".into(), token)]);
1775 let table = CV::Table(map, def.clone());
1776 entry.insert(table);
1777 }
1778 }
1779 }
1780
1781 let mut credential_values = HashMap::new();
1782 if let CV::Table(map, _) = value {
1783 let base_map = self.values()?;
1784 for (k, v) in map {
1785 let entry = match base_map.get(&k) {
1786 Some(base_entry) => {
1787 let mut entry = base_entry.clone();
1788 entry.merge(v, true)?;
1789 entry
1790 }
1791 None => v,
1792 };
1793 credential_values.insert(k, entry);
1794 }
1795 }
1796 self.credential_values
1797 .fill(credential_values)
1798 .expect("was not filled at beginning of the function");
1799 Ok(())
1800 }
1801
1802 fn maybe_get_tool(
1805 &self,
1806 tool: &str,
1807 from_config: &Option<ConfigRelativePath>,
1808 ) -> Option<PathBuf> {
1809 let var = tool.to_uppercase();
1810
1811 match self.get_env_os(&var).as_ref().and_then(|s| s.to_str()) {
1812 Some(tool_path) => {
1813 let maybe_relative = tool_path.contains('/') || tool_path.contains('\\');
1814 let path = if maybe_relative {
1815 self.cwd.join(tool_path)
1816 } else {
1817 PathBuf::from(tool_path)
1818 };
1819 Some(path)
1820 }
1821
1822 None => from_config.as_ref().map(|p| p.resolve_program(self)),
1823 }
1824 }
1825
1826 fn get_tool(&self, tool: Tool, from_config: &Option<ConfigRelativePath>) -> PathBuf {
1837 let tool_str = tool.as_str();
1838 self.maybe_get_tool(tool_str, from_config)
1839 .or_else(|| {
1840 let toolchain = self.get_env_os("RUSTUP_TOOLCHAIN")?;
1854 if toolchain.to_str()?.contains(&['/', '\\']) {
1857 return None;
1858 }
1859 let tool_resolved = paths::resolve_executable(Path::new(tool_str)).ok()?;
1862 let rustup_resolved = paths::resolve_executable(Path::new("rustup")).ok()?;
1863 let tool_meta = tool_resolved.metadata().ok()?;
1864 let rustup_meta = rustup_resolved.metadata().ok()?;
1865 if tool_meta.len() != rustup_meta.len() {
1870 return None;
1871 }
1872 let tool_exe = Path::new(tool_str).with_extension(env::consts::EXE_EXTENSION);
1874 let toolchain_exe = home::rustup_home()
1875 .ok()?
1876 .join("toolchains")
1877 .join(&toolchain)
1878 .join("bin")
1879 .join(&tool_exe);
1880 toolchain_exe.exists().then_some(toolchain_exe)
1881 })
1882 .unwrap_or_else(|| PathBuf::from(tool_str))
1883 }
1884
1885 pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
1886 self.jobserver.as_ref()
1887 }
1888
1889 pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
1890 let http = self
1891 .easy
1892 .try_borrow_with(|| http_handle(self).map(RefCell::new))?;
1893 {
1894 let mut http = http.borrow_mut();
1895 http.reset();
1896 let timeout = configure_http_handle(self, &mut http)?;
1897 timeout.configure(&mut http)?;
1898 }
1899 Ok(http)
1900 }
1901
1902 pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
1903 self.http_config.try_borrow_with(|| {
1904 let mut http = self.get::<CargoHttpConfig>("http")?;
1905 let curl_v = curl::Version::get();
1906 disables_multiplexing_for_bad_curl(curl_v.version(), &mut http, self);
1907 Ok(http)
1908 })
1909 }
1910
1911 pub fn future_incompat_config(&self) -> CargoResult<&CargoFutureIncompatConfig> {
1912 self.future_incompat_config
1913 .try_borrow_with(|| self.get::<CargoFutureIncompatConfig>("future-incompat-report"))
1914 }
1915
1916 pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
1917 self.net_config
1918 .try_borrow_with(|| self.get::<CargoNetConfig>("net"))
1919 }
1920
1921 pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
1922 self.build_config
1923 .try_borrow_with(|| self.get::<CargoBuildConfig>("build"))
1924 }
1925
1926 pub fn progress_config(&self) -> &ProgressConfig {
1927 &self.progress_config
1928 }
1929
1930 pub fn env_config(&self) -> CargoResult<&Arc<HashMap<String, OsString>>> {
1933 let env_config = self.env_config.try_borrow_with(|| {
1934 CargoResult::Ok(Arc::new({
1935 let env_config = self.get::<EnvConfig>("env")?;
1936 for disallowed in &["CARGO_HOME", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN"] {
1952 if env_config.contains_key(*disallowed) {
1953 bail!(
1954 "setting the `{disallowed}` environment variable is not supported \
1955 in the `[env]` configuration table"
1956 );
1957 }
1958 }
1959 env_config
1960 .into_iter()
1961 .filter_map(|(k, v)| {
1962 if v.is_force() || self.get_env_os(&k).is_none() {
1963 Some((k, v.resolve(self).to_os_string()))
1964 } else {
1965 None
1966 }
1967 })
1968 .collect()
1969 }))
1970 })?;
1971
1972 Ok(env_config)
1973 }
1974
1975 pub fn validate_term_config(&self) -> CargoResult<()> {
1981 drop(self.get::<TermConfig>("term")?);
1982 Ok(())
1983 }
1984
1985 pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
1989 self.target_cfgs
1990 .try_borrow_with(|| target::load_target_cfgs(self))
1991 }
1992
1993 pub fn doc_extern_map(&self) -> CargoResult<&RustdocExternMap> {
1994 self.doc_extern_map
1998 .try_borrow_with(|| self.get::<RustdocExternMap>("doc.extern-map"))
1999 }
2000
2001 pub fn target_applies_to_host(&self) -> CargoResult<bool> {
2003 target::get_target_applies_to_host(self)
2004 }
2005
2006 pub fn host_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2008 target::load_host_triple(self, target)
2009 }
2010
2011 pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
2013 target::load_target_triple(self, target)
2014 }
2015
2016 pub fn crates_io_source_id(&self) -> CargoResult<SourceId> {
2021 let source_id = self.crates_io_source_id.try_borrow_with(|| {
2022 self.check_registry_index_not_set()?;
2023 let url = CRATES_IO_INDEX.into_url().unwrap();
2024 SourceId::for_alt_registry(&url, CRATES_IO_REGISTRY)
2025 })?;
2026 Ok(*source_id)
2027 }
2028
2029 pub fn creation_time(&self) -> Instant {
2030 self.creation_time
2031 }
2032
2033 pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
2048 let d = Deserializer {
2049 gctx: self,
2050 key: ConfigKey::from_str(key),
2051 env_prefix_ok: true,
2052 };
2053 T::deserialize(d).map_err(|e| e.into())
2054 }
2055
2056 #[track_caller]
2062 #[tracing::instrument(skip_all)]
2063 pub fn assert_package_cache_locked<'a>(
2064 &self,
2065 mode: CacheLockMode,
2066 f: &'a Filesystem,
2067 ) -> &'a Path {
2068 let ret = f.as_path_unlocked();
2069 assert!(
2070 self.package_cache_lock.is_locked(mode),
2071 "package cache lock is not currently held, Cargo forgot to call \
2072 `acquire_package_cache_lock` before we got to this stack frame",
2073 );
2074 assert!(ret.starts_with(self.home_path.as_path_unlocked()));
2075 ret
2076 }
2077
2078 #[tracing::instrument(skip_all)]
2084 pub fn acquire_package_cache_lock(&self, mode: CacheLockMode) -> CargoResult<CacheLock<'_>> {
2085 self.package_cache_lock.lock(self, mode)
2086 }
2087
2088 #[tracing::instrument(skip_all)]
2094 pub fn try_acquire_package_cache_lock(
2095 &self,
2096 mode: CacheLockMode,
2097 ) -> CargoResult<Option<CacheLock<'_>>> {
2098 self.package_cache_lock.try_lock(self, mode)
2099 }
2100
2101 pub fn global_cache_tracker(&self) -> CargoResult<RefMut<'_, GlobalCacheTracker>> {
2106 let tracker = self.global_cache_tracker.try_borrow_with(|| {
2107 Ok::<_, anyhow::Error>(RefCell::new(GlobalCacheTracker::new(self)?))
2108 })?;
2109 Ok(tracker.borrow_mut())
2110 }
2111
2112 pub fn deferred_global_last_use(&self) -> CargoResult<RefMut<'_, DeferredGlobalLastUse>> {
2114 let deferred = self.deferred_global_last_use.try_borrow_with(|| {
2115 Ok::<_, anyhow::Error>(RefCell::new(DeferredGlobalLastUse::new()))
2116 })?;
2117 Ok(deferred.borrow_mut())
2118 }
2119
2120 pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
2122 if self.unstable_flags.warnings {
2123 Ok(self.build_config()?.warnings.unwrap_or_default())
2124 } else {
2125 Ok(WarningHandling::default())
2126 }
2127 }
2128}
2129
2130#[derive(Debug)]
2132pub struct ConfigError {
2133 error: anyhow::Error,
2134 definition: Option<Definition>,
2135}
2136
2137impl ConfigError {
2138 fn new(message: String, definition: Definition) -> ConfigError {
2139 ConfigError {
2140 error: anyhow::Error::msg(message),
2141 definition: Some(definition),
2142 }
2143 }
2144
2145 fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
2146 ConfigError {
2147 error: anyhow!(
2148 "`{}` expected {}, but found a {}",
2149 key,
2150 expected,
2151 found.desc()
2152 ),
2153 definition: Some(found.definition().clone()),
2154 }
2155 }
2156
2157 fn is_missing_field(&self) -> bool {
2158 self.error.downcast_ref::<MissingFieldError>().is_some()
2159 }
2160
2161 fn missing(key: &ConfigKey) -> ConfigError {
2162 ConfigError {
2163 error: anyhow!("missing config key `{}`", key),
2164 definition: None,
2165 }
2166 }
2167
2168 fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
2169 ConfigError {
2170 error: anyhow::Error::from(self)
2171 .context(format!("could not load config key `{}`", key)),
2172 definition: definition,
2173 }
2174 }
2175}
2176
2177impl std::error::Error for ConfigError {
2178 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
2179 self.error.source()
2180 }
2181}
2182
2183impl fmt::Display for ConfigError {
2184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2185 if let Some(definition) = &self.definition {
2186 write!(f, "error in {}: {}", definition, self.error)
2187 } else {
2188 self.error.fmt(f)
2189 }
2190 }
2191}
2192
2193#[derive(Debug)]
2194struct MissingFieldError(String);
2195
2196impl fmt::Display for MissingFieldError {
2197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2198 write!(f, "missing field `{}`", self.0)
2199 }
2200}
2201
2202impl std::error::Error for MissingFieldError {}
2203
2204impl serde::de::Error for ConfigError {
2205 fn custom<T: fmt::Display>(msg: T) -> Self {
2206 ConfigError {
2207 error: anyhow::Error::msg(msg.to_string()),
2208 definition: None,
2209 }
2210 }
2211
2212 fn missing_field(field: &'static str) -> Self {
2213 ConfigError {
2214 error: anyhow::Error::new(MissingFieldError(field.to_string())),
2215 definition: None,
2216 }
2217 }
2218}
2219
2220impl From<anyhow::Error> for ConfigError {
2221 fn from(error: anyhow::Error) -> Self {
2222 ConfigError {
2223 error,
2224 definition: None,
2225 }
2226 }
2227}
2228
2229#[derive(Eq, PartialEq, Clone)]
2230pub enum ConfigValue {
2231 Integer(i64, Definition),
2232 String(String, Definition),
2233 List(Vec<(String, Definition)>, Definition),
2234 Table(HashMap<String, ConfigValue>, Definition),
2235 Boolean(bool, Definition),
2236}
2237
2238impl fmt::Debug for ConfigValue {
2239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2240 match self {
2241 CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
2242 CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
2243 CV::String(s, def) => write!(f, "{} (from {})", s, def),
2244 CV::List(list, def) => {
2245 write!(f, "[")?;
2246 for (i, (s, def)) in list.iter().enumerate() {
2247 if i > 0 {
2248 write!(f, ", ")?;
2249 }
2250 write!(f, "{} (from {})", s, def)?;
2251 }
2252 write!(f, "] (from {})", def)
2253 }
2254 CV::Table(table, _) => write!(f, "{:?}", table),
2255 }
2256 }
2257}
2258
2259impl ConfigValue {
2260 fn get_definition(&self) -> &Definition {
2261 match self {
2262 CV::Boolean(_, def)
2263 | CV::Integer(_, def)
2264 | CV::String(_, def)
2265 | CV::List(_, def)
2266 | CV::Table(_, def) => def,
2267 }
2268 }
2269
2270 fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
2271 match toml {
2272 toml::Value::String(val) => Ok(CV::String(val, def)),
2273 toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
2274 toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
2275 toml::Value::Array(val) => Ok(CV::List(
2276 val.into_iter()
2277 .map(|toml| match toml {
2278 toml::Value::String(val) => Ok((val, def.clone())),
2279 v => bail!("expected string but found {} in list", v.type_str()),
2280 })
2281 .collect::<CargoResult<_>>()?,
2282 def,
2283 )),
2284 toml::Value::Table(val) => Ok(CV::Table(
2285 val.into_iter()
2286 .map(|(key, value)| {
2287 let value = CV::from_toml(def.clone(), value)
2288 .with_context(|| format!("failed to parse key `{}`", key))?;
2289 Ok((key, value))
2290 })
2291 .collect::<CargoResult<_>>()?,
2292 def,
2293 )),
2294 v => bail!(
2295 "found TOML configuration value of unknown type `{}`",
2296 v.type_str()
2297 ),
2298 }
2299 }
2300
2301 fn into_toml(self) -> toml::Value {
2302 match self {
2303 CV::Boolean(s, _) => toml::Value::Boolean(s),
2304 CV::String(s, _) => toml::Value::String(s),
2305 CV::Integer(i, _) => toml::Value::Integer(i),
2306 CV::List(l, _) => {
2307 toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect())
2308 }
2309 CV::Table(l, _) => {
2310 toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
2311 }
2312 }
2313 }
2314
2315 fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
2324 self.merge_helper(from, force, &mut ConfigKey::new())
2325 }
2326
2327 fn merge_helper(
2328 &mut self,
2329 from: ConfigValue,
2330 force: bool,
2331 parts: &mut ConfigKey,
2332 ) -> CargoResult<()> {
2333 let is_higher_priority = from.definition().is_higher_priority(self.definition());
2334 match (self, from) {
2335 (&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
2336 if is_nonmergable_list(&parts) {
2337 if force || is_higher_priority {
2339 mem::swap(new, old);
2340 }
2341 } else {
2342 if force {
2344 old.append(new);
2345 } else {
2346 new.append(old);
2347 mem::swap(new, old);
2348 }
2349 }
2350 old.sort_by(|a, b| a.1.cmp(&b.1));
2351 }
2352 (&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
2353 for (key, value) in mem::take(new) {
2354 match old.entry(key.clone()) {
2355 Occupied(mut entry) => {
2356 let new_def = value.definition().clone();
2357 let entry = entry.get_mut();
2358 parts.push(&key);
2359 entry.merge_helper(value, force, parts).with_context(|| {
2360 format!(
2361 "failed to merge key `{}` between \
2362 {} and {}",
2363 key,
2364 entry.definition(),
2365 new_def,
2366 )
2367 })?;
2368 }
2369 Vacant(entry) => {
2370 entry.insert(value);
2371 }
2372 };
2373 }
2374 }
2375 (expected @ &mut CV::List(_, _), found)
2377 | (expected @ &mut CV::Table(_, _), found)
2378 | (expected, found @ CV::List(_, _))
2379 | (expected, found @ CV::Table(_, _)) => {
2380 return Err(anyhow!(
2381 "failed to merge config value from `{}` into `{}`: expected {}, but found {}",
2382 found.definition(),
2383 expected.definition(),
2384 expected.desc(),
2385 found.desc()
2386 ));
2387 }
2388 (old, mut new) => {
2389 if force || is_higher_priority {
2390 mem::swap(old, &mut new);
2391 }
2392 }
2393 }
2394
2395 Ok(())
2396 }
2397
2398 pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
2399 match self {
2400 CV::Integer(i, def) => Ok((*i, def)),
2401 _ => self.expected("integer", key),
2402 }
2403 }
2404
2405 pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
2406 match self {
2407 CV::String(s, def) => Ok((s, def)),
2408 _ => self.expected("string", key),
2409 }
2410 }
2411
2412 pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
2413 match self {
2414 CV::Table(table, def) => Ok((table, def)),
2415 _ => self.expected("table", key),
2416 }
2417 }
2418
2419 pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
2420 match self {
2421 CV::List(list, _) => Ok(list),
2422 _ => self.expected("list", key),
2423 }
2424 }
2425
2426 pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
2427 match self {
2428 CV::Boolean(b, def) => Ok((*b, def)),
2429 _ => self.expected("bool", key),
2430 }
2431 }
2432
2433 pub fn desc(&self) -> &'static str {
2434 match *self {
2435 CV::Table(..) => "table",
2436 CV::List(..) => "array",
2437 CV::String(..) => "string",
2438 CV::Boolean(..) => "boolean",
2439 CV::Integer(..) => "integer",
2440 }
2441 }
2442
2443 pub fn definition(&self) -> &Definition {
2444 match self {
2445 CV::Boolean(_, def)
2446 | CV::Integer(_, def)
2447 | CV::String(_, def)
2448 | CV::List(_, def)
2449 | CV::Table(_, def) => def,
2450 }
2451 }
2452
2453 fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
2454 bail!(
2455 "expected a {}, but found a {} for `{}` in {}",
2456 wanted,
2457 self.desc(),
2458 key,
2459 self.definition()
2460 )
2461 }
2462}
2463
2464fn is_nonmergable_list(key: &ConfigKey) -> bool {
2467 key.matches("registry.credential-provider")
2468 || key.matches("registries.*.credential-provider")
2469 || key.matches("target.*.runner")
2470 || key.matches("host.runner")
2471 || key.matches("credential-alias.*")
2472 || key.matches("doc.browser")
2473}
2474
2475pub fn homedir(cwd: &Path) -> Option<PathBuf> {
2476 ::home::cargo_home_with_cwd(cwd).ok()
2477}
2478
2479pub fn save_credentials(
2480 gctx: &GlobalContext,
2481 token: Option<RegistryCredentialConfig>,
2482 registry: &SourceId,
2483) -> CargoResult<()> {
2484 let registry = if registry.is_crates_io() {
2485 None
2486 } else {
2487 let name = registry
2488 .alt_registry_key()
2489 .ok_or_else(|| internal("can't save credentials for anonymous registry"))?;
2490 Some(name)
2491 };
2492
2493 let home_path = gctx.home_path.clone().into_path_unlocked();
2497 let filename = match gctx.get_file_path(&home_path, "credentials", false)? {
2498 Some(path) => match path.file_name() {
2499 Some(filename) => Path::new(filename).to_owned(),
2500 None => Path::new("credentials.toml").to_owned(),
2501 },
2502 None => Path::new("credentials.toml").to_owned(),
2503 };
2504
2505 let mut file = {
2506 gctx.home_path.create_dir()?;
2507 gctx.home_path
2508 .open_rw_exclusive_create(filename, gctx, "credentials' config file")?
2509 };
2510
2511 let mut contents = String::new();
2512 file.read_to_string(&mut contents).with_context(|| {
2513 format!(
2514 "failed to read configuration file `{}`",
2515 file.path().display()
2516 )
2517 })?;
2518
2519 let mut toml = parse_document(&contents, file.path(), gctx)?;
2520
2521 if let Some(token) = toml.remove("token") {
2523 let map = HashMap::from([("token".to_string(), token)]);
2524 toml.insert("registry".into(), map.into());
2525 }
2526
2527 if let Some(token) = token {
2528 let path_def = Definition::Path(file.path().to_path_buf());
2531 let (key, mut value) = match token {
2532 RegistryCredentialConfig::Token(token) => {
2533 let key = "token".to_string();
2536 let value = ConfigValue::String(token.expose(), path_def.clone());
2537 let map = HashMap::from([(key, value)]);
2538 let table = CV::Table(map, path_def.clone());
2539
2540 if let Some(registry) = registry {
2541 let map = HashMap::from([(registry.to_string(), table)]);
2542 ("registries".into(), CV::Table(map, path_def.clone()))
2543 } else {
2544 ("registry".into(), table)
2545 }
2546 }
2547 RegistryCredentialConfig::AsymmetricKey((secret_key, key_subject)) => {
2548 let key = "secret-key".to_string();
2551 let value = ConfigValue::String(secret_key.expose(), path_def.clone());
2552 let mut map = HashMap::from([(key, value)]);
2553 if let Some(key_subject) = key_subject {
2554 let key = "secret-key-subject".to_string();
2555 let value = ConfigValue::String(key_subject, path_def.clone());
2556 map.insert(key, value);
2557 }
2558 let table = CV::Table(map, path_def.clone());
2559
2560 if let Some(registry) = registry {
2561 let map = HashMap::from([(registry.to_string(), table)]);
2562 ("registries".into(), CV::Table(map, path_def.clone()))
2563 } else {
2564 ("registry".into(), table)
2565 }
2566 }
2567 _ => unreachable!(),
2568 };
2569
2570 if registry.is_some() {
2571 if let Some(table) = toml.remove("registries") {
2572 let v = CV::from_toml(path_def, table)?;
2573 value.merge(v, false)?;
2574 }
2575 }
2576 toml.insert(key, value.into_toml());
2577 } else {
2578 if let Some(registry) = registry {
2580 if let Some(registries) = toml.get_mut("registries") {
2581 if let Some(reg) = registries.get_mut(registry) {
2582 let rtable = reg.as_table_mut().ok_or_else(|| {
2583 format_err!("expected `[registries.{}]` to be a table", registry)
2584 })?;
2585 rtable.remove("token");
2586 rtable.remove("secret-key");
2587 rtable.remove("secret-key-subject");
2588 }
2589 }
2590 } else if let Some(registry) = toml.get_mut("registry") {
2591 let reg_table = registry
2592 .as_table_mut()
2593 .ok_or_else(|| format_err!("expected `[registry]` to be a table"))?;
2594 reg_table.remove("token");
2595 reg_table.remove("secret-key");
2596 reg_table.remove("secret-key-subject");
2597 }
2598 }
2599
2600 let contents = toml.to_string();
2601 file.seek(SeekFrom::Start(0))?;
2602 file.write_all(contents.as_bytes())
2603 .with_context(|| format!("failed to write to `{}`", file.path().display()))?;
2604 file.file().set_len(contents.len() as u64)?;
2605 set_permissions(file.file(), 0o600)
2606 .with_context(|| format!("failed to set permissions of `{}`", file.path().display()))?;
2607
2608 return Ok(());
2609
2610 #[cfg(unix)]
2611 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2612 use std::os::unix::fs::PermissionsExt;
2613
2614 let mut perms = file.metadata()?.permissions();
2615 perms.set_mode(mode);
2616 file.set_permissions(perms)?;
2617 Ok(())
2618 }
2619
2620 #[cfg(not(unix))]
2621 #[allow(unused)]
2622 fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
2623 Ok(())
2624 }
2625}
2626
2627#[derive(Debug, Default, Deserialize, PartialEq)]
2628#[serde(rename_all = "kebab-case")]
2629pub struct CargoHttpConfig {
2630 pub proxy: Option<String>,
2631 pub low_speed_limit: Option<u32>,
2632 pub timeout: Option<u64>,
2633 pub cainfo: Option<ConfigRelativePath>,
2634 pub proxy_cainfo: Option<ConfigRelativePath>,
2635 pub check_revoke: Option<bool>,
2636 pub user_agent: Option<String>,
2637 pub debug: Option<bool>,
2638 pub multiplexing: Option<bool>,
2639 pub ssl_version: Option<SslVersionConfig>,
2640}
2641
2642#[derive(Debug, Default, Deserialize, PartialEq)]
2643#[serde(rename_all = "kebab-case")]
2644pub struct CargoFutureIncompatConfig {
2645 frequency: Option<CargoFutureIncompatFrequencyConfig>,
2646}
2647
2648#[derive(Debug, Default, Deserialize, PartialEq)]
2649#[serde(rename_all = "kebab-case")]
2650pub enum CargoFutureIncompatFrequencyConfig {
2651 #[default]
2652 Always,
2653 Never,
2654}
2655
2656impl CargoFutureIncompatConfig {
2657 pub fn should_display_message(&self) -> bool {
2658 use CargoFutureIncompatFrequencyConfig::*;
2659
2660 let frequency = self.frequency.as_ref().unwrap_or(&Always);
2661 match frequency {
2662 Always => true,
2663 Never => false,
2664 }
2665 }
2666}
2667
2668#[derive(Clone, Debug, PartialEq)]
2682pub enum SslVersionConfig {
2683 Single(String),
2684 Range(SslVersionConfigRange),
2685}
2686
2687impl<'de> Deserialize<'de> for SslVersionConfig {
2688 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2689 where
2690 D: serde::Deserializer<'de>,
2691 {
2692 UntaggedEnumVisitor::new()
2693 .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
2694 .map(|map| map.deserialize().map(SslVersionConfig::Range))
2695 .deserialize(deserializer)
2696 }
2697}
2698
2699#[derive(Clone, Debug, Deserialize, PartialEq)]
2700#[serde(rename_all = "kebab-case")]
2701pub struct SslVersionConfigRange {
2702 pub min: Option<String>,
2703 pub max: Option<String>,
2704}
2705
2706#[derive(Debug, Deserialize)]
2707#[serde(rename_all = "kebab-case")]
2708pub struct CargoNetConfig {
2709 pub retry: Option<u32>,
2710 pub offline: Option<bool>,
2711 pub git_fetch_with_cli: Option<bool>,
2712 pub ssh: Option<CargoSshConfig>,
2713}
2714
2715#[derive(Debug, Deserialize)]
2716#[serde(rename_all = "kebab-case")]
2717pub struct CargoSshConfig {
2718 pub known_hosts: Option<Vec<Value<String>>>,
2719}
2720
2721#[derive(Debug, Clone)]
2734pub enum JobsConfig {
2735 Integer(i32),
2736 String(String),
2737}
2738
2739impl<'de> Deserialize<'de> for JobsConfig {
2740 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2741 where
2742 D: serde::Deserializer<'de>,
2743 {
2744 UntaggedEnumVisitor::new()
2745 .i32(|int| Ok(JobsConfig::Integer(int)))
2746 .string(|string| Ok(JobsConfig::String(string.to_owned())))
2747 .deserialize(deserializer)
2748 }
2749}
2750
2751#[derive(Debug, Deserialize)]
2752#[serde(rename_all = "kebab-case")]
2753pub struct CargoBuildConfig {
2754 pub pipelining: Option<bool>,
2756 pub dep_info_basedir: Option<ConfigRelativePath>,
2757 pub target_dir: Option<ConfigRelativePath>,
2758 pub build_dir: Option<ConfigRelativePath>,
2759 pub incremental: Option<bool>,
2760 pub target: Option<BuildTargetConfig>,
2761 pub jobs: Option<JobsConfig>,
2762 pub rustflags: Option<StringList>,
2763 pub rustdocflags: Option<StringList>,
2764 pub rustc_wrapper: Option<ConfigRelativePath>,
2765 pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
2766 pub rustc: Option<ConfigRelativePath>,
2767 pub rustdoc: Option<ConfigRelativePath>,
2768 pub out_dir: Option<ConfigRelativePath>,
2770 pub artifact_dir: Option<ConfigRelativePath>,
2771 pub warnings: Option<WarningHandling>,
2772 pub sbom: Option<bool>,
2774}
2775
2776#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
2778#[serde(rename_all = "kebab-case")]
2779pub enum WarningHandling {
2780 #[default]
2781 Warn,
2783 Allow,
2785 Deny,
2787}
2788
2789#[derive(Debug, Deserialize)]
2799#[serde(transparent)]
2800pub struct BuildTargetConfig {
2801 inner: Value<BuildTargetConfigInner>,
2802}
2803
2804#[derive(Debug)]
2805enum BuildTargetConfigInner {
2806 One(String),
2807 Many(Vec<String>),
2808}
2809
2810impl<'de> Deserialize<'de> for BuildTargetConfigInner {
2811 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2812 where
2813 D: serde::Deserializer<'de>,
2814 {
2815 UntaggedEnumVisitor::new()
2816 .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
2817 .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
2818 .deserialize(deserializer)
2819 }
2820}
2821
2822impl BuildTargetConfig {
2823 pub fn values(&self, gctx: &GlobalContext) -> CargoResult<Vec<String>> {
2825 let map = |s: &String| {
2826 if s.ends_with(".json") {
2827 self.inner
2830 .definition
2831 .root(gctx)
2832 .join(s)
2833 .to_str()
2834 .expect("must be utf-8 in toml")
2835 .to_string()
2836 } else {
2837 s.to_string()
2839 }
2840 };
2841 let values = match &self.inner.val {
2842 BuildTargetConfigInner::One(s) => vec![map(s)],
2843 BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
2844 };
2845 Ok(values)
2846 }
2847}
2848
2849#[derive(Debug, Deserialize)]
2850#[serde(rename_all = "kebab-case")]
2851pub struct CargoResolverConfig {
2852 pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
2853 pub feature_unification: Option<FeatureUnification>,
2854}
2855
2856#[derive(Debug, Deserialize, PartialEq, Eq)]
2857#[serde(rename_all = "kebab-case")]
2858pub enum IncompatibleRustVersions {
2859 Allow,
2860 Fallback,
2861}
2862
2863#[derive(Copy, Clone, Debug, Deserialize)]
2864#[serde(rename_all = "kebab-case")]
2865pub enum FeatureUnification {
2866 Package,
2867 Selected,
2868 Workspace,
2869}
2870
2871#[derive(Deserialize, Default)]
2872#[serde(rename_all = "kebab-case")]
2873pub struct TermConfig {
2874 pub verbose: Option<bool>,
2875 pub quiet: Option<bool>,
2876 pub color: Option<String>,
2877 pub hyperlinks: Option<bool>,
2878 pub unicode: Option<bool>,
2879 #[serde(default)]
2880 #[serde(deserialize_with = "progress_or_string")]
2881 pub progress: Option<ProgressConfig>,
2882}
2883
2884#[derive(Debug, Default, Deserialize)]
2885#[serde(rename_all = "kebab-case")]
2886pub struct ProgressConfig {
2887 #[serde(default)]
2888 pub when: ProgressWhen,
2889 pub width: Option<usize>,
2890 pub term_integration: Option<bool>,
2892}
2893
2894#[derive(Debug, Default, Deserialize)]
2895#[serde(rename_all = "kebab-case")]
2896pub enum ProgressWhen {
2897 #[default]
2898 Auto,
2899 Never,
2900 Always,
2901}
2902
2903fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
2904where
2905 D: serde::de::Deserializer<'de>,
2906{
2907 struct ProgressVisitor;
2908
2909 impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
2910 type Value = Option<ProgressConfig>;
2911
2912 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2913 formatter.write_str("a string (\"auto\" or \"never\") or a table")
2914 }
2915
2916 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
2917 where
2918 E: serde::de::Error,
2919 {
2920 match s {
2921 "auto" => Ok(Some(ProgressConfig {
2922 when: ProgressWhen::Auto,
2923 width: None,
2924 term_integration: None,
2925 })),
2926 "never" => Ok(Some(ProgressConfig {
2927 when: ProgressWhen::Never,
2928 width: None,
2929 term_integration: None,
2930 })),
2931 "always" => Err(E::custom("\"always\" progress requires a `width` key")),
2932 _ => Err(E::unknown_variant(s, &["auto", "never"])),
2933 }
2934 }
2935
2936 fn visit_none<E>(self) -> Result<Self::Value, E>
2937 where
2938 E: serde::de::Error,
2939 {
2940 Ok(None)
2941 }
2942
2943 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
2944 where
2945 D: serde::de::Deserializer<'de>,
2946 {
2947 let pc = ProgressConfig::deserialize(deserializer)?;
2948 if let ProgressConfig {
2949 when: ProgressWhen::Always,
2950 width: None,
2951 ..
2952 } = pc
2953 {
2954 return Err(serde::de::Error::custom(
2955 "\"always\" progress requires a `width` key",
2956 ));
2957 }
2958 Ok(Some(pc))
2959 }
2960 }
2961
2962 deserializer.deserialize_option(ProgressVisitor)
2963}
2964
2965#[derive(Debug)]
2966enum EnvConfigValueInner {
2967 Simple(String),
2968 WithOptions {
2969 value: String,
2970 force: bool,
2971 relative: bool,
2972 },
2973}
2974
2975impl<'de> Deserialize<'de> for EnvConfigValueInner {
2976 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2977 where
2978 D: serde::Deserializer<'de>,
2979 {
2980 #[derive(Deserialize)]
2981 struct WithOptions {
2982 value: String,
2983 #[serde(default)]
2984 force: bool,
2985 #[serde(default)]
2986 relative: bool,
2987 }
2988
2989 UntaggedEnumVisitor::new()
2990 .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
2991 .map(|map| {
2992 let with_options: WithOptions = map.deserialize()?;
2993 Ok(EnvConfigValueInner::WithOptions {
2994 value: with_options.value,
2995 force: with_options.force,
2996 relative: with_options.relative,
2997 })
2998 })
2999 .deserialize(deserializer)
3000 }
3001}
3002
3003#[derive(Debug, Deserialize)]
3004#[serde(transparent)]
3005pub struct EnvConfigValue {
3006 inner: Value<EnvConfigValueInner>,
3007}
3008
3009impl EnvConfigValue {
3010 pub fn is_force(&self) -> bool {
3011 match self.inner.val {
3012 EnvConfigValueInner::Simple(_) => false,
3013 EnvConfigValueInner::WithOptions { force, .. } => force,
3014 }
3015 }
3016
3017 pub fn resolve<'a>(&'a self, gctx: &GlobalContext) -> Cow<'a, OsStr> {
3018 match self.inner.val {
3019 EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
3020 EnvConfigValueInner::WithOptions {
3021 ref value,
3022 relative,
3023 ..
3024 } => {
3025 if relative {
3026 let p = self.inner.definition.root(gctx).join(&value);
3027 Cow::Owned(p.into_os_string())
3028 } else {
3029 Cow::Borrowed(OsStr::new(value.as_str()))
3030 }
3031 }
3032 }
3033 }
3034}
3035
3036pub type EnvConfig = HashMap<String, EnvConfigValue>;
3037
3038fn parse_document(toml: &str, _file: &Path, _gctx: &GlobalContext) -> CargoResult<toml::Table> {
3039 toml.parse().map_err(Into::into)
3041}
3042
3043#[derive(Debug, Deserialize, Clone)]
3054pub struct StringList(Vec<String>);
3055
3056impl StringList {
3057 pub fn as_slice(&self) -> &[String] {
3058 &self.0
3059 }
3060}
3061
3062#[macro_export]
3063macro_rules! __shell_print {
3064 ($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
3065 let mut shell = $config.shell();
3066 let out = shell.$which();
3067 drop(out.write_fmt(format_args!($($arg)*)));
3068 if $newline {
3069 drop(out.write_all(b"\n"));
3070 }
3071 });
3072}
3073
3074#[macro_export]
3075macro_rules! drop_println {
3076 ($config:expr) => ( $crate::drop_print!($config, "\n") );
3077 ($config:expr, $($arg:tt)*) => (
3078 $crate::__shell_print!($config, out, true, $($arg)*)
3079 );
3080}
3081
3082#[macro_export]
3083macro_rules! drop_eprintln {
3084 ($config:expr) => ( $crate::drop_eprint!($config, "\n") );
3085 ($config:expr, $($arg:tt)*) => (
3086 $crate::__shell_print!($config, err, true, $($arg)*)
3087 );
3088}
3089
3090#[macro_export]
3091macro_rules! drop_print {
3092 ($config:expr, $($arg:tt)*) => (
3093 $crate::__shell_print!($config, out, false, $($arg)*)
3094 );
3095}
3096
3097#[macro_export]
3098macro_rules! drop_eprint {
3099 ($config:expr, $($arg:tt)*) => (
3100 $crate::__shell_print!($config, err, false, $($arg)*)
3101 );
3102}
3103
3104enum Tool {
3105 Rustc,
3106 Rustdoc,
3107}
3108
3109impl Tool {
3110 fn as_str(&self) -> &str {
3111 match self {
3112 Tool::Rustc => "rustc",
3113 Tool::Rustdoc => "rustdoc",
3114 }
3115 }
3116}
3117
3118fn disables_multiplexing_for_bad_curl(
3128 curl_version: &str,
3129 http: &mut CargoHttpConfig,
3130 gctx: &GlobalContext,
3131) {
3132 use crate::util::network;
3133
3134 if network::proxy::http_proxy_exists(http, gctx) && http.multiplexing.is_none() {
3135 let bad_curl_versions = ["7.87.0", "7.88.0", "7.88.1"];
3136 if bad_curl_versions
3137 .iter()
3138 .any(|v| curl_version.starts_with(v))
3139 {
3140 tracing::info!("disabling multiplexing with proxy, curl version is {curl_version}");
3141 http.multiplexing = Some(false);
3142 }
3143 }
3144}
3145
3146#[cfg(test)]
3147mod tests {
3148 use super::CargoHttpConfig;
3149 use super::GlobalContext;
3150 use super::Shell;
3151 use super::disables_multiplexing_for_bad_curl;
3152
3153 #[test]
3154 fn disables_multiplexing() {
3155 let mut gctx = GlobalContext::new(Shell::new(), "".into(), "".into());
3156 gctx.set_search_stop_path(std::path::PathBuf::new());
3157 gctx.set_env(Default::default());
3158
3159 let mut http = CargoHttpConfig::default();
3160 http.proxy = Some("127.0.0.1:3128".into());
3161 disables_multiplexing_for_bad_curl("7.88.1", &mut http, &gctx);
3162 assert_eq!(http.multiplexing, Some(false));
3163
3164 let cases = [
3165 (None, None, "7.87.0", None),
3166 (None, None, "7.88.0", None),
3167 (None, None, "7.88.1", None),
3168 (None, None, "8.0.0", None),
3169 (Some("".into()), None, "7.87.0", Some(false)),
3170 (Some("".into()), None, "7.88.0", Some(false)),
3171 (Some("".into()), None, "7.88.1", Some(false)),
3172 (Some("".into()), None, "8.0.0", None),
3173 (Some("".into()), Some(false), "7.87.0", Some(false)),
3174 (Some("".into()), Some(false), "7.88.0", Some(false)),
3175 (Some("".into()), Some(false), "7.88.1", Some(false)),
3176 (Some("".into()), Some(false), "8.0.0", Some(false)),
3177 ];
3178
3179 for (proxy, multiplexing, curl_v, result) in cases {
3180 let mut http = CargoHttpConfig {
3181 multiplexing,
3182 proxy,
3183 ..Default::default()
3184 };
3185 disables_multiplexing_for_bad_curl(curl_v, &mut http, &gctx);
3186 assert_eq!(http.multiplexing, result);
3187 }
3188 }
3189}