use std::fs;
use std::path::Path;
fn main() {
println!("cargo::rerun-if-changed=locales");
println!("cargo::rerun-if-changed=themes");
#[cfg(target_os = "windows")]
{
let ico_path = Path::new("../../docs/icons/windows/app.ico");
if ico_path.exists() {
let mut res = winresource::WindowsResource::new();
res.set_icon(ico_path.to_str().unwrap());
if let Err(e) = res.compile() {
eprintln!("Warning: Failed to embed Windows icon: {}", e);
}
}
}
if let Err(e) = generate_locale_options() {
eprintln!("Warning: Failed to generate locale options: {}", e);
}
if let Err(e) = generate_builtin_themes() {
eprintln!("Warning: Failed to generate builtin themes: {}", e);
}
#[cfg(feature = "embed-plugins")]
{
println!("cargo::rerun-if-changed=plugins");
if let Err(e) = generate_plugins_hash() {
eprintln!("Warning: Failed to generate plugins hash: {}", e);
}
}
}
#[cfg(feature = "embed-plugins")]
fn generate_plugins_hash() -> Result<(), Box<dyn std::error::Error>> {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
let plugins_dir = Path::new("plugins");
let mut hasher = DefaultHasher::new();
hash_directory(plugins_dir, &mut hasher)?;
let hash = format!("{:016x}", hasher.finish());
let out_dir = std::env::var("OUT_DIR")?;
let dest_path = Path::new(&out_dir).join("plugins_hash.txt");
fs::write(&dest_path, &hash)?;
println!("cargo::warning=Generated plugins hash: {}", hash);
Ok(())
}
#[cfg(feature = "embed-plugins")]
fn hash_directory(dir: &Path, hasher: &mut impl std::hash::Hasher) -> std::io::Result<()> {
use std::hash::Hash;
if !dir.exists() {
return Ok(());
}
let mut entries: Vec<_> = fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
entries.sort_by_key(|e| e.path());
for entry in entries {
let path = entry.path();
path.strip_prefix("plugins").unwrap_or(&path).hash(hasher);
if path.is_dir() {
hash_directory(&path, hasher)?;
} else {
let contents = fs::read(&path)?;
contents.hash(hasher);
}
}
Ok(())
}
fn generate_builtin_themes() -> Result<(), Box<dyn std::error::Error>> {
let themes_dir = Path::new("themes");
let mut themes: Vec<(String, String, String)> = Vec::new();
collect_theme_files(themes_dir, "", &mut themes)?;
themes.sort_by(|a, b| (&a.1, &a.0).cmp(&(&b.1, &b.0)));
let out_dir = std::env::var("OUT_DIR")?;
let dest_path = Path::new(&out_dir).join("builtin_themes.rs");
let theme_entries: Vec<String> = themes
.iter()
.map(|(name, pack, rel_path)| {
format!(
r#" BuiltinTheme {{
name: "{}",
pack: "{}",
json: include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/themes/{}")),
}}"#,
name, pack, rel_path
)
})
.collect();
let content = format!(
r#"// Auto-generated by build.rs from themes/**/*.json files
// DO NOT EDIT MANUALLY
/// All builtin themes embedded at compile time.
pub const BUILTIN_THEMES: &[BuiltinTheme] = &[
{}
];
"#,
theme_entries.join(",\n")
);
fs::write(&dest_path, content)?;
println!("cargo::warning=Generated {} builtin themes", themes.len());
Ok(())
}
fn collect_theme_files(
dir: &Path,
pack: &str,
themes: &mut Vec<(String, String, String)>,
) -> std::io::Result<()> {
if !dir.exists() {
return Ok(());
}
let mut entries: Vec<_> = fs::read_dir(dir)?.filter_map(|e| e.ok()).collect();
entries.sort_by_key(|e| e.path());
for entry in entries {
let path = entry.path();
if path.is_dir() {
let dir_name = path.file_name().unwrap().to_string_lossy();
let new_pack = if pack.is_empty() {
dir_name.to_string()
} else {
format!("{}/{}", pack, dir_name)
};
collect_theme_files(&path, &new_pack, themes)?;
} else if path.extension().is_some_and(|ext| ext == "json") {
let name = path.file_stem().unwrap().to_string_lossy().to_string();
let rel_path = path
.strip_prefix("themes")
.unwrap()
.components()
.map(|c| c.as_os_str().to_string_lossy())
.collect::<Vec<_>>()
.join("/");
themes.push((name, pack.to_string(), rel_path));
}
}
Ok(())
}
fn generate_locale_options() -> Result<(), Box<dyn std::error::Error>> {
let locales_dir = Path::new("locales");
let mut locales: Vec<String> = fs::read_dir(locales_dir)?
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.extension()? == "json" {
path.file_stem()?.to_str().map(|s| s.to_string())
} else {
None
}
})
.collect();
locales.sort();
let out_dir = std::env::var("OUT_DIR")?;
let dest_path = Path::new(&out_dir).join("locale_options.rs");
let locale_entries: Vec<String> = locales.iter().map(|l| format!("Some(\"{}\")", l)).collect();
let content = format!(
r#"// Auto-generated by build.rs from locales/*.json files
// DO NOT EDIT MANUALLY
/// Available locale options for the settings dropdown
/// None (null) means auto-detect from environment
pub const GENERATED_LOCALE_OPTIONS: &[Option<&str>] = &[
None, // Auto-detect
{}
];
"#,
locale_entries.join(",\n ")
);
fs::write(&dest_path, content)?;
println!(
"cargo::warning=Generated locale options with {} locales",
locales.len()
);
Ok(())
}