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::sync::OnceLock;
7
8static ENV: OnceLock<CargoEnv> = OnceLock::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 Some(k) = k.to_str() else {
55 continue;
56 };
57 let Ok(v) = v.into_string() else {
58 continue;
59 };
60 if let Some(feature_name) = k.strip_prefix(CARGO_FEATURE_PREFIX) {
61 let feature_name = Name(feature_name.to_owned());
62 features.insert(feature_name);
63 } else if let Some(cfg_name) = k.strip_prefix(CARGO_CFG_PREFIX) {
64 let cfg_name = Name(cfg_name.to_owned());
65 cfgs.insert(cfg_name, v);
66 }
67 }
68 CargoEnv { features, cfgs }
69 }
70}
71
72struct Name(String);
73
74impl Ord for Name {
75 fn cmp(&self, rhs: &Self) -> Ordering {
76 Lookup::new(&self.0).cmp(Lookup::new(&rhs.0))
77 }
78}
79
80impl PartialOrd for Name {
81 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
82 Some(self.cmp(rhs))
83 }
84}
85
86impl Eq for Name {}
87
88impl PartialEq for Name {
89 fn eq(&self, rhs: &Self) -> bool {
90 Lookup::new(&self.0).eq(Lookup::new(&rhs.0))
91 }
92}
93
94#[repr(transparent)]
95struct Lookup(str);
96
97impl Lookup {
98 fn new(name: &str) -> &Self {
99 unsafe { &*(name as *const str as *const Self) }
100 }
101}
102
103impl Borrow<Lookup> for Name {
104 fn borrow(&self) -> &Lookup {
105 Lookup::new(&self.0)
106 }
107}
108
109impl Ord for Lookup {
110 fn cmp(&self, rhs: &Self) -> Ordering {
111 self.0
112 .bytes()
113 .map(CaseAgnosticByte)
114 .cmp(rhs.0.bytes().map(CaseAgnosticByte))
115 }
116}
117
118impl PartialOrd for Lookup {
119 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
120 Some(self.cmp(rhs))
121 }
122}
123
124impl Eq for Lookup {}
125
126impl PartialEq for Lookup {
127 fn eq(&self, rhs: &Self) -> bool {
128 self.0
129 .bytes()
130 .map(CaseAgnosticByte)
131 .eq(rhs.0.bytes().map(CaseAgnosticByte))
132 }
133}
134
135struct CaseAgnosticByte(u8);
136
137impl Ord for CaseAgnosticByte {
138 fn cmp(&self, rhs: &Self) -> Ordering {
139 self.0.to_ascii_lowercase().cmp(&rhs.0.to_ascii_lowercase())
140 }
141}
142
143impl PartialOrd for CaseAgnosticByte {
144 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
145 Some(self.cmp(rhs))
146 }
147}
148
149impl Eq for CaseAgnosticByte {}
150
151impl PartialEq for CaseAgnosticByte {
152 fn eq(&self, rhs: &Self) -> bool {
153 self.cmp(rhs) == Ordering::Equal
154 }
155}