use std::path::PathBuf;
use std::process::Command;
use std::time::Instant;
use crate::runtime::{ExecContext, ExecError, ExecOutput, Runtime};
use crate::wasm::{cache_path_for_source, engine::make_engine, WasmModule};
pub struct RustRuntime {
engine: wasmtime::Engine,
}
impl Default for RustRuntime {
fn default() -> Self {
Self {
engine: make_engine(),
}
}
}
impl RustRuntime {
pub fn new(engine: wasmtime::Engine) -> Self {
Self { engine }
}
}
impl Runtime for RustRuntime {
fn language(&self) -> &'static str {
"rust"
}
fn execute(&self, source: &str, ctx: &ExecContext) -> Result<ExecOutput, ExecError> {
let start = Instant::now();
let wrapped = wrap_in_main(source);
let cache_target = cache_path_for_source("rust", &wrapped);
let wasm_bytes = match &cache_target {
Some(path)
if path.exists()
&& std::fs::metadata(path)
.map(|m| m.len() > 0)
.unwrap_or(false) =>
{
std::fs::read(path).map_err(ExecError::Io)?
}
Some(path) => {
let bytes = compile_rust_to_wasm(&wrapped, ctx)?;
let _ = std::fs::write(path, &bytes);
bytes
}
None => compile_rust_to_wasm(&wrapped, ctx)?,
};
let module = WasmModule::from_bytes("rust", &self.engine, &wasm_bytes)?;
let mut out = module.execute("", ctx)?;
out.duration = start.elapsed();
Ok(out)
}
}
fn wrap_in_main(source: &str) -> String {
if source.contains("fn main") {
source.to_string()
} else {
format!("fn main() {{\n{source}\n}}\n")
}
}
fn compile_rust_to_wasm(source: &str, ctx: &ExecContext) -> Result<Vec<u8>, ExecError> {
let tmp_root = std::env::temp_dir().join("outl-rustc");
std::fs::create_dir_all(&tmp_root).map_err(ExecError::Io)?;
let src_path = tmp_root.join(format!("snippet-{}.rs", std::process::id()));
std::fs::write(&src_path, source).map_err(ExecError::Io)?;
let wasm_out = src_path.with_extension("wasm");
let output = Command::new("rustc")
.arg("--target")
.arg("wasm32-wasip1")
.arg("-O")
.arg("-o")
.arg(&wasm_out)
.arg(&src_path)
.current_dir(&ctx.workspace_root)
.output()
.map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
ExecError::Sandbox(
"`rustc` not found on PATH. Install via `rustup` and \
add the wasm32-wasip1 target: `rustup target add wasm32-wasip1`."
.into(),
)
} else {
ExecError::Sandbox(format!("spawn rustc: {e}"))
}
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
if stderr.contains("the `wasm32-wasip1` target") || stderr.contains("toolchain") {
return Err(ExecError::Sandbox(format!(
"{stderr}\n\nhint: `rustup target add wasm32-wasip1`"
)));
}
return Err(ExecError::Language(stderr));
}
let bytes = std::fs::read(&wasm_out).map_err(ExecError::Io)?;
let _ = std::fs::remove_file(&src_path);
let _ = std::fs::remove_file(&wasm_out);
Ok(bytes)
}
pub fn cache_dir_for_rust() -> Option<PathBuf> {
let mut p = crate::wasm::cache_dir()?;
p.push("rust");
std::fs::create_dir_all(&p).ok()?;
Some(p)
}