use std::fmt::Write as _;
use std::path::Path;
const STDLIB_FLAGSHIP_MODULES: &[&str] = &[
"drop_panic.rs",
"resource_lifecycle.rs",
"panic_on_index.rs",
"async_soundness.rs",
"numeric_truncation.rs",
"unsafe_soundness.rs",
"deserialization.rs",
"time_ordering.rs",
];
fn main() {
let manifest_dir =
std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR set by cargo");
let stdlib_dir = Path::new(&manifest_dir).join("src").join("stdlib");
let mut entries: Vec<CatalogEntry> = Vec::new();
for module in STDLIB_FLAGSHIP_MODULES {
let path = stdlib_dir.join(module);
println!("cargo:rerun-if-changed={}", path.display());
let src = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("build.rs: cannot read {}: {e}", path.display()));
let file = syn::parse_file(&src)
.unwrap_or_else(|e| panic!("build.rs: cannot parse {}: {e}", path.display()));
collect_antigens(&file.items, &mut entries);
}
entries.sort_by(|a, b| a.name.cmp(&b.name));
let mut generated = String::new();
generated.push_str(
"// @generated by antigen/build.rs — do not edit.\n\
// The bundled stdlib catalog: (name, fingerprint-string, provenance-string).\n",
);
let _ = writeln!(
generated,
"pub(crate) const STDLIB_CATALOG: &[(&str, &str, &str)] = &[",
);
for e in &entries {
let _ = writeln!(
generated,
" ({}, {}, {}),",
raw_str(&e.name),
raw_str(&e.fingerprint),
raw_str(&e.provenance),
);
}
generated.push_str("];\n");
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR set by cargo");
let out_path = Path::new(&out_dir).join("stdlib_catalog.rs");
std::fs::write(&out_path, generated)
.unwrap_or_else(|e| panic!("build.rs: cannot write {}: {e}", out_path.display()));
println!("cargo:rerun-if-changed=build.rs");
}
struct CatalogEntry {
name: String,
fingerprint: String,
provenance: String,
}
fn collect_antigens(items: &[syn::Item], out: &mut Vec<CatalogEntry>) {
for item in items {
let attrs = item_attrs(item);
for attr in attrs {
if !attr.path().is_ident("antigen") {
continue;
}
if let Some(entry) = parse_antigen_attr(attr) {
out.push(entry);
}
}
}
}
fn item_attrs(item: &syn::Item) -> &[syn::Attribute] {
match item {
syn::Item::Struct(i) => &i.attrs,
syn::Item::Enum(i) => &i.attrs,
syn::Item::Union(i) => &i.attrs,
syn::Item::Fn(i) => &i.attrs,
syn::Item::Trait(i) => &i.attrs,
syn::Item::Type(i) => &i.attrs,
syn::Item::Const(i) => &i.attrs,
syn::Item::Static(i) => &i.attrs,
syn::Item::Impl(i) => &i.attrs,
syn::Item::Mod(i) => &i.attrs,
_ => &[],
}
}
fn parse_antigen_attr(attr: &syn::Attribute) -> Option<CatalogEntry> {
let mut name: Option<String> = None;
let mut fingerprint: Option<String> = None;
let mut provenance: Option<String> = None;
let parsed = attr.parse_nested_meta(|meta| {
let key = meta
.path
.get_ident()
.map(std::string::ToString::to_string)
.unwrap_or_default();
match key.as_str() {
"name" => {
let value = meta.value()?;
let lit: syn::LitStr = value.parse()?;
name = Some(lit.value());
},
"fingerprint" => {
let value = meta.value()?;
let lit: syn::LitStr = value.parse()?;
fingerprint = Some(lit.value());
},
"provenance" => {
let value = meta.value()?;
let path: syn::Path = value.parse()?;
if let Some(seg) = path.segments.last() {
provenance = Some(seg.ident.to_string());
}
},
_ => {
if meta.input.peek(syn::Token![=]) {
let value = meta.value()?;
let _: syn::Expr = value.parse()?;
}
},
}
Ok(())
});
parsed.unwrap_or_else(|e| panic!("build.rs: malformed #[antigen] attribute: {e}"));
let name = name?;
let fingerprint = fingerprint?;
let provenance = provenance.unwrap_or_else(|| "Imagined".to_string());
Some(CatalogEntry {
name,
fingerprint,
provenance,
})
}
fn raw_str(s: &str) -> String {
if !s.contains('"') && !s.contains('\\') {
return format!("{s:?}"); }
let mut max_after_quote = 0usize;
let bytes = s.as_bytes();
let mut i = 0usize;
while i < bytes.len() {
if bytes[i] == b'"' {
let mut run = 0usize;
let mut j = i + 1;
while j < bytes.len() && bytes[j] == b'#' {
run += 1;
j += 1;
}
max_after_quote = max_after_quote.max(run);
}
i += 1;
}
let hashes = "#".repeat(max_after_quote + 1);
format!("r{hashes}\"{s}\"{hashes}")
}