use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use sha2::{Digest, Sha256};
use crate::embedded_stdlib::EmbeddedStdlib;
use crate::error::Error;
const EMBEDDED_RUNTIME: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/runtime.cwasm"));
fn runtime_content_hash() -> String {
static HASH: OnceLock<String> = OnceLock::new();
HASH.get_or_init(|| {
let mut hasher = Sha256::new();
hasher.update(EMBEDDED_RUNTIME);
let result = hasher.finalize();
result[..8].iter().map(|b| format!("{b:02x}")).collect()
})
.clone()
}
#[derive(Debug, Clone)]
pub struct EmbeddedResources {
pub stdlib_path: PathBuf,
pub runtime_path: PathBuf,
#[allow(dead_code)]
temp_dir: PathBuf,
}
impl EmbeddedResources {
pub fn get() -> Result<&'static Self, Error> {
static RESOURCES: OnceLock<Result<EmbeddedResources, String>> = OnceLock::new();
RESOURCES
.get_or_init(|| Self::extract().map_err(|e| e.to_string()))
.as_ref()
.map_err(|e| Error::Initialization(e.clone()))
}
fn extract() -> Result<Self, Error> {
let temp_base = std::env::temp_dir().join("eryx-embedded");
std::fs::create_dir_all(&temp_base)
.map_err(|e| Error::Initialization(format!("Failed to create temp directory: {e}")))?;
let stdlib = EmbeddedStdlib::get()?;
let stdlib_path = stdlib.path().to_path_buf();
let runtime_path = Self::extract_runtime(&temp_base)?;
Ok(Self {
stdlib_path,
runtime_path,
temp_dir: temp_base,
})
}
fn extract_runtime(temp_dir: &Path) -> Result<PathBuf, Error> {
let version = env!("CARGO_PKG_VERSION");
let content_hash = runtime_content_hash();
let runtime_path = temp_dir.join(format!("runtime-{version}-{content_hash}.cwasm"));
if runtime_path.exists() {
tracing::debug!(path = %runtime_path.display(), "Using cached runtime");
return Ok(runtime_path);
}
if let Ok(entries) = std::fs::read_dir(temp_dir) {
let prefix = format!("runtime-{version}-");
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.starts_with(&prefix) && name_str.ends_with(".cwasm") {
tracing::debug!(path = %entry.path().display(), "Removing stale runtime");
let _ = std::fs::remove_file(entry.path());
}
}
}
tracing::info!(path = %runtime_path.display(), "Extracting embedded runtime");
let mut temp_file = tempfile::NamedTempFile::with_prefix_in("runtime-", temp_dir)
.map_err(|e| Error::Initialization(format!("Failed to create temp file: {e}")))?;
temp_file
.write_all(EMBEDDED_RUNTIME)
.map_err(|e| Error::Initialization(format!("Failed to write runtime file: {e}")))?;
temp_file
.as_file()
.sync_all()
.map_err(|e| Error::Initialization(format!("Failed to sync runtime file: {e}")))?;
match temp_file.persist(&runtime_path) {
Ok(_) => {}
Err(e) if runtime_path.exists() => {
tracing::debug!("Runtime extracted by another process");
drop(e);
}
Err(e) => {
return Err(Error::Initialization(format!(
"Failed to persist runtime file: {}",
e.error
)));
}
}
Ok(runtime_path)
}
#[must_use]
pub fn stdlib(&self) -> &Path {
&self.stdlib_path
}
#[must_use]
pub fn runtime(&self) -> &Path {
&self.runtime_path
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn embedded_runtime_is_included() {
assert!(
EMBEDDED_RUNTIME.len() > 1_000_000,
"Embedded runtime should be > 1MB"
);
}
}