use regex::Regex;
use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::io;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::Command;
fn copy_libs(source: &Path, dest: &Path) -> Result<(), String> {
if !source.exists() {
return Err(format!(
"Source directory does not exist: {}",
source.display()
));
}
fs::create_dir_all(dest).map_err(|e| {
format!(
"Failed to create destination directory {}: {}",
dest.display(),
e
)
})?;
for entry in fs::read_dir(source).map_err(|e| {
format!(
"Failed to read source directory {}: {}",
source.display(),
e
)
})? {
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
let source_path = entry.path();
let file_name = source_path.file_name().ok_or("Failed to get filename")?;
if !is_library_file(&source_path) {
continue;
}
let dest_path = dest.join(file_name);
let metadata = fs::symlink_metadata(&source_path).map_err(|e| {
format!(
"Failed to read metadata for {}: {}",
source_path.display(),
e
)
})?;
if metadata.file_type().is_symlink() {
continue;
}
if metadata.is_file() {
if dest_path.exists() {
fs::remove_file(&dest_path).map_err(|e| {
format!(
"Failed to remove existing file {}: {}",
dest_path.display(),
e
)
})?;
}
fs::copy(&source_path, &dest_path).map_err(|e| {
format!(
"Failed to copy {} -> {}: {}",
source_path.display(),
dest_path.display(),
e
)
})?;
println!(
"cargo:warning=Bundled library: {}",
file_name.to_string_lossy()
);
}
}
Ok(())
}
fn is_library_file(path: &Path) -> bool {
let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
if filename.ends_with(".dylib") {
return true;
}
if filename.contains(".so") {
return true;
}
if filename.ends_with(".dll") {
return true;
}
false
}
fn bundle_boxlite_deps(runtime_dir: &Path) -> Vec<(String, PathBuf)> {
let re = Regex::new(r"^DEP_[A-Z0-9]+_([A-Z0-9]+)_BOXLITE_DEP$").unwrap();
let mut collected = Vec::new();
for (key, source_path_str) in env::vars() {
if let Some(caps) = re.captures(&key) {
let name = caps[1].to_lowercase();
let source_path = Path::new(&source_path_str);
if !source_path.exists() {
panic!("Dependency path does not exist: {}", source_path_str);
}
println!(
"cargo:warning=Found dependency: {} at {}",
name, source_path_str
);
if source_path.is_dir() {
match copy_libs(source_path, runtime_dir) {
Ok(()) => {
collected.push((name, runtime_dir.to_path_buf()));
}
Err(e) => {
panic!("Failed to copy {}: {}", name, e);
}
}
} else {
let file_name = source_path.file_name().expect("Failed to get filename");
let dest_path = runtime_dir.join(file_name);
if dest_path.exists() {
fs::remove_file(&dest_path).unwrap_or_else(|e| {
panic!("Failed to remove {}: {}", dest_path.display(), e)
});
}
fs::copy(source_path, &dest_path).unwrap_or_else(|e| {
panic!(
"Failed to copy {} -> {}: {}",
source_path.display(),
dest_path.display(),
e
)
});
println!("cargo:warning=Bundled: {}", file_name.to_string_lossy());
collected.push((name, dest_path));
}
}
}
collected
}
#[cfg(target_os = "linux")]
fn compile_seccomp_filters() {
use std::collections::HashMap;
use std::convert::TryInto;
use std::fs;
use std::io::Cursor;
let target = env::var("TARGET").expect("Missing TARGET env var");
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("Missing target arch");
let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR");
let json_path = format!("resources/seccomp/{}.json", target);
let json_path = if Path::new(&json_path).exists() {
json_path
} else {
println!(
"cargo:warning=No seccomp filter for {}, using unimplemented.json",
target
);
"resources/seccomp/unimplemented.json".to_string()
};
let bpf_path = format!("{}/seccomp_filter.bpf", out_dir);
println!(
"cargo:warning=Compiling seccomp filter: {} -> {}",
json_path, bpf_path
);
let json_content = fs::read(&json_path)
.unwrap_or_else(|e| panic!("Failed to read seccomp JSON {}: {}", json_path, e));
let arch: seccompiler::TargetArch = target_arch
.as_str()
.try_into()
.unwrap_or_else(|e| panic!("Unsupported target architecture {}: {:?}", target_arch, e));
let reader = Cursor::new(json_content);
let bpf_map = seccompiler::compile_from_json(reader, arch).unwrap_or_else(|e| {
panic!(
"Failed to compile seccomp filters from {}: {}",
json_path, e
)
});
let mut converted_map: HashMap<String, Vec<u64>> = HashMap::new();
for (thread_name, filter) in bpf_map {
let instructions: Vec<u64> = filter
.iter()
.map(|instr| {
unsafe { std::mem::transmute_copy(instr) }
})
.collect();
converted_map.insert(thread_name, instructions);
}
let bincode_config = bincode::config::standard().with_fixed_int_encoding();
let serialized = bincode::encode_to_vec(&converted_map, bincode_config)
.unwrap_or_else(|e| panic!("Failed to serialize BPF filters: {}", e));
fs::write(&bpf_path, serialized)
.unwrap_or_else(|e| panic!("Failed to write BPF filter to {}: {}", bpf_path, e));
println!(
"cargo:warning=Successfully compiled seccomp filter ({} bytes)",
fs::metadata(&bpf_path).unwrap().len()
);
println!("cargo:rerun-if-changed={}", json_path);
println!("cargo:rerun-if-changed=resources/seccomp/");
}
#[cfg(not(target_os = "linux"))]
fn compile_seccomp_filters() {
println!("cargo:warning=Seccomp compilation skipped (not Linux)");
}
fn download_file(url: &str, dest: &Path) -> io::Result<()> {
println!("cargo:warning=Downloading {}...", url);
let output = Command::new("curl")
.args(["-fsSL", "-o", dest.to_str().unwrap(), url])
.output()?;
if !output.status.success() {
return Err(io::Error::other(format!(
"curl failed: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
fn runtime_target() -> Option<&'static str> {
let os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
match (os.as_str(), arch.as_str()) {
("macos", "aarch64") => Some("darwin-arm64"),
("linux", "x86_64") => Some("linux-x64-gnu"),
("linux", "aarch64") => Some("linux-arm64-gnu"),
_ => None,
}
}
fn extract_runtime_tarball(tarball: &Path, dest: &Path) -> io::Result<()> {
let status = Command::new("tar")
.args([
"-xzf",
tarball.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
"--strip-components=1",
])
.status()?;
if !status.success() {
return Err(io::Error::other("tar extraction failed"));
}
Ok(())
}
fn create_library_symlinks(dir: &Path) {
let re = Regex::new(r"^(lib\w+)\.(\d+\.)*\d+\.dylib$|^(lib\w+\.so)\.\d+(\.\d+)*$").unwrap();
let entries: Vec<_> = fs::read_dir(dir)
.into_iter()
.flatten()
.filter_map(|e| e.ok())
.collect();
for entry in &entries {
let filename = entry.file_name();
let filename = filename.to_string_lossy();
if let Some(caps) = re.captures(&filename) {
let base = caps.get(1).or(caps.get(3)).map(|m| m.as_str());
if let Some(base) = base {
let symlink_name = if caps.get(1).is_some() {
format!("{}.dylib", base)
} else {
base.to_string()
};
let symlink_path = dir.join(&symlink_name);
if !symlink_path.exists() {
#[cfg(unix)]
{
std::os::unix::fs::symlink(filename.as_ref(), &symlink_path).ok();
println!(
"cargo:warning=Created symlink: {} -> {}",
symlink_name, filename
);
}
}
}
}
}
}
fn download_prebuilt_runtime(runtime_dir: &Path) {
if runtime_dir.exists()
&& fs::read_dir(runtime_dir)
.map(|entries| {
entries
.filter_map(|e| e.ok())
.any(|e| is_library_file(&e.path()))
})
.unwrap_or(false)
{
println!("cargo:warning=Prebuilt runtime already present, skipping download");
return;
}
let target = match runtime_target() {
Some(t) => t,
None => {
println!("cargo:warning=Unsupported platform for prebuilt download, skipping");
return;
}
};
let version = env::var("CARGO_PKG_VERSION").unwrap();
let default_url = format!(
"https://github.com/boxlite-ai/boxlite/releases/download/v{}/boxlite-runtime-{}.tar.gz",
version, target
);
println!("cargo:rerun-if-env-changed=BOXLITE_RUNTIME_URL");
let url = env::var("BOXLITE_RUNTIME_URL").unwrap_or(default_url);
fs::create_dir_all(runtime_dir)
.unwrap_or_else(|e| panic!("Failed to create runtime directory: {}", e));
let tarball_path = runtime_dir.join("boxlite-runtime.tar.gz");
match download_file(&url, &tarball_path) {
Ok(()) => {}
Err(e) => {
println!(
"cargo:warning=Failed to download prebuilt runtime from {}: {}",
url, e
);
println!("cargo:warning=Native libraries will not be available.");
return;
}
}
match extract_runtime_tarball(&tarball_path, runtime_dir) {
Ok(()) => {
fs::remove_file(&tarball_path).ok();
create_library_symlinks(runtime_dir);
let files: Vec<_> = fs::read_dir(runtime_dir)
.into_iter()
.flatten()
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().to_string())
.collect();
println!(
"cargo:warning=Downloaded prebuilt runtime v{}: [{}]",
version,
files.join(", ")
);
}
Err(e) => {
fs::remove_file(&tarball_path).ok();
println!("cargo:warning=Failed to extract runtime tarball: {}", e);
}
}
}
enum DepsMode {
Source,
Stub,
Prebuilt,
}
impl DepsMode {
fn from_env() -> Self {
match env::var("BOXLITE_DEPS_STUB").ok().as_deref() {
Some("2") => Self::Prebuilt,
Some(_) => Self::Stub,
None => Self::Source,
}
}
}
fn auto_detect_registry() {
if env::var("BOXLITE_DEPS_STUB").is_err() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
if manifest_dir.join(".cargo_vcs_info.json").exists() {
unsafe { env::set_var("BOXLITE_DEPS_STUB", "2") };
}
}
}
struct EmbeddedManifest {
runtime_dir: PathBuf,
}
impl EmbeddedManifest {
fn new(runtime_dir: &Path) -> Self {
Self {
runtime_dir: runtime_dir.to_path_buf(),
}
}
fn scan_entries(&self) -> Vec<(String, PathBuf)> {
let mut entries = Vec::new();
if self.runtime_dir.exists() {
for entry in fs::read_dir(&self.runtime_dir)
.unwrap_or_else(|e| panic!("Failed to read runtime dir: {}", e))
{
let entry = entry.unwrap();
let path = entry.path();
let meta = fs::symlink_metadata(&path).unwrap();
if !meta.is_file() {
continue;
}
let name = entry.file_name().to_string_lossy().to_string();
entries.push((name, path));
}
}
entries.sort_by(|a, b| a.0.cmp(&b.0));
entries
}
fn emit_manifest(manifest_path: &Path, entries: &[(String, PathBuf)]) {
Self::write_manifest_rs(manifest_path, entries);
Self::emit_content_hash(entries);
if !entries.is_empty() {
Self::log_summary(entries);
}
}
fn write_manifest_rs(manifest_path: &Path, entries: &[(String, PathBuf)]) {
if entries.is_empty() {
fs::write(
manifest_path,
"// Auto-generated by build.rs — no embedded files\n\
pub const MANIFEST: &[(&str, &[u8])] = &[];\n",
)
.unwrap_or_else(|e| panic!("Failed to write empty manifest: {}", e));
return;
}
let mut code = String::from("// Auto-generated by build.rs — do not edit\n");
code.push_str("pub const MANIFEST: &[(&str, &[u8])] = &[\n");
for (name, path) in entries {
let abs = path.to_string_lossy().replace('\\', "/");
code.push_str(&format!(
" (\"{}\", include_bytes!(\"{}\")),\n",
name, abs
));
println!("cargo:rerun-if-changed={}", path.display());
}
code.push_str("];\n");
fs::write(manifest_path, &code)
.unwrap_or_else(|e| panic!("Failed to write embedded manifest: {}", e));
}
fn emit_content_hash(entries: &[(String, PathBuf)]) {
let mut hasher = Sha256::new();
for (name, path) in entries {
hasher.update(name.as_bytes());
let content = fs::read(path)
.unwrap_or_else(|e| panic!("Failed to read {} for hashing: {}", path.display(), e));
hasher.update(&content);
}
let hash = format!("{:x}", hasher.finalize());
let prefix = &hash[..12];
println!("cargo:rustc-env=BOXLITE_MANIFEST_HASH={}", prefix);
println!(
"cargo:rustc-env=BOXLITE_BUILD_PROFILE={}",
env::var("PROFILE").unwrap()
);
println!("cargo:warning=Embedded manifest hash: {}", prefix);
}
fn log_summary(entries: &[(String, PathBuf)]) {
let total_size: u64 = entries
.iter()
.map(|(_, p)| fs::metadata(p).map(|m| m.len()).unwrap_or(0))
.sum();
println!(
"cargo:warning=Embedded runtime: {} files, {:.1} MB total",
entries.len(),
total_size as f64 / (1024.0 * 1024.0)
);
}
fn generate(&self, mode: &DepsMode) {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let manifest_path = out_dir.join("embedded_manifest.rs");
let enabled = env::var("CARGO_FEATURE_EMBEDDED_RUNTIME").is_ok();
if !enabled || matches!(mode, DepsMode::Stub) {
Self::emit_manifest(&manifest_path, &[]);
return;
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let workspace_root = manifest_dir
.parent()
.expect("CARGO_MANIFEST_DIR has no parent");
fs::create_dir_all(&self.runtime_dir)
.unwrap_or_else(|e| panic!("Failed to create runtime directory: {}", e));
let profile = env::var("PROFILE").unwrap();
self.copy_prebuilt_binary(
workspace_root,
"boxlite-shim",
&profile,
Self::find_prebuilt_shim,
);
self.copy_prebuilt_binary(
workspace_root,
"boxlite-guest",
&profile,
Self::find_prebuilt_guest,
);
let entries = self.scan_entries();
Self::emit_manifest(&manifest_path, &entries);
}
fn copy_prebuilt_binary(
&self,
workspace_root: &Path,
name: &str,
profile: &str,
finder: fn(&Path, &str) -> Option<PathBuf>,
) {
let Some(source) = finder(workspace_root, profile) else {
println!("cargo:warning={} not found, skipping embed", name);
return;
};
println!("cargo:rerun-if-changed={}", source.display());
let dest = self.runtime_dir.join(name);
fs::copy(&source, &dest).unwrap_or_else(|e| {
panic!(
"Failed to copy {} -> {}: {}",
source.display(),
dest.display(),
e
)
});
println!(
"cargo:warning=Embedded {}: {} ({:.1} MB)",
name,
source.display(),
fs::metadata(&dest).map(|m| m.len()).unwrap_or(0) as f64 / (1024.0 * 1024.0)
);
}
fn find_prebuilt_shim(workspace_root: &Path, profile: &str) -> Option<PathBuf> {
let target_dir = workspace_root.join("target");
let native = target_dir.join(profile).join("boxlite-shim");
if native.is_file() {
return Some(native);
}
for arch in Self::preferred_arches() {
let gnu = target_dir
.join(format!("{}-unknown-linux-gnu", arch))
.join(profile)
.join("boxlite-shim");
if gnu.is_file() {
return Some(gnu);
}
}
None
}
fn find_prebuilt_guest(workspace_root: &Path, profile: &str) -> Option<PathBuf> {
let target_dir = workspace_root.join("target");
for arch in Self::preferred_arches() {
let path = target_dir
.join(format!("{}-unknown-linux-musl", arch))
.join(profile)
.join("boxlite-guest");
if path.is_file() {
return Some(path);
}
}
None
}
fn preferred_arches() -> Vec<&'static str> {
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
match arch.as_str() {
"aarch64" => vec!["aarch64", "x86_64"],
"x86_64" => vec!["x86_64", "aarch64"],
_ => vec!["x86_64", "aarch64"],
}
}
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=BOXLITE_DEPS_STUB");
auto_detect_registry();
compile_seccomp_filters();
let mode = DepsMode::from_env();
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let runtime_dir = out_dir.join("runtime");
match &mode {
DepsMode::Stub => {
println!("cargo:warning=BOXLITE_DEPS_STUB=1: skipping dependency bundling");
println!("cargo:runtime_dir=/nonexistent");
EmbeddedManifest::new(&runtime_dir).generate(&mode);
return;
}
DepsMode::Prebuilt => {
println!("cargo:warning=BOXLITE_DEPS_STUB=2: downloading prebuilt runtime");
fs::create_dir_all(&runtime_dir)
.unwrap_or_else(|e| panic!("Failed to create runtime directory: {}", e));
download_prebuilt_runtime(&runtime_dir);
println!(
"cargo:rustc-env=BOXLITE_RUNTIME_DIR={}",
runtime_dir.display()
);
}
DepsMode::Source => {
fs::create_dir_all(&runtime_dir)
.unwrap_or_else(|e| panic!("Failed to create runtime directory: {}", e));
let collected = bundle_boxlite_deps(&runtime_dir);
if !collected.is_empty() {
let names: Vec<_> = collected.iter().map(|(name, _)| name.as_str()).collect();
println!("cargo:warning=Bundled: {}", names.join(", "));
}
}
};
println!("cargo:rustc-link-search=native={}", runtime_dir.display());
println!("cargo:runtime_dir={}", runtime_dir.display());
compute_guest_hash(&runtime_dir);
EmbeddedManifest::new(&runtime_dir).generate(&mode);
#[cfg(target_os = "macos")]
println!("cargo:rustc-link-arg=-Wl,-rpath,@loader_path");
#[cfg(target_os = "linux")]
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
}
fn compute_guest_hash(runtime_dir: &Path) {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".to_string());
let workspace_root = manifest_dir.parent().unwrap_or(&manifest_dir);
let guest_path =
EmbeddedManifest::find_prebuilt_guest(workspace_root, &profile).or_else(|| {
let p = runtime_dir.join("boxlite-guest");
p.is_file().then_some(p)
});
let Some(guest_path) = guest_path else {
println!("cargo:warning=boxlite-guest not found, skipping compile-time hash");
return;
};
match sha256_file(&guest_path) {
Ok(hash) => {
println!("cargo:rustc-env=BOXLITE_GUEST_HASH={}", hash);
println!("cargo:rerun-if-changed={}", guest_path.display());
println!(
"cargo:warning=Embedded guest hash: {}... (from {})",
&hash[..12],
guest_path.display()
);
}
Err(e) => {
println!(
"cargo:warning=Failed to hash boxlite-guest at {}: {}",
guest_path.display(),
e
);
}
}
}
fn sha256_file(path: &Path) -> io::Result<String> {
let mut file = fs::File::open(path)?;
let mut hasher = Sha256::new();
let mut buffer = vec![0u8; 64 * 1024];
loop {
let n = file.read(&mut buffer)?;
if n == 0 {
break;
}
hasher.update(&buffer[..n]);
}
Ok(format!("{:x}", hasher.finalize()))
}