use std::cmp::Ordering; use std::env; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; fn main() { // Step 1: Find `migrations/` folder recursively let root = Path::new("src"); // Get the OUT_DIR from Cargo - this is writable let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by Cargo")); for migration_path in find_migrations_dirs(root) { // Step 3: Output file path to OUT_DIR instead of source directory let parent = migration_path.parent().unwrap(); // Create a unique filename based on the migration path to avoid conflicts let migration_name = parent .strip_prefix("src") .unwrap_or(parent) .to_str() .unwrap_or("default") .replace("/", "_") .replace("\\", "_"); let dest_path = out_dir.join(format!("migrations_{}.rs", migration_name)); let mut out_file = File::create(&dest_path).expect("Failed to create migrations.rs"); let skip_name = migration_path.to_str().unwrap_or_default().len(); // Step 2: Collect all files inside the migrations dir let mut files = Vec::new(); visit_dirs(&migration_path, &mut files).expect("Failed to read migrations directory"); files.sort_by(|path_a, path_b| { let parts_a = path_a.to_str().unwrap().replace("\\", "/")[skip_name + 1..] .split("/") .map(|x| x.to_owned()) .collect::>(); let parts_b = path_b.to_str().unwrap().replace("\\", "/")[skip_name + 1..] .split("/") .map(|x| x.to_owned()) .collect::>(); let prefix_a = if parts_a.len() == 2 { parts_a.first().map(|x| x.to_owned()).unwrap_or_default() } else { "".to_owned() }; let prefix_b = if parts_a.len() == 2 { parts_b.first().map(|x| x.to_owned()).unwrap_or_default() } else { "".to_owned() }; let prefix_cmp = prefix_a.cmp(&prefix_b); if prefix_cmp != Ordering::Equal { return prefix_cmp; } let path_a = path_a.file_name().unwrap().to_str().unwrap(); let path_b = path_b.file_name().unwrap().to_str().unwrap(); let prefix_a = path_a .split("_") .next() .and_then(|prefix| prefix.parse::().ok()) .unwrap_or_default(); let prefix_b = path_b .split("_") .next() .and_then(|prefix| prefix.parse::().ok()) .unwrap_or_default(); if prefix_a != 0 && prefix_b != 0 { prefix_a.cmp(&prefix_b) } else { path_a.cmp(path_b) } }); writeln!(out_file, "/// @generated").unwrap(); writeln!(out_file, "/// Auto-generated by build.rs").unwrap(); writeln!( out_file, "pub static MIGRATIONS: &[(&str, &str, &str)] = &[" ) .unwrap(); for path in &files { let parts = path.to_str().unwrap().replace("\\", "/")[skip_name + 1..] .split("/") .map(|x| x.to_owned()) .collect::>(); let prefix = if parts.len() == 2 { parts.first().map(|x| x.to_owned()).unwrap_or_default() } else { "".to_owned() }; let rel_name = &path.file_name().unwrap().to_str().unwrap(); // Copy migration file to OUT_DIR let relative_path = path.strip_prefix(root).unwrap(); let dest_migration_file = out_dir.join(relative_path); if let Some(parent) = dest_migration_file.parent() { fs::create_dir_all(parent) .expect("Failed to create migration directory in OUT_DIR"); } fs::copy(path, &dest_migration_file).expect("Failed to copy migration file to OUT_DIR"); // Use path relative to OUT_DIR for include_str let relative_to_out_dir = relative_path.to_str().unwrap().replace("\\", "/"); writeln!( out_file, " (\"{prefix}\", \"{rel_name}\", include_str!(r#\"{}\"#)),", relative_to_out_dir ) .unwrap(); println!("cargo:rerun-if-changed={}", path.display()); } writeln!(out_file, "];").unwrap(); println!("cargo:rerun-if-changed={}", migration_path.display()); } } fn find_migrations_dirs(root: &Path) -> Vec { let mut found = Vec::new(); find_migrations_dirs_rec(root, &mut found); found } fn find_migrations_dirs_rec(dir: &Path, found: &mut Vec) { if let Ok(entries) = fs::read_dir(dir) { for entry in entries.flatten() { let path = entry.path(); if path.is_dir() { if path.file_name().unwrap_or_default() == "migrations" { found.push(path.clone()); } find_migrations_dirs_rec(&path, found); } } } } fn visit_dirs(dir: &Path, files: &mut Vec) -> std::io::Result<()> { for entry in fs::read_dir(dir)? { let entry = entry?; let path = entry.path(); if path.is_dir() { visit_dirs(&path, files)?; } else if path.is_file() { files.push(path); } } Ok(()) }