use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo::rustc-check-cfg=cfg(has_app_icon)");
let browser_enabled = std::env::var("CARGO_FEATURE_BROWSER").is_ok();
if browser_enabled {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let html_src = manifest_dir.join("assets/browser-index.html");
let html_dst = out_dir.join("browser-index.html");
println!("cargo:rerun-if-changed=assets/browser-index.html");
assert!(
html_src.exists(),
"assets/browser-index.html not found — this is a packaging error. \
Please report it at https://github.com/d33p0st/toolkit-zero/issues"
);
std::fs::copy(&html_src, &html_dst)
.expect("failed to copy assets/browser-index.html to OUT_DIR");
let icon_src = manifest_dir.join("assets/app-icon.png");
println!("cargo:rerun-if-changed=assets/app-icon.png");
if icon_src.exists() {
let icon_dst = out_dir.join("app-icon.png");
std::fs::copy(&icon_src, &icon_dst)
.expect("failed to copy assets/app-icon.png to OUT_DIR");
println!("cargo:rustc-cfg=has_app_icon");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os == "windows" {
let ico_path = out_dir.join("app-icon.ico");
write_ico_from_png(&icon_src, &ico_path);
let mut res = winresource::WindowsResource::new();
res.set_icon(ico_path.to_str().unwrap());
if let Err(e) = res.compile() {
println!("cargo:warning=winresource: {e}");
}
}
}
}
let compiler_enabled = std::env::var("CARGO_FEATURE_COMPILER").is_ok();
if compiler_enabled {
let target = std::env::var("TARGET").unwrap();
if !target.starts_with("wasm32-") {
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
for channel in &["stable", "nightly"] {
let cache_dir = toolchain_cache_dir(&target, channel);
let cached = cache_dir.join("toolchain.tar.xz");
println!("cargo:rerun-if-changed={}", cached.display());
if !cached.exists() {
std::fs::create_dir_all(&cache_dir)
.expect("compiler feature: failed to create toolchain cache dir");
download_toolchain(channel, &target, &cached);
}
std::fs::copy(&cached, out_dir.join(format!("toolchain-{channel}.tar.xz")))
.unwrap_or_else(|e| panic!("compiler feature: failed to copy {channel} toolchain to OUT_DIR: {e}"));
}
}
}
let location_enabled = std::env::var("CARGO_FEATURE_LOCATION").is_ok()
|| std::env::var("CARGO_FEATURE_LOCATION_BROWSER").is_ok();
if !location_enabled {
return;
}
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let chacha20poly1305_wasm_dir = manifest_dir.join("chacha20poly1305-wasm");
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let wasm_dst = out_dir.join("chacha20poly1305.wasm");
if chacha20poly1305_wasm_dir.join("Cargo.toml").exists() {
println!("cargo:rerun-if-changed=chacha20poly1305-wasm/src/lib.rs");
println!("cargo:rerun-if-changed=chacha20poly1305-wasm/Cargo.toml");
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let status = Command::new(&cargo)
.args([
"build",
"--manifest-path", chacha20poly1305_wasm_dir.join("Cargo.toml").to_str().unwrap(),
"--target", "wasm32-unknown-unknown",
"--release",
"--quiet",
])
.env_remove("CARGO_ENCODED_RUSTFLAGS")
.env_remove("RUSTFLAGS")
.status()
.expect("failed to invoke cargo for chacha20poly1305-wasm");
assert!(
status.success(),
"chacha20poly1305-wasm WASM compilation failed.\n\
Tip: make sure the wasm32-unknown-unknown target is installed:\n\
rustup target add wasm32-unknown-unknown"
);
let wasm_src = chacha20poly1305_wasm_dir
.join("target/wasm32-unknown-unknown/release/chacha20poly1305_wasm.wasm");
std::fs::copy(&wasm_src, &wasm_dst)
.expect("failed to copy chacha20poly1305.wasm to OUT_DIR");
} else {
println!("cargo:rerun-if-changed=assets/chacha20poly1305.wasm");
let prebuilt = manifest_dir.join("assets/chacha20poly1305.wasm");
assert!(
prebuilt.exists(),
"assets/chacha20poly1305.wasm not found — this is a packaging error. \
Please report it at https://github.com/d33p0st/toolkit-zero/issues"
);
std::fs::copy(&prebuilt, &wasm_dst)
.expect("failed to copy assets/chacha20poly1305.wasm to OUT_DIR");
}
}
fn write_ico_from_png(png_path: &Path, ico_path: &Path) {
let png = std::fs::read(png_path).expect("failed to read app-icon.png");
assert!(
png.len() >= 24 && &png[1..4] == b"PNG",
"assets/app-icon.png is not a valid PNG"
);
let w = u32::from_be_bytes([png[16], png[17], png[18], png[19]]);
let h = u32::from_be_bytes([png[20], png[21], png[22], png[23]]);
let mut ico: Vec<u8> = Vec::with_capacity(22 + png.len());
ico.extend_from_slice(&[0, 0, 1, 0, 1, 0]);
ico.push(if w >= 256 { 0 } else { w as u8 }); ico.push(if h >= 256 { 0 } else { h as u8 }); ico.push(0); ico.push(0); ico.extend_from_slice(&[1, 0]); ico.extend_from_slice(&[32, 0]); ico.extend_from_slice(&(png.len() as u32).to_le_bytes()); ico.extend_from_slice(&22u32.to_le_bytes()); ico.extend_from_slice(&png);
std::fs::write(ico_path, &ico).expect("failed to write app-icon.ico");
}
fn toolchain_cache_dir(target: &str, channel: &str) -> PathBuf {
let cargo_home = std::env::var("CARGO_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.expect("neither CARGO_HOME, HOME, nor USERPROFILE is set");
PathBuf::from(home).join(".cargo")
});
cargo_home.join("toolchain-cache").join(target).join(channel)
}
fn download_toolchain(channel: &str, target: &str, dest: &Path) {
use std::io::Read;
let manifest_url = format!("https://static.rust-lang.org/dist/channel-rust-{channel}.toml");
eprintln!("compiler feature: fetching {manifest_url} …");
let mut manifest_bytes = Vec::new();
ureq::get(&manifest_url)
.call()
.unwrap_or_else(|e| panic!("compiler feature: failed to fetch {manifest_url}: {e}"))
.into_reader()
.read_to_end(&mut manifest_bytes)
.unwrap_or_else(|e| panic!("compiler feature: failed to read {channel} manifest body: {e}"));
let manifest = String::from_utf8(manifest_bytes)
.expect("compiler feature: manifest is not valid UTF-8");
let section = format!("[pkg.rust.target.{}]", target);
let (xz_url, xz_hash) = parse_manifest_section(&manifest, §ion)
.unwrap_or_else(|| panic!(
"compiler feature: target '{target}' not found in channel-rust-{channel}.toml.\n\
This target may not have a pre-built toolchain bundle."
));
eprintln!("compiler feature: downloading {xz_url}");
let mut tarball_bytes = Vec::new();
ureq::get(&xz_url)
.call()
.unwrap_or_else(|e| panic!("compiler feature: failed to download {channel} toolchain tarball: {e}"))
.into_reader()
.read_to_end(&mut tarball_bytes)
.expect("compiler feature: failed to read toolchain tarball body");
let expected_hex = xz_hash.strip_prefix("sha256:").unwrap_or(&xz_hash);
verify_sha256(&tarball_bytes, expected_hex, &xz_url);
std::fs::write(dest, &tarball_bytes)
.expect("compiler feature: failed to write toolchain tarball to cache");
eprintln!("compiler feature: cached at {}", dest.display());
}
fn parse_manifest_section(text: &str, section: &str) -> Option<(String, String)> {
let mut in_section = false;
let mut xz_url: Option<String> = None;
let mut xz_hash: Option<String> = None;
for line in text.lines() {
let line = line.trim();
if line.starts_with('[') {
if line == section {
in_section = true;
} else if in_section {
break; }
continue;
}
if !in_section {
continue;
}
if let Some(rest) = line.strip_prefix("xz_url = \"") {
xz_url = Some(rest.trim_end_matches('"').to_string());
} else if let Some(rest) = line.strip_prefix("xz_hash = \"") {
xz_hash = Some(rest.trim_end_matches('"').to_string());
}
if xz_url.is_some() && xz_hash.is_some() {
break;
}
}
Some((xz_url?, xz_hash?))
}
fn verify_sha256(data: &[u8], expected_hex: &str, url: &str) {
use sha2::{Digest, Sha256};
let hash = Sha256::digest(data);
let actual_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
assert_eq!(
actual_hex, expected_hex,
"compiler feature: SHA-256 mismatch for {url}\n expected: {expected_hex}\n got: {actual_hex}"
);
}