use std::collections::BTreeSet; use std::env; use std::fs::{ self, File }; use std::io::Write; use std::path::{ Path, PathBuf }; use std::process::Command; struct Error(String); impl std::fmt::Display for Error { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fmt.write_str(&self.0) } } impl From for Error { fn from(e: std::io::Error) -> Self { Error(format!("System I/O error: {}", e)) } } impl From for Error { fn from(e: std::path::StripPrefixError) -> Self { Error(format!("Can't strip path prefix: {}", e)) } } type Result = std::result::Result; fn main() { match process_all() { Err(error) => println!("cargo-error={}", error), _ => { }, } } fn process_all() -> Result<()> { let mut asset_paths: BTreeSet = BTreeSet::new(); process_shaders(&mut asset_paths)?; process_textures(&mut asset_paths)?; process_models(&mut asset_paths)?; generate_asset_module(asset_paths)?; Ok(()) } fn process_shaders(asset_paths: &mut BTreeSet) -> Result<()> { let out_dir = env::var_os("OUT_DIR").unwrap(); fs::create_dir_all(Path::new(&out_dir).join("shaders/"))?; for input in fs::read_dir("shaders")? { let input_path = input?.path(); if !input_path.is_file() { continue; } if let Some(Some(filename)) = input_path.file_name() .map(|name| name.to_str()) && filename.get(.. 1) == Some(".") { continue; } let output_path = Path::new(&out_dir) .join(format!("shaders/{}.spv", input_path.file_name().unwrap().to_str().unwrap())); let result = Command::new("glslang") .arg("-V") .arg(&input_path) .arg("-o") .arg(&output_path) .output()?; if !result.status.success() { println!("cargo::error=Failed to compile shader {}; details follow.", input_path.display()); for line in std::str::from_utf8(&result.stdout).unwrap().lines() { println!("cargo::error={}", line); } } println!("cargo::rerun-if-changed={}", input_path.display()); asset_paths.insert(output_path.to_path_buf()); } Ok(()) } fn process_textures(asset_paths: &mut BTreeSet) -> Result<()> { for input in fs::read_dir("textures")? { let input_path = input?.path(); if !input_path.is_file() { continue; } if let Some(Some(filename)) = input_path.file_name() .map(|name| name.to_str()) && filename.get(.. 1) == Some(".") { continue; } if let Some(Some(extension)) = input_path.extension() .map(|a| a.to_str()) { if extension != "png" { continue; } } else { continue; } println!("cargo::rerun-if-changed={}", input_path.display()); asset_paths.insert(input_path.to_path_buf()); } Ok(()) } fn process_models(asset_paths: &mut BTreeSet) -> Result<()> { for input in fs::read_dir("models")? { let input_path = input?.path(); if !input_path.is_file() { continue; } if let Some(Some(filename)) = input_path.file_name() .map(|name| name.to_str()) && filename.get(.. 1) == Some(".") { continue; } if let Some(Some(extension)) = input_path.extension() .map(|a| a.to_str()) { if extension != "obj" { continue; } } else { continue; } println!("cargo::rerun-if-changed={}", input_path.display()); asset_paths.insert(input_path.to_path_buf()); } Ok(()) } fn generate_asset_module(asset_paths: BTreeSet) -> Result<()> { let out_dir = env::var_os("OUT_DIR") .ok_or(Error("Don't have $OUT_DIR.".to_string()))?; let output_path = Path::new(&out_dir).join("assets.rs"); let mut file = File::create(output_path)?; let source_dir = env::current_dir()?; println!("cargo::warning=assets {:?}", asset_paths); writeln!(file, "#[deny(unsafe_code)]")?; writeln!(file, "pub fn asset(name: &str) -> crate::error::Result<&[u8]> {{"); writeln!(file, " match name {{"); for asset_path in asset_paths { let name = if asset_path.starts_with(&out_dir) { asset_path.strip_prefix(&out_dir)? } else { &asset_path }.to_str() .ok_or(Error("Can't stringify path - weird filesystem?".to_string()))?; let include_path = if asset_path.is_absolute() { asset_path.to_path_buf() } else { Path::new(&source_dir).join(&asset_path).to_path_buf() }; let mut include_path = include_path.to_path_buf(); if include_path.starts_with(&out_dir) { include_path = include_path.strip_prefix(&out_dir)?.to_path_buf(); } else { include_path = include_path.strip_prefix(&source_dir)?.to_path_buf(); for _ in Path::new(&out_dir).strip_prefix(&source_dir)?.components() { include_path = Path::new("../").join(include_path).to_path_buf(); } } let include_path = include_path .to_str() .ok_or(Error("Can't stringify path - weird filesystem?" .to_string()))?; writeln!(file, " \"{}\" => Ok(include_bytes!(\"{}\")),", name.escape_default(), include_path.escape_default()); } writeln!(file, " _ => Err(crate::error::Error {{"); writeln!(file, " message: format!(\"No such asset {{:?}}\", name)"); writeln!(file, " }})"); writeln!(file, " }}"); writeln!(file, "}}"); Ok(()) }