extern crate bindgen; use std::env; use std::path::{Path, PathBuf}; use std::fmt::Debug; use std::fmt::{Display, Formatter}; fn find_static_lib(dir: &Path, base_name: &str, target_is_windows: bool) -> Option { if !dir.is_dir() { return None; } let prefixes = ["", "lib"]; let extensions = if target_is_windows { &[".lib", ".a"][..] } else { &[".a", ".lib"][..] }; for prefix in prefixes { for ext in extensions { let name = format!("{}{}{}", prefix, base_name, ext); let path = dir.join(&name); if path.exists() { return Some(path); } } } None } fn lib_name_from_path( lib_path: &Path, target_is_windows: bool, ) -> Result> { let file_name = lib_path .file_stem() .ok_or(Error::msg("cannot get file stem"))? .to_str() .ok_or(Error::msg("cannot into string"))?; // On Unix, `cargo::rustc-link-lib=static=NAME` makes the linker search for libNAME.a, // so strip any leading "lib" prefix to avoid liblibNAME.a. // On MSVC, the linker searches for NAME.lib directly, so keep the full name // (e.g. libCZIAPIStatic.lib needs link-lib=static=libCZIAPIStatic). if target_is_windows { Ok(file_name.to_string()) } else { Ok(file_name.strip_prefix("lib").unwrap_or(file_name).to_string()) } } struct Error(String); impl Debug for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } impl std::error::Error for Error {} impl Error { fn msg(msg: S) -> Box { Box::new(Error(msg.to_string())) } } fn restore_importexport(libcziapi_inc: &Path) -> Result<(), Box> { let header = libcziapi_inc.join("importexport.h"); let relative = header.strip_prefix("libczi").unwrap_or(&header); let status = std::process::Command::new("git") .args(["checkout", "HEAD", "--", relative.to_str().unwrap()]) .current_dir("libczi") .status()?; if !status.success() { return Err(Error::msg("failed to restore importexport.h")); } Ok(()) } struct ImportExportGuard<'a> { libcziapi_inc: &'a Path, } impl<'a> Drop for ImportExportGuard<'a> { fn drop(&mut self) { if let Err(e) = restore_importexport(self.libcziapi_inc) { eprintln!("warning: failed to restore importexport.h: {e}"); } } } fn patch_importexport( libcziapi_inc: &Path, ) -> Result, Box> { let path = libcziapi_inc.join("importexport.h"); let original = std::fs::read_to_string(&path)?; let patched = original.replace( "#define EXTERNALLIBCZIAPI_API(_returntype_) _returntype_", "#define EXTERNALLIBCZIAPI_API(_returntype_) extern \"C\" _returntype_", ); if patched != original { std::fs::write(&path, &patched)?; } Ok(ImportExportGuard { libcziapi_inc }) } fn fix_xwin_lib_case() -> Result<(), Box> { let toolchain_key = format!( "CMAKE_TOOLCHAIN_FILE_{}", env::var("TARGET")?.replace('-', "_") ); let toolchain_file = env::var(&toolchain_key) .or_else(|_| env::var("TARGET_CMAKE_TOOLCHAIN_FILE")) .or_else(|_| env::var("CMAKE_TOOLCHAIN_FILE"))?; let toolchain_path = PathBuf::from(&toolchain_file); let cargo_xwin_base = toolchain_path .parent() .and_then(|p| p.parent()) .and_then(|p| p.parent()) .ok_or(Error::msg("cannot determine cargo-xwin base dir"))?; let xwin_sdk_lib = cargo_xwin_base.join("xwin/sdk/lib/um/x86_64"); if !xwin_sdk_lib.exists() { return Ok(()); } #[cfg(unix)] { use std::os::unix::fs::symlink; let fixes = [ ("Windowscodecs.lib", "WINDOWSCODECS.lib"), ("Windowscodecs.lib", "windowscodecs.lib"), ]; for (link_name, target_name) in &fixes { let target = xwin_sdk_lib.join(target_name); let link = xwin_sdk_lib.join(link_name); if target.exists() && !link.exists() { let _ = symlink(&target, &link); } } } Ok(()) } fn main() -> Result<(), Box> { if env::var("DOCS_RS").is_err() { let host = env::var("HOST")?; let target = env::var("TARGET")?; let is_cross = host != target || env::var(format!( "CMAKE_TOOLCHAIN_FILE_{}", target.replace('-', "_") )) .is_ok() || env::var("TARGET_CMAKE_TOOLCHAIN_FILE").is_ok() || env::var("CMAKE_TOOLCHAIN_FILE").is_ok(); let is_windows = target.contains("windows"); let _is_macos = target.contains("apple"); let libczi_dir = PathBuf::from("libczi"); let libczi_src = libczi_dir.join("Src/libCZI"); let libcziapi_inc = libczi_dir.join("Src/libCZIAPI/inc"); let libcziapi_src = libczi_dir.join("Src/libCZIAPI/src"); let libcziapi_h = libcziapi_inc.join("libCZIApi.h"); let mut cmake_config = cmake::Config::new(&libczi_dir); cmake_config .cxxflag("-fms-extensions") .define( "ADDITIONAL_LIBS_REQUIRED_FOR_ATOMIC", "", ) .define("LIBCZI_BUILD_UNITTESTS", "OFF") .define("LIBCZI_BUILD_CZICMD", "OFF") .define("LIBCZI_BUILD_DYNLIB", "OFF") .define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_EIGEN3", "OFF") .define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_ZSTD", "OFF") .define("LIBCZI_BUILD_CURL_BASED_STREAM", "OFF") .define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_LIBCURL", "OFF") .define("LIBCZI_BUILD_AZURESDK_BASED_STREAM", "OFF") .define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_RAPIDJSON", "OFF") .define("LIBCZI_BUILD_LIBCZIAPI", "ON"); if is_cross { if is_windows { cmake_config .define("CMAKE_SYSTEM_NAME", "Windows") .define("CRASH_ON_UNALIGNED_ACCESS", "FALSE"); let has_toolchain_file = env::var("TARGET_CMAKE_TOOLCHAIN_FILE").is_ok() || env::var(format!( "CMAKE_TOOLCHAIN_FILE_{}", target.replace('-', "_") )) .is_ok() || env::var("CMAKE_TOOLCHAIN_FILE").is_ok(); if !has_toolchain_file { cmake_config .define("CMAKE_C_COMPILER", "clang") .define("CMAKE_CXX_COMPILER", "clang++") .define("CMAKE_RC_COMPILER", "llvm-rc"); } fix_xwin_lib_case()?; } else if target.contains("apple") { cmake_config .define("CMAKE_SYSTEM_NAME", "Darwin") .define("CRASH_ON_UNALIGNED_ACCESS", "OFF"); if target.contains("aarch64") { cmake_config.define("NEON_INTRINSICS_CAN_BE_USED", "ON"); } } else { cmake_config .define("CRASH_ON_UNALIGNED_ACCESS", "OFF") .define("_UNALIGNED_ACCESS_RESULT_EXITCODE", "0"); if target.contains("aarch64") { cmake_config.define("_NEON_INTRINSICS_RESULT_EXITCODE", "0"); } } } if is_cross && is_windows { cmake_config.build_target("libCZIAPIStatic"); } let _importexport_guard = patch_importexport(&libcziapi_inc)?; let dst = cmake_config.build(); #[cfg(not(feature = "dynamic"))] let bindings = bindgen::Builder::default(); #[cfg(feature = "dynamic")] let bindings = bindgen::Builder::default(); let bindings = bindings .merge_extern_blocks(true) .clang_args([ "-fms-extensions", "-x", "c++", "-std=c++14", "-I", libcziapi_inc .to_str() .ok_or(Error::msg("cannot into string"))?, "-I", libcziapi_src .to_str() .ok_or(Error::msg("cannot into string"))?, "-I", libczi_src .to_str() .ok_or(Error::msg("cannot into string"))?, ]) .header( libcziapi_h .to_str() .ok_or(Error::msg("cannot into string"))?, ) .generate()?; bindings.write_to_file(dst.join("lib_czi_api.rs"))?; #[cfg(not(feature = "dynamic"))] { let libcziapi_dir = dst.join("build/Src/libCZIAPI"); let libcziapi_dir2 = dst.join("Src/libCZIAPI"); let libczi_dir = dst.join("build/Src/libCZI"); let libczi_dir2 = dst.join("Src/libCZI"); let libcziapi_lib = find_static_lib(&libcziapi_dir, "libCZIAPIStatic", is_windows) .or_else(|| find_static_lib(&libcziapi_dir2, "libCZIAPIStatic", is_windows)) .ok_or(Error::msg("cannot find libCZIAPIStatic for linking"))?; let libcziapi_name = lib_name_from_path(&libcziapi_lib, is_windows)?; let profile = env::var("PROFILE")?; let libczi_base = match profile.as_str() { "debug" => "libCZIStaticd", "release" => "libCZIStatic", _ => return Err(Error::msg(format!("unsupported profile: {}", profile))), }; let libczi_lib = find_static_lib(&libczi_dir, libczi_base, is_windows) .or_else(|| find_static_lib(&libczi_dir2, libczi_base, is_windows)) .ok_or(Error::msg(format!( "cannot find {} for linking", libczi_base )))?; let libczi_name = lib_name_from_path(&libczi_lib, is_windows)?; if libcziapi_dir.exists() { println!( "cargo::rustc-link-search=native={}", libcziapi_dir.display() ); } if libcziapi_dir2.exists() { println!( "cargo::rustc-link-search=native={}", libcziapi_dir2.display() ); } println!("cargo::rustc-link-lib=static={}", libcziapi_name); if libczi_dir.exists() { println!( "cargo::rustc-link-search=native={}", libczi_dir.display() ); } if libczi_dir2.exists() { println!( "cargo::rustc-link-search=native={}", libczi_dir2.display() ); } println!("cargo::rustc-link-lib=static={}", libczi_name); println!( "cargo::rustc-link-search=native={}", dst.join("lib").display() ); println!( "cargo::rustc-link-search=native={}", dst.join("lib64").display() ); if is_windows && is_cross { let zstd_dir = dst.join("build/_deps/zstd-build/lib"); let zstd_dir2 = dst.join("_deps/zstd-build/lib"); if zstd_dir.exists() { println!( "cargo::rustc-link-search=native={}", zstd_dir.display() ); } if zstd_dir2.exists() { println!( "cargo::rustc-link-search=native={}", zstd_dir2.display() ); } let zstd_name = find_static_lib(&zstd_dir, "zstd_static", is_windows) .or_else(|| find_static_lib(&zstd_dir2, "zstd_static", is_windows)) .map(|p| lib_name_from_path(&p, is_windows).unwrap_or_else(|_| "zstd_static".to_string())) .unwrap_or_else(|| "zstd_static".to_string()); println!("cargo::rustc-link-lib=static={}", zstd_name); } else { println!("cargo::rustc-link-lib=static=zstd"); } if is_windows { println!("cargo::rustc-link-lib=kernel32"); println!("cargo::rustc-link-lib=advapi32"); println!("cargo::rustc-link-lib=bcrypt"); println!("cargo::rustc-link-lib=ntdll"); println!("cargo::rustc-link-lib=user32"); println!("cargo::rustc-link-lib=ole32"); println!("cargo::rustc-link-lib=shell32"); println!("cargo::rustc-link-lib=Windowscodecs"); } } #[cfg(feature = "dynamic")] { println!( "cargo::rustc-link-search=native={}", dst.join("build/Src/libCZIAPI").display() ); println!("cargo::rustc-link-lib=libCZIAPI"); } } println!("cargo::rerun-if-changed=build.rs"); Ok(()) }