use regex::Regex;
use std::env;
use std::fs;
use std::io;
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") };
}
}
}
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");
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-link-search=native={}", 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:runtime_dir={}", runtime_dir.display());
println!(
"cargo:rustc-env=BOXLITE_RUNTIME_DIR={}",
runtime_dir.display()
);
#[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");
}