land_runtime/
compiler.rs

1use anyhow::{anyhow, bail, Result};
2use std::collections::HashMap;
3use std::io::Write;
4use std::path::{Path, PathBuf};
5use std::process::{Command, Stdio};
6use tracing::{debug, info};
7use which::which;
8use wit_bindgen_core::wit_parser::Resolve;
9use wit_bindgen_core::{Files, WorldGenerator};
10use wit_component::ComponentEncoder;
11
12/// GuestGeneratorType is the type of the guest generator.
13pub enum GuestGeneratorType {
14    Rust,
15    Js,
16    TinyGo,
17}
18
19impl GuestGeneratorType {
20    /// create generator by type
21    fn create_generator(&self) -> Result<Box<dyn WorldGenerator>> {
22        match self {
23            GuestGeneratorType::Rust => {
24                let opts = wit_bindgen_rust::Opts {
25                    macro_export: true,
26                    rustfmt: true,
27                    ..Default::default()
28                };
29                let builder = opts.build();
30                Ok(builder)
31            }
32            _ => Err(anyhow!("unsupport guest generator")),
33        }
34    }
35}
36
37/// parse wit file and return world id
38pub fn generate_guest(
39    wit_dir: &Path,
40    world: Option<String>,
41    t: GuestGeneratorType,
42) -> Result<HashMap<String, String>> {
43    let mut generator = t.create_generator()?;
44
45    let mut resolve = Resolve::default();
46    let pkg = resolve.push_dir(wit_dir)?.0;
47
48    let mut output_maps = HashMap::new();
49    let mut files = Files::default();
50    let world = resolve.select_world(pkg, world.as_deref())?;
51    generator.generate(&resolve, world, &mut files);
52    for (name, contents) in files.iter() {
53        output_maps.insert(
54            name.to_string(),
55            String::from_utf8_lossy(contents).to_string(),
56        );
57    }
58    Ok(output_maps)
59}
60
61/// compile_rust compiles the Rust code in the current directory.
62pub fn compile_rust(arch: &str, target: &str) -> Result<()> {
63    // cargo build --target arch --release
64    let mut cmd = Command::new("cargo");
65    let child = cmd
66        .arg("build")
67        .arg("--release")
68        .arg("--target")
69        .arg(arch)
70        .stdout(Stdio::inherit())
71        .stderr(Stdio::inherit())
72        .spawn()
73        .expect("failed to execute cargo child process");
74    let output = child
75        .wait_with_output()
76        .expect("failed to wait on cargo child process");
77    if output.status.success() {
78        info!("Cargo build wasm success");
79    } else {
80        return Err(anyhow!("Cargo build wasm failed: {:?}", output));
81    }
82
83    // check target file is exist
84    if !PathBuf::from(target).exists() {
85        return Err(anyhow!("Wasm file not found: {}", target));
86    }
87
88    Ok(())
89}
90
91pub fn compile_js(target: &str, src_js_path: &str, js_engine_path: Option<String>) -> Result<()> {
92    // js need wizer command
93    let cmd = match which("wizer") {
94            Ok(cmd) => cmd,
95            Err(_) => {
96                return Err(anyhow::anyhow!(
97                    "Wizer not found \n\tplease install wizer first: \n\tcargo install wizer --all-features\n\tmore infomation see: https://github.com/bytecodealliance/wizer"
98                ))
99            }
100        };
101
102    // create dir
103    let engine_dir = Path::new(&target).parent().unwrap();
104    std::fs::create_dir_all(engine_dir).expect("create dir failed");
105    debug!("Create engine dir: {}", &engine_dir.display());
106
107    // prepare js engine
108    let engine_file = engine_dir.join("js_engine.wasm");
109    let engine_wasm = if let Some(js_engine) = js_engine_path {
110        if !PathBuf::from(&js_engine).exists() {
111            bail!("File not found: {}", &js_engine);
112        }
113        std::fs::read(&js_engine).unwrap()
114    } else {
115        let engine_bytes = include_bytes!("../engine/quickjs.wasm");
116        engine_bytes.to_vec()
117    };
118    debug!("Use engine_wasm len: {}", engine_wasm.len());
119    debug!("Initialize target wasm file: {}", &target);
120    std::fs::write(&engine_file, engine_wasm)?;
121
122    // call wizer
123    let src_content = std::fs::read(src_js_path)?;
124
125    // wizer leaf_wasm_js.wasm -o leaf_wasm_js_wizer.wasm --allow-wasi --inherit-stdio=true --inherit-env=true
126    let mut child = Command::new(cmd)
127        .arg(&engine_file)
128        .arg("-o")
129        .arg(target)
130        .arg("--allow-wasi")
131        .arg("--inherit-stdio=true")
132        .arg("--inherit-env=true")
133        .arg("--wasm-bulk-memory=true")
134        .stdin(Stdio::piped())
135        .stdout(Stdio::inherit())
136        .stderr(Stdio::inherit())
137        .spawn()
138        .expect("failed to execute wizer child process");
139    let mut stdin = child.stdin.take().expect("failed to get stdin");
140
141    std::thread::spawn(move || {
142        stdin
143            .write_all(src_content.as_slice())
144            .expect("failed to write to stdin");
145    });
146
147    let output = child
148        .wait_with_output()
149        .expect("failed to wait on wizer child process");
150    if output.status.success() {
151        // print output
152        debug!(
153            "Wizer output: \n{}",
154            std::str::from_utf8(&output.stdout).unwrap()
155        );
156        info!("Wizer success: {}", &target);
157    } else {
158        panic!("Wizer failed: {output:?}");
159    }
160
161    Ok(())
162}
163
164/// convert_component is used to convert wasm module to component
165pub fn convert_component(path: &str, output: Option<String>) -> Result<()> {
166    debug!("Convert component, {path}");
167    let file_bytes = std::fs::read(path).expect("parse wasm file error");
168    let wasi_adapter = include_bytes!("../engine/wasi_snapshot_preview1.reactor.wasm");
169
170    let component = ComponentEncoder::default()
171        .module(&file_bytes)
172        .expect("Pull custom sections from module")
173        .validate(true)
174        .adapter("wasi_snapshot_preview1", wasi_adapter)
175        .expect("Add adapter to component")
176        .encode()
177        .expect("Encode component");
178
179    let output = output.unwrap_or_else(|| path.to_string());
180    std::fs::write(&output, component).expect("Write component file error");
181    info!("Convert component success, {}", &output);
182    Ok(())
183}
184
185#[cfg(test)]
186mod tests {
187    use super::generate_guest;
188    use std::path::Path;
189
190    #[test]
191    fn test_compile() {
192        let wit_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../wit");
193        let outputs = generate_guest(
194            wit_dir.as_path(),
195            Some("http-service".to_string()),
196            super::GuestGeneratorType::Rust,
197        )
198        .unwrap();
199        assert_eq!(outputs.len(), 1);
200        assert_eq!(outputs.contains_key("http_service.rs"), true);
201    }
202}