Files
libczirw-sys/build.rs
T
Wim Pomp 8c67d8c592
Publish / Publish (crates.io) (push) Failing after 1m51s
- cross-compilation
2026-07-15 04:03:01 +02:00

452 lines
16 KiB
Rust

extern crate bindgen;
use std::env;
use std::path::{Path, PathBuf};
use std::fmt::Debug;
#[cfg(not(feature = "dynamic"))]
use bindgen::callbacks::ItemInfo;
#[cfg(not(feature = "dynamic"))]
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
fn find_static_lib(dir: &Path, base_name: &str, target_is_windows: bool) -> Option<PathBuf> {
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<String, Box<dyn std::error::Error>> {
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<S: ToString>(msg: S) -> Box<dyn std::error::Error> {
Box::new(Error(msg.to_string()))
}
}
fn fix_xwin_lib_case() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
if env::var("DOCS_RS").is_err() {
let host = env::var("HOST")?;
let target = env::var("TARGET")?;
let is_cross = host != target;
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");
}
}
}
if is_cross && is_windows {
cmake_config.build_target("libCZIAPIStatic");
}
let dst = cmake_config.build();
#[cfg(not(feature = "dynamic"))]
let bindings = {
let libcziapi_search_dirs = [
dst.join("build/Src/libCZIAPI"),
dst.join("Src/libCZIAPI"),
dst.join("lib"),
dst.join("lib64"),
];
let libcziapi_static = libcziapi_search_dirs
.iter()
.find_map(|dir| find_static_lib(dir, "libCZIAPIStatic", is_windows))
.ok_or(Error::msg("cannot find libCZIAPIStatic library"))?;
bindgen::Builder::default().parse_callbacks(Box::new(DeMangler::new(
libcziapi_static,
is_cross && is_windows,
)?))
};
#[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(())
}
#[cfg(not(feature = "dynamic"))]
#[derive(Debug)]
struct DeMangler {
map: HashMap<String, String>,
}
#[cfg(not(feature = "dynamic"))]
impl DeMangler {
fn new(a_file: PathBuf, use_llvm_nm: bool) -> Result<Self, Box<dyn std::error::Error>> {
let nm_cmd = if use_llvm_nm { "llvm-nm" } else { "nm" };
let cmd = std::process::Command::new(nm_cmd)
.arg("--defined-only")
.arg(&a_file)
.output()?;
let mut map = HashMap::new();
for line in std::str::from_utf8(&cmd.stdout)?.lines() {
let line = line.trim();
let mangled = if let Some(sym) = line.split_whitespace().last() {
sym
} else {
continue;
};
let plain_name = Self::demangle(mangled);
if let Some(name) = plain_name {
if name.starts_with("libCZI_") {
Self::insert_mapping(&mut map, &name, mangled, &a_file)?;
}
}
}
Ok(Self { map })
}
fn demangle(symbol: &str) -> Option<String> {
// Only demangle actual function symbols, not catch/dtor/cppxdata metadata
if let Some(rest) = symbol.strip_prefix('?') {
// MSVC: real functions start with "libCZI_", not "catch$", "dtor$", etc.
if !rest.starts_with("libCZI_") {
return None;
}
let flags = msvc_demangler::DemangleFlags::llvm();
let demangled = msvc_demangler::demangle(symbol, flags).ok()?;
Self::extract_msvc_name(&demangled)
} else if symbol.starts_with("_Z") {
// Itanium: demangle and extract the function name
let sym = cpp_demangle::Symbol::new(symbol.as_bytes()).ok()?;
let demangled = sym.demangle().ok()?;
// e.g. "libCZI_Foo(int, bool)" -> "libCZI_Foo"
// e.g. "ns::libCZI_Foo(int)" -> "libCZI_Foo"
demangled.split('(').next().and_then(|s| {
let s = s.trim();
if let Some(name) = s.rsplit("::").next() {
Some(name.to_string())
} else {
Some(s.to_string())
}
})
} else {
None
}
}
fn extract_msvc_name(demangled: &str) -> Option<String> {
// Find the identifier that looks like a function name.
// MSVC demangled output: "return_type calling_conv name(params)"
for word in demangled.split_whitespace() {
if let Some(rest) = word.strip_prefix("libCZI_") {
let name = format!("libCZI_{}", rest.split('(').next().unwrap_or(rest));
return Some(name);
}
}
None
}
fn insert_mapping(
map: &mut HashMap<String, String>,
demangled: &str,
mangled: &str,
a_file: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(existing_mangled) = map.get(demangled) {
if existing_mangled != mangled {
return Err(Error::msg(format!(
"conflicting mangled symbols for {} in {}: {}, {}",
demangled,
a_file.to_string_lossy(),
existing_mangled,
mangled
)));
}
} else {
map.insert(demangled.to_string(), mangled.to_string());
}
Ok(())
}
}
#[cfg(not(feature = "dynamic"))]
impl bindgen::callbacks::ParseCallbacks for DeMangler {
fn generated_link_name_override(&self, item_info: ItemInfo<'_>) -> Option<String> {
self.map.get(item_info.name).cloned()
}
}