use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
const SDK_VERSION: &str = "0.3.2";
struct PlatformConfig {
platform_tag: &'static str,
lib_name: &'static str,
bundled_dir: &'static str,
}
struct WheelInfo {
url: String,
sha256: String,
}
fn get_platform_config(target_os: &str, target_arch: &str) -> Option<PlatformConfig> {
match (target_os, target_arch) {
("linux", "x86_64") => Some(PlatformConfig {
platform_tag: "manylinux_2_32_x86_64",
lib_name: "libop_uniffi_core.so",
bundled_dir: "linux-x86_64",
}),
("linux", "aarch64") => Some(PlatformConfig {
platform_tag: "manylinux_2_32_aarch64",
lib_name: "libop_uniffi_core.so",
bundled_dir: "linux-aarch64",
}),
("macos", "x86_64") => Some(PlatformConfig {
platform_tag: "macosx_10_9_x86_64",
lib_name: "libop_uniffi_core.dylib",
bundled_dir: "macos-x86_64",
}),
("macos", "aarch64") => Some(PlatformConfig {
platform_tag: "macosx_11_0_arm64",
lib_name: "libop_uniffi_core.dylib",
bundled_dir: "macos-aarch64",
}),
_ => None,
}
}
fn find_bundled_library(config: &PlatformConfig) -> Option<PathBuf> {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").ok()?;
let bundled_path = PathBuf::from(manifest_dir)
.join("src")
.join("libs")
.join(config.bundled_dir)
.join(config.lib_name);
if bundled_path.exists() {
Some(bundled_path)
} else {
None
}
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=ONEPASSWORD_LIB_PATH");
println!("cargo:rerun-if-env-changed=ONEPASSWORD_SKIP_DOWNLOAD");
println!("cargo:rerun-if-env-changed=ONEPASSWORD_ALLOW_SYSTEM_LIB");
println!("cargo:rerun-if-env-changed=DOCS_RS");
if env::var("DOCS_RS").is_ok() {
println!("cargo:warning=Building for docs.rs - skipping library setup");
return;
}
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_else(|_| "unknown".to_string());
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_else(|_| "unknown".to_string());
println!("cargo:warning=Building for {target_os}-{target_arch}");
if let Ok(custom_path) = env::var("ONEPASSWORD_LIB_PATH") {
let path = PathBuf::from(&custom_path);
if path.exists() {
let path_display = path.display();
println!("cargo:warning=Using custom library path: {path_display}");
if let Some(parent) = path.parent() {
let parent_display = parent.display();
println!("cargo:rustc-link-search=native={parent_display}");
}
return;
} else {
panic!("ONEPASSWORD_LIB_PATH points to non-existent file: {custom_path}");
}
}
let config = get_platform_config(&target_os, &target_arch).unwrap_or_else(|| {
panic!(
"Unsupported platform: {target_os}-{target_arch}. \
Supported platforms: linux-x86_64, linux-aarch64, macos-x86_64, macos-aarch64"
);
});
if let Some(bundled_path) = find_bundled_library(&config) {
let bundled_display = bundled_path.display();
println!("cargo:warning=Using bundled library: {bundled_display}");
if let Some(parent) = bundled_path.parent() {
let parent_display = parent.display();
println!("cargo:rustc-link-search=native={parent_display}");
}
return;
}
if env::var("ONEPASSWORD_SKIP_DOWNLOAD").is_ok() {
println!("cargo:warning=Skipping library download (ONEPASSWORD_SKIP_DOWNLOAD set)");
return;
}
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
let lib_dir = out_dir.join("lib");
fs::create_dir_all(&lib_dir).expect("Failed to create lib directory");
let lib_path = lib_dir.join(config.lib_name);
if lib_path.exists() {
let lib_path_display = lib_path.display();
let lib_dir_display = lib_dir.display();
println!("cargo:warning=Library already exists: {lib_path_display}");
println!("cargo:rustc-link-search=native={lib_dir_display}");
return;
}
println!("cargo:warning=Downloading 1Password SDK library...");
match download_and_extract(&config, &lib_dir) {
Ok(_) => {
let lib_path_display = lib_path.display();
let lib_dir_display = lib_dir.display();
println!("cargo:warning=Library extracted to: {lib_path_display}");
println!("cargo:rustc-link-search=native={lib_dir_display}");
}
Err(e) => {
panic!("Failed to download 1Password SDK library: {e}");
}
}
}
fn download_and_extract(config: &PlatformConfig, lib_dir: &Path) -> Result<(), String> {
let wheel_info = get_wheel_info(config)?;
println!("cargo:warning=Downloading from: {}", wheel_info.url);
println!("cargo:warning=Expected SHA256: {}", wheel_info.sha256);
let wheel_data = download_file(&wheel_info.url)?;
verify_checksum(&wheel_data, &wheel_info.sha256)?;
extract_library_from_wheel(&wheel_data, lib_dir, config.lib_name)?;
Ok(())
}
fn get_wheel_info(config: &PlatformConfig) -> Result<WheelInfo, String> {
let api_url = format!("https://pypi.org/pypi/onepassword-sdk/{SDK_VERSION}/json");
let response = reqwest::blocking::get(&api_url)
.map_err(|e| format!("Failed to fetch PyPI metadata: {e}"))?;
if !response.status().is_success() {
let status = response.status();
return Err(format!("PyPI API returned status: {status}"));
}
let json: serde_json::Value = response
.json()
.map_err(|e| format!("Failed to parse PyPI response: {e}"))?;
let urls = json["urls"].as_array().ok_or("No urls in PyPI response")?;
for url_info in urls {
let filename = url_info["filename"].as_str().unwrap_or("");
if filename.contains(config.platform_tag) && filename.ends_with(".whl") {
let url = url_info["url"]
.as_str()
.ok_or("No url field in wheel info")?
.to_string();
let sha256 = url_info["digests"]["sha256"]
.as_str()
.ok_or("No sha256 digest in wheel info")?
.to_string();
println!("cargo:warning=Found wheel: {filename}");
return Ok(WheelInfo { url, sha256 });
}
}
Err(format!(
"Could not find wheel for platform '{}' in SDK version {SDK_VERSION}",
config.platform_tag
))
}
fn download_file(url: &str) -> Result<Vec<u8>, String> {
let response = reqwest::blocking::get(url).map_err(|e| format!("Download failed: {e}"))?;
if !response.status().is_success() {
let status = response.status();
return Err(format!("Download returned status: {status}"));
}
response
.bytes()
.map(|b| b.to_vec())
.map_err(|e| format!("Failed to read response: {e}"))
}
fn calculate_sha256(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
hex::encode(hasher.finalize())
}
fn verify_checksum(data: &[u8], expected: &str) -> Result<(), String> {
let actual = calculate_sha256(data);
if actual != expected.to_lowercase() {
return Err(format!(
"SHA256 checksum mismatch!\nExpected: {expected}\nActual: {actual}\n\
The downloaded file may be corrupted or tampered with."
));
}
println!("cargo:warning=SHA256 checksum verified");
Ok(())
}
fn extract_library_from_wheel(
wheel_data: &[u8],
output_dir: &Path,
lib_name: &str,
) -> Result<(), String> {
use std::io::Cursor;
use zip::ZipArchive;
let reader = Cursor::new(wheel_data);
let mut archive =
ZipArchive::new(reader).map_err(|e| format!("Failed to open wheel as ZIP: {e}"))?;
let mut all_files = Vec::new();
for i in 0..archive.len() {
let mut file = archive
.by_index(i)
.map_err(|e| format!("Failed to read ZIP entry: {e}"))?;
let name = file.name().to_string();
all_files.push(name.clone());
if name.ends_with(lib_name) {
let output_path = output_dir.join(lib_name);
let mut output_file = fs::File::create(&output_path)
.map_err(|e| format!("Failed to create output file: {e}"))?;
std::io::copy(&mut file, &mut output_file)
.map_err(|e| format!("Failed to write library: {e}"))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&output_path)
.map_err(|e| format!("Failed to get file permissions: {e}"))?
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&output_path, perms)
.map_err(|e| format!("Failed to set permissions: {e}"))?;
}
let output_display = output_path.display();
println!("cargo:warning=Extracted library from {name} to {output_display}");
return Ok(());
}
}
let files_list = all_files.join("\n");
Err(format!(
"Library '{lib_name}' not found in wheel. Available files:\n{files_list}"
))
}