#![doc(html_logo_url = "https://raw.githubusercontent.com/baoyachi/shadow-rs/master/shadow-rs.png")]
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
#[cfg(feature = "metadata")]
pub extern crate cargo_metadata;
#[cfg(feature = "metadata")]
pub extern crate serde_json;
mod build;
mod ci;
mod date_time;
mod env;
mod err;
mod gen_const;
mod git;
mod hook;
pub use is_debug::*;
use build::*;
use crate::ci::CiType;
pub use crate::date_time::DateTime;
pub use const_format::*;
use std::collections::{BTreeMap, BTreeSet};
use std::env as std_env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use crate::gen_const::{
cargo_metadata_fn, clap_long_version_branch_const, clap_long_version_tag_const,
clap_version_branch_const, clap_version_tag_const, version_branch_const, version_tag_const,
BUILD_CONST_CLAP_LONG_VERSION, BUILD_CONST_VERSION,
};
pub use err::{SdResult, ShadowError};
use crate::hook::HookExt;
pub use {build::ShadowConst, env::*, git::*};
pub trait Format {
fn human_format(&self) -> String;
}
const SHADOW_RS: &str = "shadow.rs";
pub(crate) const CARGO_CLIPPY_ALLOW_ALL: &str =
"#[allow(clippy::all, clippy::pedantic, clippy::restriction, clippy::nursery)]";
#[macro_export]
macro_rules! shadow {
($build_mod:ident) => {
#[doc = r#"shadow-rs mod"#]
pub mod $build_mod {
include!(concat!(env!("OUT_DIR"), "/shadow.rs"));
}
};
}
pub fn new() -> SdResult<()> {
Shadow::build(default_deny())?;
Ok(())
}
#[allow(clippy::all, clippy::pedantic, clippy::restriction, clippy::nursery)]
pub fn default_deny() -> BTreeSet<ShadowConst> {
BTreeSet::from([CARGO_METADATA])
}
pub fn new_deny(deny_const: BTreeSet<ShadowConst>) -> SdResult<()> {
Shadow::build(deny_const)?;
Ok(())
}
pub fn new_hook<F>(f: F) -> SdResult<()>
where
F: HookExt,
{
let shadow = Shadow::build(f.default_deny())?;
shadow.hook(f.hook_inner())
}
pub(crate) fn get_std_env() -> BTreeMap<String, String> {
let mut env_map = BTreeMap::new();
for (k, v) in std_env::vars() {
env_map.insert(k, v);
}
env_map
}
#[derive(Debug)]
pub struct Shadow {
pub f: File,
pub map: BTreeMap<ShadowConst, ConstVal>,
pub std_env: BTreeMap<String, String>,
pub deny_const: BTreeSet<ShadowConst>,
}
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 fn build(deny_const: BTreeSet<ShadowConst>) -> SdResult<Shadow> {
let src_path = std::env::var("CARGO_MANIFEST_DIR")?;
let out_path = std::env::var("OUT_DIR")?;
Self::build_with(src_path, out_path, deny_const)
}
pub fn build_with(
src_path: String,
out_path: String,
deny_const: BTreeSet<ShadowConst>,
) -> SdResult<Shadow> {
let out = {
let path = Path::new(out_path.as_str());
if !out_path.ends_with('/') {
path.join(format!("{out_path}/{SHADOW_RS}"))
} else {
path.join(SHADOW_RS)
}
};
let mut shadow = Shadow {
f: File::create(out)?,
map: Default::default(),
std_env: Default::default(),
deny_const,
};
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()?;
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(())
}
pub fn cargo_rerun_if_env_changed(&self) {
for k in self.std_env.keys() {
println!("cargo:rerun-if-env-changed={k}");
}
}
pub fn cargo_rerun_env_inject(&self, env: &[&str]) {
for k in env {
println!("cargo:rerun-if-env-changed={}", *k);
}
}
fn gen_const(&mut self) -> SdResult<()> {
for (k, v) in self.map.clone() {
println!("cargo:rerun-if-env-changed={k}");
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: {}
"#,
DateTime::now().human_format()
);
writeln!(&self.f, "{desc}\n\n")?;
Ok(())
}
fn write_const(&mut 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()
),
};
writeln!(&self.f, "{desc}")?;
writeln!(&self.f, "{define}\n")?;
Ok(())
}
fn gen_version(&mut self) -> SdResult<Vec<&'static str>> {
let (ver_fn, clap_ver_fn, clap_long_ver_fn) = match self.map.get(TAG) {
None => (
version_branch_const(),
clap_version_branch_const(),
clap_long_version_branch_const(),
),
Some(tag) => {
if !tag.v.is_empty() {
(
version_tag_const(),
clap_version_tag_const(),
clap_long_version_tag_const(),
)
} else {
(
version_branch_const(),
clap_version_branch_const(),
clap_long_version_branch_const(),
)
}
}
};
writeln!(&self.f, "{ver_fn}\n")?;
writeln!(&self.f, "{clap_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");
for (k, v) in &self.map {
let tmp = match v.t {
ConstType::Str | ConstType::Bool => {
format!(r#"{}println!("{k}:{{{k}}}\n");{}"#, "\t", "\n")
}
ConstType::Slice => {
format!(r#"{}println!("{k}:{{:?}}\n",{});{}"#, "\t", k, "\n",)
}
};
print_val.push_str(tmp.as_str());
}
for k in gen_const {
let tmp = format!(r#"{}println!("{k}:{{{k}}}\n");{}"#, "\t", "\n");
print_val.push_str(tmp.as_str());
}
let everything_define = format!(
"/// Prints all built-in `shadow-rs` build constants to standard output.\n\
#[allow(dead_code)]\n\
{CARGO_CLIPPY_ALLOW_ALL}\n\
pub fn print_build_in() {\
{{print_val}}\
}\n",
);
writeln!(&self.f, "{everything_define}")?;
writeln!(&self.f, "{}", cargo_metadata_fn(self))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_build() -> SdResult<()> {
Shadow::build_with("./".into(), "./".into(), Default::default())?;
let shadow = fs::read_to_string("./shadow.rs")?;
assert!(!shadow.is_empty());
assert!(shadow.lines().count() > 0);
Ok(())
}
#[test]
fn test_build_deny() -> SdResult<()> {
let mut deny = BTreeSet::new();
deny.insert(CARGO_TREE);
Shadow::build_with("./".into(), "./".into(), deny)?;
let shadow = fs::read_to_string("./shadow.rs")?;
assert!(!shadow.is_empty());
assert!(shadow.lines().count() > 0);
println!("{shadow}");
let expect = "pub const CARGO_TREE :&str";
assert!(!shadow.contains(expect));
Ok(())
}
#[test]
fn test_env() {
for (k, v) in std::env::vars() {
println!("K:{k},V:{v}");
}
}
}