use std::env;
use std::fs;
use std::path::PathBuf;
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
let snapshot_dir = manifest_dir.join("registry-snapshot");
let providers_dir = snapshot_dir.join("providers");
let transcription_providers_dir = snapshot_dir.join("transcription-providers");
let embedded_dir = out_dir.join("embedded_registry");
fs::create_dir_all(&embedded_dir).expect("failed to create embedded_registry output dir");
let embedded_providers_dir = embedded_dir.join("providers");
fs::create_dir_all(&embedded_providers_dir).expect("failed to create embedded providers dir");
let embedded_transcription_dir = embedded_dir.join("transcription-providers");
fs::create_dir_all(&embedded_transcription_dir)
.expect("failed to create embedded transcription-providers dir");
let manifest_src = snapshot_dir.join("manifest.json");
let manifest_dst = embedded_dir.join("manifest.json");
fs::copy(&manifest_src, &manifest_dst).expect("failed to copy embedded manifest");
println!("cargo:rerun-if-changed={}", manifest_src.display());
let mut provider_files: Vec<_> = fs::read_dir(&providers_dir)
.expect("failed to read registry-snapshot/providers")
.filter_map(|e| {
let e = e.expect("dir entry");
let path = e.path();
if path.extension().is_some_and(|ext| ext == "json") {
Some(path)
} else {
None
}
})
.collect();
provider_files.sort();
let mut entries = Vec::new();
for path in &provider_files {
let id = path
.file_stem()
.expect("provider file has no stem")
.to_str()
.expect("provider file name is not valid UTF-8");
let dst = embedded_providers_dir.join(format!("{id}.json"));
fs::copy(path, &dst).expect("failed to copy embedded provider");
println!("cargo:rerun-if-changed={}", path.display());
entries.push((
id.to_string(),
dst.to_str().expect("path is not valid UTF-8").to_string(),
));
}
let mut transcription_entries = Vec::new();
if transcription_providers_dir.is_dir() {
let mut files: Vec<_> = fs::read_dir(&transcription_providers_dir)
.expect("failed to read registry-snapshot/transcription-providers")
.filter_map(|e| {
let e = e.expect("dir entry");
let path = e.path();
if path.extension().is_some_and(|ext| ext == "json") {
Some(path)
} else {
None
}
})
.collect();
files.sort();
for path in &files {
let id = path
.file_stem()
.expect("transcription provider file has no stem")
.to_str()
.expect("transcription provider file name is not valid UTF-8");
let dst = embedded_transcription_dir.join(format!("{id}.json"));
fs::copy(path, &dst).expect("failed to copy embedded transcription provider");
println!("cargo:rerun-if-changed={}", path.display());
transcription_entries.push((
id.to_string(),
dst.to_str().expect("path is not valid UTF-8").to_string(),
));
}
}
let models_dir = snapshot_dir.join("models");
let mut model_catalog_entries = Vec::new();
if models_dir.is_dir() {
let embedded_models_dir = embedded_dir.join("models");
fs::create_dir_all(&embedded_models_dir).expect("failed to create embedded models dir");
let mut model_files: Vec<_> = fs::read_dir(&models_dir)
.expect("failed to read registry-snapshot/models")
.filter_map(|e| {
let e = e.expect("dir entry");
let path = e.path();
if path.extension().is_some_and(|ext| ext == "json") {
Some(path)
} else {
None
}
})
.collect();
model_files.sort();
for path in &model_files {
let stem = path
.file_stem()
.expect("model file has no stem")
.to_str()
.expect("model file name is not valid UTF-8");
let id = stem.replace("__", ":");
let safe_name = format!("{stem}.json");
let dst = embedded_models_dir.join(&safe_name);
fs::copy(path, &dst).expect("failed to copy embedded canonical model");
println!("cargo:rerun-if-changed={}", path.display());
model_catalog_entries.push((
id,
dst.to_str().expect("path is not valid UTF-8").to_string(),
));
}
}
let bases_dir = snapshot_dir.join("bases");
let mut base_entries = Vec::new();
if bases_dir.is_dir() {
let embedded_bases_dir = embedded_dir.join("bases");
fs::create_dir_all(&embedded_bases_dir).expect("failed to create embedded bases dir");
let mut base_files: Vec<_> = fs::read_dir(&bases_dir)
.expect("failed to read registry-snapshot/bases")
.filter_map(|e| {
let e = e.expect("dir entry");
let path = e.path();
if path.extension().is_some_and(|ext| ext == "json") {
Some(path)
} else {
None
}
})
.collect();
base_files.sort();
for path in &base_files {
let id = path
.file_stem()
.expect("base file has no stem")
.to_str()
.expect("base file name is not valid UTF-8");
let dst = embedded_bases_dir.join(format!("{id}.json"));
fs::copy(path, &dst).expect("failed to copy embedded base");
println!("cargo:rerun-if-changed={}", path.display());
base_entries.push((
id.to_string(),
dst.to_str().expect("path is not valid UTF-8").to_string(),
));
}
}
let schema_src = snapshot_dir.join("schemas");
if schema_src.is_dir() {
let embedded_schema_dir = embedded_dir.join("schemas");
fs::create_dir_all(&embedded_schema_dir).expect("failed to create embedded schemas dir");
for entry in fs::read_dir(&schema_src).expect("failed to read schemas dir") {
let entry = entry.expect("schema dir entry");
let path = entry.path();
if path.is_file() {
let dst = embedded_schema_dir.join(path.file_name().unwrap());
fs::copy(&path, &dst).expect("failed to copy schema");
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
let mut src = String::new();
src.push_str("// Auto-generated by build.rs. Do not edit.\n");
src.push_str("// Embeds the registry snapshot into the binary.\n\n");
src.push_str("pub const MANIFEST_JSON: &str = include_str!(\"manifest.json\");\n\n");
src.push_str("pub const PROVIDER_FILES: &[(&str, &str)] = &[\n");
for (id, path) in &entries {
src.push_str(&format!(" ({id:?}, include_str!({path:?})),\n"));
}
src.push_str("];\n\n");
src.push_str("pub const TRANSCRIPTION_PROVIDER_FILES: &[(&str, &str)] = &[\n");
for (id, path) in &transcription_entries {
src.push_str(&format!(" ({id:?}, include_str!({path:?})),\n"));
}
src.push_str("];\n\n");
src.push_str("pub const BASE_FILES: &[(&str, &str)] = &[\n");
for (id, path) in &base_entries {
src.push_str(&format!(" ({id:?}, include_str!({path:?})),\n"));
}
src.push_str("];\n\n");
src.push_str("pub const MODEL_CATALOG_FILES: &[(&str, &str)] = &[\n");
for (id, path) in &model_catalog_entries {
src.push_str(&format!(" ({id:?}, include_str!({path:?})),\n"));
}
src.push_str("];\n");
let out_file = embedded_dir.join("embedded.rs");
fs::write(&out_file, src).expect("failed to write embedded.rs");
println!("cargo:rerun-if-changed={}", out_file.display());
println!(
"cargo:rustc-env=NAVI_EMBEDDED_REGISTRY_DIR={}",
embedded_dir.display()
);
}