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

            
33
28
    if html_dest.exists() {
34
12
        let org_modified = fs::metadata(org_path)
35
12
            .and_then(|m| m.modified())
36
12
            .unwrap_or(std::time::SystemTime::UNIX_EPOCH);
37
12
        let html_modified = fs::metadata(&html_dest)
38
12
            .and_then(|m| m.modified())
39
12
            .unwrap_or(std::time::SystemTime::UNIX_EPOCH);
40
12
        if html_modified >= org_modified {
41
12
            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
16
    if eval_babel {
50
6
        args.extend_from_slice(&[
51
6
            "--eval",
52
6
            "(progn (require 'ob-emacs-lisp) (setq org-confirm-babel-evaluate nil))",
53
6
        ]);
54
10
    }
55
16
    args.extend_from_slice(&["--eval", "(org-html-export-to-html)", "-f", "kill-emacs"]);
56

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

            
74
4
fn build_org_docs() {
75
4
    let project_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
76
4
        .parent()
77
4
        .unwrap()
78
4
        .to_path_buf();
79
4
    let doc_dir = project_root.join("doc");
80
4
    let static_doc = PathBuf::from("static/doc");
81

            
82
    // doc/disdoc.org → static/doc/
83
4
    export_org_file(&doc_dir.join("disdoc.org"), &static_doc, false);
84

            
85
    // doc/scripts/*.org → static/doc/scripts/
86
4
    if let Ok(entries) = fs::read_dir(doc_dir.join("scripts")) {
87
18
        for entry in entries.flatten() {
88
18
            let path = entry.path();
89
18
            if path.extension().is_some_and(|e| e == "org") {
90
12
                export_org_file(&path, &static_doc.join("scripts"), false);
91
12
            }
92
        }
93
    }
94

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

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

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

            
140
4
    if profile == "release" {
141
        cmd.arg("--release");
142
4
    }
143

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

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

            
157
4
    let out_wasm = out_dir.join("nomisync_frontend_bg.wasm");
158

            
159
4
    Bindgen::new()
160
4
        .input_path(&wasm_file)
161
4
        .web(true)
162
4
        .expect("Failed to set web mode")
163
4
        .omit_default_module_path(false)
164
4
        .typescript(false)
165
4
        .generate(&out_dir)
166
4
        .expect("Failed to generate WASM bindings");
167

            
168
4
    if profile == "release" {
169
        let status = Command::new("wasm-opt")
170
            .args(["-Oz", "--output"])
171
            .arg(&out_wasm)
172
            .arg(&out_wasm)
173
            .status();
174

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