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
12pub enum GuestGeneratorType {
14 Rust,
15 Js,
16 TinyGo,
17}
18
19impl GuestGeneratorType {
20 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
37pub 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
61pub fn compile_rust(arch: &str, target: &str) -> Result<()> {
63 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 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 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 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 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 let src_content = std::fs::read(src_js_path)?;
124
125 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 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
164pub 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}