diff --git a/Cargo.toml b/Cargo.toml index 10eeeb09e..7d06f915c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ directories = "4.0.1" walkdir = { version = "2", optional = true } tempfile = "3.3.0" owo-colors = { version = "3.4.0", features = ["supports-colors"] } +once_cell = "1.12" [target.'cfg(not(windows))'.dependencies] nix = { version = "0.24", default-features = false, features = ["user"] } diff --git a/src/cargo_toml.rs b/src/cargo_config.rs similarity index 60% rename from src/cargo_toml.rs rename to src/cargo_config.rs index 93d909dfa..361bab5ea 100644 --- a/src/cargo_toml.rs +++ b/src/cargo_config.rs @@ -4,20 +4,44 @@ use std::path::Path; use crate::errors::*; use crate::file; +use crate::shell::MessageInfo; +use once_cell::sync::OnceCell; type Table = toml::value::Table; type Value = toml::value::Value; +pub const CARGO_NO_PREFIX_ENVVARS: &[&str] = &[ + "http_proxy", + "TERM", + "RUSTDOCFLAGS", + "RUSTFLAGS", + "BROWSER", + "HTTPS_PROXY", + "HTTP_TIMEOUT", + "https_proxy", +]; + +// TODO(ahuszagh) This should really be cargo_toml and cargo_env. +// Should have it as a once_cell? + +// CARGO_ALIAS_ + // the strategy is to merge, with arrays merging together // and the deeper the config file is, the higher its priority. // arrays merge, numbers/strings get replaced, objects merge in. // we don't want to make any assumptions about the cargo // config data, in case we need to use it later. #[derive(Debug, Clone, Default)] -pub struct CargoConfig(Table); +pub struct CargoToml(Table); -impl CargoConfig { - fn merge(&mut self, parent: &CargoConfig) -> Result<()> { +impl CargoToml { + fn parse(path: &Path) -> Result { + let contents = file::read(&path) + .wrap_err_with(|| format!("could not read cargo config file at `{path:?}`"))?; + Ok(CargoToml(toml::from_str(&contents)?)) + } + + fn merge(&mut self, parent: &CargoToml) -> Result<()> { // can error on mismatched-data fn validate_types(x: &Value, y: &Value) -> Option<()> { @@ -66,6 +90,8 @@ impl CargoConfig { } // get all the aliases from the map + // TODO(ahuszagh) This needs to be an result of an option. + // If we don't have as_str, needs to fail as a result pub fn alias<'a>(&'a self) -> Option>> { let parse_alias = |v: &'a Value| -> Option> { if let Some(s) = v.as_str() { @@ -86,35 +112,99 @@ impl CargoConfig { } } -fn parse_config_file(path: &Path) -> Result { - let contents = file::read(&path) - .wrap_err_with(|| format!("could not read cargo config file `{path:?}`"))?; - Ok(CargoConfig(toml::from_str(&contents)?)) +// wrapper for all the cargo environment variables. +#[derive(Debug, Clone, Default)] +pub struct CargoEnv(BTreeMap); + +impl CargoEnv { + fn create() -> CargoEnv { + let mut result = BTreeMap::new(); + for (key, value) in env::vars() { + if key.starts_with("CARGO_") || CARGO_NO_PREFIX_ENVVARS.contains(&key.as_str()) { + result.insert(key, value); + } + } + + CargoEnv(result) + } + + // get all the aliases from the map + pub fn alias<'a>(&'a self) -> Result>> { + let mut result = BTreeMap::new(); + for (key, value) in &self.0 { + if let Some(alias) = key.strip_prefix("CARGO_ALIAS_") { + // cargo skips empty aliases + match alias.chars().all(char::is_uppercase) { + true if !alias.is_empty() => { + let value = value.split_whitespace().collect(); + result.insert(key.to_lowercase(), value); + } + false => { + MessageInfo::default().warn(format_args!( + "Environment variables are expected to use uppercase letters and underscores, the variable `{key}` will be ignored and have no effect" + ))?; + } + // `CARGO_ALIAS_` is ignored + _ => (), + } + } + } + + Ok(result) + } +} + +// merged result of cargo TOML config and environment variables +// we use a toml setting here, since it abstracts the types +#[derive(Debug, Clone, Default)] +pub struct CargoConfig(BTreeMap); + +impl CargoConfig { + fn create(config_toml: &CargoToml, config_env: &CargoEnv) -> Result { + let mut result = BTreeMap::new(); + let toml_aliases = config_toml.alias(); // TODO(ahuszagh) Must be used + let env_aliases = config_env.alias()?; + // TODO(ahuszagh) Here... + // We only care about values we actually know. + + +// for (key, value) in env::vars() { +// if key.starts_with("CARGO_") || CARGO_NO_PREFIX_ENVVARS.contains(&key.as_str()) { +// result.insert(key, value); +// } +// } + + Ok(CargoConfig(result)) + } + + fn alias(&self) -> BTreeMap> { + todo!(); + } } // finding cargo config files actually runs from the // current working directory the command is invoked, // not from the project root. same is true with work // spaces: the project layout does not matter. -pub fn read_config_files() -> Result { +pub fn read_config_files() -> Result { // note: cargo supports both `config` and `config.toml` // `config` exists for compatibility reasons, but if // present, only it will be read. - let read_and_merge = |result: &mut CargoConfig, dir: &Path| -> Result<()> { + let read_and_merge = |result: &mut CargoToml, dir: &Path| -> Result<()> { let noext = dir.join("config"); let ext = dir.join("config.toml"); if noext.exists() { - let parent = parse_config_file(&noext)?; + let parent = CargoToml::parse(&noext)?; result.merge(&parent)?; } else if ext.exists() { - let parent = parse_config_file(&ext)?; + let parent = CargoToml::parse(&ext)?; result.merge(&parent)?; } Ok(()) }; - let mut result = CargoConfig::default(); + let mut result = CargoToml::default(); let cwd = env::current_dir()?; let mut dir: &Path = &cwd; loop { @@ -131,14 +221,40 @@ pub fn read_config_files() -> Result { Ok(result) } +static CARGO_TOML: OnceCell = OnceCell::new(); + +pub fn get_cargo_toml() -> &'static CargoToml { + CARGO_TOML + .get_or_try_init::<_, eyre::Report>(|| { + read_config_files() + }) + .unwrap() +} + +static CARGO_ENV: OnceCell = OnceCell::new(); + +pub fn get_cargo_env() -> &'static CargoEnv { + CARGO_ENV.get_or_init(|| CargoEnv::create()) +} + +static CARGO_CONFIG: OnceCell = OnceCell::new(); + +pub fn get_cargo_config() -> &'static CargoConfig { + CARGO_CONFIG + .get_or_try_init::<_, eyre::Report>(|| { + CargoConfig::create(get_cargo_toml(), get_cargo_env()) + }) + .unwrap() +} + #[cfg(test)] mod tests { use super::*; #[test] fn test_parse() -> Result<()> { - let config1 = CargoConfig(toml::from_str(CARGO_TOML1)?); - let config2 = CargoConfig(toml::from_str(CARGO_TOML2)?); + let config1 = CargoToml(toml::from_str(CARGO_TOML1)?); + let config2 = CargoToml(toml::from_str(CARGO_TOML2)?); let alias1 = config1.alias().expect("unable to get aliases."); let alias2 = config2.alias().expect("unable to get aliases."); assert_eq!( @@ -184,9 +300,9 @@ mod tests { } #[test] - fn test_read_config() -> Result<()> { + fn test_read_config() { // cross contains a few aliases, so test those - let config = read_config_files()?; + let config = get_cargo_toml(); let aliases = config.alias().expect("must have aliases"); assert_eq!( aliases.get("build-docker-image"), @@ -196,8 +312,6 @@ mod tests { aliases.get("xtask"), Some(&vec!["run", "-p", "xtask", "--"]), ); - - Ok(()) } const CARGO_TOML1: &str = r#" diff --git a/src/cli.rs b/src/cli.rs index 1b5fb6f43..ab02d942a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,8 @@ use std::{env, path::PathBuf}; use crate::cargo::Subcommand; -use crate::cargo_toml::read_config_files; +// TODO(ahuszagh) Change to get_cargo_config +use crate::cargo_config::get_cargo_toml; use crate::errors::Result; use crate::rustc::TargetList; use crate::shell::{self, MessageInfo}; @@ -150,7 +151,7 @@ fn parse_subcommand( } let subcommand = Subcommand::from(arg.as_ref()); if subcommand == Subcommand::Other { - let config = read_config_files()?; + let config = get_cargo_toml(); if let Some(aliases) = config.alias() { if let Some(alias) = aliases.get(arg.as_str()) { let mut iter = alias.iter().cloned().map(|x| x.to_owned()); diff --git a/src/docker/shared.rs b/src/docker/shared.rs index be23bb54b..b3ea25a8e 100644 --- a/src/docker/shared.rs +++ b/src/docker/shared.rs @@ -6,6 +6,7 @@ use std::{env, fs}; use super::custom::{Dockerfile, PreBuild}; use super::engine::*; use crate::cargo::{cargo_metadata_with_args, CargoMetadata}; +use crate::cargo_config::CARGO_NO_PREFIX_ENVVARS; use crate::config::{bool_from_envvar, Config}; use crate::errors::*; use crate::extensions::{CommandExt, SafeCommand}; @@ -425,16 +426,6 @@ pub(crate) fn cargo_safe_command(uses_xargo: bool) -> SafeCommand { } fn add_cargo_configuration_envvars(docker: &mut Command) { - let non_cargo_prefix = &[ - "http_proxy", - "TERM", - "RUSTDOCFLAGS", - "RUSTFLAGS", - "BROWSER", - "HTTPS_PROXY", - "HTTP_TIMEOUT", - "https_proxy", - ]; let cargo_prefix_skip = &[ "CARGO_HOME", "CARGO_TARGET_DIR", @@ -445,7 +436,7 @@ fn add_cargo_configuration_envvars(docker: &mut Command) { "CARGO_BUILD_RUSTDOC", ]; let is_cargo_passthrough = |key: &str| -> bool { - non_cargo_prefix.contains(&key) + CARGO_NO_PREFIX_ENVVARS.contains(&key) || key.starts_with("CARGO_") && !cargo_prefix_skip.contains(&key) }; diff --git a/src/lib.rs b/src/lib.rs index 3f0e97bdf..87f67f9cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ mod tests; mod cargo; -mod cargo_toml; +mod cargo_config; mod cli; mod config; mod cross_toml;