1use crate::build::{ConstType, ConstVal};
2use crate::ci::CiType;
3use crate::date_time::now_date_time;
4use crate::env::{new_project, new_system_env};
5use crate::gen_const::{
6 clap_long_version_branch_const, clap_long_version_tag_const, version_branch_const,
7 version_tag_const, BUILD_CONST_CLAP_LONG_VERSION, BUILD_CONST_VERSION,
8};
9use crate::git::new_git;
10use crate::{
11 get_std_env, BuildPattern, SdResult, ShadowBuilder, ShadowConst, CARGO_CLIPPY_ALLOW_ALL, TAG,
12};
13use std::collections::{BTreeMap, BTreeSet};
14use std::fs::File;
15use std::io::Write;
16use std::path::Path;
17
18pub(crate) const DEFINE_SHADOW_RS: &str = "shadow.rs";
19
20#[derive(Debug)]
51pub struct Shadow {
52 pub f: File,
56
57 pub map: BTreeMap<ShadowConst, ConstVal>,
61
62 pub std_env: BTreeMap<String, String>,
66
67 pub deny_const: BTreeSet<ShadowConst>,
71
72 pub out_path: String,
76
77 pub build_pattern: BuildPattern,
82}
83
84impl Shadow {
85 pub fn hook<F>(&self, f: F) -> SdResult<()>
88 where
89 F: Fn(&File) -> SdResult<()>,
90 {
91 let desc = r#"// Below code generated by project custom from by build.rs"#;
92 writeln!(&self.f, "\n{desc}\n")?;
93 f(&self.f)?;
94 Ok(())
95 }
96
97 fn try_ci(&self) -> CiType {
101 if let Some(c) = self.std_env.get("GITLAB_CI") {
102 if c == "true" {
103 return CiType::Gitlab;
104 }
105 }
106
107 if let Some(c) = self.std_env.get("GITHUB_ACTIONS") {
108 if c == "true" {
109 return CiType::Github;
110 }
111 }
112
113 CiType::None
114 }
115
116 pub fn deny_contains(&self, deny_const: ShadowConst) -> bool {
124 self.deny_const.contains(&deny_const)
125 }
126
127 pub(crate) fn build_inner(builder: ShadowBuilder) -> SdResult<Shadow> {
128 let out_path = builder.get_out_path()?;
129 let src_path = builder.get_src_path()?;
130 let build_pattern = builder.get_build_pattern().clone();
131 let deny_const = builder.get_deny_const().clone();
132
133 let out = {
134 let path = Path::new(out_path);
135 if !out_path.ends_with('/') {
136 path.join(format!("{out_path}/{DEFINE_SHADOW_RS}"))
137 } else {
138 path.join(DEFINE_SHADOW_RS)
139 }
140 };
141
142 let mut shadow = Shadow {
143 f: File::create(out)?,
144 map: Default::default(),
145 std_env: Default::default(),
146 deny_const,
147 out_path: out_path.to_string(),
148 build_pattern,
149 };
150 shadow.std_env = get_std_env();
151
152 let ci_type = shadow.try_ci();
153 let src_path = Path::new(src_path.as_str());
154
155 let mut map = new_git(src_path, ci_type, &shadow.std_env);
156 for (k, v) in new_project(&shadow.std_env) {
157 map.insert(k, v);
158 }
159 for (k, v) in new_system_env(&shadow) {
160 map.insert(k, v);
161 }
162 shadow.map = map;
163
164 shadow.filter_deny();
166
167 shadow.write_all()?;
168
169 if let Some(h) = builder.get_hook() {
171 shadow.hook(h.hook_inner())?
172 }
173
174 Ok(shadow)
175 }
176
177 fn filter_deny(&mut self) {
178 self.deny_const.iter().for_each(|x| {
179 self.map.remove(x);
180 })
181 }
182
183 fn write_all(&mut self) -> SdResult<()> {
184 self.gen_header()?;
185
186 self.gen_const()?;
187
188 let gen_version = self.gen_version()?;
190
191 self.gen_build_in(gen_version)?;
192
193 Ok(())
194 }
195
196 fn gen_const(&mut self) -> SdResult<()> {
197 let out_dir = &self.out_path;
198 self.build_pattern.rerun_if(self.map.keys(), out_dir);
199
200 for (k, v) in &self.map {
201 self.write_const(k, v)?;
202 }
203 Ok(())
204 }
205
206 fn gen_header(&self) -> SdResult<()> {
207 let desc = format!(
208 r#"// Code automatically generated by `shadow-rs` (https://github.com/baoyachi/shadow-rs), do not edit.
209// Author: https://www.github.com/baoyachi
210// Generation time: {}
211"#,
212 now_date_time().to_rfc2822()
213 );
214 writeln!(&self.f, "{desc}\n\n")?;
215 Ok(())
216 }
217
218 fn write_const(&self, shadow_const: ShadowConst, val: &ConstVal) -> SdResult<()> {
219 let desc = format!("#[doc=r#\"{}\"#]", val.desc);
220 let define = match val.t {
221 ConstType::Str => format!(
222 "#[allow(dead_code)]\n\
223 {}\n\
224 pub const {} :{} = r#\"{}\"#;",
225 CARGO_CLIPPY_ALLOW_ALL,
226 shadow_const.to_ascii_uppercase(),
227 ConstType::Str,
228 val.v
229 ),
230 ConstType::Bool => format!(
231 "#[allow(dead_code)]\n\
232 {}\n\
233 pub const {} :{} = {};",
234 CARGO_CLIPPY_ALLOW_ALL,
235 shadow_const.to_ascii_uppercase(),
236 ConstType::Bool,
237 val.v.parse::<bool>().unwrap()
238 ),
239 ConstType::Slice => format!(
240 "#[allow(dead_code)]\n\
241 {}\n\
242 pub const {} :{} = &{:?};",
243 CARGO_CLIPPY_ALLOW_ALL,
244 shadow_const.to_ascii_uppercase(),
245 ConstType::Slice,
246 val.v.as_bytes()
247 ),
248 ConstType::Usize => format!(
249 "#[allow(dead_code)]\n\
250 {}\n\
251 pub const {} :{} = {};",
252 CARGO_CLIPPY_ALLOW_ALL,
253 shadow_const.to_ascii_uppercase(),
254 ConstType::Usize,
255 val.v.parse::<usize>().unwrap_or_default()
256 ),
257 ConstType::Int => format!(
258 "#[allow(dead_code)]\n\
259 {}\n\
260 pub const {} :{} = {};",
261 CARGO_CLIPPY_ALLOW_ALL,
262 shadow_const.to_ascii_uppercase(),
263 ConstType::Int,
264 val.v.parse::<i64>().unwrap_or_default()
265 ),
266 };
267
268 writeln!(&self.f, "{desc}")?;
269 writeln!(&self.f, "{define}\n")?;
270 Ok(())
271 }
272
273 fn gen_version(&mut self) -> SdResult<Vec<&'static str>> {
274 let (ver_fn, clap_long_ver_fn) = match self.map.get(TAG) {
275 None => (version_branch_const(), clap_long_version_branch_const()),
276 Some(tag) => {
277 if !tag.v.is_empty() {
278 (version_tag_const(), clap_long_version_tag_const())
279 } else {
280 (version_branch_const(), clap_long_version_branch_const())
281 }
282 }
283 };
284 writeln!(&self.f, "{ver_fn}\n")?;
285 writeln!(&self.f, "{clap_long_ver_fn}\n")?;
286
287 Ok(vec![BUILD_CONST_VERSION, BUILD_CONST_CLAP_LONG_VERSION])
288 }
289
290 fn gen_build_in(&self, gen_const: Vec<&'static str>) -> SdResult<()> {
291 let mut print_val = String::from("\n");
292 let mut params = String::from("\n");
293 let mut default = String::from("\n");
294 let mut all = String::from("\n");
295
296 for (k, v) in &self.map {
298 let tmp = match v.t {
299 ConstType::Str | ConstType::Bool | ConstType::Usize | ConstType::Int => {
300 default.push_str(&format!("\t\t\t{k}: true,\n"));
301 all.push_str(&format!("\t\t\t{k}: true,\n"));
302 format!(
303 r#"{}if self.{k} {{ writeln!(f, "{k}:{{{k}}}\n")?; }}{}"#,
304 "\t\t", "\n"
305 )
306 }
307 ConstType::Slice => {
308 default.push_str(&format!("\t\t\t{k}: false,\n"));
309 all.push_str(&format!("\t\t\t{k}: true,\n"));
310 format!(
311 r#"{}if self.{k} {{ writeln!(f, "{k}:{{:?}}\n",{})?; }}{}"#,
312 "\t\t", k, "\n",
313 )
314 }
315 };
316 print_val.push_str(tmp.as_str());
317 params.push_str(&format!("\tpub {k}: bool,\n"));
318 }
319
320 for k in gen_const {
322 let tmp = format!(
323 r#"{}if self.{k} {{ writeln!(f, "{k}:{{{k}}}\n")?; }}{}"#,
324 "\t\t", "\n"
325 );
326 print_val.push_str(tmp.as_str());
327 params.push_str(&format!("\tpub {k}: bool,\n"));
328 default.push_str(&format!("\t\t\t{k}: true,\n"));
329 all.push_str(&format!("\t\t\t{k}: true,\n"));
330 }
331
332 default.push_str("\t\t");
333 all.push_str("\t\t");
334 print_val.push_str("\t\tOk(())\n\t");
335
336 let build_info_display_define = format!(
337 "/// A struct that implements [`core::fmt::Display`] which\n\
338 /// writes consts generated by `shadow-rs` to it's formatter\n\
339 #[allow(non_snake_case)]\n\
340 {CARGO_CLIPPY_ALLOW_ALL}\n\
341 pub struct BuildInfoDisplay {\
342 {{params}}\
343 }\n\n\
344 impl Default for BuildInfoDisplay {{\n\
345 \t#[allow(dead_code)]\n\
346 \t{CARGO_CLIPPY_ALLOW_ALL}\n\
347 \t/// Every constant that `shadow-rs` tracks will be printed\n\
348 \t/// except for slices (CARGO_METADATA for example)\n\
349 \tfn default() -> Self {{\n\
350 \t\tSelf {\
351 {{default}}\
352 }\n\
353 \t}}\n\
354 }}\n\n\
355 impl BuildInfoDisplay {{\n\
356 \t#[allow(dead_code)]\n\
357 \t{CARGO_CLIPPY_ALLOW_ALL}\n\
358 \t/// Every constant that `shadow-rs` tracks will be printed\n\
359 \tpub fn all() -> Self {{\n\
360 \t\tSelf {\
361 {{all}}\
362 }\n\
363 \t}}\n\
364 }}\n\n\
365 impl core::fmt::Display for BuildInfoDisplay {{\n\
366 \t#[allow(dead_code)]\n\
367 \t{CARGO_CLIPPY_ALLOW_ALL}\n\
368 \tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\
369 {{print_val}}\
370 }\n\
371 }}\n",
372 );
373
374 writeln!(&self.f, "{build_info_display_define}")?;
375
376 #[cfg(not(feature = "no_std"))]
377 {
378 let print_build_in_define = format!(
379 "/// Prints all built-in `shadow-rs` build constants\n\
380 /// (except for slices) to standard output.\n\
381 #[allow(dead_code)]\n\
382 {CARGO_CLIPPY_ALLOW_ALL}\n\
383 pub fn print_build_in() {{\n\
384 \tprintln!(\"{{}}\", BuildInfoDisplay::default());\n\
385 }}\n"
386 );
387
388 writeln!(&self.f, "{print_build_in_define}")?;
389
390 #[cfg(feature = "metadata")]
391 {
392 use crate::gen_const::cargo_metadata_fn;
393 writeln!(&self.f, "{}", cargo_metadata_fn(self))?;
394 }
395 }
396
397 Ok(())
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404 use crate::CARGO_TREE;
405 use std::fs;
406
407 #[test]
408 fn test_build() -> SdResult<()> {
409 ShadowBuilder::builder()
410 .src_path("./")
411 .out_path("./")
412 .build()?;
413 let shadow = fs::read_to_string(DEFINE_SHADOW_RS)?;
414 assert!(!shadow.is_empty());
415 assert!(shadow.lines().count() > 0);
416
417 fs::remove_file(DEFINE_SHADOW_RS)?;
418
419 ShadowBuilder::builder()
420 .src_path("./")
421 .out_path("./")
422 .deny_const(BTreeSet::from([CARGO_TREE]))
423 .build()?;
424
425 let content = fs::read_to_string(DEFINE_SHADOW_RS)?;
426 assert!(!content.is_empty());
427 assert!(content.lines().count() > 0);
428 let expect = "pub const CARGO_TREE :&str";
429 assert!(!content.contains(expect));
430
431 Ok(())
432 }
433}