mrubyedge_cli/subcommands/
wasm.rs

1extern 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}