Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
//! 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();
|
||||
}
|
||||
Reference in New Issue
Block a user