use std::path::{Path, PathBuf};
use std::process::Command;
const PINNED_FLATC: &str = "24.12.23";
const SCHEMA_DIR: &str = "../schemas/flatbuffers";
const SCHEMA_FINGERPRINT: &str = "src/generated/.schema-fingerprint";
fn main() {
println!("cargo:rerun-if-env-changed=FREENET_REGEN_FLATBUFFERS");
let schemas = discover_schemas();
if let Some(schemas) = &schemas {
println!("cargo:rerun-if-changed={SCHEMA_DIR}");
for schema in schemas {
println!("cargo:rerun-if-changed={}", schema.display());
}
}
if std::env::var_os("FREENET_REGEN_FLATBUFFERS").is_none() {
warn_if_schemas_stale();
return;
}
let schemas = schemas.unwrap_or_else(|| {
panic!("FREENET_REGEN_FLATBUFFERS is set but {SCHEMA_DIR} does not exist")
});
if schemas.is_empty() {
panic!("no .fbs schemas found in {SCHEMA_DIR}");
}
match installed_flatc_version() {
Some(version) if version == PINNED_FLATC => {}
Some(version) => panic!(
"FREENET_REGEN_FLATBUFFERS is set but flatc {version} is installed; the bindings \
are pinned to {PINNED_FLATC}. Regenerating with a different compiler rewrites \
every generated file. Install flatc {PINNED_FLATC} \
(https://github.com/google/flatbuffers/releases/tag/v{PINNED_FLATC})."
),
None => panic!(
"FREENET_REGEN_FLATBUFFERS is set but no flatc was found on PATH. Install flatc \
{PINNED_FLATC} (https://github.com/google/flatbuffers/releases/tag/v{PINNED_FLATC})."
),
}
let mut cmd = Command::new("flatc");
cmd.arg("--rust").arg("-o").arg("src/generated");
for schema in &schemas {
cmd.arg(schema);
}
match cmd.status() {
Ok(status) if status.success() => {}
Ok(status) => panic!("flatc exited with {status}"),
Err(err) => panic!("failed to run flatc: {err}"),
}
match Command::new("cargo").arg("fmt").status() {
Ok(status) if status.success() => {}
Ok(status) => panic!("cargo fmt exited with {status} after regenerating"),
Err(err) => panic!("failed to run cargo fmt after regenerating: {err}"),
}
let Some(fingerprint) = fingerprint(&schemas) else {
panic!("could not read the schemas to fingerprint them");
};
if let Err(err) = std::fs::write(SCHEMA_FINGERPRINT, format!("{fingerprint}\n")) {
panic!("failed to write {SCHEMA_FINGERPRINT}: {err}");
}
}
fn discover_schemas() -> Option<Vec<PathBuf>> {
let mut files: Vec<PathBuf> = std::fs::read_dir(Path::new(SCHEMA_DIR))
.ok()?
.filter_map(|entry| entry.ok().map(|e| e.path()))
.filter(|path| path.extension().is_some_and(|ext| ext == "fbs"))
.collect();
files.sort();
Some(files)
}
fn warn_if_schemas_stale() {
let Some(schemas) = discover_schemas() else {
return;
};
let Some(current) = fingerprint(&schemas) else {
return;
};
let Ok(recorded) = std::fs::read_to_string(SCHEMA_FINGERPRINT) else {
return;
};
if recorded.trim() != current {
println!(
"cargo:warning=the flatbuffers schemas do not match the committed bindings. \
If you edited a schema, regenerate with: FREENET_REGEN_FLATBUFFERS=1 cargo \
build (needs flatc {PINNED_FLATC}), and in typescript/: npm run flatc-schemas"
);
}
}
fn fingerprint(schemas: &[PathBuf]) -> Option<String> {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
let mut eat = |bytes: &[u8]| {
for byte in bytes {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x100_0000_01b3);
}
};
for schema in schemas {
eat(schema.file_name()?.to_str()?.as_bytes());
eat(&std::fs::read(schema).ok()?);
}
Some(format!("{hash:016x}"))
}
fn installed_flatc_version() -> Option<String> {
let out = Command::new("flatc").arg("--version").output().ok()?;
if !out.status.success() {
return None;
}
let stdout = String::from_utf8(out.stdout).ok()?;
Some(stdout.split_whitespace().last()?.to_string())
}