Files
MyAiDesk/nomifun-tauri/crates/backend/nomifun-runtime/build.rs
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

191 lines
7.1 KiB
Rust

//! Build-time bundling of the bun runtime.
//!
//! - Reads `BUN_VARIANT` ("default" | "baseline"; default = "default") env
//! and `[package.metadata.nomifun-runtime] bun_version` from Cargo.toml.
//! - For win-arm64: emits HAS_EMBEDDED_BUN=false and exits early.
//! - Otherwise: downloads `bun-<platform>-<arch>[-baseline].zip`,
//! extracts the `bun` binary, zstd-compresses it, emits constants
//! into `$OUT_DIR/bun_meta.rs`.
//! - Caches decompressed+compressed artifacts in `$CARGO_HOME/nomifun-bun-cache/`.
#[path = "build_support.rs"]
mod build_support;
use std::fs;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use sha2::{Digest, Sha256};
fn main() {
println!("cargo:rerun-if-env-changed=BUN_VARIANT");
println!("cargo:rerun-if-env-changed=BUN_VERSION_OVERRIDE");
println!("cargo:rerun-if-env-changed=NOMIFUN_EMBED_BUN");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=build_support.rs");
println!("cargo:rerun-if-changed=Cargo.toml");
let target = std::env::var("TARGET").expect("TARGET env set by cargo");
let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR"));
let variant = std::env::var("BUN_VARIANT").unwrap_or_else(|_| "default".into());
let version = read_bun_version();
let meta_path = out_dir.join("bun_meta.rs");
// Default: dev builds skip bun download (no network). CI release
// sets NOMIFUN_EMBED_BUN=1 to actually embed.
if std::env::var("NOMIFUN_EMBED_BUN").as_deref() != Ok("1") {
write_meta_stub(&meta_path, &out_dir);
return;
}
if !build_support::has_embedded_bun(&target) {
write_meta_stub(&meta_path, &out_dir);
return;
}
let Some(asset) = build_support::asset_name_for(&target, &variant) else {
println!("cargo:warning=No bun asset mapped for target {target}; emitting stub");
write_meta_stub(&meta_path, &out_dir);
return;
};
let cache_root = cargo_cache_root().join(&version).join(format!("{target}-{variant}"));
fs::create_dir_all(&cache_root).expect("create cache dir");
let bun_exe_path = cache_root.join(build_support::bun_exe_name(&target));
let compressed_path = cache_root.join("bun.blob.zst");
let sha_path = cache_root.join("bun.sha256");
// Step 1: ensure decompressed bun is cached.
if !bun_exe_path.is_file() {
let url = build_support::download_url(&version, &asset);
println!("cargo:info=Downloading {url}");
let zip_path = cache_root.join(&asset);
download(&url, &zip_path);
unzip_bun(&zip_path, &cache_root, build_support::bun_exe_name(&target));
}
// Step 2: compute sha256 if missing.
let sha_hex = if sha_path.is_file() {
fs::read_to_string(&sha_path).expect("read sha").trim().to_string()
} else {
let hex = sha256_of(&bun_exe_path);
fs::write(&sha_path, &hex).expect("write sha");
hex
};
// Step 3: compress if missing.
if !compressed_path.is_file() {
zstd_compress_file(&bun_exe_path, &compressed_path);
}
// Step 4: copy compressed blob into OUT_DIR so include_bytes! can find it.
let out_blob = out_dir.join("bun.blob.zst");
fs::copy(&compressed_path, &out_blob).expect("copy blob to OUT_DIR");
// Step 5: emit bun_meta.rs.
let meta = format!(
"// @generated by build.rs — do not edit\n\
pub const BUN_BLOB: &[u8] = include_bytes!(\"bun.blob.zst\");\n\
pub const BUN_SHA256: &str = \"{sha_hex}\";\n\
pub const BUN_VERSION: &str = \"{version}\";\n\
pub const HAS_EMBEDDED_BUN: bool = true;\n"
);
fs::write(&meta_path, meta).expect("write bun_meta.rs");
}
fn read_bun_version() -> String {
if let Ok(v) = std::env::var("BUN_VERSION_OVERRIDE")
&& !v.trim().is_empty()
{
return v;
}
let toml_src = fs::read_to_string("Cargo.toml").expect("read Cargo.toml");
let parsed: toml::Value = toml::from_str(&toml_src).expect("parse Cargo.toml");
parsed
.get("package")
.and_then(|p| p.get("metadata"))
.and_then(|m| m.get("nomifun-runtime"))
.and_then(|r| r.get("bun_version"))
.and_then(|v| v.as_str())
.expect("[package.metadata.nomifun-runtime] bun_version missing")
.to_string()
}
fn cargo_cache_root() -> PathBuf {
let home = std::env::var("CARGO_HOME").map(PathBuf::from).unwrap_or_else(|_| {
dirs::home_dir()
.map(|h| h.join(".cargo"))
.unwrap_or_else(|| PathBuf::from(".cargo"))
});
home.join("nomifun-bun-cache")
}
fn write_meta_stub(meta_path: &Path, out_dir: &Path) {
let stub_blob = out_dir.join("bun.blob.zst");
if !stub_blob.is_file() {
fs::write(&stub_blob, []).expect("write stub blob");
}
let body = "// @generated by build.rs — do not edit\n\
pub const BUN_BLOB: &[u8] = include_bytes!(\"bun.blob.zst\");\n\
pub const BUN_SHA256: &str = \"\";\n\
pub const BUN_VERSION: &str = \"\";\n\
pub const HAS_EMBEDDED_BUN: bool = false;\n";
fs::write(meta_path, body).expect("write stub bun_meta.rs");
}
fn download(url: &str, out: &Path) {
let bytes = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(600))
.build()
.expect("build http client")
.get(url)
.send()
.unwrap_or_else(|e| panic!("GET {url} failed: {e}"))
.error_for_status()
.unwrap_or_else(|e| panic!("non-2xx from {url}: {e}"))
.bytes()
.unwrap_or_else(|e| panic!("read body: {e}"));
fs::write(out, &bytes).unwrap_or_else(|e| panic!("write {}: {e}", out.display()));
}
fn unzip_bun(zip_path: &Path, out_dir: &Path, exe_name: &str) {
let f = fs::File::open(zip_path).expect("open zip");
let mut archive = zip::ZipArchive::new(f).expect("read zip");
for i in 0..archive.len() {
let mut entry = archive.by_index(i).expect("zip entry");
let name = entry.name().to_string();
if name.ends_with(exe_name) && !entry.is_dir() {
let out = out_dir.join(exe_name);
let mut w = fs::File::create(&out).expect("create exe");
std::io::copy(&mut entry, &mut w).expect("extract exe");
return;
}
}
panic!("bun executable {exe_name} not found in zip {}", zip_path.display());
}
fn sha256_of(path: &Path) -> String {
let mut f = fs::File::open(path).expect("open for sha");
let mut hasher = Sha256::new();
let mut buf = [0u8; 64 * 1024];
loop {
let n = f.read(&mut buf).expect("read chunk");
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
hex::encode(hasher.finalize())
}
fn zstd_compress_file(input: &Path, output: &Path) {
let mut src = fs::File::open(input).expect("open src");
let dst = fs::File::create(output).expect("create dst");
let mut enc = zstd::stream::write::Encoder::new(dst, 19).expect("zstd encoder");
std::io::copy(&mut src, &mut enc).expect("compress");
let mut out = enc.finish().expect("finish encoder");
out.write_all(&[]).ok();
}