1
use sha2::{Digest, Sha256};
2
use std::env;
3
use std::fs;
4
use std::io::{self, Read};
5
use std::process::Command;
6

            
7
16
fn checksum(file_path: &str) -> io::Result<String> {
8
16
    let mut file = fs::File::open(file_path)?;
9
16
    let mut hasher = Sha256::new();
10
16
    let mut buffer = [0; 1024];
11

            
12
    loop {
13
136
        let bytes_read = file.read(&mut buffer)?;
14
136
        if bytes_read == 0 {
15
16
            break;
16
120
        }
17
120
        hasher.update(&buffer[..bytes_read]);
18
    }
19

            
20
16
    let hash = hasher.finalize();
21
512
    Ok(hash.iter().fold(String::new(), |mut acc, b| {
22
        use std::fmt::Write;
23
512
        write!(acc, "{b:02x}").unwrap();
24
512
        acc
25
512
    }))
26
16
}
27

            
28
/// Regenerate `doc/versions.org` from git history with git-cliff. It is a
29
/// build artifact (gitignored), `#+include`d by `disdoc.org`, so the disdoc
30
/// export below fails if it is missing. The pre-commit hook regenerates it the
31
/// same way before staging README; doing it here keeps a plain `cargo build`
32
/// self-contained (one build, no out-of-band step) instead of depending on a
33
/// hook having run.
34
4
fn regenerate_changelog() {
35
4
    let root = format!("{}/..", env::var("CARGO_MANIFEST_DIR").unwrap());
36
4
    let status = Command::new("git-cliff")
37
4
        .current_dir(&root)
38
4
        .args(["-o", "doc/versions.org"])
39
4
        .status()
40
4
        .expect(
41
4
            "failed to spawn git-cliff for doc/versions.org. cargo build requires \
42
4
             git-cliff on PATH (same as emacs); `cargo install git-cliff`",
43
        );
44
4
    assert!(status.success(), "git-cliff changelog generation failed");
45
4
}
46

            
47
4
fn main() {
48
    // `versions.org` is regenerated from every commit, so re-run when HEAD moves.
49
4
    println!("cargo:rerun-if-changed=../.git/HEAD");
50
4
    regenerate_changelog();
51

            
52
4
    println!("cargo:rerun-if-changed=../doc/disdoc.org");
53
4
    let status = Command::new("emacs")
54
4
        .args([
55
4
            "-q",
56
4
            "--batch",
57
4
            "--eval",
58
4
            "(progn (require 'ob-emacs-lisp) (require 'ob-shell) (setq org-confirm-babel-evaluate nil create-lockfiles nil))",
59
4
            "../doc/disdoc.org",
60
4
            "-l",
61
4
            "ox-md",
62
4
            "--eval",
63
4
            "(org-md-export-to-markdown)",
64
4
            "-f",
65
4
            "save-buffer",
66
4
            "-f",
67
4
            "kill-emacs",
68
4
        ])
69
4
        .status()
70
4
        .expect("Failed to export doc");
71

            
72
4
    assert!(status.success(), "Building finance doc failed");
73

            
74
4
    fs::copy("../doc/disdoc.md", "../target/nomisync.md").expect("Can't update finance doc file");
75

            
76
4
    println!(
77
        "cargo:rustc-env=CARGO_TARGET_DIR={}/../",
78
4
        env::var("CARGO_MANIFEST_DIR").unwrap()
79
    );
80

            
81
4
    println!("cargo:rerun-if-changed=../doc/nomisync.org");
82
4
    let status = Command::new("emacs")
83
4
        .args([
84
4
            "-q",
85
4
            "--batch",
86
4
            "../doc/nomisync.org",
87
4
            "--eval",
88
4
            "(org-babel-do-load-languages 'org-babel-load-languages '((sql . t)))",
89
4
            "--eval",
90
4
            "(setq org-confirm-babel-evaluate nil create-lockfiles nil)",
91
4
            "--eval",
92
4
            "(org-babel-tangle)",
93
4
            "-f",
94
4
            "kill-emacs",
95
4
        ])
96
4
        .status()
97
4
        .expect("Failed to export SQL");
98

            
99
4
    assert!(status.success(), "Building SQL from org failed");
100

            
101
4
    sync_migration("../doc/nomisync.sql", "../migrations/0002_nomisync.sql");
102
4
    sync_migration(
103
4
        "../doc/0004_tags_canonical.sql",
104
4
        "../migrations/0004_tags_canonical.sql",
105
    );
106

            
107
4
    println!("cargo:rerun-if-changed=../migrations");
108
4
}
109

            
110
8
fn sync_migration(tangled: &str, migration: &str) {
111
8
    if fs::metadata(tangled).is_err() {
112
        return;
113
8
    }
114

            
115
8
    let status = Command::new("sqlfluff")
116
8
        .args(["fix", "--dialect", "postgres", tangled, "--force"])
117
8
        .status()
118
8
        .expect("Failed to format SQL");
119

            
120
8
    assert!(status.success(), "Formatting tangled migration failed");
121

            
122
8
    let needs_copy = match fs::metadata(migration) {
123
8
        Ok(_) => checksum(tangled).unwrap() != checksum(migration).unwrap(),
124
        Err(_) => true,
125
    };
126

            
127
8
    if needs_copy {
128
        fs::copy(tangled, migration).expect("Can't update migration file");
129
8
    } else {
130
8
        fs::remove_file(tangled).unwrap();
131
8
    }
132
8
}