use std::env; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; fn main() { if let Err(err) = stage_openssl() { panic!("[build.rs] failed to stage OpenSSL runtime libraries: {err}"); } if let Err(err) = stage_sbom_placeholders() { panic!("[build.rs] failed to stage SBOM placeholders: {err}"); } tauri_build::build() } fn stage_sbom_placeholders() -> Result<(), String> { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|e| e.to_string())?); for name in [ "open-data-platform-SBOM-cargo.cdx.xml", "open-data-platform-SBOM-npm.cdx.xml", ] { let path = manifest_dir.join(name); if path.exists() { continue; } let stub = "\n\n"; fs::write(&path, stub).map_err(|e| format!("write {}: {e}", path.display()))?; println!( "cargo:warning=wrote SBOM placeholder at {}; CI regenerates real SBOMs before bundling", path.display() ); } Ok(()) } fn stage_openssl() -> Result<(), String> { println!("cargo:rerun-if-env-changed=OPENSSL_COPY_SKIP_EXISTING"); println!("cargo:rerun-if-env-changed=VCPKG_ROOT"); let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").map_err(|e| e.to_string())?); let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); let skip_existing = env::var("OPENSSL_COPY_SKIP_EXISTING").ok().as_deref() == Some("1"); match target_os.as_str() { "macos" => stage_macos(&manifest_dir, skip_existing), "windows" => stage_windows(&manifest_dir, skip_existing), _ => Ok(()), } } fn copy_if_needed(src: &Path, dest: &Path, skip_existing: bool) -> Result<(), String> { if skip_existing && dest.exists() { println!( "cargo:warning=OPENSSL_COPY_SKIP_EXISTING=1; keeping existing {}", dest.display() ); return Ok(()); } if let Some(parent) = dest.parent() { fs::create_dir_all(parent).map_err(|e| format!("mkdir {}: {e}", parent.display()))?; } fs::copy(src, dest) .map_err(|e| format!("copy {} -> {}: {e}", src.display(), dest.display()))?; println!("cargo:rerun-if-changed={}", src.display()); Ok(()) } fn stage_macos(manifest_dir: &Path, skip_existing: bool) -> Result<(), String> { let names = ["libcrypto.3.dylib", "libssl.3.dylib"]; let frameworks = manifest_dir.join("frameworks"); if names.iter().all(|n| frameworks.join(n).exists()) { if skip_existing { println!("cargo:warning=OPENSSL_COPY_SKIP_EXISTING=1; OpenSSL dylibs already staged"); } return Ok(()); } let prefix = match brew_openssl_prefix() { Ok(p) => p, Err(err) => { println!( "cargo:warning={err}; skipping OpenSSL dylib staging (required only for bundling)" ); return Ok(()); } }; for name in names { let src = prefix.join("lib").join(name); if !src.exists() { println!( "cargo:warning=expected {} to exist; skipping (required only for bundling)", src.display() ); return Ok(()); } let dest = frameworks.join(name); copy_if_needed(&src, &dest, skip_existing)?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut perms = fs::metadata(&dest) .map_err(|e| format!("stat {}: {e}", dest.display()))? .permissions(); perms.set_mode(perms.mode() | 0o200); let _ = fs::set_permissions(&dest, perms); } } rewrite_macos_install_names(&frameworks, &names, &prefix)?; Ok(()) } fn rewrite_macos_install_names( frameworks: &Path, names: &[&str], brew_prefix: &Path, ) -> Result<(), String> { for name in names { let dest = frameworks.join(name); let new_id = format!("@executable_path/../Frameworks/{name}"); run_install_name_tool(&["-id", &new_id, dest.to_str().unwrap()])?; let output = Command::new("otool") .args(["-L", dest.to_str().unwrap()]) .output() .map_err(|e| format!("otool -L {}: {e}", dest.display()))?; if !output.status.success() { return Err(format!( "otool -L {} failed: {}", dest.display(), String::from_utf8_lossy(&output.stderr) )); } let listing = String::from_utf8_lossy(&output.stdout).into_owned(); for dep in names { if dep == name { continue; } for line in listing.lines() { let trimmed = line.trim(); let path = trimmed.split_whitespace().next().unwrap_or(""); if path.is_empty() || path == dest.to_str().unwrap() { continue; } if path.ends_with(&format!("/{dep}")) && (path.starts_with(brew_prefix.to_str().unwrap_or("")) || path.starts_with("/usr/local/") || path.starts_with("/opt/homebrew/") || path.starts_with("@loader_path") || path.starts_with("@rpath")) { let new_dep = format!("@executable_path/../Frameworks/{dep}"); run_install_name_tool(&["-change", path, &new_dep, dest.to_str().unwrap()])?; } } } let _ = Command::new("codesign") .args(["--force", "--sign", "-", dest.to_str().unwrap()]) .status(); } Ok(()) } fn run_install_name_tool(args: &[&str]) -> Result<(), String> { let output = Command::new("install_name_tool") .args(args) .output() .map_err(|e| format!("install_name_tool {:?}: {e}", args))?; if !output.status.success() { return Err(format!( "install_name_tool {:?} failed: {}", args, String::from_utf8_lossy(&output.stderr) )); } Ok(()) } fn brew_openssl_prefix() -> Result { if let Ok(output) = Command::new("brew") .args(["--prefix", "openssl@3"]) .output() && output.status.success() { let path = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !path.is_empty() { return Ok(PathBuf::from(path)); } } for fallback in ["/opt/homebrew/opt/openssl@3", "/usr/local/opt/openssl@3"] { let p = PathBuf::from(fallback); if p.exists() { return Ok(p); } } Err("could not resolve Homebrew openssl@3 prefix".into()) } fn stage_windows(manifest_dir: &Path, skip_existing: bool) -> Result<(), String> { let dest_names = ["libcrypto-3-x64.dll", "libssl-3-x64.dll"]; if dest_names.iter().all(|n| manifest_dir.join(n).exists()) { if skip_existing { println!("cargo:warning=OPENSSL_COPY_SKIP_EXISTING=1; OpenSSL DLLs already staged"); } return Ok(()); } let Ok(vcpkg_root) = env::var("VCPKG_ROOT") else { return Err( "VCPKG_ROOT is not set; install OpenSSL via vcpkg (openssl:x64-windows or openssl:arm64-windows) before building" .into(), ); }; let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); let triplets: &[&str] = match target_arch.as_str() { "aarch64" => &["arm64-windows", "x64-windows"], _ => &["x64-windows", "arm64-windows"], }; let bin_dir = triplets .iter() .map(|t| PathBuf::from(&vcpkg_root).join(format!("installed/{t}/bin"))) .find(|p| p.exists()) .ok_or_else(|| { format!( "no vcpkg dynamic OpenSSL install found under {}/installed/{{x64,arm64}}-windows/bin; run `vcpkg install openssl:x64-windows` (or openssl:arm64-windows)", vcpkg_root ) })?; for (pattern_prefix, dest_name) in [ ("libcrypto-3-", "libcrypto-3-x64.dll"), ("libssl-3-", "libssl-3-x64.dll"), ] { let src = fs::read_dir(&bin_dir) .map_err(|e| format!("read_dir {}: {e}", bin_dir.display()))? .filter_map(|e| e.ok()) .map(|e| e.path()) .find(|p| { p.file_name() .and_then(|n| n.to_str()) .map(|n| n.starts_with(pattern_prefix) && n.ends_with(".dll")) .unwrap_or(false) }) .ok_or_else(|| { format!( "no DLL matching '{}*.dll' in {}", pattern_prefix, bin_dir.display() ) })?; let dest = manifest_dir.join(dest_name); copy_if_needed(&src, &dest, skip_existing)?; } Ok(()) }