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

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

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

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

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

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

            
45
16
    let mut args: Vec<&str> = vec!["-q", "--batch"];
46
16
    let org_str = org_path.to_str().unwrap();
47
16
    args.push(org_str);
48
16
    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
16
    args.extend_from_slice(&["--eval", "(setq create-lockfiles nil)"]);
53
16
    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
10
    }
59
16
    args.extend_from_slice(&["--eval", "(org-html-export-to-html)", "-f", "kill-emacs"]);
60

            
61
16
    let status = Command::new("emacs").args(&args).status();
62
16
    match status {
63
16
        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
2
        Ok(s) => eprintln!(
70
            "Warning: emacs export failed for {} (exit {})",
71
            org_str,
72
2
            s.code().unwrap_or(-1)
73
        ),
74
        Err(_) => eprintln!("Warning: emacs not found, skipping doc generation"),
75
    }
76
16
}
77

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

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

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

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

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

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

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

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

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

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

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

            
172
2
    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
2
    }
187
2
}