1
use std::env;
2
use std::fs;
3
use std::path::{Path, PathBuf};
4
use std::process::Command;
5
use wasm_bindgen_cli_support::Bindgen;
6

            
7
4
fn main() {
8
4
    let output = Command::new("git")
9
4
        .args(["rev-parse", "HEAD"])
10
4
        .output()
11
4
        .expect("Can't get git revision");
12
4
    let git_hash = String::from_utf8(output.stdout)
13
4
        .expect("Can't parse git revision")
14
4
        .trim()
15
4
        .to_owned();
16
4
    println!("cargo:rustc-env=GIT_HASH={git_hash}");
17

            
18
4
    let build_date = chrono::Utc::now().to_rfc3339();
19
4
    println!("cargo:rustc-env=BUILD_DATE={build_date}");
20

            
21
4
    println!("cargo:rerun-if-changed=frontend/src");
22
4
    build_wasm_frontend();
23

            
24
4
    println!("cargo:rerun-if-changed=../doc");
25
4
    build_org_docs();
26
4
}
27

            
28
32
fn export_org_file(org_path: &Path, out_dir: &Path, eval_babel: bool) {
29
32
    let html_name = org_path.file_stem().unwrap().to_str().unwrap().to_owned() + ".html";
30
32
    let html_in_place = org_path.with_extension("html");
31
32
    let html_dest = out_dir.join(&html_name);
32

            
33
32
    if html_dest.exists() {
34
14
        let org_modified = fs::metadata(org_path)
35
14
            .and_then(|m| m.modified())
36
14
            .unwrap_or(std::time::SystemTime::UNIX_EPOCH);
37
14
        let html_modified = fs::metadata(&html_dest)
38
14
            .and_then(|m| m.modified())
39
14
            .unwrap_or(std::time::SystemTime::UNIX_EPOCH);
40
14
        if html_modified >= org_modified {
41
14
            return;
42
        }
43
18
    }
44

            
45
18
    let mut args: Vec<&str> = vec!["-q", "--batch"];
46
18
    let org_str = org_path.to_str().unwrap();
47
18
    args.push(org_str);
48
18
    args.extend_from_slice(&["-l", "ox-html"]);
49
    // Batch export visits/writes files; a stale `.#` lock from a killed
50
    // prior emacs would abort with `file-locked` in batch mode. Disable
51
    // lock files entirely (suppresses both creating and checking them).
52
18
    args.extend_from_slice(&["--eval", "(setq create-lockfiles nil)"]);
53
18
    if eval_babel {
54
6
        args.extend_from_slice(&[
55
6
            "--eval",
56
6
            "(progn (require 'ob-emacs-lisp) (setq org-confirm-babel-evaluate nil))",
57
6
        ]);
58
12
    }
59
18
    args.extend_from_slice(&["--eval", "(org-html-export-to-html)", "-f", "kill-emacs"]);
60

            
61
18
    let status = Command::new("emacs").args(&args).status();
62
18
    match status {
63
18
        Ok(s) if s.success() => {
64
14
            if html_in_place.exists() {
65
14
                fs::create_dir_all(out_dir).expect("Failed to create doc output dir");
66
14
                fs::rename(&html_in_place, &html_dest).expect("Failed to move HTML");
67
14
            }
68
        }
69
4
        Ok(s) => eprintln!(
70
            "Warning: emacs export failed for {} (exit {})",
71
            org_str,
72
4
            s.code().unwrap_or(-1)
73
        ),
74
        Err(_) => eprintln!("Warning: emacs not found, skipping doc generation"),
75
    }
76
32
}
77

            
78
4
fn build_org_docs() {
79
4
    let project_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
80
4
        .parent()
81
4
        .unwrap()
82
4
        .to_path_buf();
83
4
    let doc_dir = project_root.join("doc");
84
4
    let static_doc = PathBuf::from("static/doc");
85

            
86
    // doc/disdoc.org → static/doc/
87
4
    export_org_file(&doc_dir.join("disdoc.org"), &static_doc, false);
88

            
89
    // doc/scripts/*.org → static/doc/scripts/
90
4
    if let Ok(entries) = fs::read_dir(doc_dir.join("scripts")) {
91
32
        for entry in entries.flatten() {
92
32
            let path = entry.path();
93
32
            if path.extension().is_some_and(|e| e == "org") {
94
16
                export_org_file(&path, &static_doc.join("scripts"), false);
95
20
            }
96
        }
97
    }
98

            
99
    // doc/nomiscript/*.org → static/doc/nomiscript/ (with babel eval)
100
4
    if let Ok(entries) = fs::read_dir(doc_dir.join("nomiscript")) {
101
12
        for entry in entries.flatten() {
102
12
            let path = entry.path();
103
12
            if path.extension().is_some_and(|e| e == "org") {
104
12
                export_org_file(&path, &static_doc.join("nomiscript"), true);
105
12
            }
106
        }
107
    }
108
4
}
109

            
110
4
fn build_wasm_frontend() {
111
4
    let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".to_string());
112
4
    let project_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
113
4
        .parent()
114
4
        .unwrap()
115
4
        .to_path_buf();
116
4
    let wasm_target_dir = project_root.join("target-wasm");
117
4
    let wasm_file = wasm_target_dir
118
4
        .join("wasm32-unknown-unknown")
119
4
        .join(&profile)
120
4
        .join("nomisync_frontend.wasm");
121

            
122
4
    let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
123
4
    let mut cmd = Command::new(&cargo);
124
4
    cmd.args([
125
4
        "build",
126
4
        "-p",
127
4
        "nomisync-frontend",
128
4
        "--target",
129
4
        "wasm32-unknown-unknown",
130
4
        "--features",
131
4
        "auto-init",
132
4
        "-j",
133
4
        "1",
134
4
    ])
135
4
    .current_dir(&project_root)
136
4
    .env("CARGO_TARGET_DIR", &wasm_target_dir)
137
4
    .env_remove("CARGO_MAKEFLAGS")
138
4
    .env_remove("MAKEFLAGS")
139
4
    .env_remove("RUSTC_WRAPPER")
140
4
    .env_remove("RUSTC_WORKSPACE_WRAPPER")
141
4
    .env_remove("RUSTFLAGS")
142
4
    .env_remove("CARGO_ENCODED_RUSTFLAGS");
143

            
144
4
    if profile == "release" {
145
        cmd.arg("--release");
146
4
    }
147

            
148
4
    assert!(
149
4
        cmd.status().expect("Failed to execute cargo").success(),
150
        "Failed to build WASM frontend"
151
    );
152
4
    assert!(
153
4
        wasm_file.exists(),
154
        "WASM file not found: {}",
155
        wasm_file.display()
156
    );
157

            
158
4
    let out_dir = PathBuf::from("static/wasm");
159
4
    std::fs::create_dir_all(&out_dir).expect("Failed to create output directory");
160

            
161
4
    let out_wasm = out_dir.join("nomisync_frontend_bg.wasm");
162

            
163
4
    Bindgen::new()
164
4
        .input_path(&wasm_file)
165
4
        .web(true)
166
4
        .expect("Failed to set web mode")
167
4
        .omit_default_module_path(false)
168
4
        .typescript(false)
169
4
        .generate(&out_dir)
170
4
        .expect("Failed to generate WASM bindings");
171

            
172
4
    if profile == "release" {
173
        let status = Command::new("wasm-opt")
174
            .args(["-Oz", "--output"])
175
            .arg(&out_wasm)
176
            .arg(&out_wasm)
177
            .status();
178

            
179
        if let Ok(status) = status {
180
            if !status.success() {
181
                eprintln!("Warning: wasm-opt failed, skipping optimization");
182
            }
183
        } else {
184
            eprintln!("Warning: wasm-opt not found, skipping optimization");
185
        }
186
4
    }
187
4
}