build.rs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. //! Build script
  2. #![allow(clippy::unwrap_used)]
  3. use std::cmp::Ordering;
  4. use std::env;
  5. use std::fs::{self, File};
  6. use std::io::Write;
  7. use std::path::{Path, PathBuf};
  8. fn main() {
  9. // Step 1: Find `migrations/` folder recursively
  10. let root = Path::new("src");
  11. // Get the OUT_DIR from Cargo - this is writable
  12. let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by Cargo"));
  13. for migration_path in find_migrations_dirs(root) {
  14. // Step 3: Output file path to OUT_DIR instead of source directory
  15. let parent = migration_path.parent().unwrap();
  16. // Create a unique filename based on the migration path to avoid conflicts
  17. let migration_name = parent
  18. .strip_prefix("src")
  19. .unwrap_or(parent)
  20. .to_str()
  21. .unwrap_or("default")
  22. .replace("/", "_")
  23. .replace("\\", "_");
  24. let dest_path = out_dir.join(format!("migrations_{migration_name}.rs"));
  25. let mut out_file = File::create(&dest_path).expect("Failed to create migrations.rs");
  26. let skip_name = migration_path.to_str().unwrap_or_default().len();
  27. // Step 2: Collect all files inside the migrations dir
  28. let mut files = Vec::new();
  29. visit_dirs(&migration_path, &mut files).expect("Failed to read migrations directory");
  30. files.sort_by(|path_a, path_b| {
  31. let parts_a = path_a.to_str().unwrap().replace("\\", "/")[skip_name + 1..]
  32. .split("/")
  33. .map(|x| x.to_owned())
  34. .collect::<Vec<_>>();
  35. let parts_b = path_b.to_str().unwrap().replace("\\", "/")[skip_name + 1..]
  36. .split("/")
  37. .map(|x| x.to_owned())
  38. .collect::<Vec<_>>();
  39. let prefix_a = if parts_a.len() == 2 {
  40. parts_a.first().map(|x| x.to_owned()).unwrap_or_default()
  41. } else {
  42. "".to_owned()
  43. };
  44. let prefix_b = if parts_a.len() == 2 {
  45. parts_b.first().map(|x| x.to_owned()).unwrap_or_default()
  46. } else {
  47. "".to_owned()
  48. };
  49. let prefix_cmp = prefix_a.cmp(&prefix_b);
  50. if prefix_cmp != Ordering::Equal {
  51. return prefix_cmp;
  52. }
  53. let path_a = path_a.file_name().unwrap().to_str().unwrap();
  54. let path_b = path_b.file_name().unwrap().to_str().unwrap();
  55. let prefix_a = path_a
  56. .split("_")
  57. .next()
  58. .and_then(|prefix| prefix.parse::<usize>().ok())
  59. .unwrap_or_default();
  60. let prefix_b = path_b
  61. .split("_")
  62. .next()
  63. .and_then(|prefix| prefix.parse::<usize>().ok())
  64. .unwrap_or_default();
  65. if prefix_a != 0 && prefix_b != 0 {
  66. prefix_a.cmp(&prefix_b)
  67. } else {
  68. path_a.cmp(path_b)
  69. }
  70. });
  71. writeln!(out_file, "/// @generated").unwrap();
  72. writeln!(out_file, "/// Auto-generated by build.rs").unwrap();
  73. writeln!(
  74. out_file,
  75. "pub static MIGRATIONS: &[(&str, &str, &str)] = &["
  76. )
  77. .unwrap();
  78. for path in &files {
  79. let parts = path.to_str().unwrap().replace("\\", "/")[skip_name + 1..]
  80. .split("/")
  81. .map(|x| x.to_owned())
  82. .collect::<Vec<_>>();
  83. let prefix = if parts.len() == 2 {
  84. parts.first().map(|x| x.to_owned()).unwrap_or_default()
  85. } else {
  86. "".to_owned()
  87. };
  88. let rel_name = &path.file_name().unwrap().to_str().unwrap();
  89. // Copy migration file to OUT_DIR
  90. let relative_path = path.strip_prefix(root).unwrap();
  91. let dest_migration_file = out_dir.join(relative_path);
  92. if let Some(parent) = dest_migration_file.parent() {
  93. fs::create_dir_all(parent)
  94. .expect("Failed to create migration directory in OUT_DIR");
  95. }
  96. fs::copy(path, &dest_migration_file).expect("Failed to copy migration file to OUT_DIR");
  97. // Use path relative to OUT_DIR for include_str
  98. let relative_to_out_dir = relative_path.to_str().unwrap().replace("\\", "/");
  99. writeln!(
  100. out_file,
  101. " (\"{prefix}\", \"{rel_name}\", include_str!(r#\"{relative_to_out_dir}\"#)),"
  102. )
  103. .unwrap();
  104. println!("cargo:rerun-if-changed={}", path.display());
  105. }
  106. writeln!(out_file, "];").unwrap();
  107. println!("cargo:rerun-if-changed={}", migration_path.display());
  108. }
  109. }
  110. fn find_migrations_dirs(root: &Path) -> Vec<PathBuf> {
  111. let mut found = Vec::new();
  112. find_migrations_dirs_rec(root, &mut found);
  113. found
  114. }
  115. fn find_migrations_dirs_rec(dir: &Path, found: &mut Vec<PathBuf>) {
  116. if let Ok(entries) = fs::read_dir(dir) {
  117. for entry in entries.flatten() {
  118. let path = entry.path();
  119. if path.is_dir() {
  120. if path.file_name().unwrap_or_default() == "migrations" {
  121. found.push(path.clone());
  122. }
  123. find_migrations_dirs_rec(&path, found);
  124. }
  125. }
  126. }
  127. }
  128. fn visit_dirs(dir: &Path, files: &mut Vec<PathBuf>) -> std::io::Result<()> {
  129. for entry in fs::read_dir(dir)? {
  130. let entry = entry?;
  131. let path = entry.path();
  132. if path.is_dir() {
  133. visit_dirs(&path, files)?;
  134. } else if path.is_file() {
  135. files.push(path);
  136. }
  137. }
  138. Ok(())
  139. }