1use crate::build::{ConstType, ConstVal};
2use crate::ci::CiType;
3use crate::env::{new_project, new_system_env};
4use crate::gen_const::{
5 clap_long_version_branch_const, clap_long_version_tag_const, version_branch_const,
6 version_tag_const, BUILD_CONST_CLAP_LONG_VERSION, BUILD_CONST_VERSION,
7};
8use crate::git::new_git;
9use crate::{
10 get_std_env, BuildPattern, DateTime, SdResult, ShadowBuilder, ShadowConst,
11 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.clone() {
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 DateTime::now().to_rfc2822()
213 );
214 writeln!(&self.f, "{desc}\n\n")?;
215 Ok(())
216 }
217
218 fn write_const(&mut 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 };
249
250 writeln!(&self.f, "{desc}")?;
251 writeln!(&self.f, "{define}\n")?;
252 Ok(())
253 }
254
255 fn gen_version(&mut self) -> SdResult<Vec<&'static str>> {
256 let (ver_fn, clap_long_ver_fn) = match self.map.get(TAG) {
257 None => (version_branch_const(), clap_long_version_branch_const()),
258 Some(tag) => {
259 if !tag.v.is_empty() {
260 (version_tag_const(), clap_long_version_tag_const())
261 } else {
262 (version_branch_const(), clap_long_version_branch_const())
263 }
264 }
265 };
266 writeln!(&self.f, "{ver_fn}\n")?;
267 writeln!(&self.f, "{clap_long_ver_fn}\n")?;
268
269 Ok(vec![BUILD_CONST_VERSION, BUILD_CONST_CLAP_LONG_VERSION])
270 }
271
272 fn gen_build_in(&self, gen_const: Vec<&'static str>) -> SdResult<()> {
273 let mut print_val = String::from("\n");
274
275 for (k, v) in &self.map {
277 let tmp = match v.t {
278 ConstType::Str | ConstType::Bool => {
279 format!(r#"{}println!("{k}:{{{k}}}\n");{}"#, "\t", "\n")
280 }
281 ConstType::Slice => {
282 format!(r#"{}println!("{k}:{{:?}}\n",{});{}"#, "\t", k, "\n",)
283 }
284 };
285 print_val.push_str(tmp.as_str());
286 }
287
288 for k in gen_const {
290 let tmp = format!(r#"{}println!("{k}:{{{k}}}\n");{}"#, "\t", "\n");
291 print_val.push_str(tmp.as_str());
292 }
293
294 #[cfg(not(feature = "no_std"))]
295 {
296 let everything_define = format!(
297 "/// Prints all built-in `shadow-rs` build constants to standard output.\n\
298 #[allow(dead_code)]\n\
299 {CARGO_CLIPPY_ALLOW_ALL}\n\
300 pub fn print_build_in() {\
301 {{print_val}}\
302 }\n",
303 );
304
305 writeln!(&self.f, "{everything_define}")?;
306
307 use crate::gen_const::cargo_metadata_fn;
308 writeln!(&self.f, "{}", cargo_metadata_fn(self))?;
309 }
310
311 Ok(())
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::CARGO_TREE;
319 use std::fs;
320
321 #[test]
322 fn test_build() -> SdResult<()> {
323 ShadowBuilder::builder()
324 .src_path("./")
325 .out_path("./")
326 .build()?;
327 let shadow = fs::read_to_string(DEFINE_SHADOW_RS)?;
328 assert!(!shadow.is_empty());
329 assert!(shadow.lines().count() > 0);
330 Ok(())
331 }
332
333 #[test]
334 fn test_build_deny() -> SdResult<()> {
335 ShadowBuilder::builder()
336 .src_path("./")
337 .out_path("./")
338 .deny_const(BTreeSet::from([CARGO_TREE]))
339 .build()?;
340
341 let shadow = fs::read_to_string(DEFINE_SHADOW_RS)?;
342 assert!(!shadow.is_empty());
343 assert!(shadow.lines().count() > 0);
344 let expect = "pub const CARGO_TREE :&str";
346 assert!(!shadow.contains(expect));
347 Ok(())
348 }
349
350 #[test]
351 fn test_env() {
352 for (k, v) in std::env::vars() {
353 println!("K:{k},V:{v}");
354 }
355 }
356}