use oxdock_fs::{GuardedPath, current_head_commit};
use std::collections::HashMap;
#[derive(Debug, Default)]
pub struct BuiltinEnv {
values: HashMap<String, String>,
}
impl BuiltinEnv {
pub const WORKSPACE_GIT_COMMIT: &'static str = "WORKSPACE_GIT_COMMIT";
pub fn collect(build_context: &GuardedPath) -> Self {
let mut values = HashMap::new();
if let Ok(Some(commit)) = current_head_commit(build_context) {
values.insert(Self::WORKSPACE_GIT_COMMIT.to_string(), commit);
}
{
for (key, value) in std::env::vars() {
if key.starts_with("CARGO_FEATURE_") || key.starts_with("CARGO_CFG_") {
values.entry(key).or_insert(value);
}
}
for feature in cargo_features_from_env() {
let key = format!("CARGO_FEATURE_{}", normalize_feature_key(&feature));
values.entry(key).or_insert_with(|| "1".to_string());
}
}
Self { values }
}
pub fn into_envs(self) -> HashMap<String, String> {
self.values
}
}
fn cargo_features_from_env() -> Vec<String> {
let mut out = Vec::new();
if let Ok(cfg_features) = std::env::var("CARGO_CFG_FEATURE") {
out.extend(split_feature_list(&cfg_features));
}
out
}
fn split_feature_list(value: &str) -> Vec<String> {
value
.split([',', ' '])
.filter(|s| !s.trim().is_empty())
.map(|s| s.trim().to_string())
.collect()
}
fn normalize_feature_key(name: &str) -> String {
name.replace('-', "_").to_ascii_uppercase()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serial_cargo_env::manifest_env_guard;
use oxdock_fs::GuardedPath;
use oxdock_sys_test_utils::TestEnvGuard;
#[test]
fn collect_includes_cargo_feature_and_cfg_envs() {
let temp = GuardedPath::tempdir().expect("tempdir");
let root = temp.as_guarded_path().clone();
let _guard = manifest_env_guard(&root, true);
let _env_guard_a = TestEnvGuard::set("CARGO_FEATURE_OXDOCK_TEST", "1");
let _env_guard_b = TestEnvGuard::set("CARGO_CFG_OXDOCK_TEST", "enabled");
let env = BuiltinEnv::collect(&root).into_envs();
assert_eq!(env.get("CARGO_FEATURE_OXDOCK_TEST"), Some(&"1".into()));
assert_eq!(env.get("CARGO_CFG_OXDOCK_TEST"), Some(&"enabled".into()));
}
#[test]
fn collect_derives_feature_keys_from_cfg_feature_list() {
let temp = GuardedPath::tempdir().expect("tempdir");
let root = temp.as_guarded_path().clone();
let _guard = manifest_env_guard(&root, true);
let _cfg = TestEnvGuard::set("CARGO_CFG_FEATURE", "my-feat, other_feat,, spaced ");
let env = BuiltinEnv::collect(&root).into_envs();
assert_eq!(env.get("CARGO_FEATURE_MY_FEAT"), Some(&"1".to_string()));
assert_eq!(env.get("CARGO_FEATURE_OTHER_FEAT"), Some(&"1".to_string()));
assert_eq!(env.get("CARGO_FEATURE_SPACED"), Some(&"1".to_string()));
}
#[test]
fn collect_prefers_existing_feature_env_over_derived_value() {
let temp = GuardedPath::tempdir().expect("tempdir");
let root = temp.as_guarded_path().clone();
let _guard = manifest_env_guard(&root, true);
let _cfg = TestEnvGuard::set("CARGO_CFG_FEATURE", "my-feat");
let _existing = TestEnvGuard::set("CARGO_FEATURE_MY_FEAT", "custom");
let env = BuiltinEnv::collect(&root).into_envs();
assert_eq!(
env.get("CARGO_FEATURE_MY_FEAT"),
Some(&"custom".to_string()),
"derived '1' must not overwrite an explicit env value"
);
}
}