use std::{ env, fs, path::{Path, PathBuf}, }; const FNV_OFFSET: u64 = 0xcbf29ce484222325; const FNV_PRIME: u64 = 0x100000001b3; fn hash_bytes(mut hash: u64, bytes: &[u8]) -> u64 { for byte in bytes { hash ^= u64::from(*byte); hash = hash.wrapping_mul(FNV_PRIME); } hash } fn collect_files(root: &Path, dir: &Path, files: &mut Vec) { let mut entries = fs::read_dir(dir) .unwrap_or_else(|error| panic!("failed to read {}: {error}", dir.display())) .filter_map(Result::ok) .map(|entry| entry.path()) .collect::>(); entries.sort(); for path in entries { if path.is_dir() { collect_files(root, &path, files); } else if path.is_file() { println!( "cargo:rerun-if-changed={}", path.strip_prefix(root).unwrap_or(&path).display() ); files.push(path); } } } fn asset_version(manifest_dir: &Path) -> String { let mut files = Vec::new(); collect_files(manifest_dir, &manifest_dir.join("static"), &mut files); collect_files(manifest_dir, &manifest_dir.join("lang"), &mut files); files.sort(); let mut hash = FNV_OFFSET; for path in files { let relative = path.strip_prefix(manifest_dir).unwrap_or(&path); hash = hash_bytes(hash, relative.to_string_lossy().as_bytes()); hash = hash_bytes(hash, &[0]); let bytes = fs::read(&path) .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display())); hash = hash_bytes(hash, &bytes); hash = hash_bytes(hash, &[0xff]); } format!( "{}-{hash:016x}", env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION") ) } fn main() { println!("cargo:rerun-if-changed=lang"); println!("cargo:rerun-if-changed=static"); let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); let lang_dir = manifest_dir.join("lang"); let mut files = fs::read_dir(&lang_dir) .unwrap_or_else(|error| panic!("failed to read {}: {error}", lang_dir.display())) .filter_map(Result::ok) .map(|entry| entry.path()) .filter(|path| path.extension().and_then(|value| value.to_str()) == Some("json")) .collect::>(); files.sort(); if files.is_empty() { panic!("lang directory must contain at least one .json language file"); } let mut generated = String::from("pub const EMBEDDED_LANGUAGE_FILES: &[(&str, &str)] = &[\n"); for path in files { let name = path .file_name() .and_then(|value| value.to_str()) .expect("language filename must be valid UTF-8"); println!("cargo:rerun-if-changed=lang/{name}"); generated.push_str(&format!( " ({name:?}, include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/lang/{name}\"))),\n" )); } generated.push_str("];\n"); let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); fs::write(out_dir.join("languages.rs"), generated) .expect("failed to generate embedded languages"); println!( "cargo:rustc-env=RUSTPAD_ASSET_VERSION={}", asset_version(&manifest_dir) ); }