use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::{env, fs};
use anyhow::Result;
use built::write_built_file;
fn get_wasm_runtime_manifest_path() -> PathBuf {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let output = std::process::Command::new(&cargo)
.args(["metadata", "--format-version=1"])
.output()
.expect("Cargo is not installed or not found in PATH");
assert!(
output.status.success(),
"Failed to get cargo metadata: {}",
String::from_utf8_lossy(&output.stderr)
);
#[derive(serde::Deserialize)]
struct CargoMetadata {
packages: Vec<CargoPackage>,
}
#[derive(serde::Deserialize)]
struct CargoPackage {
name: String,
manifest_path: PathBuf,
}
let metadata: CargoMetadata =
serde_json::from_slice(&output.stdout).expect("Failed to parse cargo metadata");
let hyperlight_wasm_runtime = metadata
.packages
.into_iter()
.find(|pkg| pkg.name == "hyperlight-wasm-runtime")
.expect("hyperlight-wasm-runtime crate not found in cargo metadata");
hyperlight_wasm_runtime.manifest_path
}
fn find_target_dir() -> PathBuf {
let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let target = env::var("TARGET").unwrap();
let target_dir = out_dir
.ancestors()
.nth(4)
.expect("OUT_DIR does not have enough ancestors to find target directory");
if target_dir.file_name() == Some(target.as_str().as_ref())
&& let Some(parent) = target_dir.parent()
&& parent.join("CACHEDIR.TAG").exists()
{
return parent.to_path_buf();
}
target_dir.to_path_buf()
}
fn build_wasm_runtime() -> PathBuf {
let profile = env::var_os("PROFILE").unwrap();
let target_dir = find_target_dir();
let target_dir = target_dir.join("hyperlight-wasm-runtime");
let manifest_path = get_wasm_runtime_manifest_path();
let runtime_dir = manifest_path.parent().unwrap();
if !runtime_dir.exists() {
panic!("missing hyperlight-wasm-runtime in-tree dependency");
}
println!("cargo::rerun-if-changed={}", runtime_dir.display());
println!("cargo::rerun-if-env-changed=WIT_WORLD");
println!("cargo::rerun-if-env-changed=WIT_WORLD_NAME");
let cargo_profile = if profile == "debug" { "dev" } else { "release" };
let mut cargo_cmd = cargo_hyperlight::cargo().unwrap();
let mut cmd = cargo_cmd
.arg("build")
.arg("--profile")
.arg(cargo_profile)
.arg("-v")
.arg("--target-dir")
.arg(&target_dir)
.arg("--manifest-path")
.arg(&manifest_path)
.arg("--locked")
.env_clear_cargo();
if std::env::var("CARGO_FEATURE_GDB").is_ok() {
cmd = cmd.arg("--features").arg("gdb");
}
if std::env::var("CARGO_FEATURE_PULLEY").is_ok() {
cmd = cmd.arg("--features").arg("pulley");
}
if std::env::var("CARGO_FEATURE_TRACE_GUEST").is_ok() {
cmd = cmd.arg("--features").arg("trace_guest");
}
cmd.status()
.unwrap_or_else(|e| panic!("could not run cargo build hyperlight-wasm-runtime: {e:?}"));
let resource = target_dir
.join("x86_64-hyperlight-none")
.join(profile)
.join("hyperlight-wasm-runtime");
if let Ok(path) = resource.canonicalize() {
if std::env::var("CARGO_FEATURE_GDB").is_ok() {
println!(
"cargo:warning=Hyperlight wasm runtime guest binary at: {}",
path.display()
);
}
path
} else {
panic!(
"could not find hyperlight-wasm-runtime after building it (expected {:?})",
resource
)
}
}
fn main() -> Result<()> {
let wasm_runtime_resource = build_wasm_runtime();
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("wasm_runtime_resource.rs");
let contents = format!(
"pub (super) static WASM_RUNTIME: [u8; include_bytes!({name:?}).len()] = *include_bytes!({name:?});",
name = wasm_runtime_resource.as_os_str()
);
fs::write(dest_path, contents).unwrap();
let wasm_runtime_bytes = fs::read(&wasm_runtime_resource).unwrap();
let elf = goblin::elf::Elf::parse(&wasm_runtime_bytes).unwrap();
let section_name = ".note_hyperlight_metadata";
let wasmtime_version_number = if let Some(header) = elf.section_headers.iter().find(|hdr| {
if let Some(name) = elf.shdr_strtab.get_at(hdr.sh_name) {
name == section_name
} else {
false
}
}) {
let start = header.sh_offset as usize;
let size = header.sh_size as usize;
let end = start + size;
let metadata_bytes = &wasm_runtime_bytes[start..end];
if let Some(null_pos) = metadata_bytes.iter().position(|&b| b == 0) {
std::str::from_utf8(&metadata_bytes[..null_pos]).unwrap()
} else {
std::str::from_utf8(metadata_bytes).unwrap()
}
} else {
panic!(".note_hyperlight_metadata section not found in hyperlight-wasm-runtime binary");
};
write_built_file()?;
let built_path = Path::new(&out_dir).join("built.rs");
let mut file = OpenOptions::new()
.create(false)
.append(true)
.open(built_path)
.unwrap();
let metadata = fs::metadata(&wasm_runtime_resource).unwrap();
let created = metadata.modified().unwrap();
let created_datetime: chrono::DateTime<chrono::Local> = created.into();
let wasm_runtime_created = format!(
"static WASM_RUNTIME_CREATED: &str = \"{created_datetime}\";",
created_datetime = created_datetime
);
let wasm_runtime_size = format!(
"static WASM_RUNTIME_SIZE: &str = \"{size}\";",
size = metadata.len()
);
let wasm_runtime_wasmtime_version = format!(
"static WASM_RUNTIME_WASMTIME_VERSION: &str = \"{wasmtime_version_number}\";",
wasmtime_version_number = wasmtime_version_number
);
writeln!(file, "{}", wasm_runtime_created).unwrap();
writeln!(file, "{}", wasm_runtime_size).unwrap();
writeln!(file, "{}", wasm_runtime_wasmtime_version).unwrap();
let hyperlight_wasm_runtime = fs::read(wasm_runtime_resource).unwrap();
let hash = blake3::hash(&hyperlight_wasm_runtime);
let hash_str = format!("static WASM_RUNTIME_BLAKE3_HASH: &str = \"{}\";", hash);
writeln!(file, "{}", hash_str).unwrap();
println!("cargo:rerun-if-changed=build.rs");
cfg_aliases::cfg_aliases! {
gdb: { all(feature = "gdb", debug_assertions) },
}
Ok(())
}