Files
emod-cli/build.rs

190 lines
6.0 KiB
Rust
Raw Permalink Normal View History

2026-04-26 20:48:26 +08:00
use std::{
collections::BTreeMap,
2026-04-26 20:48:26 +08:00
env, fs, io,
path::{Path, PathBuf},
};
const EMPTY_DIRS_FILE: &str = ".empty-dirs";
const DEBUG_MOD_ROOT: &str = "debug_mod";
2026-04-26 20:48:26 +08:00
fn main() {
println!("cargo:rerun-if-changed=examples");
println!("cargo:rerun-if-changed={}", DEBUG_MOD_ROOT);
2026-04-26 20:48:26 +08:00
let examples_root = PathBuf::from("examples");
let debug_mod_root = PathBuf::from(DEBUG_MOD_ROOT);
if !debug_mod_root.is_dir() {
panic!(
"required debug MOD resource directory was not found: {}",
DEBUG_MOD_ROOT
);
}
2026-04-26 20:48:26 +08:00
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is not set"));
let out_file = out_dir.join("embedded_examples.rs");
let mut dirs = Vec::new();
let mut files = Vec::new();
let mut empty_dirs_by_example = BTreeMap::new();
2026-04-26 20:48:26 +08:00
let mut debug_mod_files = Vec::new();
2026-04-26 20:48:26 +08:00
if examples_root.is_dir() {
collect_examples(
&examples_root,
&examples_root,
&mut dirs,
&mut files,
&mut empty_dirs_by_example,
)
.expect("failed to collect example templates");
2026-04-26 20:48:26 +08:00
}
collect_files(&debug_mod_root, &debug_mod_root, &mut debug_mod_files)
.expect("failed to collect debug MOD resources");
2026-04-26 20:48:26 +08:00
dirs.sort();
dirs.dedup();
2026-04-26 20:48:26 +08:00
files.sort_by(|a, b| a.0.cmp(&b.0));
for empty_dirs in empty_dirs_by_example.values_mut() {
empty_dirs.sort();
empty_dirs.dedup();
}
debug_mod_files.sort_by(|a, b| a.0.cmp(&b.0));
2026-04-26 20:48:26 +08:00
let mut generated = String::new();
generated.push_str(
"#[allow(dead_code)]\nstruct EmbeddedFile {\n path: &'static str,\n contents: &'static [u8],\n}\n\n",
2026-04-26 20:48:26 +08:00
);
generated.push_str("#[allow(dead_code)]\nstatic EMBEDDED_EXAMPLE_DIRS: &[&str] = &[\n");
2026-04-26 20:48:26 +08:00
for dir in &dirs {
generated.push_str(&format!(" {:?},\n", dir));
}
generated.push_str("];\n\n");
generated
.push_str("#[allow(dead_code)]\nstatic EMBEDDED_EXAMPLE_FILES: &[EmbeddedFile] = &[\n");
2026-04-26 20:48:26 +08:00
for (relative_path, absolute_path) in &files {
generated.push_str(&format!(
" EmbeddedFile {{ path: {:?}, contents: include_bytes!(r#\"{}\"#) }},\n",
relative_path,
absolute_path.display()
));
}
generated.push_str("];\n\n");
generated.push_str(
"#[allow(dead_code)]\nstruct EmbeddedExampleEmptyDirs {\n example: &'static str,\n dirs: &'static [&'static str],\n}\n\n",
);
generated.push_str(
"#[allow(dead_code)]\nstatic EMBEDDED_EXAMPLE_EMPTY_DIRS: &[EmbeddedExampleEmptyDirs] = &[\n",
);
for (example, empty_dirs) in &empty_dirs_by_example {
generated.push_str(&format!(
" EmbeddedExampleEmptyDirs {{ example: {:?}, dirs: &[\n",
example
));
for dir in empty_dirs {
generated.push_str(&format!(" {:?},\n", dir));
}
generated.push_str(" ] },\n");
}
generated.push_str("];\n\n");
generated
.push_str("#[allow(dead_code)]\nstatic EMBEDDED_DEBUG_MOD_FILES: &[EmbeddedFile] = &[\n");
for (relative_path, absolute_path) in &debug_mod_files {
generated.push_str(&format!(
" EmbeddedFile {{ path: {:?}, contents: include_bytes!(r#\"{}\"#) }},\n",
relative_path,
absolute_path.display()
));
}
2026-04-26 20:48:26 +08:00
generated.push_str("];\n");
fs::write(out_file, generated).expect("failed to write embedded resource list");
}
fn collect_files(root: &Path, dir: &Path, files: &mut Vec<(String, PathBuf)>) -> io::Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
collect_files(root, &path, files)?;
} else if path.is_file() {
let relative_path = path
.strip_prefix(root)
.expect("embedded resource path is outside root")
.to_string_lossy()
.replace('\\', "/");
files.push((relative_path, path.canonicalize()?));
}
}
Ok(())
2026-04-26 20:48:26 +08:00
}
fn collect_examples(
root: &Path,
dir: &Path,
dirs: &mut Vec<String>,
files: &mut Vec<(String, PathBuf)>,
empty_dirs_by_example: &mut BTreeMap<String, Vec<String>>,
2026-04-26 20:48:26 +08:00
) -> io::Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let relative_path = path
.strip_prefix(root)
.expect("example path is outside examples root")
.to_string_lossy()
.replace('\\', "/");
if path.is_dir() {
dirs.push(relative_path);
collect_examples(root, &path, dirs, files, empty_dirs_by_example)?;
2026-04-26 20:48:26 +08:00
} else if path.is_file() {
if path.file_name().and_then(|name| name.to_str()) == Some(EMPTY_DIRS_FILE) {
collect_empty_dirs(&relative_path, &path, dirs, empty_dirs_by_example)?;
} else {
files.push((relative_path, path.canonicalize()?));
}
}
}
Ok(())
}
fn collect_empty_dirs(
relative_path: &str,
path: &Path,
dirs: &mut Vec<String>,
empty_dirs_by_example: &mut BTreeMap<String, Vec<String>>,
) -> io::Result<()> {
let (example, file_name) = relative_path.split_once('/').ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("{} must be inside an example directory", EMPTY_DIRS_FILE),
)
})?;
if file_name != EMPTY_DIRS_FILE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unexpected {} path: {}", EMPTY_DIRS_FILE, relative_path),
));
}
let contents = fs::read_to_string(path)?;
let empty_dirs = empty_dirs_by_example
.entry(example.to_string())
.or_insert_with(Vec::new);
for line in contents.lines() {
let dir = line.trim();
if dir.is_empty() || dir.starts_with('#') {
continue;
2026-04-26 20:48:26 +08:00
}
dirs.push(format!("{}/{}", example, dir));
empty_dirs.push(dir.to_string());
2026-04-26 20:48:26 +08:00
}
Ok(())
}