+205 -205 +/-4 browse
1 | diff --git a/core/build.rs b/core/build.rs |
2 | deleted file mode 100644 |
3 | index 44e41d2..0000000 |
4 | --- a/core/build.rs |
5 | +++ /dev/null |
6 | @@ -1,95 +0,0 @@ |
7 | - /* |
8 | - * This file is part of mailpot |
9 | - * |
10 | - * Copyright 2020 - Manos Pitsidianakis |
11 | - * |
12 | - * This program is free software: you can redistribute it and/or modify |
13 | - * it under the terms of the GNU Affero General Public License as |
14 | - * published by the Free Software Foundation, either version 3 of the |
15 | - * License, or (at your option) any later version. |
16 | - * |
17 | - * This program is distributed in the hope that it will be useful, |
18 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | - * GNU Affero General Public License for more details. |
21 | - * |
22 | - * You should have received a copy of the GNU Affero General Public License |
23 | - * along with this program. If not, see <https://www.gnu.org/licenses/>. |
24 | - */ |
25 | - |
26 | - use std::{ |
27 | - fs::OpenOptions, |
28 | - process::{Command, Stdio}, |
29 | - }; |
30 | - |
31 | - // // Source: https://stackoverflow.com/a/64535181 |
32 | - // fn is_output_file_outdated<P1, P2>(input: P1, output: P2) -> io::Result<bool> |
33 | - // where |
34 | - // P1: AsRef<Path>, |
35 | - // P2: AsRef<Path>, |
36 | - // { |
37 | - // let out_meta = metadata(output); |
38 | - // if let Ok(meta) = out_meta { |
39 | - // let output_mtime = meta.modified()?; |
40 | - // |
41 | - // // if input file is more recent than our output, we are outdated |
42 | - // let input_meta = metadata(input)?; |
43 | - // let input_mtime = input_meta.modified()?; |
44 | - // |
45 | - // Ok(input_mtime > output_mtime) |
46 | - // } else { |
47 | - // // output file not found, we are outdated |
48 | - // Ok(true) |
49 | - // } |
50 | - // } |
51 | - |
52 | - include!("make_migrations.rs"); |
53 | - |
54 | - const MIGRATION_RS: &str = "src/migrations.rs.inc"; |
55 | - |
56 | - fn main() { |
57 | - println!("cargo:rerun-if-changed=src/migrations.rs.inc"); |
58 | - println!("cargo:rerun-if-changed=migrations"); |
59 | - println!("cargo:rerun-if-changed=src/schema.sql.m4"); |
60 | - |
61 | - let mut output = Command::new("m4") |
62 | - .arg("./src/schema.sql.m4") |
63 | - .output() |
64 | - .unwrap(); |
65 | - if String::from_utf8_lossy(&output.stdout).trim().is_empty() { |
66 | - panic!( |
67 | - "m4 output is empty. stderr was {}", |
68 | - String::from_utf8_lossy(&output.stderr) |
69 | - ); |
70 | - } |
71 | - let user_version: i32 = make_migrations("migrations", MIGRATION_RS, &mut output.stdout); |
72 | - let mut verify = Command::new(std::env::var("SQLITE_BIN").unwrap_or("sqlite3".into())) |
73 | - .stdin(Stdio::piped()) |
74 | - .stdout(Stdio::piped()) |
75 | - .stderr(Stdio::piped()) |
76 | - .spawn() |
77 | - .unwrap(); |
78 | - println!( |
79 | - "Verifying by creating an in-memory database in sqlite3 and feeding it the output schema." |
80 | - ); |
81 | - verify |
82 | - .stdin |
83 | - .take() |
84 | - .unwrap() |
85 | - .write_all(&output.stdout) |
86 | - .unwrap(); |
87 | - let exit = verify.wait_with_output().unwrap(); |
88 | - if !exit.status.success() { |
89 | - panic!( |
90 | - "sqlite3 could not read SQL schema: {}", |
91 | - String::from_utf8_lossy(&exit.stdout) |
92 | - ); |
93 | - } |
94 | - let mut file = std::fs::File::create("./src/schema.sql").unwrap(); |
95 | - file.write_all(&output.stdout).unwrap(); |
96 | - file.write_all( |
97 | - &format!("\n\n-- Set current schema version.\n\nPRAGMA user_version = {user_version};\n") |
98 | - .as_bytes(), |
99 | - ) |
100 | - .unwrap(); |
101 | - } |
102 | diff --git a/core/build/make_migrations.rs b/core/build/make_migrations.rs |
103 | new file mode 100644 |
104 | index 0000000..91f3f2e |
105 | --- /dev/null |
106 | +++ b/core/build/make_migrations.rs |
107 | @@ -0,0 +1,110 @@ |
108 | + /* |
109 | + * This file is part of mailpot |
110 | + * |
111 | + * Copyright 2023 - Manos Pitsidianakis |
112 | + * |
113 | + * This program is free software: you can redistribute it and/or modify |
114 | + * it under the terms of the GNU Affero General Public License as |
115 | + * published by the Free Software Foundation, either version 3 of the |
116 | + * License, or (at your option) any later version. |
117 | + * |
118 | + * This program is distributed in the hope that it will be useful, |
119 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
120 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
121 | + * GNU Affero General Public License for more details. |
122 | + * |
123 | + * You should have received a copy of the GNU Affero General Public License |
124 | + * along with this program. If not, see <https://www.gnu.org/licenses/>. |
125 | + */ |
126 | + |
127 | + use std::{fs::read_dir, io::Write, path::Path}; |
128 | + |
129 | + /// Scans migrations directory for file entries, and creates a rust file with an array containing |
130 | + /// the migration slices. |
131 | + /// |
132 | + /// |
133 | + /// If a migration is a data migration (not a CREATE, DROP or ALTER statement) it is appended to |
134 | + /// the schema file. |
135 | + /// |
136 | + /// Returns the current `user_version` PRAGMA value. |
137 | + pub fn make_migrations<M: AsRef<Path>, O: AsRef<Path>>( |
138 | + migrations_path: M, |
139 | + output_file: O, |
140 | + schema_file: &mut Vec<u8>, |
141 | + ) -> i32 { |
142 | + let migrations_folder_path = migrations_path.as_ref(); |
143 | + let output_file_path = output_file.as_ref(); |
144 | + |
145 | + let mut paths = vec![]; |
146 | + let mut undo_paths = vec![]; |
147 | + for entry in read_dir(migrations_folder_path).unwrap() { |
148 | + let entry = entry.unwrap(); |
149 | + let path = entry.path(); |
150 | + if path.is_dir() || path.extension().map(|os| os.to_str().unwrap()) != Some("sql") { |
151 | + continue; |
152 | + } |
153 | + if path |
154 | + .file_name() |
155 | + .unwrap() |
156 | + .to_str() |
157 | + .unwrap() |
158 | + .ends_with("undo.sql") |
159 | + { |
160 | + undo_paths.push(path); |
161 | + } else { |
162 | + paths.push(path); |
163 | + } |
164 | + } |
165 | + |
166 | + paths.sort(); |
167 | + undo_paths.sort(); |
168 | + let mut migr_rs = OpenOptions::new() |
169 | + .write(true) |
170 | + .create(true) |
171 | + .truncate(true) |
172 | + .open(output_file_path) |
173 | + .unwrap(); |
174 | + migr_rs |
175 | + .write_all(b"\n//(user_version, redo sql, undo sql\n&[") |
176 | + .unwrap(); |
177 | + for (i, (p, u)) in paths.iter().zip(undo_paths.iter()).enumerate() { |
178 | + // This should be a number string, padded with 2 zeros if it's less than 3 |
179 | + // digits. e.g. 001, \d{3} |
180 | + let mut num = p.file_stem().unwrap().to_str().unwrap(); |
181 | + let is_data = num.ends_with(".data"); |
182 | + if is_data { |
183 | + num = num.strip_suffix(".data").unwrap(); |
184 | + } |
185 | + |
186 | + if !u.file_name().unwrap().to_str().unwrap().starts_with(num) { |
187 | + panic!("Undo file {u:?} should match with {p:?}"); |
188 | + } |
189 | + |
190 | + if num.parse::<u32>().is_err() { |
191 | + panic!("Migration file {p:?} should start with a number"); |
192 | + } |
193 | + assert_eq!(num.parse::<usize>().unwrap(), i + 1, "migration sql files should start with 1, not zero, and no intermediate numbers should be missing. Panicked on file: {}", p.display()); |
194 | + migr_rs.write_all(b"(").unwrap(); |
195 | + migr_rs |
196 | + .write_all(num.trim_start_matches('0').as_bytes()) |
197 | + .unwrap(); |
198 | + migr_rs.write_all(b",r##\"").unwrap(); |
199 | + |
200 | + let redo = std::fs::read_to_string(p).unwrap(); |
201 | + migr_rs.write_all(redo.trim().as_bytes()).unwrap(); |
202 | + migr_rs.write_all(b"\"##,r##\"").unwrap(); |
203 | + migr_rs |
204 | + .write_all(std::fs::read_to_string(u).unwrap().trim().as_bytes()) |
205 | + .unwrap(); |
206 | + migr_rs.write_all(b"\"##),").unwrap(); |
207 | + if is_data { |
208 | + schema_file.extend(b"\n\n-- ".iter()); |
209 | + schema_file.extend(num.as_bytes().iter()); |
210 | + schema_file.extend(b".data.sql\n\n".iter()); |
211 | + schema_file.extend(redo.into_bytes().into_iter()); |
212 | + } |
213 | + } |
214 | + migr_rs.write_all(b"]").unwrap(); |
215 | + migr_rs.flush().unwrap(); |
216 | + paths.len() as i32 |
217 | + } |
218 | diff --git a/core/build/mod.rs b/core/build/mod.rs |
219 | new file mode 100644 |
220 | index 0000000..44e41d2 |
221 | --- /dev/null |
222 | +++ b/core/build/mod.rs |
223 | @@ -0,0 +1,95 @@ |
224 | + /* |
225 | + * This file is part of mailpot |
226 | + * |
227 | + * Copyright 2020 - Manos Pitsidianakis |
228 | + * |
229 | + * This program is free software: you can redistribute it and/or modify |
230 | + * it under the terms of the GNU Affero General Public License as |
231 | + * published by the Free Software Foundation, either version 3 of the |
232 | + * License, or (at your option) any later version. |
233 | + * |
234 | + * This program is distributed in the hope that it will be useful, |
235 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
236 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
237 | + * GNU Affero General Public License for more details. |
238 | + * |
239 | + * You should have received a copy of the GNU Affero General Public License |
240 | + * along with this program. If not, see <https://www.gnu.org/licenses/>. |
241 | + */ |
242 | + |
243 | + use std::{ |
244 | + fs::OpenOptions, |
245 | + process::{Command, Stdio}, |
246 | + }; |
247 | + |
248 | + // // Source: https://stackoverflow.com/a/64535181 |
249 | + // fn is_output_file_outdated<P1, P2>(input: P1, output: P2) -> io::Result<bool> |
250 | + // where |
251 | + // P1: AsRef<Path>, |
252 | + // P2: AsRef<Path>, |
253 | + // { |
254 | + // let out_meta = metadata(output); |
255 | + // if let Ok(meta) = out_meta { |
256 | + // let output_mtime = meta.modified()?; |
257 | + // |
258 | + // // if input file is more recent than our output, we are outdated |
259 | + // let input_meta = metadata(input)?; |
260 | + // let input_mtime = input_meta.modified()?; |
261 | + // |
262 | + // Ok(input_mtime > output_mtime) |
263 | + // } else { |
264 | + // // output file not found, we are outdated |
265 | + // Ok(true) |
266 | + // } |
267 | + // } |
268 | + |
269 | + include!("make_migrations.rs"); |
270 | + |
271 | + const MIGRATION_RS: &str = "src/migrations.rs.inc"; |
272 | + |
273 | + fn main() { |
274 | + println!("cargo:rerun-if-changed=src/migrations.rs.inc"); |
275 | + println!("cargo:rerun-if-changed=migrations"); |
276 | + println!("cargo:rerun-if-changed=src/schema.sql.m4"); |
277 | + |
278 | + let mut output = Command::new("m4") |
279 | + .arg("./src/schema.sql.m4") |
280 | + .output() |
281 | + .unwrap(); |
282 | + if String::from_utf8_lossy(&output.stdout).trim().is_empty() { |
283 | + panic!( |
284 | + "m4 output is empty. stderr was {}", |
285 | + String::from_utf8_lossy(&output.stderr) |
286 | + ); |
287 | + } |
288 | + let user_version: i32 = make_migrations("migrations", MIGRATION_RS, &mut output.stdout); |
289 | + let mut verify = Command::new(std::env::var("SQLITE_BIN").unwrap_or("sqlite3".into())) |
290 | + .stdin(Stdio::piped()) |
291 | + .stdout(Stdio::piped()) |
292 | + .stderr(Stdio::piped()) |
293 | + .spawn() |
294 | + .unwrap(); |
295 | + println!( |
296 | + "Verifying by creating an in-memory database in sqlite3 and feeding it the output schema." |
297 | + ); |
298 | + verify |
299 | + .stdin |
300 | + .take() |
301 | + .unwrap() |
302 | + .write_all(&output.stdout) |
303 | + .unwrap(); |
304 | + let exit = verify.wait_with_output().unwrap(); |
305 | + if !exit.status.success() { |
306 | + panic!( |
307 | + "sqlite3 could not read SQL schema: {}", |
308 | + String::from_utf8_lossy(&exit.stdout) |
309 | + ); |
310 | + } |
311 | + let mut file = std::fs::File::create("./src/schema.sql").unwrap(); |
312 | + file.write_all(&output.stdout).unwrap(); |
313 | + file.write_all( |
314 | + &format!("\n\n-- Set current schema version.\n\nPRAGMA user_version = {user_version};\n") |
315 | + .as_bytes(), |
316 | + ) |
317 | + .unwrap(); |
318 | + } |
319 | diff --git a/core/make_migrations.rs b/core/make_migrations.rs |
320 | deleted file mode 100644 |
321 | index 91f3f2e..0000000 |
322 | --- a/core/make_migrations.rs |
323 | +++ /dev/null |
324 | @@ -1,110 +0,0 @@ |
325 | - /* |
326 | - * This file is part of mailpot |
327 | - * |
328 | - * Copyright 2023 - Manos Pitsidianakis |
329 | - * |
330 | - * This program is free software: you can redistribute it and/or modify |
331 | - * it under the terms of the GNU Affero General Public License as |
332 | - * published by the Free Software Foundation, either version 3 of the |
333 | - * License, or (at your option) any later version. |
334 | - * |
335 | - * This program is distributed in the hope that it will be useful, |
336 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
337 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
338 | - * GNU Affero General Public License for more details. |
339 | - * |
340 | - * You should have received a copy of the GNU Affero General Public License |
341 | - * along with this program. If not, see <https://www.gnu.org/licenses/>. |
342 | - */ |
343 | - |
344 | - use std::{fs::read_dir, io::Write, path::Path}; |
345 | - |
346 | - /// Scans migrations directory for file entries, and creates a rust file with an array containing |
347 | - /// the migration slices. |
348 | - /// |
349 | - /// |
350 | - /// If a migration is a data migration (not a CREATE, DROP or ALTER statement) it is appended to |
351 | - /// the schema file. |
352 | - /// |
353 | - /// Returns the current `user_version` PRAGMA value. |
354 | - pub fn make_migrations<M: AsRef<Path>, O: AsRef<Path>>( |
355 | - migrations_path: M, |
356 | - output_file: O, |
357 | - schema_file: &mut Vec<u8>, |
358 | - ) -> i32 { |
359 | - let migrations_folder_path = migrations_path.as_ref(); |
360 | - let output_file_path = output_file.as_ref(); |
361 | - |
362 | - let mut paths = vec![]; |
363 | - let mut undo_paths = vec![]; |
364 | - for entry in read_dir(migrations_folder_path).unwrap() { |
365 | - let entry = entry.unwrap(); |
366 | - let path = entry.path(); |
367 | - if path.is_dir() || path.extension().map(|os| os.to_str().unwrap()) != Some("sql") { |
368 | - continue; |
369 | - } |
370 | - if path |
371 | - .file_name() |
372 | - .unwrap() |
373 | - .to_str() |
374 | - .unwrap() |
375 | - .ends_with("undo.sql") |
376 | - { |
377 | - undo_paths.push(path); |
378 | - } else { |
379 | - paths.push(path); |
380 | - } |
381 | - } |
382 | - |
383 | - paths.sort(); |
384 | - undo_paths.sort(); |
385 | - let mut migr_rs = OpenOptions::new() |
386 | - .write(true) |
387 | - .create(true) |
388 | - .truncate(true) |
389 | - .open(output_file_path) |
390 | - .unwrap(); |
391 | - migr_rs |
392 | - .write_all(b"\n//(user_version, redo sql, undo sql\n&[") |
393 | - .unwrap(); |
394 | - for (i, (p, u)) in paths.iter().zip(undo_paths.iter()).enumerate() { |
395 | - // This should be a number string, padded with 2 zeros if it's less than 3 |
396 | - // digits. e.g. 001, \d{3} |
397 | - let mut num = p.file_stem().unwrap().to_str().unwrap(); |
398 | - let is_data = num.ends_with(".data"); |
399 | - if is_data { |
400 | - num = num.strip_suffix(".data").unwrap(); |
401 | - } |
402 | - |
403 | - if !u.file_name().unwrap().to_str().unwrap().starts_with(num) { |
404 | - panic!("Undo file {u:?} should match with {p:?}"); |
405 | - } |
406 | - |
407 | - if num.parse::<u32>().is_err() { |
408 | - panic!("Migration file {p:?} should start with a number"); |
409 | - } |
410 | - assert_eq!(num.parse::<usize>().unwrap(), i + 1, "migration sql files should start with 1, not zero, and no intermediate numbers should be missing. Panicked on file: {}", p.display()); |
411 | - migr_rs.write_all(b"(").unwrap(); |
412 | - migr_rs |
413 | - .write_all(num.trim_start_matches('0').as_bytes()) |
414 | - .unwrap(); |
415 | - migr_rs.write_all(b",r##\"").unwrap(); |
416 | - |
417 | - let redo = std::fs::read_to_string(p).unwrap(); |
418 | - migr_rs.write_all(redo.trim().as_bytes()).unwrap(); |
419 | - migr_rs.write_all(b"\"##,r##\"").unwrap(); |
420 | - migr_rs |
421 | - .write_all(std::fs::read_to_string(u).unwrap().trim().as_bytes()) |
422 | - .unwrap(); |
423 | - migr_rs.write_all(b"\"##),").unwrap(); |
424 | - if is_data { |
425 | - schema_file.extend(b"\n\n-- ".iter()); |
426 | - schema_file.extend(num.as_bytes().iter()); |
427 | - schema_file.extend(b".data.sql\n\n".iter()); |
428 | - schema_file.extend(redo.into_bytes().into_iter()); |
429 | - } |
430 | - } |
431 | - migr_rs.write_all(b"]").unwrap(); |
432 | - migr_rs.flush().unwrap(); |
433 | - paths.len() as i32 |
434 | - } |