use crate::config::Registry;
use crate::fatal::{fatal, required_env_path};
use crate::paths::output_path_in_out_dir;
use std::collections::BTreeSet;
use std::fs;
use std::io::Write;
use std::path::Path;
pub fn scan(registry: &Registry<'_>) {
let manifest_dir = required_env_path("CARGO_MANIFEST_DIR");
let out_dir = required_env_path("OUT_DIR");
let scan_path = manifest_dir.join(registry.scan_dir);
println!("cargo:rerun-if-changed={}", scan_path.display());
let modules = collect_module_files(&scan_path, registry.item_const_name);
let mut out = String::new();
out.push_str("// @generated by vyre-build-scan - do not edit.\n");
out.push_str(&format!("// Source: {}\n", registry.scan_dir));
out.push_str("// To add a new entry: drop a .rs file in the source directory with\n");
out.push_str(&format!(
"// `pub const {}: YourType = YourType;` - next build picks it up.\n\n",
registry.item_const_name
));
out.push_str(&format!(
"pub static {}: &[{}] = &[\n",
registry.const_name, registry.element_type,
));
for module in &modules {
out.push_str(&format!(
" &{}::{}::{},\n",
registry.module_prefix, module, registry.item_const_name
));
}
out.push_str("];\n");
let output_path = output_path_in_out_dir(&out_dir, registry.output_file)
.unwrap_or_else(|message| fatal(message));
let mut file = fs::File::create(&output_path)
.unwrap_or_else(|err| panic!("build_scan: cannot create {}: {err}", output_path.display()));
file.write_all(out.as_bytes())
.unwrap_or_else(|err| panic!("build_scan: cannot write {}: {err}", output_path.display()));
for module in &modules {
let file_path = scan_path.join(format!("{module}.rs"));
println!("cargo:rerun-if-changed={}", file_path.display());
}
}
pub fn scan_all(registries: &[Registry<'_>]) {
for registry in registries {
scan(registry);
}
}
fn collect_module_files(dir: &Path, item_const_name: &str) -> Vec<String> {
let mut names: BTreeSet<String> = BTreeSet::new();
let entries = fs::read_dir(dir)
.unwrap_or_else(|err| panic!("build_scan: cannot read {}: {err}", dir.display()));
for entry in entries {
let entry = entry.unwrap_or_else(|err| {
panic!(
"build_scan: cannot read dir entry in {}: {err}",
dir.display()
)
});
let path = entry.path();
if !path.is_file() {
continue;
}
let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
continue;
};
let Some(ext) = path.extension().and_then(|s| s.to_str()) else {
continue;
};
if ext != "rs" || stem == "mod" || stem.starts_with('_') {
continue;
}
let contents = fs::read_to_string(&path)
.unwrap_or_else(|err| panic!("build_scan: cannot read {}: {err}", path.display()));
if contents.contains(&format!("pub const {item_const_name}")) {
names.insert(stem.to_string());
}
}
names.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::collect_module_files;
use std::{env, fs, path::PathBuf};
#[test]
fn skips_mod_rs_and_underscore_files() {
let tmp = tempdir_sibling("scan_skips");
fs::create_dir_all(&tmp).unwrap();
fs::write(tmp.join("mod.rs"), "").unwrap();
fs::write(tmp.join("_registry.rs"), "").unwrap();
fs::write(tmp.join("atomics.rs"), "pub const REGISTERED: () = ();").unwrap();
fs::write(tmp.join("barrier.rs"), "pub const REGISTERED: () = ();").unwrap();
let got = collect_module_files(&tmp, "REGISTERED");
assert_eq!(got, vec!["atomics".to_string(), "barrier".to_string()]);
let _ = fs::remove_dir_all(&tmp);
}
#[test]
fn empty_dir_yields_empty_list() {
let tmp = tempdir_sibling("scan_empty");
fs::create_dir_all(&tmp).unwrap();
let got = collect_module_files(&tmp, "REGISTERED");
assert!(got.is_empty());
let _ = fs::remove_dir_all(&tmp);
}
#[test]
fn deterministic_order_is_alphabetical() {
let tmp = tempdir_sibling("scan_order");
fs::create_dir_all(&tmp).unwrap();
for name in ["xor", "and", "or", "not"] {
fs::write(
tmp.join(format!("{name}.rs")),
"pub const REGISTERED: () = ();",
)
.unwrap();
}
let got = collect_module_files(&tmp, "REGISTERED");
assert_eq!(
got,
vec![
"and".to_string(),
"not".to_string(),
"or".to_string(),
"xor".to_string()
]
);
let _ = fs::remove_dir_all(&tmp);
}
fn tempdir_sibling(suffix: &str) -> PathBuf {
let mut base = env::temp_dir();
base.push(format!("vyre-build-scan-{}-{}", std::process::id(), suffix));
base
}
}