//! Embeds `migrations/sqlite/*.sql` into the binary. //! //! This replaces what `sqlx::migrate!` did as a proc macro. The generated table //! is `(version, description, sql)`; checksums are computed at runtime rather //! than baked in, so the manifest test and the runner agree by construction. //! //! Filename parsing matches sqlx's exactly -- `_.sql`, //! with `_` becoming a space in the description -- because the `_sqlx_migrations` //! ledger in every existing install was written by sqlx and keeps being read and //! appended to by the runner in `migrate.rs`. use std::fmt::Write as _; use std::path::{Path, PathBuf}; fn main() { let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../migrations/sqlite"); let dir = dir.canonicalize().unwrap_or(dir); // Re-run when a migration is added, removed or edited. println!("cargo:rerun-if-changed={}", dir.display()); let mut entries: Vec<(i64, String, PathBuf)> = Vec::new(); for entry in std::fs::read_dir(&dir).expect("migrations/sqlite is unreadable") { let path = entry.expect("unreadable dir entry").path(); let Some(name) = path.file_name().and_then(|n| n.to_str()) else { continue; }; // sqlx skips anything not matching _.sql. CHECKSUMS lands here. let Some((version, rest)) = name.split_once('_') else { continue; }; if !Path::new(rest) .extension() .is_some_and(|ext| ext.eq_ignore_ascii_case("sql")) { continue; } let Ok(version) = version.parse::() else { panic!("migration {name}: expected an integer version prefix"); }; let description = rest.trim_end_matches(".sql").replace('_', " "); println!("cargo:rerun-if-changed={}", path.display()); entries.push((version, description, path)); } entries.sort_by_key(|(version, _, _)| *version); if let Some(dup) = entries.windows(2).find(|w| w[0].0 == w[1].0) { panic!("duplicate migration version {}", dup[0].0); } let mut out = String::from( "// @generated by build.rs from migrations/sqlite. Do not edit.\n\ pub(crate) static MIGRATIONS: &[(i64, &str, &str)] = &[\n", ); for (version, description, path) in &entries { let _ = writeln!( out, " ({version}, {description:?}, include_str!({:?})),", path.display().to_string() ); } out.push_str("];\n"); let dest = Path::new(&std::env::var("OUT_DIR").expect("OUT_DIR unset")).join("migrations.rs"); std::fs::write(&dest, out).expect("failed to write generated migrations"); }