use std::path::{Path, PathBuf};
const RUSTPYTHON_VERSION: &str = "0.5.0";
const RECIPE: &str = "v3";
fn main() {
set_stack_size();
if let Err(e) = embed_rustpython_wasm() {
panic!("failed to provision the RustPython wasm blob: {e}");
}
}
fn blob_filename() -> String {
format!("rustpython-{RUSTPYTHON_VERSION}-{RECIPE}.wasm.zst")
}
fn embed_rustpython_wasm() -> std::io::Result<()> {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=DOCS_RS");
if std::env::var_os("DOCS_RS").is_some() {
let stub = out_dir().join(blob_filename());
std::fs::write(&stub, [])?;
println!("cargo:rustc-env=RUSTPYTHON_WASM_ZSTD_PATH={}", stub.display());
emit_blob_hash(&stub)?;
return Ok(());
}
let cache = resolve_cache_dir();
std::fs::create_dir_all(&cache)?;
let blob = cache.join(blob_filename());
println!("cargo:rerun-if-changed={}", blob.display());
println!("cargo:rustc-env=RUSTPYTHON_WASM_ZSTD_PATH={}", blob.display());
if blob.exists() {
return emit_blob_hash(&blob);
}
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
runtime.block_on(async {
let claim = objectiveai_sdk::lockfile::wait_acquire(
&cache.join("locks"),
&blob_filename(),
&format!("pid {}", std::process::id()),
)
.await?;
let result = if blob.exists() {
Ok(())
} else {
build_and_publish(&cache, &blob)
};
claim.release()?;
result
})?;
emit_blob_hash(&blob)
}
fn resolve_cache_dir() -> PathBuf {
let manifest_dir = PathBuf::from(
std::env::var_os("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"),
);
if manifest_dir
.join("../objectiveai-sdk-rs/src/lockfile.rs")
.exists()
{
manifest_dir.join(".cache")
} else {
out_dir().join("rustpython-cache")
}
}
fn out_dir() -> PathBuf {
PathBuf::from(std::env::var_os("OUT_DIR").expect("cargo sets OUT_DIR"))
}
fn emit_blob_hash(blob: &Path) -> std::io::Result<()> {
let bytes = std::fs::read(blob)?;
let hash = twox_hash::XxHash3_128::oneshot(&bytes);
println!("cargo:rustc-env=RUSTPYTHON_WASM_HASH={hash:032x}");
Ok(())
}
fn build_and_publish(cache: &Path, blob: &Path) -> std::io::Result<()> {
clean_stale_tmp(cache);
ensure_wasip1_target();
run_cargo_install(cache)?;
let candidates = [
cache.join("install/bin/rustpython.wasm"),
cache.join("target/wasm32-wasip1/release/rustpython.wasm"),
];
let wasm_path = candidates
.iter()
.find(|p| p.exists())
.ok_or_else(|| {
std::io::Error::other(format!(
"cargo install succeeded but rustpython.wasm was not found at {} or {}",
candidates[0].display(),
candidates[1].display(),
))
})?;
let wasm = std::fs::read(wasm_path)?;
if !wasm.starts_with(b"\0asm") {
return Err(std::io::Error::other(format!(
"{} does not start with the wasm magic bytes",
wasm_path.display()
)));
}
let tmp = cache.join(format!("{}.tmp-{}", blob_filename(), std::process::id()));
{
let mut tmp_file = std::fs::File::create(&tmp)?;
zstd::stream::copy_encode(wasm.as_slice(), &mut tmp_file, 19)?;
tmp_file.sync_all()?;
}
rename_with_retry(&tmp, blob)?;
for dir in ["install", "target"] {
let _ = std::fs::remove_dir_all(cache.join(dir));
}
Ok(())
}
fn clean_stale_tmp(cache: &Path) {
let Ok(entries) = std::fs::read_dir(cache) else {
return;
};
for entry in entries.flatten() {
if entry.file_name().to_string_lossy().contains(".wasm.zst.tmp-") {
let _ = std::fs::remove_file(entry.path());
}
}
}
fn rename_with_retry(from: &Path, to: &Path) -> std::io::Result<()> {
let mut last = None;
for _ in 0..10 {
match std::fs::rename(from, to) {
Ok(()) => return Ok(()),
Err(e) => {
last = Some(e);
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
Err(last.expect("loop ran"))
}
fn ensure_wasip1_target() {
let mut cmd = std::process::Command::new("rustup");
cmd.args(["target", "add", "wasm32-wasip1"]);
if let Some(toolchain) = std::env::var_os("RUSTUP_TOOLCHAIN") {
cmd.arg("--toolchain").arg(toolchain);
}
cmd.stdout(std::process::Stdio::null());
cmd.stderr(std::process::Stdio::null());
match cmd.status() {
Ok(status) if status.success() => {}
Ok(status) => {
println!("cargo:warning=rustup target add wasm32-wasip1 exited with {status}");
}
Err(e) => {
println!("cargo:warning=could not run rustup target add wasm32-wasip1: {e}");
}
}
}
fn run_cargo_install(cache: &Path) -> std::io::Result<()> {
let attempts: [&[&str]; 2] = [
&["--locked", "--no-default-features", "--features", "freeze-stdlib,stdio,host_env"],
&["--no-default-features", "--features", "freeze-stdlib,stdio,host_env"],
];
let mut failures = String::new();
for extra in attempts {
let status = cargo_install_once(cache, extra)?;
if status.success() {
return Ok(());
}
failures.push_str(&format!("\n cargo install {extra:?} -> {status}"));
}
Err(std::io::Error::other(format!(
"all cargo install attempts for rustpython {RUSTPYTHON_VERSION} \
(wasm32-wasip1) failed:{failures}\nsee the build output above for details",
)))
}
fn cargo_install_once(
cache: &Path,
extra_args: &[&str],
) -> std::io::Result<std::process::ExitStatus> {
let cargo = std::env::var_os("CARGO").expect("cargo sets CARGO");
let mut cmd = std::process::Command::new(cargo);
cmd.args([
"install",
"rustpython",
"--version",
RUSTPYTHON_VERSION,
"--target",
"wasm32-wasip1",
"--force",
]);
cmd.arg("--root").arg(cache.join("install"));
cmd.args(extra_args);
cmd.current_dir(cache);
for (key, _) in std::env::vars_os() {
let name = key.to_string_lossy();
let keep = matches!(
name.as_ref(),
"CARGO" | "CARGO_HOME" | "RUSTUP_HOME" | "RUSTUP_TOOLCHAIN"
);
if !keep && (name.starts_with("CARGO_") || name.starts_with("RUST")) {
cmd.env_remove(&key);
}
}
for jobserver in ["CARGO_MAKEFLAGS", "MAKEFLAGS", "MFLAGS"] {
cmd.env_remove(jobserver);
}
cmd.env("CARGO_TARGET_DIR", cache.join("target"));
cmd.env("RUSTFLAGS", "");
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::inherit());
let output = cmd.output()?;
if !output.stdout.is_empty() {
use std::io::Write;
let _ = std::io::stderr().write_all(&output.stdout);
}
Ok(output.status)
}
fn set_stack_size() {
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let flag = match (os.as_str(), env.as_str()) {
("windows", "msvc") => "/STACK:16777216",
("windows", "gnu") => "-Wl,--stack,16777216",
("macos", _) | ("ios", _) => "-Wl,-stack_size,0x1000000",
("linux", _) | ("freebsd", _) | ("netbsd", _) | ("openbsd", _) | ("dragonfly", _) => {
"-Wl,-z,stacksize=16777216"
}
_ => return,
};
println!("cargo:rustc-link-arg={flag}");
}