use crate::build::{ConstType, ConstVal};
use crate::ci::CiType;
use crate::date_time::now_date_time;
use crate::env::{new_project, new_system_env};
use crate::gen_const::{
clap_long_version_branch_const, clap_long_version_tag_const, version_branch_const,
version_tag_const, BUILD_CONST_CLAP_LONG_VERSION, BUILD_CONST_VERSION,
};
use crate::git::new_git;
use crate::{
get_std_env, BuildPattern, SdResult, ShadowBuilder, ShadowConst, CARGO_CLIPPY_ALLOW_ALL, TAG,
};
use std::collections::{BTreeMap, BTreeSet};
use std::fs::File;
use std::io::Write;
use std::path::Path;
pub(crate) const DEFINE_SHADOW_RS: &str = "shadow.rs";
#[derive(Debug)]
pub struct Shadow {
pub f: File,
pub map: BTreeMap<ShadowConst, ConstVal>,
pub std_env: BTreeMap<String, String>,
pub deny_const: BTreeSet<ShadowConst>,
pub out_path: String,
pub build_pattern: BuildPattern,
}
impl Shadow {
pub fn hook<F>(&self, f: F) -> SdResult<()>
where
F: Fn(&File) -> SdResult<()>,
{
let desc = r#"// Below code generated by project custom from by build.rs"#;
writeln!(&self.f, "\n{desc}\n")?;
f(&self.f)?;
Ok(())
}
fn try_ci(&self) -> CiType {
if let Some(c) = self.std_env.get("GITLAB_CI") {
if c == "true" {
return CiType::Gitlab;
}
}
if let Some(c) = self.std_env.get("GITHUB_ACTIONS") {
if c == "true" {
return CiType::Github;
}
}
CiType::None
}
pub fn deny_contains(&self, deny_const: ShadowConst) -> bool {
self.deny_const.contains(&deny_const)
}
pub(crate) fn build_inner(builder: ShadowBuilder) -> SdResult<Shadow> {
let out_path = builder.get_out_path()?;
let src_path = builder.get_src_path()?;
let build_pattern = builder.get_build_pattern().clone();
let deny_const = builder.get_deny_const().clone();
let out = {
let path = Path::new(out_path);
if !out_path.ends_with('/') {
path.join(format!("{out_path}/{DEFINE_SHADOW_RS}"))
} else {
path.join(DEFINE_SHADOW_RS)
}
};
let mut shadow = Shadow {
f: File::create(out)?,
map: Default::default(),
std_env: Default::default(),
deny_const,
out_path: out_path.to_string(),
build_pattern,
};
shadow.std_env = get_std_env();
let ci_type = shadow.try_ci();
let src_path = Path::new(src_path.as_str());
let mut map = new_git(src_path, ci_type, &shadow.std_env);
for (k, v) in new_project(&shadow.std_env) {
map.insert(k, v);
}
for (k, v) in new_system_env(&shadow) {
map.insert(k, v);
}
shadow.map = map;
shadow.filter_deny();
shadow.write_all()?;
if let Some(h) = builder.get_hook() {
shadow.hook(h.hook_inner())?
}
Ok(shadow)
}
fn filter_deny(&mut self) {
self.deny_const.iter().for_each(|x| {
self.map.remove(x);
})
}
fn write_all(&mut self) -> SdResult<()> {
self.gen_header()?;
self.gen_const()?;
let gen_version = self.gen_version()?;
self.gen_build_in(gen_version)?;
Ok(())
}
fn gen_const(&mut self) -> SdResult<()> {
let out_dir = &self.out_path;
self.build_pattern.rerun_if(self.map.keys(), out_dir);
for (k, v) in &self.map {
self.write_const(k, v)?;
}
Ok(())
}
fn gen_header(&self) -> SdResult<()> {
let desc = format!(
r#"// Code automatically generated by `shadow-rs` (https://github.com/baoyachi/shadow-rs), do not edit.
// Author: https://www.github.com/baoyachi
// Generation time: {}
"#,
now_date_time().to_rfc2822()
);
writeln!(&self.f, "{desc}\n\n")?;
Ok(())
}
fn write_const(&self, shadow_const: ShadowConst, val: &ConstVal) -> SdResult<()> {
let desc = format!("#[doc=r#\"{}\"#]", val.desc);
let define = match val.t {
ConstType::Str => format!(
"#[allow(dead_code)]\n\
{}\n\
pub const {} :{} = r#\"{}\"#;",
CARGO_CLIPPY_ALLOW_ALL,
shadow_const.to_ascii_uppercase(),
ConstType::Str,
val.v
),
ConstType::Bool => format!(
"#[allow(dead_code)]\n\
{}\n\
pub const {} :{} = {};",
CARGO_CLIPPY_ALLOW_ALL,
shadow_const.to_ascii_uppercase(),
ConstType::Bool,
val.v.parse::<bool>().unwrap()
),
ConstType::Slice => format!(
"#[allow(dead_code)]\n\
{}\n\
pub const {} :{} = &{:?};",
CARGO_CLIPPY_ALLOW_ALL,
shadow_const.to_ascii_uppercase(),
ConstType::Slice,
val.v.as_bytes()
),
ConstType::Usize => format!(
"#[allow(dead_code)]\n\
{}\n\
pub const {} :{} = {};",
CARGO_CLIPPY_ALLOW_ALL,
shadow_const.to_ascii_uppercase(),
ConstType::Usize,
val.v.parse::<usize>().unwrap_or_default()
),
ConstType::Int => format!(
"#[allow(dead_code)]\n\
{}\n\
pub const {} :{} = {};",
CARGO_CLIPPY_ALLOW_ALL,
shadow_const.to_ascii_uppercase(),
ConstType::Int,
val.v.parse::<i64>().unwrap_or_default()
),
};
writeln!(&self.f, "{desc}")?;
writeln!(&self.f, "{define}\n")?;
Ok(())
}
fn gen_version(&mut self) -> SdResult<Vec<&'static str>> {
let (ver_fn, clap_long_ver_fn) = match self.map.get(TAG) {
None => (version_branch_const(), clap_long_version_branch_const()),
Some(tag) => {
if !tag.v.is_empty() {
(version_tag_const(), clap_long_version_tag_const())
} else {
(version_branch_const(), clap_long_version_branch_const())
}
}
};
writeln!(&self.f, "{ver_fn}\n")?;
writeln!(&self.f, "{clap_long_ver_fn}\n")?;
Ok(vec![BUILD_CONST_VERSION, BUILD_CONST_CLAP_LONG_VERSION])
}
fn gen_build_in(&self, gen_const: Vec<&'static str>) -> SdResult<()> {
let mut print_val = String::from("\n");
let mut params = String::from("\n");
let mut default = String::from("\n");
let mut all = String::from("\n");
for (k, v) in &self.map {
let tmp = match v.t {
ConstType::Str | ConstType::Bool | ConstType::Usize | ConstType::Int => {
default.push_str(&format!("\t\t\t{k}: true,\n"));
all.push_str(&format!("\t\t\t{k}: true,\n"));
format!(
r#"{}if self.{k} {{ writeln!(f, "{k}:{{{k}}}\n")?; }}{}"#,
"\t\t", "\n"
)
}
ConstType::Slice => {
default.push_str(&format!("\t\t\t{k}: false,\n"));
all.push_str(&format!("\t\t\t{k}: true,\n"));
format!(
r#"{}if self.{k} {{ writeln!(f, "{k}:{{:?}}\n",{})?; }}{}"#,
"\t\t", k, "\n",
)
}
};
print_val.push_str(tmp.as_str());
params.push_str(&format!("\tpub {k}: bool,\n"));
}
for k in gen_const {
let tmp = format!(
r#"{}if self.{k} {{ writeln!(f, "{k}:{{{k}}}\n")?; }}{}"#,
"\t\t", "\n"
);
print_val.push_str(tmp.as_str());
params.push_str(&format!("\tpub {k}: bool,\n"));
default.push_str(&format!("\t\t\t{k}: true,\n"));
all.push_str(&format!("\t\t\t{k}: true,\n"));
}
default.push_str("\t\t");
all.push_str("\t\t");
print_val.push_str("\t\tOk(())\n\t");
let build_info_display_define = format!(
"/// A struct that implements [`core::fmt::Display`] which\n\
/// writes consts generated by `shadow-rs` to it's formatter\n\
#[allow(non_snake_case)]\n\
{CARGO_CLIPPY_ALLOW_ALL}\n\
pub struct BuildInfoDisplay {\
{{params}}\
}\n\n\
impl Default for BuildInfoDisplay {{\n\
\t#[allow(dead_code)]\n\
\t{CARGO_CLIPPY_ALLOW_ALL}\n\
\t/// Every constant that `shadow-rs` tracks will be printed\n\
\t/// except for slices (CARGO_METADATA for example)\n\
\tfn default() -> Self {{\n\
\t\tSelf {\
{{default}}\
}\n\
\t}}\n\
}}\n\n\
impl BuildInfoDisplay {{\n\
\t#[allow(dead_code)]\n\
\t{CARGO_CLIPPY_ALLOW_ALL}\n\
\t/// Every constant that `shadow-rs` tracks will be printed\n\
\tpub fn all() -> Self {{\n\
\t\tSelf {\
{{all}}\
}\n\
\t}}\n\
}}\n\n\
impl core::fmt::Display for BuildInfoDisplay {{\n\
\t#[allow(dead_code)]\n\
\t{CARGO_CLIPPY_ALLOW_ALL}\n\
\tfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\
{{print_val}}\
}\n\
}}\n",
);
writeln!(&self.f, "{build_info_display_define}")?;
#[cfg(not(feature = "no_std"))]
{
let print_build_in_define = format!(
"/// Prints all built-in `shadow-rs` build constants\n\
/// (except for slices) to standard output.\n\
#[allow(dead_code)]\n\
{CARGO_CLIPPY_ALLOW_ALL}\n\
pub fn print_build_in() {{\n\
\tprintln!(\"{{}}\", BuildInfoDisplay::default());\n\
}}\n"
);
writeln!(&self.f, "{print_build_in_define}")?;
#[cfg(feature = "metadata")]
{
use crate::gen_const::cargo_metadata_fn;
writeln!(&self.f, "{}", cargo_metadata_fn(self))?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CARGO_TREE;
use std::fs;
#[test]
fn test_build() -> SdResult<()> {
ShadowBuilder::builder()
.src_path("./")
.out_path("./")
.build()?;
let shadow = fs::read_to_string(DEFINE_SHADOW_RS)?;
assert!(!shadow.is_empty());
assert!(shadow.lines().count() > 0);
fs::remove_file(DEFINE_SHADOW_RS)?;
ShadowBuilder::builder()
.src_path("./")
.out_path("./")
.deny_const(BTreeSet::from([CARGO_TREE]))
.build()?;
let content = fs::read_to_string(DEFINE_SHADOW_RS)?;
assert!(!content.is_empty());
assert!(content.lines().count() > 0);
let expect = "pub const CARGO_TREE :&str";
assert!(!content.contains(expect));
Ok(())
}
}