1
use std::fs;
2
use std::path::Path;
3
use std::process::Command;
4

            
5
4
fn build_wasm(manifest_path: &str, name: &str) {
6
4
    let status = Command::new("cargo")
7
4
        .args([
8
4
            "build",
9
4
            "--target",
10
4
            "wasm32-unknown-unknown",
11
4
            "--release",
12
4
            "--manifest-path",
13
4
            manifest_path,
14
4
        ])
15
4
        .env_remove("RUSTFLAGS")
16
4
        .env_remove("CARGO_ENCODED_RUSTFLAGS")
17
4
        .status()
18
4
        .unwrap_or_else(|_| panic!("Failed to build {name} WASM"));
19

            
20
4
    assert!(status.success(), "{name} WASM build failed");
21
4
}
22

            
23
4
fn main() {
24
4
    if std::env::var("MIRI_SYSROOT").is_ok() {
25
        return;
26
4
    }
27

            
28
4
    let manifest_dir = env!("CARGO_MANIFEST_DIR");
29
4
    let scripts_dir = Path::new(manifest_dir)
30
4
        .parent()
31
4
        .unwrap()
32
4
        .join("doc/scripts");
33
4
    let wasm_output_dir = Path::new(manifest_dir)
34
4
        .parent()
35
4
        .unwrap()
36
4
        .join("web/static/wasm");
37

            
38
4
    if !scripts_dir.exists() {
39
        return;
40
4
    }
41

            
42
4
    fs::create_dir_all(&wasm_output_dir).ok();
43

            
44
4
    println!("cargo:rerun-if-changed={}", scripts_dir.display());
45

            
46
4
    let org_files: Vec<_> = fs::read_dir(&scripts_dir)
47
4
        .expect("Failed to read doc/scripts directory")
48
4
        .filter_map(Result::ok)
49
12
        .filter(|e| e.path().extension().is_some_and(|ext| ext == "org"))
50
4
        .collect();
51

            
52
4
    for entry in org_files {
53
4
        let org_path = entry.path();
54
4
        println!("cargo:rerun-if-changed={}", org_path.display());
55

            
56
4
        fs::create_dir_all(scripts_dir.join("src")).ok();
57

            
58
4
        let status = Command::new("emacs")
59
4
            .args([
60
4
                "-q",
61
4
                "--batch",
62
4
                "--eval",
63
4
                "(setq org-confirm-babel-evaluate nil)",
64
4
                "--eval",
65
4
                &format!(
66
4
                    "(progn (find-file \"{}\") (org-babel-tangle))",
67
4
                    org_path.display()
68
4
                ),
69
4
            ])
70
4
            .current_dir(&scripts_dir)
71
4
            .status()
72
4
            .expect("Failed to run emacs org-babel-tangle");
73

            
74
4
        assert!(
75
4
            status.success(),
76
            "org-babel-tangle failed for {}",
77
            org_path.display()
78
        );
79

            
80
4
        let cargo_toml = scripts_dir.join("Cargo.toml");
81
4
        if !cargo_toml.exists() {
82
            eprintln!(
83
                "Warning: No Cargo.toml generated from {}",
84
                org_path.display()
85
            );
86
            continue;
87
4
        }
88

            
89
4
        let cargo_content =
90
4
            fs::read_to_string(&cargo_toml).expect("Failed to read generated Cargo.toml");
91
4
        let lib_name = cargo_content
92
4
            .lines()
93
8
            .find(|line| line.trim().starts_with("name = "))
94
4
            .and_then(|line| {
95
4
                let start = line.find('"')? + 1;
96
4
                let end = line.rfind('"')?;
97
4
                Some(&line[start..end])
98
4
            });
99

            
100
4
        let Some(lib_name) = lib_name else {
101
            eprintln!(
102
                "Warning: Could not find lib name in Cargo.toml from {}",
103
                org_path.display()
104
            );
105
            continue;
106
        };
107

            
108
4
        let wasm_name = lib_name.replace('-', "_");
109

            
110
4
        build_wasm(cargo_toml.to_str().unwrap(), lib_name);
111

            
112
4
        let wasm_source = scripts_dir
113
4
            .join("target/wasm32-unknown-unknown/release")
114
4
            .join(format!("{wasm_name}.wasm"));
115

            
116
4
        if wasm_source.exists() {
117
4
            let wasm_dest = wasm_output_dir.join(format!("{wasm_name}.wasm"));
118
4
            fs::copy(&wasm_source, &wasm_dest).unwrap_or_else(|_| {
119
                panic!(
120
                    "Failed to copy {} to {}",
121
                    wasm_source.display(),
122
                    wasm_dest.display()
123
                )
124
            });
125
4
            println!("cargo:warning=Built and installed {wasm_name}.wasm");
126
        } else {
127
            panic!("WASM file not found: {}", wasm_source.display());
128
        }
129

            
130
4
        fs::remove_file(&cargo_toml).ok();
131
4
        fs::remove_file(scripts_dir.join("Cargo.lock")).ok();
132
4
        fs::remove_dir_all(scripts_dir.join("src")).ok();
133
4
        fs::remove_dir_all(scripts_dir.join("target")).ok();
134
    }
135
4
}