jxl-sys 0.1.10+libjxl-0.11.2

bindings to libjxl
Documentation
use anyhow::{Result, anyhow, bail};
use cmake::Config;
use regex::Regex;
use semver::Version;
#[cfg(all(target_os = "windows", target_env = "msvc"))]
use std::process::{Command, Stdio};
use std::{env, ffi::OsStr, fs, path::PathBuf, thread};

const PREFIX: &str = "libjxl-";

fn validate_version() -> Result<()> {
    let crate_ver = Version::parse(&env::var("CARGO_PKG_VERSION")?)?;

    if !crate_ver.build.starts_with(PREFIX) {
        bail!(
            "expected build-metadata `+libjxl-x.y.z`, got `+{}`",
            crate_ver.build
        );
    }
    let libjxl_meta_str = &crate_ver.build[PREFIX.len()..];
    let libjxl_meta = Version::parse(libjxl_meta_str)?;

    let cmake_txt = fs::read_to_string("libjxl/lib/CMakeLists.txt")?;
    let cap = |var: &str| {
        Regex::new(&format!(r#"set\({var}\s+(\d+)\)"#))?
            .captures(&cmake_txt)
            .and_then(|c| c.get(1))
            .map(|m| m.as_str())
            .ok_or(anyhow!("`{}` not found in CMakeLists.txt", var))
    };
    let upstream_str = format!(
        "{}.{}.{}",
        cap("JPEGXL_MAJOR_VERSION")?,
        cap("JPEGXL_MINOR_VERSION")?,
        cap("JPEGXL_PATCH_VERSION")?,
    );
    let upstream = Version::parse(&upstream_str)?;

    if libjxl_meta != upstream {
        bail!(
            "version mismatch: crate metadata says libjxl={} but upstream is {}",
            libjxl_meta,
            upstream
        );
    }

    Ok(())
}

fn main() -> Result<()> {
    println!("cargo:rerun-if-changed=libjxl/");
    println!("cargo:rerun-if-env-changed=CMAKE_GENERATOR");
    println!("cargo:rustc-link-lib=static=jxl");
    println!("cargo:rustc-link-lib=static=jxl_cms");
    println!("cargo:rustc-link-lib=static=jxl_threads");
    println!("cargo:rustc-link-lib=static=hwy");
    println!("cargo:rustc-link-lib=static=brotlidec");
    println!("cargo:rustc-link-lib=static=brotlienc");
    println!("cargo:rustc-link-lib=static=brotlicommon");

    #[cfg(target_os = "macos")]
    println!("cargo:rustc-link-lib=c++");
    #[cfg(target_os = "linux")]
    println!("cargo:rustc-link-lib=dylib=stdc++");

    validate_version()?;

    let mut cfg = Config::new("libjxl");
    if cfg!(all(target_os = "windows", target_env = "msvc")) {
        if env::var_os("CMAKE_GENERATOR").is_none() && ninja_available() {
            cfg.generator("Ninja");
        }

        // Force Release libs for debug builds to avoid MSVCRTD/CRT mismatch.
        cfg.profile("Release");
        cfg.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL");
        cfg.define("CMAKE_C_FLAGS_RELEASE", "/O2 /Ob2 /DNDEBUG");
        cfg.define("CMAKE_CXX_FLAGS_RELEASE", "/O2 /Ob2 /DNDEBUG");
    }

    let dst = cfg
        .define("BUILD_TESTING", "OFF")
        .define("BUILD_SHARED_LIBS", "OFF")
        .define("JPEGXL_ENABLE_BENCHMARK", "OFF")
        .define("JPEGXL_ENABLE_EXAMPLES", "OFF")
        .define("JPEGXL_ENABLE_DOXYGEN", "OFF")
        .define("JPEGXL_ENABLE_OPENEXR", "OFF")
        .define("JPEGXL_BUNDLE_LIBPNG", "OFF")
        .define("JPEGXL_ENABLE_JNI", "OFF")
        .define("JPEGXL_ENABLE_JPEGLI", "OFF")
        .define("JPEGXL_ENABLE_MANPAGES", "OFF")
        .define("JPEGXL_ENABLE_SJPEG", "OFF")
        .define("JPEGXL_ENABLE_TOOLS", "OFF")
        .env(
            "CMAKE_BUILD_PARALLEL_LEVEL",
            format!("{}", thread::available_parallelism()?),
        );

    let dst = if std::env::var("CARGO_FEATURE_LTO").is_ok() {
        dst.define("CMAKE_CXX_FLAGS", "-flto=thin")
            .define("CMAKE_C_FLAGS", "-flto=thin")
            .define("CMAKE_POLICY_DEFAULT_CMP0069", "NEW") // enable IPO policy
            .define("CMAKE_C_COMPILER", "clang")
            .define("CMAKE_CXX_COMPILER", "clang++")
            .define("CMAKE_EXE_LINKER_FLAGS", "-flto=thin")
            .define("CMAKE_INTERPROCEDURAL_OPTIMIZATION", "TRUE")
    } else {
        dst
    }
    .build();

    let include = dst.join("include");
    println!(
        "cargo:rustc-link-search=native={}",
        dst.join("lib").display()
    );
    println!(
        "cargo:rustc-link-search=native={}",
        dst.join("lib64").display()
    );

    let include_jxl = include.join("jxl");
    let mut bindings = bindgen::Builder::default()
        .allowlist_item("Jxl.*")
        .clang_arg(format!("-I{}", include.display()))
        .default_enum_style(bindgen::EnumVariation::Rust {
            non_exhaustive: true,
        })
        .derive_default(true)
        .generate_comments(true)
        .use_core();

    for entry in include_jxl.read_dir()? {
        let entry = entry?;
        let is_valid_c_header = entry.file_type()?.is_file()
            && entry.path().extension().and_then(OsStr::to_str) == Some("h")
            && !entry
                .path()
                .file_stem()
                .and_then(OsStr::to_str)
                .map(|stem| stem.ends_with("_cxx"))
                .unwrap_or(true);
        if is_valid_c_header {
            bindings = bindings.header(entry.path().to_string_lossy());
        }
    }

    bindings
        .generate()?
        .write_to_file(PathBuf::from(env::var("OUT_DIR")?).join("bindings.rs"))?;

    Ok(())
}

#[cfg(all(target_os = "windows", target_env = "msvc"))]
fn ninja_available() -> bool {
    Command::new("ninja")
        .arg("--version")
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .map(|status| status.success())
        .unwrap_or(false)
}

#[cfg(not(all(target_os = "windows", target_env = "msvc")))]
fn ninja_available() -> bool {
    false
}