Skip to main content

max / goingson

2.7 KB · 67 lines History Blame Raw
1 //! Embeds `migrations/sqlite/*.sql` into the binary.
2 //!
3 //! This replaces what `sqlx::migrate!` did as a proc macro. The generated table
4 //! is `(version, description, sql)`; checksums are computed at runtime rather
5 //! than baked in, so the manifest test and the runner agree by construction.
6 //!
7 //! Filename parsing matches sqlx's exactly -- `<version>_<description>.sql`,
8 //! with `_` becoming a space in the description -- because the `_sqlx_migrations`
9 //! ledger in every existing install was written by sqlx and keeps being read and
10 //! appended to by the runner in `migrate.rs`.
11
12 use std::fmt::Write as _;
13 use std::path::{Path, PathBuf};
14
15 fn main() {
16 let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../migrations/sqlite");
17 let dir = dir.canonicalize().unwrap_or(dir);
18
19 // Re-run when a migration is added, removed or edited.
20 println!("cargo:rerun-if-changed={}", dir.display());
21
22 let mut entries: Vec<(i64, String, PathBuf)> = Vec::new();
23 for entry in std::fs::read_dir(&dir).expect("migrations/sqlite is unreadable") {
24 let path = entry.expect("unreadable dir entry").path();
25 let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
26 continue;
27 };
28 // sqlx skips anything not matching <version>_<rest>.sql. CHECKSUMS lands here.
29 let Some((version, rest)) = name.split_once('_') else {
30 continue;
31 };
32 if !Path::new(rest)
33 .extension()
34 .is_some_and(|ext| ext.eq_ignore_ascii_case("sql"))
35 {
36 continue;
37 }
38 let Ok(version) = version.parse::<i64>() else {
39 panic!("migration {name}: expected an integer version prefix");
40 };
41 let description = rest.trim_end_matches(".sql").replace('_', " ");
42 println!("cargo:rerun-if-changed={}", path.display());
43 entries.push((version, description, path));
44 }
45
46 entries.sort_by_key(|(version, _, _)| *version);
47 if let Some(dup) = entries.windows(2).find(|w| w[0].0 == w[1].0) {
48 panic!("duplicate migration version {}", dup[0].0);
49 }
50
51 let mut out = String::from(
52 "// @generated by build.rs from migrations/sqlite. Do not edit.\n\
53 pub(crate) static MIGRATIONS: &[(i64, &str, &str)] = &[\n",
54 );
55 for (version, description, path) in &entries {
56 let _ = writeln!(
57 out,
58 " ({version}, {description:?}, include_str!({:?})),",
59 path.display().to_string()
60 );
61 }
62 out.push_str("];\n");
63
64 let dest = Path::new(&std::env::var("OUT_DIR").expect("OUT_DIR unset")).join("migrations.rs");
65 std::fs::write(&dest, out).expect("failed to write generated migrations");
66 }
67