use std::cell::{RefCell, RefMut};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet};
use std::env;
use std::fmt;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::{self, SeekFrom};
use std::mem;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Once;
use std::time::Instant;
use anyhow::{anyhow, bail};
use curl::easy::Easy;
use lazycell::LazyCell;
use serde::Deserialize;
use url::Url;
use self::ConfigValue as CV;
use crate::core::shell::Verbosity;
use crate::core::{nightly_features_allowed, CliUnstable, Shell, SourceId, Workspace};
use crate::ops;
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::toml as cargo_toml;
use crate::util::{paths, validate_package_name};
use crate::util::{FileLock, Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
mod de;
use de::Deserializer;
mod value;
pub use value::{Definition, OptValue, Value};
mod key;
use key::ConfigKey;
mod path;
pub use path::{ConfigRelativePath, PathAndArgs};
mod target;
pub use target::{TargetCfgConfig, TargetConfig};
macro_rules! get_value_typed {
($name:ident, $ty:ty, $variant:ident, $expected:expr) => {
fn $name(&self, key: &ConfigKey) -> Result<OptValue<$ty>, ConfigError> {
let cv = self.get_cv(key)?;
let env = self.get_env::<$ty>(key)?;
match (cv, env) {
(Some(CV::$variant(val, definition)), Some(env)) => {
if definition.is_higher_priority(&env.definition) {
Ok(Some(Value { val, definition }))
} else {
Ok(Some(env))
}
}
(Some(CV::$variant(val, definition)), None) => Ok(Some(Value { val, definition })),
(Some(cv), _) => Err(ConfigError::expected(key, $expected, &cv)),
(None, Some(env)) => Ok(Some(env)),
(None, None) => Ok(None),
}
}
};
}
#[derive(Debug)]
pub struct Config {
home_path: Filesystem,
shell: RefCell<Shell>,
values: LazyCell<HashMap<String, ConfigValue>>,
cli_config: Option<Vec<String>>,
cwd: PathBuf,
cargo_exe: LazyCell<PathBuf>,
rustdoc: LazyCell<PathBuf>,
extra_verbose: bool,
frozen: bool,
locked: bool,
offline: bool,
jobserver: Option<jobserver::Client>,
unstable_flags: CliUnstable,
easy: LazyCell<RefCell<Easy>>,
crates_io_source_id: LazyCell<SourceId>,
cache_rustc_info: bool,
creation_time: Instant,
target_dir: Option<Filesystem>,
env: HashMap<String, String>,
updated_sources: LazyCell<RefCell<HashSet<SourceId>>>,
package_cache_lock: RefCell<Option<(Option<FileLock>, usize)>>,
http_config: LazyCell<CargoHttpConfig>,
net_config: LazyCell<CargoNetConfig>,
build_config: LazyCell<CargoBuildConfig>,
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
}
impl Config {
pub fn new(shell: Shell, cwd: PathBuf, homedir: PathBuf) -> Config {
static mut GLOBAL_JOBSERVER: *mut jobserver::Client = 0 as *mut _;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
if let Some(client) = jobserver::Client::from_env() {
GLOBAL_JOBSERVER = Box::into_raw(Box::new(client));
}
});
let env: HashMap<_, _> = env::vars_os()
.filter_map(|(k, v)| {
match (k.into_string(), v.into_string()) {
(Ok(k), Ok(v)) => Some((k, v)),
_ => None,
}
})
.collect();
let cache_rustc_info = match env.get("CARGO_CACHE_RUSTC_INFO") {
Some(cache) => cache != "0",
_ => true,
};
Config {
home_path: Filesystem::new(homedir),
shell: RefCell::new(shell),
cwd,
values: LazyCell::new(),
cli_config: None,
cargo_exe: LazyCell::new(),
rustdoc: LazyCell::new(),
extra_verbose: false,
frozen: false,
locked: false,
offline: false,
jobserver: unsafe {
if GLOBAL_JOBSERVER.is_null() {
None
} else {
Some((*GLOBAL_JOBSERVER).clone())
}
},
unstable_flags: CliUnstable::default(),
easy: LazyCell::new(),
crates_io_source_id: LazyCell::new(),
cache_rustc_info,
creation_time: Instant::now(),
target_dir: None,
env,
updated_sources: LazyCell::new(),
package_cache_lock: RefCell::new(None),
http_config: LazyCell::new(),
net_config: LazyCell::new(),
build_config: LazyCell::new(),
target_cfgs: LazyCell::new(),
}
}
pub fn default() -> CargoResult<Config> {
let shell = Shell::new();
let cwd =
env::current_dir().chain_err(|| "couldn't get the current directory of the process")?;
let homedir = homedir(&cwd).ok_or_else(|| {
anyhow!(
"Cargo couldn't find your home directory. \
This probably means that $HOME was not set."
)
})?;
Ok(Config::new(shell, cwd, homedir))
}
pub fn home(&self) -> &Filesystem {
&self.home_path
}
pub fn git_path(&self) -> Filesystem {
self.home_path.join("git")
}
pub fn registry_index_path(&self) -> Filesystem {
self.home_path.join("registry").join("index")
}
pub fn registry_cache_path(&self) -> Filesystem {
self.home_path.join("registry").join("cache")
}
pub fn registry_source_path(&self) -> Filesystem {
self.home_path.join("registry").join("src")
}
pub fn default_registry(&self) -> CargoResult<Option<String>> {
Ok(match self.get_string("registry.default")? {
Some(registry) => Some(registry.val),
None => None,
})
}
pub fn shell(&self) -> RefMut<'_, Shell> {
self.shell.borrow_mut()
}
pub fn rustdoc(&self) -> CargoResult<&Path> {
self.rustdoc
.try_borrow_with(|| Ok(self.get_tool("rustdoc", &self.build_config()?.rustdoc)))
.map(AsRef::as_ref)
}
pub fn load_global_rustc(&self, ws: Option<&Workspace<'_>>) -> CargoResult<Rustc> {
let cache_location = ws.map(|ws| {
ws.target_dir()
.join(".rustc_info.json")
.into_path_unlocked()
});
let wrapper = self.maybe_get_tool("rustc_wrapper", &self.build_config()?.rustc_wrapper);
let rustc_workspace_wrapper = self.maybe_get_tool(
"rustc_workspace_wrapper",
&self.build_config()?.rustc_workspace_wrapper,
);
if !self.cli_unstable().unstable_options && rustc_workspace_wrapper.is_some() {
bail!("Usage of `RUSTC_WORKSPACE_WRAPPER` requires `-Z unstable-options`")
}
Rustc::new(
self.get_tool("rustc", &self.build_config()?.rustc),
wrapper,
rustc_workspace_wrapper,
&self
.home()
.join("bin")
.join("rustc")
.into_path_unlocked()
.with_extension(env::consts::EXE_EXTENSION),
if self.cache_rustc_info {
cache_location
} else {
None
},
)
}
pub fn cargo_exe(&self) -> CargoResult<&Path> {
self.cargo_exe
.try_borrow_with(|| {
fn from_current_exe() -> CargoResult<PathBuf> {
let exe = env::current_exe()?.canonicalize()?;
Ok(exe)
}
fn from_argv() -> CargoResult<PathBuf> {
let argv0 = env::args_os()
.map(PathBuf::from)
.next()
.ok_or_else(|| anyhow!("no argv[0]"))?;
paths::resolve_executable(&argv0)
}
let exe = from_current_exe()
.or_else(|_| from_argv())
.chain_err(|| "couldn't get the path to cargo executable")?;
Ok(exe)
})
.map(AsRef::as_ref)
}
pub fn updated_sources(&self) -> RefMut<'_, HashSet<SourceId>> {
self.updated_sources
.borrow_with(|| RefCell::new(HashSet::new()))
.borrow_mut()
}
pub fn values(&self) -> CargoResult<&HashMap<String, ConfigValue>> {
self.values.try_borrow_with(|| self.load_values())
}
pub fn values_mut(&mut self) -> CargoResult<&mut HashMap<String, ConfigValue>> {
match self.values.borrow_mut() {
Some(map) => Ok(map),
None => bail!("config values not loaded yet"),
}
}
pub fn set_values(&self, values: HashMap<String, ConfigValue>) -> CargoResult<()> {
if self.values.borrow().is_some() {
bail!("config values already found")
}
match self.values.fill(values) {
Ok(()) => Ok(()),
Err(_) => bail!("could not fill values"),
}
}
pub fn reload_rooted_at<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<()> {
let values = self.load_values_from(path.as_ref())?;
self.values.replace(values);
self.merge_cli_args()?;
Ok(())
}
pub fn cwd(&self) -> &Path {
&self.cwd
}
pub fn target_dir(&self) -> CargoResult<Option<Filesystem>> {
if let Some(dir) = &self.target_dir {
Ok(Some(dir.clone()))
} else if let Some(dir) = env::var_os("CARGO_TARGET_DIR") {
Ok(Some(Filesystem::new(self.cwd.join(dir))))
} else if let Some(val) = &self.build_config()?.target_dir {
let val = val.resolve_path(self);
Ok(Some(Filesystem::new(val)))
} else {
Ok(None)
}
}
fn get_cv(&self, key: &ConfigKey) -> CargoResult<Option<ConfigValue>> {
log::trace!("get cv {:?}", key);
let vals = self.values()?;
let mut parts = key.parts().enumerate();
let mut val = match vals.get(parts.next().unwrap().1) {
Some(val) => val,
None => return Ok(None),
};
for (i, part) in parts {
match val {
CV::Table(map, _) => {
val = match map.get(part) {
Some(val) => val,
None => return Ok(None),
}
}
CV::Integer(_, def)
| CV::String(_, def)
| CV::List(_, def)
| CV::Boolean(_, def) => {
let key_so_far: Vec<&str> = key.parts().take(i).collect();
bail!(
"expected table for configuration key `{}`, \
but found {} in {}",
key_so_far.join("."),
val.desc(),
def
)
}
}
}
Ok(Some(val.clone()))
}
pub fn set_env(&mut self, env: HashMap<String, String>) {
self.env = env;
}
fn get_env<T>(&self, key: &ConfigKey) -> Result<OptValue<T>, ConfigError>
where
T: FromStr,
<T as FromStr>::Err: fmt::Display,
{
match self.env.get(key.as_env_key()) {
Some(value) => {
let definition = Definition::Environment(key.as_env_key().to_string());
Ok(Some(Value {
val: value
.parse()
.map_err(|e| ConfigError::new(format!("{}", e), definition.clone()))?,
definition,
}))
}
None => Ok(None),
}
}
fn has_key(&self, key: &ConfigKey, env_prefix_ok: bool) -> bool {
if self.env.contains_key(key.as_env_key()) {
return true;
}
if env_prefix_ok {
let env_prefix = format!("{}_", key.as_env_key());
if self.env.keys().any(|k| k.starts_with(&env_prefix)) {
return true;
}
}
if let Ok(o_cv) = self.get_cv(key) {
if o_cv.is_some() {
return true;
}
}
false
}
pub fn get_string(&self, key: &str) -> CargoResult<OptValue<String>> {
self.get::<Option<Value<String>>>(key)
}
pub fn get_path(&self, key: &str) -> CargoResult<OptValue<PathBuf>> {
self.get::<Option<Value<ConfigRelativePath>>>(key).map(|v| {
v.map(|v| Value {
val: v.val.resolve_program(self),
definition: v.definition,
})
})
}
fn string_to_path(&self, value: String, definition: &Definition) -> PathBuf {
let is_path = value.contains('/') || (cfg!(windows) && value.contains('\\'));
if is_path {
definition.root(self).join(value)
} else {
PathBuf::from(value)
}
}
pub fn get_list(&self, key: &str) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
let key = ConfigKey::from_str(key);
self._get_list(&key)
}
fn _get_list(&self, key: &ConfigKey) -> CargoResult<OptValue<Vec<(String, Definition)>>> {
match self.get_cv(key)? {
Some(CV::List(val, definition)) => Ok(Some(Value { val, definition })),
Some(val) => self.expected("list", key, &val),
None => Ok(None),
}
}
fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
let mut res = Vec::new();
match self.get_cv(key)? {
Some(CV::List(val, _def)) => res.extend(val),
Some(CV::String(val, def)) => {
let split_vs = val.split_whitespace().map(|s| (s.to_string(), def.clone()));
res.extend(split_vs);
}
Some(val) => {
return self.expected("string or array of strings", key, &val);
}
None => {}
}
self.get_env_list(key, &mut res)?;
Ok(res)
}
fn get_env_list(
&self,
key: &ConfigKey,
output: &mut Vec<(String, Definition)>,
) -> CargoResult<()> {
let env_val = match self.env.get(key.as_env_key()) {
Some(v) => v,
None => return Ok(()),
};
let def = Definition::Environment(key.as_env_key().to_string());
if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
let toml_s = format!("value={}", env_val);
let toml_v: toml::Value = toml::de::from_str(&toml_s).map_err(|e| {
ConfigError::new(format!("could not parse TOML list: {}", e), def.clone())
})?;
let values = toml_v
.as_table()
.unwrap()
.get("value")
.unwrap()
.as_array()
.expect("env var was not array");
for value in values {
let s = value.as_str().ok_or_else(|| {
ConfigError::new(
format!("expected string, found {}", value.type_str()),
def.clone(),
)
})?;
output.push((s.to_string(), def.clone()));
}
} else {
output.extend(
env_val
.split_whitespace()
.map(|s| (s.to_string(), def.clone())),
);
}
Ok(())
}
fn get_table(&self, key: &ConfigKey) -> CargoResult<OptValue<HashMap<String, CV>>> {
match self.get_cv(key)? {
Some(CV::Table(val, definition)) => Ok(Some(Value { val, definition })),
Some(val) => self.expected("table", key, &val),
None => Ok(None),
}
}
get_value_typed! {get_integer, i64, Integer, "an integer"}
get_value_typed! {get_bool, bool, Boolean, "true/false"}
get_value_typed! {get_string_priv, String, String, "a string"}
fn expected<T>(&self, ty: &str, key: &ConfigKey, val: &CV) -> CargoResult<T> {
val.expected(ty, &key.to_string())
.map_err(|e| anyhow!("invalid configuration for key `{}`\n{}", key, e))
}
pub fn configure(
&mut self,
verbose: u32,
quiet: bool,
color: Option<&str>,
frozen: bool,
locked: bool,
offline: bool,
target_dir: &Option<PathBuf>,
unstable_flags: &[String],
cli_config: &[String],
) -> CargoResult<()> {
self.unstable_flags.parse(unstable_flags)?;
if !cli_config.is_empty() {
self.unstable_flags.fail_if_stable_opt("--config", 6699)?;
self.cli_config = Some(cli_config.iter().map(|s| s.to_string()).collect());
self.merge_cli_args()?;
}
let extra_verbose = verbose >= 2;
let verbose = verbose != 0;
#[derive(Deserialize, Default)]
struct TermConfig {
verbose: Option<bool>,
color: Option<String>,
}
let term = self.get::<TermConfig>("term").unwrap_or_default();
let color = color.or_else(|| term.color.as_deref());
let verbosity = match (verbose, term.verbose, quiet) {
(true, _, false) | (_, Some(true), false) => Verbosity::Verbose,
(false, _, true) => Verbosity::Quiet,
(true, _, true) => {
bail!("cannot set both --verbose and --quiet");
}
(false, _, false) => Verbosity::Normal,
};
let cli_target_dir = match target_dir.as_ref() {
Some(dir) => Some(Filesystem::new(dir.clone())),
None => None,
};
self.shell().set_verbosity(verbosity);
self.shell().set_color_choice(color)?;
self.extra_verbose = extra_verbose;
self.frozen = frozen;
self.locked = locked;
self.offline = offline
|| self
.net_config()
.ok()
.and_then(|n| n.offline)
.unwrap_or(false);
self.target_dir = cli_target_dir;
if nightly_features_allowed() {
if let Some(val) = self.get::<Option<bool>>("unstable.mtime_on_use")? {
self.unstable_flags.mtime_on_use |= val;
}
}
Ok(())
}
pub fn cli_unstable(&self) -> &CliUnstable {
&self.unstable_flags
}
pub fn extra_verbose(&self) -> bool {
self.extra_verbose
}
pub fn network_allowed(&self) -> bool {
!self.frozen() && !self.offline()
}
pub fn offline(&self) -> bool {
self.offline
}
pub fn frozen(&self) -> bool {
self.frozen
}
pub fn lock_update_allowed(&self) -> bool {
!self.frozen && !self.locked
}
pub fn load_values(&self) -> CargoResult<HashMap<String, ConfigValue>> {
self.load_values_from(&self.cwd)
}
fn load_values_from(&self, path: &Path) -> CargoResult<HashMap<String, ConfigValue>> {
let mut cfg = CV::Table(HashMap::new(), Definition::Path(PathBuf::from(".")));
let home = self.home_path.clone().into_path_unlocked();
self.walk_tree(path, &home, |path| {
let value = self.load_file(path)?;
cfg.merge(value, false)
.chain_err(|| format!("failed to merge configuration at `{}`", path.display()))?;
Ok(())
})
.chain_err(|| "could not load Cargo configuration")?;
match cfg {
CV::Table(map, _) => Ok(map),
_ => unreachable!(),
}
}
fn load_file(&self, path: &Path) -> CargoResult<ConfigValue> {
let mut seen = HashSet::new();
self._load_file(path, &mut seen)
}
fn _load_file(&self, path: &Path, seen: &mut HashSet<PathBuf>) -> CargoResult<ConfigValue> {
if !seen.insert(path.to_path_buf()) {
bail!(
"config `include` cycle detected with path `{}`",
path.display()
);
}
let contents = fs::read_to_string(path)
.chain_err(|| format!("failed to read configuration file `{}`", path.display()))?;
let toml = cargo_toml::parse(&contents, path, self)
.chain_err(|| format!("could not parse TOML configuration in `{}`", path.display()))?;
let value = CV::from_toml(Definition::Path(path.to_path_buf()), toml).chain_err(|| {
format!(
"failed to load TOML configuration from `{}`",
path.display()
)
})?;
let value = self.load_includes(value, seen)?;
Ok(value)
}
fn load_includes(&self, mut value: CV, seen: &mut HashSet<PathBuf>) -> CargoResult<CV> {
let (includes, def) = match &mut value {
CV::Table(table, _def) => match table.remove("include") {
Some(CV::String(s, def)) => (vec![(s, def.clone())], def),
Some(CV::List(list, def)) => (list, def),
Some(other) => bail!(
"`include` expected a string or list, but found {} in `{}`",
other.desc(),
other.definition()
),
None => {
return Ok(value);
}
},
_ => unreachable!(),
};
if !self.cli_unstable().config_include {
self.shell().warn(format!("config `include` in `{}` ignored, the -Zconfig-include command-line flag is required",
def))?;
return Ok(value);
}
let mut root = CV::Table(HashMap::new(), value.definition().clone());
for (path, def) in includes {
let abs_path = match &def {
Definition::Path(p) => p.parent().unwrap().join(&path),
Definition::Environment(_) | Definition::Cli => self.cwd().join(&path),
};
self._load_file(&abs_path, seen)
.and_then(|include| root.merge(include, true))
.chain_err(|| format!("failed to load config include `{}` from `{}`", path, def))?;
}
root.merge(value, true)?;
Ok(root)
}
fn merge_cli_args(&mut self) -> CargoResult<()> {
let cli_args = match &self.cli_config {
Some(cli_args) => cli_args,
None => return Ok(()),
};
let mut loaded_args = CV::Table(HashMap::new(), Definition::Cli);
for arg in cli_args {
let arg_as_path = self.cwd.join(arg);
let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
let str_path = arg_as_path
.to_str()
.ok_or_else(|| {
anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
})?
.to_string();
let mut map = HashMap::new();
let value = CV::String(str_path, Definition::Cli);
map.insert("include".to_string(), value);
CV::Table(map, Definition::Cli)
} else {
let toml_v: toml::Value = toml::de::from_str(arg)
.chain_err(|| format!("failed to parse --config argument `{}`", arg))?;
let toml_table = toml_v.as_table().unwrap();
if toml_table.len() != 1 {
bail!(
"--config argument `{}` expected exactly one key=value pair, got {} keys",
arg,
toml_table.len()
);
}
CV::from_toml(Definition::Cli, toml_v)
.chain_err(|| format!("failed to convert --config argument `{}`", arg))?
};
let mut seen = HashSet::new();
let tmp_table = self
.load_includes(tmp_table, &mut seen)
.chain_err(|| "failed to load --config include".to_string())?;
loaded_args
.merge(tmp_table, true)
.chain_err(|| format!("failed to merge --config argument `{}`", arg))?;
}
let _ = self.values()?;
let values = self.values_mut()?;
let loaded_map = match loaded_args {
CV::Table(table, _def) => table,
_ => unreachable!(),
};
for (key, value) in loaded_map.into_iter() {
match values.entry(key) {
Vacant(entry) => {
entry.insert(value);
}
Occupied(mut entry) => entry.get_mut().merge(value, true).chain_err(|| {
format!(
"failed to merge --config key `{}` into `{}`",
entry.key(),
entry.get().definition(),
)
})?,
};
}
Ok(())
}
fn get_file_path(
&self,
dir: &Path,
filename_without_extension: &str,
warn: bool,
) -> CargoResult<Option<PathBuf>> {
let possible = dir.join(filename_without_extension);
let possible_with_extension = dir.join(format!("{}.toml", filename_without_extension));
if fs::metadata(&possible).is_ok() {
if warn && fs::metadata(&possible_with_extension).is_ok() {
let skip_warning = if let Ok(target_path) = fs::read_link(&possible) {
target_path == possible_with_extension
} else {
false
};
if !skip_warning {
self.shell().warn(format!(
"Both `{}` and `{}` exist. Using `{}`",
possible.display(),
possible_with_extension.display(),
possible.display()
))?;
}
}
Ok(Some(possible))
} else if fs::metadata(&possible_with_extension).is_ok() {
Ok(Some(possible_with_extension))
} else {
Ok(None)
}
}
fn walk_tree<F>(&self, pwd: &Path, home: &Path, mut walk: F) -> CargoResult<()>
where
F: FnMut(&Path) -> CargoResult<()>,
{
let mut stash: HashSet<PathBuf> = HashSet::new();
for current in paths::ancestors(pwd) {
if let Some(path) = self.get_file_path(¤t.join(".cargo"), "config", true)? {
walk(&path)?;
stash.insert(path);
}
}
if let Some(path) = self.get_file_path(home, "config", true)? {
if !stash.contains(&path) {
walk(&path)?;
}
}
Ok(())
}
pub fn get_registry_index(&self, registry: &str) -> CargoResult<Url> {
validate_package_name(registry, "registry name", "")?;
Ok(
match self.get_string(&format!("registries.{}.index", registry))? {
Some(index) => self.resolve_registry_index(index)?,
None => bail!("No index found for registry: `{}`", registry),
},
)
}
pub fn get_default_registry_index(&self) -> CargoResult<Option<Url>> {
Ok(match self.get_string("registry.index")? {
Some(index) => Some(self.resolve_registry_index(index)?),
None => None,
})
}
fn resolve_registry_index(&self, index: Value<String>) -> CargoResult<Url> {
let base = index
.definition
.root(self)
.join("truncated-by-url_with_base");
let _parsed = index.val.into_url()?;
let url = index.val.into_url_with_base(Some(&*base))?;
if url.password().is_some() {
bail!("Registry URLs may not contain passwords");
}
Ok(url)
}
pub fn load_credentials(&mut self) -> CargoResult<()> {
let home_path = self.home_path.clone().into_path_unlocked();
let credentials = match self.get_file_path(&home_path, "credentials", true)? {
Some(credentials) => credentials,
None => return Ok(()),
};
let mut value = self.load_file(&credentials)?;
{
let (value_map, def) = match value {
CV::Table(ref mut value, ref def) => (value, def),
_ => unreachable!(),
};
if let Some(token) = value_map.remove("token") {
if let Vacant(entry) = value_map.entry("registry".into()) {
let mut map = HashMap::new();
map.insert("token".into(), token);
let table = CV::Table(map, def.clone());
entry.insert(table);
}
}
}
if let CV::Table(map, _) = value {
let base_map = self.values_mut()?;
for (k, v) in map {
match base_map.entry(k) {
Vacant(entry) => {
entry.insert(v);
}
Occupied(mut entry) => {
entry.get_mut().merge(v, true)?;
}
}
}
}
Ok(())
}
fn maybe_get_tool(&self, tool: &str, from_config: &Option<PathBuf>) -> Option<PathBuf> {
let var = tool.to_uppercase();
match env::var_os(&var) {
Some(tool_path) => {
let maybe_relative = match tool_path.to_str() {
Some(s) => s.contains('/') || s.contains('\\'),
None => false,
};
let path = if maybe_relative {
self.cwd.join(tool_path)
} else {
PathBuf::from(tool_path)
};
Some(path)
}
None => from_config.clone(),
}
}
fn get_tool(&self, tool: &str, from_config: &Option<PathBuf>) -> PathBuf {
self.maybe_get_tool(tool, from_config)
.unwrap_or_else(|| PathBuf::from(tool))
}
pub fn jobserver_from_env(&self) -> Option<&jobserver::Client> {
self.jobserver.as_ref()
}
pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
let http = self
.easy
.try_borrow_with(|| ops::http_handle(self).map(RefCell::new))?;
{
let mut http = http.borrow_mut();
http.reset();
let timeout = ops::configure_http_handle(self, &mut http)?;
timeout.configure(&mut http)?;
}
Ok(http)
}
pub fn http_config(&self) -> CargoResult<&CargoHttpConfig> {
self.http_config
.try_borrow_with(|| Ok(self.get::<CargoHttpConfig>("http")?))
}
pub fn net_config(&self) -> CargoResult<&CargoNetConfig> {
self.net_config
.try_borrow_with(|| Ok(self.get::<CargoNetConfig>("net")?))
}
pub fn build_config(&self) -> CargoResult<&CargoBuildConfig> {
self.build_config
.try_borrow_with(|| Ok(self.get::<CargoBuildConfig>("build")?))
}
pub fn target_cfgs(&self) -> CargoResult<&Vec<(String, TargetCfgConfig)>> {
self.target_cfgs
.try_borrow_with(|| target::load_target_cfgs(self))
}
pub fn target_cfg_triple(&self, target: &str) -> CargoResult<TargetConfig> {
target::load_target_triple(self, target)
}
pub fn crates_io_source_id<F>(&self, f: F) -> CargoResult<SourceId>
where
F: FnMut() -> CargoResult<SourceId>,
{
Ok(*(self.crates_io_source_id.try_borrow_with(f)?))
}
pub fn creation_time(&self) -> Instant {
self.creation_time
}
pub fn get<'de, T: serde::de::Deserialize<'de>>(&self, key: &str) -> CargoResult<T> {
let d = Deserializer {
config: self,
key: ConfigKey::from_str(key),
env_prefix_ok: true,
};
T::deserialize(d).map_err(|e| e.into())
}
pub fn assert_package_cache_locked<'a>(&self, f: &'a Filesystem) -> &'a Path {
let ret = f.as_path_unlocked();
assert!(
self.package_cache_lock.borrow().is_some(),
"package cache lock is not currently held, Cargo forgot to call \
`acquire_package_cache_lock` before we got to this stack frame",
);
assert!(ret.starts_with(self.home_path.as_path_unlocked()));
ret
}
pub fn acquire_package_cache_lock(&self) -> CargoResult<PackageCacheLock<'_>> {
let mut slot = self.package_cache_lock.borrow_mut();
match *slot {
Some((_, ref mut cnt)) => {
*cnt += 1;
}
None => {
let path = ".package-cache";
let desc = "package cache";
match self.home_path.open_rw(path, self, desc) {
Ok(lock) => *slot = Some((Some(lock), 1)),
Err(e) => {
if maybe_readonly(&e) {
let lock = self.home_path.open_ro(path, self, desc).ok();
*slot = Some((lock, 1));
return Ok(PackageCacheLock(self));
}
Err(e).chain_err(|| "failed to acquire package cache lock")?;
}
}
}
}
return Ok(PackageCacheLock(self));
fn maybe_readonly(err: &anyhow::Error) -> bool {
err.chain().any(|err| {
if let Some(io) = err.downcast_ref::<io::Error>() {
if io.kind() == io::ErrorKind::PermissionDenied {
return true;
}
#[cfg(unix)]
return io.raw_os_error() == Some(libc::EROFS);
}
false
})
}
}
pub fn release_package_cache_lock(&self) {}
}
#[derive(Debug)]
pub struct ConfigError {
error: anyhow::Error,
definition: Option<Definition>,
}
impl ConfigError {
fn new(message: String, definition: Definition) -> ConfigError {
ConfigError {
error: anyhow::Error::msg(message),
definition: Some(definition),
}
}
fn expected(key: &ConfigKey, expected: &str, found: &ConfigValue) -> ConfigError {
ConfigError {
error: anyhow!(
"`{}` expected {}, but found a {}",
key,
expected,
found.desc()
),
definition: Some(found.definition().clone()),
}
}
fn missing(key: &ConfigKey) -> ConfigError {
ConfigError {
error: anyhow!("missing config key `{}`", key),
definition: None,
}
}
fn with_key_context(self, key: &ConfigKey, definition: Definition) -> ConfigError {
ConfigError {
error: anyhow::Error::from(self)
.context(format!("could not load config key `{}`", key)),
definition: Some(definition),
}
}
}
impl std::error::Error for ConfigError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.error.source()
}
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(definition) = &self.definition {
write!(f, "error in {}: {}", definition, self.error)
} else {
self.error.fmt(f)
}
}
}
impl serde::de::Error for ConfigError {
fn custom<T: fmt::Display>(msg: T) -> Self {
ConfigError {
error: anyhow::Error::msg(msg.to_string()),
definition: None,
}
}
}
impl From<anyhow::Error> for ConfigError {
fn from(error: anyhow::Error) -> Self {
ConfigError {
error,
definition: None,
}
}
}
#[derive(Eq, PartialEq, Clone)]
pub enum ConfigValue {
Integer(i64, Definition),
String(String, Definition),
List(Vec<(String, Definition)>, Definition),
Table(HashMap<String, ConfigValue>, Definition),
Boolean(bool, Definition),
}
impl fmt::Debug for ConfigValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CV::Integer(i, def) => write!(f, "{} (from {})", i, def),
CV::Boolean(b, def) => write!(f, "{} (from {})", b, def),
CV::String(s, def) => write!(f, "{} (from {})", s, def),
CV::List(list, def) => {
write!(f, "[")?;
for (i, (s, def)) in list.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{} (from {})", s, def)?;
}
write!(f, "] (from {})", def)
}
CV::Table(table, _) => write!(f, "{:?}", table),
}
}
}
impl ConfigValue {
fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
match toml {
toml::Value::String(val) => Ok(CV::String(val, def)),
toml::Value::Boolean(b) => Ok(CV::Boolean(b, def)),
toml::Value::Integer(i) => Ok(CV::Integer(i, def)),
toml::Value::Array(val) => Ok(CV::List(
val.into_iter()
.map(|toml| match toml {
toml::Value::String(val) => Ok((val, def.clone())),
v => bail!("expected string but found {} in list", v.type_str()),
})
.collect::<CargoResult<_>>()?,
def,
)),
toml::Value::Table(val) => Ok(CV::Table(
val.into_iter()
.map(|(key, value)| {
let value = CV::from_toml(def.clone(), value)
.chain_err(|| format!("failed to parse key `{}`", key))?;
Ok((key, value))
})
.collect::<CargoResult<_>>()?,
def,
)),
v => bail!(
"found TOML configuration value of unknown type `{}`",
v.type_str()
),
}
}
fn into_toml(self) -> toml::Value {
match self {
CV::Boolean(s, _) => toml::Value::Boolean(s),
CV::String(s, _) => toml::Value::String(s),
CV::Integer(i, _) => toml::Value::Integer(i),
CV::List(l, _) => {
toml::Value::Array(l.into_iter().map(|(s, _)| toml::Value::String(s)).collect())
}
CV::Table(l, _) => {
toml::Value::Table(l.into_iter().map(|(k, v)| (k, v.into_toml())).collect())
}
}
}
fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
match (self, from) {
(&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
let new = mem::replace(new, Vec::new());
old.extend(new.into_iter());
}
(&mut CV::Table(ref mut old, _), CV::Table(ref mut new, _)) => {
let new = mem::replace(new, HashMap::new());
for (key, value) in new {
match old.entry(key.clone()) {
Occupied(mut entry) => {
let new_def = value.definition().clone();
let entry = entry.get_mut();
entry.merge(value, force).chain_err(|| {
format!(
"failed to merge key `{}` between \
{} and {}",
key,
entry.definition(),
new_def,
)
})?;
}
Vacant(entry) => {
entry.insert(value);
}
};
}
}
(expected @ &mut CV::List(_, _), found)
| (expected @ &mut CV::Table(_, _), found)
| (expected, found @ CV::List(_, _))
| (expected, found @ CV::Table(_, _)) => {
return Err(anyhow!(
"failed to merge config value from `{}` into `{}`: expected {}, but found {}",
found.definition(),
expected.definition(),
expected.desc(),
found.desc()
));
}
(old, mut new) => {
if force || new.definition().is_higher_priority(old.definition()) {
mem::swap(old, &mut new);
}
}
}
Ok(())
}
pub fn i64(&self, key: &str) -> CargoResult<(i64, &Definition)> {
match self {
CV::Integer(i, def) => Ok((*i, def)),
_ => self.expected("integer", key),
}
}
pub fn string(&self, key: &str) -> CargoResult<(&str, &Definition)> {
match self {
CV::String(s, def) => Ok((s, def)),
_ => self.expected("string", key),
}
}
pub fn table(&self, key: &str) -> CargoResult<(&HashMap<String, ConfigValue>, &Definition)> {
match self {
CV::Table(table, def) => Ok((table, def)),
_ => self.expected("table", key),
}
}
pub fn list(&self, key: &str) -> CargoResult<&[(String, Definition)]> {
match self {
CV::List(list, _) => Ok(list),
_ => self.expected("list", key),
}
}
pub fn boolean(&self, key: &str) -> CargoResult<(bool, &Definition)> {
match self {
CV::Boolean(b, def) => Ok((*b, def)),
_ => self.expected("bool", key),
}
}
pub fn desc(&self) -> &'static str {
match *self {
CV::Table(..) => "table",
CV::List(..) => "array",
CV::String(..) => "string",
CV::Boolean(..) => "boolean",
CV::Integer(..) => "integer",
}
}
pub fn definition(&self) -> &Definition {
match self {
CV::Boolean(_, def)
| CV::Integer(_, def)
| CV::String(_, def)
| CV::List(_, def)
| CV::Table(_, def) => def,
}
}
fn expected<T>(&self, wanted: &str, key: &str) -> CargoResult<T> {
bail!(
"expected a {}, but found a {} for `{}` in {}",
wanted,
self.desc(),
key,
self.definition()
)
}
}
pub fn homedir(cwd: &Path) -> Option<PathBuf> {
::home::cargo_home_with_cwd(cwd).ok()
}
pub fn save_credentials(cfg: &Config, token: String, registry: Option<String>) -> CargoResult<()> {
let home_path = cfg.home_path.clone().into_path_unlocked();
let filename = match cfg.get_file_path(&home_path, "credentials", false)? {
Some(path) => match path.file_name() {
Some(filename) => Path::new(filename).to_owned(),
None => Path::new("credentials").to_owned(),
},
None => Path::new("credentials").to_owned(),
};
let mut file = {
cfg.home_path.create_dir()?;
cfg.home_path
.open_rw(filename, cfg, "credentials' config file")?
};
let (key, mut value) = {
let key = "token".to_string();
let value = ConfigValue::String(token, Definition::Path(file.path().to_path_buf()));
let mut map = HashMap::new();
map.insert(key, value);
let table = CV::Table(map, Definition::Path(file.path().to_path_buf()));
if let Some(registry) = registry.clone() {
let mut map = HashMap::new();
map.insert(registry, table);
(
"registries".into(),
CV::Table(map, Definition::Path(file.path().to_path_buf())),
)
} else {
("registry".into(), table)
}
};
let mut contents = String::new();
file.read_to_string(&mut contents).chain_err(|| {
format!(
"failed to read configuration file `{}`",
file.path().display()
)
})?;
let mut toml = cargo_toml::parse(&contents, file.path(), cfg)?;
if let Some(token) = toml.as_table_mut().unwrap().remove("token") {
let mut map = HashMap::new();
map.insert("token".to_string(), token);
toml.as_table_mut()
.unwrap()
.insert("registry".into(), map.into());
}
if registry.is_some() {
if let Some(table) = toml.as_table_mut().unwrap().remove("registries") {
let v = CV::from_toml(Definition::Path(file.path().to_path_buf()), table)?;
value.merge(v, false)?;
}
}
toml.as_table_mut().unwrap().insert(key, value.into_toml());
let contents = toml.to_string();
file.seek(SeekFrom::Start(0))?;
file.write_all(contents.as_bytes())?;
file.file().set_len(contents.len() as u64)?;
set_permissions(file.file(), 0o600)?;
return Ok(());
#[cfg(unix)]
fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
use std::os::unix::fs::PermissionsExt;
let mut perms = file.metadata()?.permissions();
perms.set_mode(mode);
file.set_permissions(perms)?;
Ok(())
}
#[cfg(not(unix))]
#[allow(unused)]
fn set_permissions(file: &File, mode: u32) -> CargoResult<()> {
Ok(())
}
}
pub struct PackageCacheLock<'a>(&'a Config);
impl Drop for PackageCacheLock<'_> {
fn drop(&mut self) {
let mut slot = self.0.package_cache_lock.borrow_mut();
let (_, cnt) = slot.as_mut().unwrap();
*cnt -= 1;
if *cnt == 0 {
*slot = None;
}
}
}
#[derive(Debug, Default, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct CargoHttpConfig {
pub proxy: Option<String>,
pub low_speed_limit: Option<u32>,
pub timeout: Option<u64>,
pub cainfo: Option<ConfigRelativePath>,
pub check_revoke: Option<bool>,
pub user_agent: Option<String>,
pub debug: Option<bool>,
pub multiplexing: Option<bool>,
pub ssl_version: Option<SslVersionConfig>,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum SslVersionConfig {
Single(String),
Range(SslVersionConfigRange),
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub struct SslVersionConfigRange {
pub min: Option<String>,
pub max: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CargoNetConfig {
pub retry: Option<u32>,
pub offline: Option<bool>,
pub git_fetch_with_cli: Option<bool>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CargoBuildConfig {
pub pipelining: Option<bool>,
pub dep_info_basedir: Option<ConfigRelativePath>,
pub target_dir: Option<ConfigRelativePath>,
pub incremental: Option<bool>,
pub target: Option<ConfigRelativePath>,
pub jobs: Option<u32>,
pub rustflags: Option<StringList>,
pub rustdocflags: Option<StringList>,
pub rustc_wrapper: Option<PathBuf>,
pub rustc_workspace_wrapper: Option<PathBuf>,
pub rustc: Option<PathBuf>,
pub rustdoc: Option<PathBuf>,
pub out_dir: Option<ConfigRelativePath>,
}
#[derive(Debug, Deserialize)]
pub struct StringList(Vec<String>);
impl StringList {
pub fn as_slice(&self) -> &[String] {
&self.0
}
}