use std::{
path::{Path, PathBuf},
time::SystemTime,
};
use microsandbox_utils::AGENTD_BINARY;
#[cfg(feature = "prebuilt")]
use microsandbox_utils::agentd_download_url;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=../utils/lib/lib.rs");
println!("cargo:rerun-if-changed=../agentd");
println!("cargo:rerun-if-changed=../protocol");
let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
build_agentd(&workspace_root, &out_dir);
}
fn build_agentd(workspace_root: &Path, out_dir: &Path) {
#[cfg(feature = "prebuilt")]
{
let _ = workspace_root;
let dest = out_dir.join(AGENTD_BINARY);
if dest.exists() {
return;
}
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let url = agentd_download_url(env!("CARGO_PKG_VERSION"), &arch);
download_to(&url, &dest);
return;
}
#[cfg(not(feature = "prebuilt"))]
{
let source = workspace_root.join("build").join(AGENTD_BINARY);
println!("cargo:rerun-if-changed={}", source.display());
if !source.exists() {
panic!(
"{AGENTD_BINARY} binary not found at `{}`.\n\
Run `just build-deps` first.",
source.display()
);
}
let agentd_src = workspace_root.join("crates/agentd");
let protocol_src = workspace_root.join("crates/protocol");
if let Ok(bin_time) = std::fs::metadata(&source).and_then(|m| m.modified())
&& newest_tree_mtime(&agentd_src)
.into_iter()
.chain(newest_tree_mtime(&protocol_src))
.any(|src_time| src_time > bin_time)
{
panic!(
"build/{AGENTD_BINARY} is older than crates/agentd or crates/protocol source.\n\
Run `just build-agentd` to rebuild the guest agent binary."
);
}
let dest = out_dir.join(AGENTD_BINARY);
std::fs::copy(&source, &dest).expect("failed to copy agentd to OUT_DIR");
}
}
fn newest_tree_mtime(root: &Path) -> Option<SystemTime> {
fn walk(path: &Path, newest: &mut Option<SystemTime>) {
let entries = match std::fs::read_dir(path) {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let entry_path = entry.path();
let meta = match entry.metadata() {
Ok(meta) => meta,
Err(_) => continue,
};
if meta.is_dir() {
walk(&entry_path, newest);
continue;
}
let modified = match meta.modified() {
Ok(modified) => modified,
Err(_) => continue,
};
match newest {
Some(current) if *current >= modified => {}
_ => *newest = Some(modified),
}
}
}
let mut newest = None;
walk(root, &mut newest);
newest
}
#[cfg(feature = "prebuilt")]
fn download_to(url: &str, dest: &Path) {
eprintln!("Downloading {url}");
let part_path = {
let mut s = dest.as_os_str().to_os_string();
s.push(".part");
PathBuf::from(s)
};
let response = ureq::get(url).call().unwrap_or_else(|e| {
panic!("failed to download {url}: {e}");
});
let mut reader = response.into_body().into_reader();
let mut file = std::fs::File::create(&part_path).unwrap_or_else(|e| {
panic!("failed to create {}: {e}", part_path.display());
});
std::io::copy(&mut reader, &mut file).unwrap_or_else(|e| {
panic!("failed to write {}: {e}", part_path.display());
});
std::fs::rename(&part_path, dest).unwrap_or_else(|e| {
panic!(
"failed to rename {} to {}: {e}",
part_path.display(),
dest.display()
);
});
}