mrubyedge_cli/subcommands/
wasm.rs1extern crate mruby_compiler2_sys;
2extern crate rand;
3
4use clap::Args;
5use std::{
6 fs::File,
7 io::Read,
8 path::{Path, PathBuf},
9 process::Command,
10};
11
12use askama::Template;
13use rand::distributions::{Alphanumeric, DistString};
14
15use crate::rbs_parser;
16use crate::template;
17
18const MRUBY_EDGE_DEFAULT_VERSION: &'static str = ">= 1";
19
20#[derive(Debug, Clone, Args)]
21pub struct WasmArgs {
22 #[arg(short = 'f', long)]
23 fnname: Option<PathBuf>,
24 #[arg(short = 'm', long)]
25 mruby_edge_version: Option<String>,
26 #[arg(short = 'F', long)]
27 features: Vec<String>,
28 #[arg(short = 'W', long)]
29 no_wasi: bool,
30 #[arg(long)]
31 skip_cleanup: bool,
32 #[arg(long)]
33 debug_mruby_edge: bool,
34 #[arg(long)]
35 verbose: bool,
36 #[arg(long)]
37 strip_binary: bool,
38 path: PathBuf,
39}
40
41fn sh_do(sharg: &str, debug: bool) -> Result<(), Box<dyn std::error::Error>> {
42 println!("running: `{}`", sharg);
43 let out = Command::new("/bin/sh").args(["-c", sharg]).output()?;
44 if debug && out.stdout.len() != 0 {
45 println!(
46 "stdout:\n{}",
47 String::from_utf8_lossy(&out.stdout).to_string().trim()
48 );
49 }
50 if debug && out.stderr.len() != 0 {
51 println!(
52 "stderr:\n{}",
53 String::from_utf8_lossy(&out.stderr).to_string().trim()
54 );
55 }
56 if !out.status.success() {
57 println!("{:?}", out.status);
58 panic!("failed to execute command");
59 }
60
61 Ok(())
62}
63
64fn file_prefix_of(file: &Path) -> Option<String> {
65 file.file_name()?
66 .to_str()?
67 .split('.')
68 .next()
69 .map(|s| s.to_string())
70}
71
72fn debug_println(debug: bool, msg: &str) {
73 if debug {
74 eprintln!("{}", msg);
75 }
76}
77
78pub fn execute(args: WasmArgs) -> Result<(), Box<dyn std::error::Error>> {
79 let mut rng = rand::thread_rng();
80 let suffix = Alphanumeric.sample_string(&mut rng, 32);
81
82 let fnname = args.fnname;
83 let path = args.path;
84 let mrubyfile = std::fs::canonicalize(&path)?;
85 let fname = file_prefix_of(mrubyfile.as_path()).unwrap();
86
87 let pwd = std::env::current_dir()?;
88 std::env::set_current_dir(std::env::var("TMPDIR").unwrap_or("/tmp".to_string()))?;
89
90 let dirname = format!("work-mrubyedge-{}", suffix);
91 std::fs::create_dir(&dirname)?;
92 std::env::set_current_dir(format!("./work-mrubyedge-{}", &suffix))?;
93 std::fs::create_dir("src")?;
94
95 let code = std::fs::read_to_string(&mrubyfile)?;
96 let out_file = format!("src/{}.mrb", fname);
97
98 if args.verbose {
99 unsafe {
100 let mut context = mruby_compiler2_sys::MRubyCompiler2Context::new();
101 context.dump_bytecode(&code)?;
102 }
103 }
104 unsafe {
105 mruby_compiler2_sys::MRubyCompiler2Context::new()
106 .compile_to_file(&code, out_file.as_ref())?
107 }
108
109 let mut features = Vec::new();
110 if args.no_wasi {
111 features.push("no-wasi");
112 } else {
113 features.push("wasi");
114 }
115 for f in args.features.iter() {
116 features.push(f.as_str());
117 }
118 let mrubyedge_feature = features
119 .iter()
120 .map(|s| format!("\"{}\"", s))
121 .collect::<Vec<String>>()
122 .join(", ");
123
124 if args.debug_mruby_edge {
125 let cargo_toml = template::cargo_toml::CargoTomlDebug {
126 mruby_edge_crate_path: "/Users/udzura/ghq/github.com/udzura/mrubyedge/mrubyedge",
127 mrubyedge_feature: &mrubyedge_feature,
128 };
129 std::fs::write("Cargo.toml", cargo_toml.render()?)?;
130 } else {
131 let cargo_toml = template::cargo_toml::CargoToml {
132 mrubyedge_version: &args
133 .mruby_edge_version
134 .unwrap_or_else(|| MRUBY_EDGE_DEFAULT_VERSION.to_string()),
135 mrubyedge_feature: &mrubyedge_feature,
136 strip: &args.strip_binary.to_string(),
137 };
138 std::fs::write("Cargo.toml", cargo_toml.render()?)?;
139 }
140
141 let export_rbs_fname = format!("{}.export.rbs", fname);
142 let export_rbs = mrubyfile.parent().unwrap().join(&export_rbs_fname);
143 let cont: String;
144
145 let mut ftypes_imports = Vec::new();
146 let import_rbs_fname = format!("{}.import.rbs", fname);
147 let import_rbs = mrubyfile.parent().unwrap().join(&import_rbs_fname);
148 if import_rbs.exists() {
149 debug_println(
150 args.verbose,
151 &format!(
152 "detected import.rbs: {}",
153 import_rbs.as_path().to_string_lossy()
154 ),
155 );
156 let mut f = File::open(import_rbs)?;
157 let mut s = String::new();
158 f.read_to_string(&mut s)?;
159
160 let (_, parsed) = rbs_parser::parse(&s).unwrap();
161 let parsed: &mut [rbs_parser::FuncDef] = Vec::leak(parsed);
162 for def in parsed.iter() {
163 ftypes_imports.push(template::RustImportFnTemplate {
164 func_name: &def.name,
165 args_decl: def.args_decl(),
166 rettype_decl: def.rettype_decl(),
167 imported_body: def.imported_body(),
168 import_helper_var: def.import_helper_var(),
169 })
170 }
171 }
172
173 if export_rbs.exists() {
174 debug_println(
175 args.verbose,
176 &format!(
177 "detected export.rbs: {}",
178 export_rbs.as_path().to_string_lossy()
179 ),
180 );
181 let mut f = File::open(export_rbs)?;
182 let mut s = String::new();
183 f.read_to_string(&mut s)?;
184
185 let (_, parsed) = rbs_parser::parse(&s).unwrap();
186 let mut ftypes = vec![];
187 let parsed: &mut [rbs_parser::FuncDef] = Vec::leak(parsed);
188 for def in parsed.iter() {
189 ftypes.push(template::RustFnTemplate {
190 func_name: &def.name,
191 args_decl: def.args_decl(),
192 args_let_vec: def.args_let_vec(),
193 str_args_converter: def.str_args_converter(),
194 rettype_decl: def.rettype_decl(),
195 handle_retval: def.handle_retval(),
196 exported_helper_var: def.exported_helper_var(),
197 })
198 }
199
200 let lib_rs = template::LibRs {
201 file_basename: &fname,
202 ftypes: &&ftypes,
203 ftypes_imports: &ftypes_imports,
204 };
205 let rendered = lib_rs.render()?;
206 cont = rendered;
207 } else {
208 if fnname.is_none() {
209 panic!("--fnname FNNAME should be specified when export.rbs does not exist")
210 }
211 let fnname = fnname.unwrap();
212
213 let ftypes = vec![template::RustFnTemplate {
214 func_name: fnname.to_str().unwrap(),
215 args_decl: "",
216 args_let_vec: "vec![]",
217 str_args_converter: "",
218 rettype_decl: "-> ()",
219 handle_retval: "()",
220 exported_helper_var: "",
221 }];
222
223 let lib_rs = template::LibRs {
224 file_basename: &fname,
225 ftypes: &&ftypes,
226 ftypes_imports: &ftypes_imports,
227 };
228 let rendered = lib_rs.render()?;
229 cont = rendered;
230 }
231 debug_println(args.verbose, "[debug] will generate main.rs:");
232 debug_println(args.verbose, &format!("{}", &cont));
233 std::fs::write("src/lib.rs", cont)?;
234
235 let target = if args.no_wasi {
236 "wasm32-unknown-unknown"
237 } else {
238 "wasm32-wasip1"
239 };
240
241 sh_do(
242 &format!("cargo build --target {} --release", target),
243 args.verbose,
244 )?;
245 sh_do(
246 &format!(
247 "cp ./target/{}/release/mywasm.wasm {}/{}.wasm",
248 target,
249 &pwd.to_str().unwrap(),
250 &fname.to_string()
251 ),
252 args.verbose,
253 )?;
254 if args.skip_cleanup {
255 println!(
256 "debug: working directory for compile wasm is remained in {}",
257 std::env::current_dir()?.as_os_str().to_str().unwrap()
258 );
259 } else {
260 sh_do(
261 &format!("cd .. && rm -rf work-mrubyedge-{}", &suffix),
262 args.verbose,
263 )?;
264 }
265
266 std::env::set_current_dir(pwd)?;
267
268 println!("[ok] wasm file is generated: {}.wasm", &fname);
269
270 Ok(())
271}