1 use crate::gen::{CfgEvaluator, CfgResult}; 2 use once_cell::sync::OnceCell; 3 use std::borrow::Borrow; 4 use std::cmp::Ordering; 5 use std::collections::{BTreeMap as Map, BTreeSet as Set}; 6 use std::env; 7 8 static ENV: OnceCell<CargoEnv> = OnceCell::new(); 9 10 struct CargoEnv { 11 features: Set<Name>, 12 cfgs: Map<Name, String>, 13 } 14 15 pub(super) struct CargoEnvCfgEvaluator; 16 17 impl CfgEvaluator for CargoEnvCfgEvaluator { eval(&self, name: &str, query_value: Option<&str>) -> CfgResult18 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 46 impl CargoEnv { load() -> Self47 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 74 struct Name(String); 75 76 impl Ord for Name { cmp(&self, rhs: &Self) -> Ordering77 fn cmp(&self, rhs: &Self) -> Ordering { 78 Lookup::new(&self.0).cmp(Lookup::new(&rhs.0)) 79 } 80 } 81 82 impl PartialOrd for Name { partial_cmp(&self, rhs: &Self) -> Option<Ordering>83 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { 84 Some(self.cmp(rhs)) 85 } 86 } 87 88 impl Eq for Name {} 89 90 impl PartialEq for Name { eq(&self, rhs: &Self) -> bool91 fn eq(&self, rhs: &Self) -> bool { 92 Lookup::new(&self.0).eq(Lookup::new(&rhs.0)) 93 } 94 } 95 96 #[repr(transparent)] 97 struct Lookup(str); 98 99 impl Lookup { new(name: &str) -> &Self100 fn new(name: &str) -> &Self { 101 unsafe { &*(name as *const str as *const Self) } 102 } 103 } 104 105 impl Borrow<Lookup> for Name { borrow(&self) -> &Lookup106 fn borrow(&self) -> &Lookup { 107 Lookup::new(&self.0) 108 } 109 } 110 111 impl Ord for Lookup { cmp(&self, rhs: &Self) -> Ordering112 fn cmp(&self, rhs: &Self) -> Ordering { 113 self.0 114 .bytes() 115 .map(CaseAgnosticByte) 116 .cmp(rhs.0.bytes().map(CaseAgnosticByte)) 117 } 118 } 119 120 impl PartialOrd for Lookup { partial_cmp(&self, rhs: &Self) -> Option<Ordering>121 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { 122 Some(self.cmp(rhs)) 123 } 124 } 125 126 impl Eq for Lookup {} 127 128 impl PartialEq for Lookup { eq(&self, rhs: &Self) -> bool129 fn eq(&self, rhs: &Self) -> bool { 130 self.0 131 .bytes() 132 .map(CaseAgnosticByte) 133 .eq(rhs.0.bytes().map(CaseAgnosticByte)) 134 } 135 } 136 137 struct CaseAgnosticByte(u8); 138 139 impl Ord for CaseAgnosticByte { cmp(&self, rhs: &Self) -> Ordering140 fn cmp(&self, rhs: &Self) -> Ordering { 141 self.0.to_ascii_lowercase().cmp(&rhs.0.to_ascii_lowercase()) 142 } 143 } 144 145 impl PartialOrd for CaseAgnosticByte { partial_cmp(&self, rhs: &Self) -> Option<Ordering>146 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { 147 Some(self.cmp(rhs)) 148 } 149 } 150 151 impl Eq for CaseAgnosticByte {} 152 153 impl PartialEq for CaseAgnosticByte { eq(&self, rhs: &Self) -> bool154 fn eq(&self, rhs: &Self) -> bool { 155 self.cmp(rhs) == Ordering::Equal 156 } 157 } 158