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