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