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 };
258
259 writeln!(&self.f, "{desc}")?;
260 writeln!(&self.f, "{define}\n")?;
261 Ok(())
262 }
263
264 fn gen_version(&mut self) -> SdResult<Vec<&'static str>> {
265 let (ver_fn, clap_long_ver_fn) = match self.map.get(TAG) {
266 None => (version_branch_const(), clap_long_version_branch_const()),
267 Some(tag) => {
268 if !tag.v.is_empty() {
269 (version_tag_const(), clap_long_version_tag_const())
270 } else {
271 (version_branch_const(), clap_long_version_branch_const())
272 }
273 }
274 };
275 writeln!(&self.f, "{ver_fn}\n")?;
276 writeln!(&self.f, "{clap_long_ver_fn}\n")?;
277
278 Ok(vec![BUILD_CONST_VERSION, BUILD_CONST_CLAP_LONG_VERSION])
279 }
280
281 fn gen_build_in(&self, gen_const: Vec<&'static str>) -> SdResult<()> {
282 let mut print_val = String::from("\n");
283 let mut params = String::from("\n");
284 let mut default = String::from("\n");
285 let mut all = String::from("\n");
286
287 for (k, v) in &self.map {
289 let tmp = match v.t {
290 ConstType::Str | ConstType::Bool | ConstType::Usize => {
291 default.push_str(&format!("\t\t\t{k}: true,\n"));
292 all.push_str(&format!("\t\t\t{k}: true,\n"));
293 format!(
294 r#"{}if self.{k} {{ writeln!(f, "{k}:{{{k}}}\n")?; }}{}"#,
295 "\t\t", "\n"
296 )
297 }
298 ConstType::Slice => {
299 default.push_str(&format!("\t\t\t{k}: false,\n"));
300 all.push_str(&format!("\t\t\t{k}: true,\n"));
301 format!(
302 r#"{}if self.{k} {{ writeln!(f, "{k}:{{:?}}\n",{})?; }}{}"#,
303 "\t\t", k, "\n",
304 )
305 }
306 };
307 print_val.push_str(tmp.as_str());
308 params.push_str(&format!("\tpub {k}: bool,\n"));
309 }
310
311 for k in gen_const {
313 let tmp = format!(
314 r#"{}if self.{k} {{ writeln!(f, "{k}:{{{k}}}\n")?; }}{}"#,
315 "\t\t", "\n"
316 );
317 print_val.push_str(tmp.as_str());
318 params.push_str(&format!("\tpub {k}: bool,\n"));
319 default.push_str(&format!("\t\t\t{k}: true,\n"));
320 all.push_str(&format!("\t\t\t{k}: true,\n"));
321 }
322
323 default.push_str("\t\t");
324 all.push_str("\t\t");
325 print_val.push_str("\t\tOk(())\n\t");
326
327 let build_info_display_define = format!(
328 "/// A struct that implements [`core::fmt::Display`] which\n\
329 /// writes consts generated by `shadow-rs` to it's formatter\n\
330 #[allow(non_snake_case)]\n\
331 {CARGO_CLIPPY_ALLOW_ALL}\n\
332 pub struct BuildInfoDisplay {\
333 {{params}}\
334 }\n\n\
335 impl Default for BuildInfoDisplay {{\n\
336 \t#[allow(dead_code)]\n\
337 \t{CARGO_CLIPPY_ALLOW_ALL}\n\
338 \t/// Every constant that `shadow-rs` tracks will be printed\n\
339 \t/// except for slices (CARGO_METADATA for example)\n\
340 \tfn default() -> Self {{\n\
341 \t\tSelf {\
342 {{default}}\
343 }\n\
344 \t}}\n\
345 }}\n\n\
346 impl BuildInfoDisplay {{\n\
347 \t#[allow(dead_code)]\n\
348 \t{CARGO_CLIPPY_ALLOW_ALL}\n\
349 \t/// Every constant that `shadow-rs` tracks will be printed\n\
350 \tpub fn all() -> Self {{\n\
351 \t\tSelf {\
352 {{all}}\
353 }\n\
354 \t}}\n\
355 }}\n\n\
356 impl core::fmt::Display for BuildInfoDisplay {{\n\
357 \t#[allow(dead_code)]\n\
358 \t{CARGO_CLIPPY_ALLOW_ALL}\n\
359 \tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\
360 {{print_val}}\
361 }\n\
362 }}\n",
363 );
364
365 writeln!(&self.f, "{build_info_display_define}")?;
366
367 #[cfg(not(feature = "no_std"))]
368 {
369 let print_build_in_define = format!(
370 "/// Prints all built-in `shadow-rs` build constants\n\
371 /// (except for slices) to standard output.\n\
372 #[allow(dead_code)]\n\
373 {CARGO_CLIPPY_ALLOW_ALL}\n\
374 pub fn print_build_in() {{\n\
375 \tprintln!(\"{{}}\", BuildInfoDisplay::default());\n\
376 }}\n"
377 );
378
379 writeln!(&self.f, "{print_build_in_define}")?;
380
381 #[cfg(feature = "metadata")]
382 {
383 use crate::gen_const::cargo_metadata_fn;
384 writeln!(&self.f, "{}", cargo_metadata_fn(self))?;
385 }
386 }
387
388 Ok(())
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395 use crate::CARGO_TREE;
396 use std::fs;
397
398 #[test]
399 fn test_build() -> SdResult<()> {
400 ShadowBuilder::builder()
401 .src_path("./")
402 .out_path("./")
403 .build()?;
404 let shadow = fs::read_to_string(DEFINE_SHADOW_RS)?;
405 assert!(!shadow.is_empty());
406 assert!(shadow.lines().count() > 0);
407
408 fs::remove_file(DEFINE_SHADOW_RS)?;
409
410 ShadowBuilder::builder()
411 .src_path("./")
412 .out_path("./")
413 .deny_const(BTreeSet::from([CARGO_TREE]))
414 .build()?;
415
416 let content = fs::read_to_string(DEFINE_SHADOW_RS)?;
417 assert!(!content.is_empty());
418 assert!(content.lines().count() > 0);
419 let expect = "pub const CARGO_TREE :&str";
420 assert!(!content.contains(expect));
421
422 Ok(())
423 }
424}