use std::env;
use std::fs;
use std::path::PathBuf;
const SIBLINGS: &[(&str, &str)] = &[
("report", "dev-report"),
("tools", "dev-tools"),
("fixtures", "dev-fixtures"),
("bench", "dev-bench"),
("async", "dev-async"),
("stress", "dev-stress"),
("chaos", "dev-chaos"),
("coverage", "dev-coverage"),
("security", "dev-security"),
("deps", "dev-deps"),
("ci", "dev-ci"),
("fuzz", "dev-fuzz"),
("flaky", "dev-flaky"),
("mutate", "dev-mutate"),
];
fn main() {
println!("cargo:rerun-if-changed=Cargo.lock");
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=build.rs");
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
let lock_path = PathBuf::from(&manifest_dir).join("Cargo.lock");
let versions = fs::read_to_string(&lock_path)
.ok()
.map(|text| parse_versions(&text))
.unwrap_or_default();
let mut out = String::with_capacity(2048);
out.push_str("// Auto-generated by build.rs from Cargo.lock — do not edit.\n");
out.push_str("// Regenerated on every cargo build via the rerun-if-changed directives.\n\n");
out.push_str("/// (alias, crate-name, version) for every sibling crate the CLI wraps.\n");
out.push_str("/// Versions are pulled from Cargo.lock at compile time so the table\n");
out.push_str("/// cannot drift away from what is actually linked.\n");
out.push_str("pub const SIBLINGS: &[(&str, &str, &str)] = &[\n");
for (alias, crate_name) in SIBLINGS {
let version = lookup_version(&versions, crate_name).unwrap_or("0.0.0");
out.push_str(&format!(
" ({:?}, {:?}, {:?}),\n",
alias, crate_name, version
));
}
out.push_str("];\n");
let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
let dest = PathBuf::from(out_dir).join("siblings.rs");
fs::write(&dest, out).expect("write siblings.rs");
}
fn parse_versions(text: &str) -> Vec<(String, String)> {
let mut out = Vec::new();
let mut pending_name: Option<String> = None;
for raw in text.lines() {
let line = raw.trim();
if line == "[[package]]" {
pending_name = None;
continue;
}
if let Some(rest) = line.strip_prefix("name = \"") {
if let Some(end) = rest.find('"') {
pending_name = Some(rest[..end].to_string());
}
continue;
}
if let Some(rest) = line.strip_prefix("version = \"") {
if let Some(end) = rest.find('"') {
if let Some(name) = pending_name.take() {
out.push((name, rest[..end].to_string()));
}
}
}
}
out
}
fn lookup_version<'a>(versions: &'a [(String, String)], name: &str) -> Option<&'a str> {
versions
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| v.as_str())
}