1#![forbid(unsafe_code)]
10
11use crate::cargo::load_buildhost_variables;
12pub use crate::error::*;
13use std::collections::BTreeMap;
14use std::fmt::{Display, Formatter};
15use std::io::Write;
16use std::path::Path;
17
18mod cargo;
19mod error;
20#[cfg(feature = "git")]
21mod git;
22
23pub enum ErrorType {
24 IOError,
25}
26
27#[derive(Debug, Clone, Eq, PartialEq)]
28pub enum VariableSource {
29 Environment,
30 Cargo,
31 Git,
32 BuildHost,
33 Other(String),
34}
35
36#[derive(Debug, Clone, Eq, PartialEq)]
37pub enum VariableType {
38 String(String),
39 Bool(bool),
40}
41impl Display for VariableType {
42 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
43 match self {
44 VariableType::String(s) => write!(f, "{s}"),
45 VariableType::Bool(b) => write!(f, "{b}"),
46 }
47 }
48}
49impl VariableType {
50 pub fn as_str(&self) -> &str {
51 match self {
52 VariableType::String(s) => s.as_str(),
53 VariableType::Bool(b) => {
54 if *b {
55 "true"
56 } else {
57 "false"
58 }
59 }
60 }
61 }
62}
63
64#[derive(Default, Debug, Clone, Eq, PartialEq)]
65pub struct BuildEnvironment {
66 pub(crate) variables: BTreeMap<String, BuildVariable>,
67}
68impl BuildEnvironment {
69 pub fn as_parsed_environment(&self) -> ParsedBuildVariables {
70 let mut out = ParsedBuildVariables::default();
71 for (k, v) in &self.variables {
72 let v = v.value.as_str();
73 if cargo::CARGO_ENV_VARIABLES.contains(&k.as_str()) {
74 out.cargo_items.insert(k, v);
75 out.grouped.entry("CARGO_ITEMS").or_default().insert(k, v);
76 } else if cargo::RUSTC_ENV_VARIABLES.contains(&k.as_str()) {
77 out.rustc_items.insert(k, v);
78 out.grouped.entry("RUSTC_ITEMS").or_default().insert(k, v);
79 } else if cargo::BUILD_HOST_VARIABLES.contains(&k.as_str()) {
80 out.build_host.insert(k, v);
81 out.grouped.entry("BUILD_HOST").or_default().insert(k, v);
82 } else {
83 #[cfg(feature = "git")]
84 if git::GIT_VARIABLES.contains(&k.as_str()) {
85 out.git_items.insert(k, v);
86 out.grouped.entry("GIT_ITEMS").or_default().insert(k, v);
87 }
88 }
89 out.all_items.insert(k, v);
90 }
91 out
92 }
93}
94#[derive(Default, Debug, Clone, Eq, PartialEq)]
95pub struct ParsedBuildVariables<'a> {
96 pub all_items: BTreeMap<&'a str, &'a str>,
97 pub cargo_items: BTreeMap<&'a str, &'a str>,
98 pub rustc_items: BTreeMap<&'a str, &'a str>,
99 pub build_host: BTreeMap<&'a str, &'a str>,
100 pub git_items: BTreeMap<&'a str, &'a str>,
101
102 pub grouped: BTreeMap<&'a str, BTreeMap<&'a str, &'a str>>,
103}
104
105#[derive(Debug, Clone, Eq, PartialEq)]
106pub struct BuildVariable {
107 pub source: VariableSource,
108 pub name: String,
109 pub value: VariableType,
110}
111
112impl BuildVariable {
113 pub fn new_str(name: &str, value: &str, source: VariableSource) -> BuildVariable {
114 BuildVariable {
115 name: name.to_string(),
116 value: VariableType::String(value.to_string()),
117 source,
118 }
119 }
120 pub fn new_bool(name: &str, value: bool, source: VariableSource) -> BuildVariable {
121 BuildVariable {
122 name: name.to_string(),
123 value: VariableType::Bool(value),
124 source,
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
130pub struct Settings {
131 include_cargo: bool,
132 include_rustc: bool,
133 include_buildhost: bool,
134 #[cfg(feature = "git")]
135 include_git: bool,
136
137 extra_envs: Vec<String>,
138 extra_varbls: Vec<BuildVariable>,
139}
140impl Default for Settings {
141 fn default() -> Self {
142 Settings {
143 include_cargo: true,
144 include_rustc: true,
145 include_buildhost: true,
146 #[cfg(feature = "git")]
147 include_git: true,
148 extra_envs: vec![],
149 extra_varbls: vec![],
150 }
151 }
152}
153impl Settings {
154 pub fn new() -> Self {
155 Settings::default()
156 }
157 pub fn without_cargo(self) -> Self {
158 Settings {
159 include_cargo: false,
160 ..self
161 }
162 }
163 pub fn without_rustc(self) -> Self {
164 Settings {
165 include_rustc: false,
166 ..self
167 }
168 }
169 pub fn without_buildhost(self) -> Self {
170 Settings {
171 include_buildhost: false,
172 ..self
173 }
174 }
175 #[cfg(feature = "git")]
176 pub fn without_git(self) -> Self {
177 Settings {
178 include_git: false,
179 ..self
180 }
181 }
182 pub fn with_extra_envs(self, envs: &[&str]) -> Self {
183 let envs: Vec<String> = envs.iter().map(|v| v.to_string()).collect();
184 Settings {
185 extra_envs: envs,
186 ..self
187 }
188 }
189 pub fn with_extra_varbls(self, varbls: &[BuildVariable]) -> Self {
190 let varbls: Vec<BuildVariable> = Vec::from(varbls);
191 Settings {
192 extra_varbls: varbls,
193 ..self
194 }
195 }
196}
197pub fn generate_build_environment() -> Result<BuildEnvironment, Error> {
198 generate_build_environment_settings(&mut Settings::default())
199}
200
201fn add_env(name: &str, envt: &mut BuildEnvironment) {
202 let value = std::env::var(name).unwrap_or_default();
203 envt.variables.insert(
204 name.to_string(),
205 BuildVariable {
206 source: VariableSource::Environment,
207 name: name.to_string(),
208 value: VariableType::String(value),
209 },
210 );
211}
212pub fn generate_build_environment_settings(
213 settings: &mut Settings,
214) -> Result<BuildEnvironment, Error> {
215 let mut envt = BuildEnvironment::default();
216 if settings.include_cargo {
217 for varbl in cargo::CARGO_ENV_VARIABLES {
218 add_env(varbl, &mut envt);
219 }
220 }
221 if settings.include_rustc {
222 for varbl in cargo::RUSTC_ENV_VARIABLES {
223 add_env(varbl, &mut envt);
224 }
225 }
226 if settings.include_buildhost {
227 load_buildhost_variables(&mut envt)?;
228 }
229
230 for extra_env in &settings.extra_envs {
231 add_env(extra_env.as_str(), &mut envt);
232 }
233 for varbl in settings.extra_varbls.drain(..) {
234 envt.variables.insert(varbl.name.clone(), varbl);
235 }
236
237 #[cfg(feature = "git")]
238 {
239 if settings.include_git {
240 if let Err(e) = git::load_git_variables(&mut envt) {
241 eprintln!("Warning: Unable to load git variables: {e:#?}");
242 }
243 }
244 }
245
246 Ok(envt)
247}
248
249pub fn generate_module() -> Result<(), Error> {
250 generate_module_settings(Settings::default())
251}
252pub fn generate_module_settings(mut settings: Settings) -> Result<(), Error> {
253 let out_dir = std::env::var_os("OUT_DIR").unwrap();
254 let dest_path = Path::new(&out_dir);
255 std::fs::create_dir_all(dest_path)?;
256 let dest_file = dest_path.join("builders.rs");
257 let mut dest_file = std::fs::OpenOptions::new()
258 .create(true)
259 .truncate(true)
260 .write(true)
261 .open(dest_file)?;
262 dest_file.set_len(0)?;
263
264 let env = generate_build_environment_settings(&mut settings)?;
265 write_environment(&mut dest_file, &env, &settings)
266}
267pub fn write_environment<T: Write>(
268 mut dest_file: &mut T,
269 env: &BuildEnvironment,
270 settings: &Settings,
271) -> Result<(), Error> {
272 let mut groups = BTreeMap::new();
273
274 for varbl in env.variables.values() {
275 let name = &varbl.name;
276 match &varbl.value {
277 VariableType::String(val) => {
278 writeln!(dest_file, r##"pub const {name}: &str = r#"{val}"#;"##)?;
279 }
280 VariableType::Bool(val) => {
281 writeln!(dest_file, "pub const {name}: bool = {val};")?;
282 }
283 }
284 }
285
286 if settings.include_cargo {
287 filter_and_write(
288 &mut dest_file,
289 env,
290 &mut groups,
291 "CARGO_ITEMS",
292 &cargo::CARGO_ENV_VARIABLES,
293 )?;
294 }
295 if settings.include_rustc {
296 filter_and_write(
297 &mut dest_file,
298 env,
299 &mut groups,
300 "RUSTC_ITEMS",
301 &cargo::RUSTC_ENV_VARIABLES,
302 )?;
303 }
304
305 filter_and_write(
306 &mut dest_file,
307 env,
308 &mut groups,
309 "BUILD_HOST",
310 &cargo::BUILD_HOST_VARIABLES,
311 )?;
312
313 #[cfg(feature = "git")]
314 {
315 if settings.include_git {
316 filter_and_write(
317 &mut dest_file,
318 env,
319 &mut groups,
320 "GIT_ITEMS",
321 &git::GIT_VARIABLES,
322 )?;
323 }
324 }
325
326 write_aggregation_block(
327 &mut dest_file,
328 "ALL_ITEMS",
329 env.variables
330 .values()
331 .cloned()
332 .collect::<Vec<BuildVariable>>()
333 .as_slice(),
334 )?;
335 write_grouped_block(&mut dest_file, groups)?;
336
337 Ok(())
338}
339
340fn filter_and_write<T: Write>(
341 dest_file: &mut T,
342 env: &BuildEnvironment,
343 groups: &mut BTreeMap<String, Vec<BuildVariable>>,
344 name: &str,
345 filter: &[&str],
346) -> Result<(), Error> {
347 let varbls: Vec<BuildVariable> = env
348 .variables
349 .values()
350 .filter(|v| filter.contains(&v.name.as_str()))
351 .cloned()
352 .collect();
353 groups.insert(name.to_string(), varbls.clone());
354 write_aggregation_block(dest_file, name, varbls.as_slice())
355}
356
357fn write_aggregation_block<T: Write>(
358 dest_file: &mut T,
359 name: &str,
360 items: &[BuildVariable],
361) -> Result<(), Error> {
362 writeln!(
363 dest_file,
364 "static {name}: std::sync::OnceLock<std::collections::BTreeMap<&'static str, &'static str>> = std::sync::OnceLock::new();"
365 )?;
366 writeln!(dest_file, "#[allow(non_snake_case)]")?;
367 writeln!(
368 dest_file,
369 "pub fn get_{name}() -> &'static std::collections::BTreeMap<&'static str, &'static str> {{"
370 )?;
371 writeln!(
372 dest_file,
373 "\t{name}.get_or_init(|| std::collections::BTreeMap::from(["
374 )?;
375 for varbl in items {
376 let name = &varbl.name;
377 match varbl.value {
378 VariableType::String(_) => writeln!(dest_file, "\t\t(\"{name}\", {name}),")?,
379 VariableType::Bool(b) => writeln!(dest_file, "\t\t(\"{name}\", \"{b}\"),")?,
380 }
381 }
382 writeln!(dest_file, "\t]))")?;
383 writeln!(dest_file, "}}")?;
384 Ok(())
385}
386
387fn write_grouped_block<T: Write>(
388 dest_file: &mut T,
389 items: BTreeMap<String, Vec<BuildVariable>>,
390) -> Result<(), Error> {
391 writeln!(
392 dest_file,
393 "static GROUPS: std::sync::OnceLock<std::collections::BTreeMap<&'static str, std::collections::BTreeMap<&'static str, &'static str>>> = std::sync::OnceLock::new();"
394 )?;
395 writeln!(dest_file, "#[allow(non_snake_case)]")?;
396 writeln!(
397 dest_file,
398 "pub fn get_GROUPS() -> &'static std::collections::BTreeMap<&'static str, std::collections::BTreeMap<&'static str, &'static str>> {{"
399 )?;
400 writeln!(
401 dest_file,
402 "\tGROUPS.get_or_init(|| std::collections::BTreeMap::from(["
403 )?;
404 for (k, items) in items {
405 writeln!(
406 dest_file,
407 "\t\t(\"{k}\", std::collections::BTreeMap::from(["
408 )?;
409 for varbl in items {
410 let name = &varbl.name;
411 match varbl.value {
412 VariableType::String(_) => writeln!(dest_file, "\t\t\t(\"{name}\", {name}),")?,
413 VariableType::Bool(b) => writeln!(dest_file, "\t\t\t(\"{name}\", \"{b}\"),")?,
414 }
415 }
416 writeln!(dest_file, "\t\t])),")?;
417 }
418 writeln!(dest_file, "\t]))")?;
419 writeln!(dest_file, "}}")?;
420 Ok(())
421}