1use crate::gen::{CfgEvaluator, CfgResult}; 2use once_cell::sync::OnceCell; 3use std::borrow::Borrow; 4use std::cmp::Ordering; 5use std::collections::{BTreeMap as Map, BTreeSet as Set}; 6use std::env; 7 8static ENV: OnceCell<CargoEnv> = OnceCell::new(); 9 10struct CargoEnv { 11 features: Set<Name>, 12 cfgs: Map<Name, String>, 13} 14 15pub(super) struct CargoEnvCfgEvaluator; 16 17impl CfgEvaluator for CargoEnvCfgEvaluator { 18 fn eval(&self, name: &str, query_value: Option<&str>) -> CfgResult { 19 let env = ENV.get_or_init(CargoEnv::load); 20 if name == "feature" { 21 return if let Some(query_value) = query_value { 22 CfgResult::from(env.features.contains(Lookup::new(query_value))) 23 } else { 24 let msg = "expected `feature = \"...\"`".to_owned(); 25 CfgResult::Undetermined { msg } 26 }; 27 } 28 if name == "test" && query_value.is_none() { 29 let msg = "cfg(test) is not supported because Cargo runs your build script only once across the lib and test build of the same crate".to_owned(); 30 return CfgResult::Undetermined { msg }; 31 } 32 if let Some(cargo_value) = env.cfgs.get(Lookup::new(name)) { 33 return if let Some(query_value) = query_value { 34 CfgResult::from(cargo_value.split(',').any(|value| value == query_value)) 35 } else { 36 CfgResult::True 37 }; 38 } 39 if name == "debug_assertions" && query_value.is_none() { 40 return CfgResult::from(cfg!(debug_assertions)); 41 } 42 CfgResult::False 43 } 44} 45 46impl CargoEnv { 47 fn load() -> Self { 48 const CARGO_FEATURE_PREFIX: &str = "CARGO_FEATURE_"; 49 const CARGO_CFG_PREFIX: &str = "CARGO_CFG_"; 50 51 let mut features = Set::new(); 52 let mut cfgs = Map::new(); 53 for (k, v) in env::vars_os() { 54 let k = match k.to_str() { 55 Some(k) => k, 56 None => continue, 57 }; 58 let v = match v.into_string() { 59 Ok(v) => v, 60 Err(_) => continue, 61 }; 62 if let Some(feature_name) = k.strip_prefix(CARGO_FEATURE_PREFIX) { 63 let feature_name = Name(feature_name.to_owned()); 64 features.insert(feature_name); 65 } else if let Some(cfg_name) = k.strip_prefix(CARGO_CFG_PREFIX) { 66 let cfg_name = Name(cfg_name.to_owned()); 67 cfgs.insert(cfg_name, v); 68 } 69 } 70 CargoEnv { features, cfgs } 71 } 72} 73 74struct Name(String); 75 76impl Ord for Name { 77 fn cmp(&self, rhs: &Self) -> Ordering { 78 Lookup::new(&self.0).cmp(Lookup::new(&rhs.0)) 79 } 80} 81 82impl PartialOrd for Name { 83 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { 84 Some(self.cmp(rhs)) 85 } 86} 87 88impl Eq for Name {} 89 90impl PartialEq for Name { 91 fn eq(&self, rhs: &Self) -> bool { 92 Lookup::new(&self.0).eq(Lookup::new(&rhs.0)) 93 } 94} 95 96#[repr(transparent)] 97struct Lookup(str); 98 99impl Lookup { 100 fn new(name: &str) -> &Self { 101 unsafe { &*(name as *const str as *const Self) } 102 } 103} 104 105impl Borrow<Lookup> for Name { 106 fn borrow(&self) -> &Lookup { 107 Lookup::new(&self.0) 108 } 109} 110 111impl Ord for Lookup { 112 fn cmp(&self, rhs: &Self) -> Ordering { 113 self.0 114 .bytes() 115 .map(CaseAgnosticByte) 116 .cmp(rhs.0.bytes().map(CaseAgnosticByte)) 117 } 118} 119 120impl PartialOrd for Lookup { 121 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { 122 Some(self.cmp(rhs)) 123 } 124} 125 126impl Eq for Lookup {} 127 128impl PartialEq for Lookup { 129 fn eq(&self, rhs: &Self) -> bool { 130 self.0 131 .bytes() 132 .map(CaseAgnosticByte) 133 .eq(rhs.0.bytes().map(CaseAgnosticByte)) 134 } 135} 136 137struct CaseAgnosticByte(u8); 138 139impl Ord for CaseAgnosticByte { 140 fn cmp(&self, rhs: &Self) -> Ordering { 141 self.0.to_ascii_lowercase().cmp(&rhs.0.to_ascii_lowercase()) 142 } 143} 144 145impl PartialOrd for CaseAgnosticByte { 146 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { 147 Some(self.cmp(rhs)) 148 } 149} 150 151impl Eq for CaseAgnosticByte {} 152 153impl PartialEq for CaseAgnosticByte { 154 fn eq(&self, rhs: &Self) -> bool { 155 self.cmp(rhs) == Ordering::Equal 156 } 157} 158