#![allow(unused_imports, dead_code)]
use anyhow::{Result, Context as _, anyhow, bail};
use std::{env, fs};
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
fn main() -> Result<()> {
println!("cargo:rerun-if-changed=build.rs");
let link_kind = get_link_kind()?;
let library = build_or_find_library(link_kind)?;
generate_or_copy_bindings(&library)?;
Ok(())
}
#[derive(Debug)]
enum LinkKind {
Static,
Dynamic,
Default,
}
fn get_link_kind() -> Result<LinkKind> {
let static_env = env_bool("TURBOJPEG_STATIC")?;
let dynamic_env = env_bool("TURBOJPEG_DYNAMIC")?.or(env_bool("TURBOJPEG_SHARED")?);
match (static_env, dynamic_env) {
(Some(true), Some(true)) =>
bail!("Both TURBOJPEG_STATIC and TURBOJPEG_DYNAMIC/TURBOJPEG_SHARED are set to 1"),
(Some(false), Some(false)) =>
bail!("Both TURBOJPEG_STATIC and TURBOJPEG_DYNAMIC/TURBOJPEG_SHARED are set to 0"),
(None, None) =>
Ok(LinkKind::Default),
(Some(true) | None, Some(false) | None) =>
Ok(LinkKind::Static),
(Some(false) | None, Some(true) | None) =>
Ok(LinkKind::Dynamic),
}
}
#[derive(Debug)]
struct Library {
include_paths: Vec<PathBuf>,
defines: HashMap<String, Option<String>>,
}
fn build_or_find_library(link_kind: LinkKind) -> Result<Library> {
match env("TURBOJPEG_SOURCE") {
Some(source) => {
if source.eq_ignore_ascii_case("vendor") {
build_vendor(link_kind)
} else if source.eq_ignore_ascii_case("pkg-config") ||
source.eq_ignore_ascii_case("pkgconfig") ||
source.eq_ignore_ascii_case("pkgconf")
{
find_pkg_config(link_kind)
} else if source.eq_ignore_ascii_case("explicit") {
find_explicit(link_kind)
} else {
bail!("Unknown value of TURBOJPEG_SOURCE, supported values are:\n\
- 'vendor' to build the library from source bundled with the turbojpeg-sys crate,\n\
- 'pkg-config' to find the library using pkg-config,\n\
- 'explicit' to use TURBOJPEG_LIB_DIR and TURBOJPEG_INCLUDE_DIR")
}
},
None => {
if cfg!(feature = "cmake") {
build_vendor(link_kind)
} else if cfg!(feature = "pkg-config") {
find_pkg_config(link_kind)
} else {
find_explicit(link_kind)
}
},
}
}
#[cfg(feature = "pkg-config")]
fn find_pkg_config(link_kind: LinkKind) -> Result<Library> {
println!("Using pkg-config to find libturbojpeg");
let mut cfg = pkg_config::Config::new();
cfg.atleast_version("3.0");
match link_kind {
LinkKind::Static => { cfg.statik(true); },
LinkKind::Dynamic => { cfg.statik(false); },
LinkKind::Default => {},
}
let lib = cfg.probe("libturbojpeg")
.context("could not find turbojpeg using pkg-config")?;
Ok(Library {
include_paths: lib.include_paths,
defines: lib.defines,
})
}
#[cfg(not(feature = "pkg-config"))]
fn find_pkg_config(_: LinkKind) -> Result<Library> {
bail!("Trying to find turbojpeg using pkg-config, but the `pkg-config` feature is disabled. \
You have two options:\n\
- enable `pkg-config` feature of `turbojpeg-sys` crate\n\
- use TURBOJPEG_SOURCE to select other source for the library")
}
fn find_explicit(link_kind: LinkKind) -> Result<Library> {
println!("Using TURBOJPEG_LIB_DIR and TURBOJPEG_INCLUDE_DIR to find turbojpeg");
let lib_dir = env_path("TURBOJPEG_LIB_DIR")
.or_else(|| env_path("TURBOJPEG_LIB_PATH"))
.context("TURBOJPEG_SOURCE is set to 'explicit', but TURBOJPEG_LIB_DIR is not set")?;
let include_dir = env_path("TURBOJPEG_INCLUDE_DIR")
.or_else(|| env_path("TURBOJPEG_INCLUDE_PATH"));
let lib_dir = fs::canonicalize(lib_dir)
.context("Cannot canonicalize TURBOJPEG_LIB_DIR")?;
let include_dir = include_dir.map(fs::canonicalize).transpose()
.context("Cannot canonicalize TURBOJPEG_INCLUDE_DIR")?;
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib={}=turbojpeg", match link_kind {
LinkKind::Static | LinkKind::Default => "static",
LinkKind::Dynamic => "dylib",
});
Ok(Library {
include_paths: include_dir.into_iter().collect(),
defines: HashMap::new(),
})
}
#[cfg(feature = "cmake")]
fn build_vendor(link_kind: LinkKind) -> Result<Library> {
println!("Building turbojpeg from source");
if !cfg!(feature = "require-simd") {
check_nasm();
}
let source_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?).join("libjpeg-turbo");
let mut cmake = cmake::Config::new(source_path);
cmake.configure_arg(format!("-DENABLE_SHARED={}", matches!(link_kind, LinkKind::Dynamic) as u32));
cmake.configure_arg(format!("-DENABLE_STATIC={}", !matches!(link_kind, LinkKind::Dynamic) as u32));
cmake.env("DESTDIR", "");
cmake.define("CMAKE_INSTALL_DEFAULT_LIBDIR", "lib");
if cfg!(feature = "require-simd") {
cmake.configure_arg("-DREQUIRE_SIMD=ON");
}
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "android" {
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let android_abi = match target_arch.as_str() {
"arm" => "armeabi-v7a",
"aarch64" => "arm64-v8a",
"x86" => "x86",
"x86_64" => "x86_64",
_ => bail!("Unsupported Android arch: {target_arch:?}"),
};
cmake.configure_arg(format!("-DANDROID_ABI={android_abi}"));
}
let dst_path = cmake.build();
let lib_path = dst_path.join("lib");
let include_path = dst_path.join("include");
let is_msvc = env("CARGO_CFG_TARGET_ENV").unwrap() == "msvc";
println!("cargo:rustc-link-search=native={}", lib_path.display());
println!("cargo:rustc-link-lib={}=turbojpeg{}", match link_kind {
LinkKind::Static | LinkKind::Default => "static",
LinkKind::Dynamic => "dylib",
}, if is_msvc && matches!(link_kind, LinkKind::Static | LinkKind::Default) {
"-static"
} else {
""
});
Ok(Library {
include_paths: vec![include_path],
defines: HashMap::new(),
})
}
fn check_nasm() {
if !Command::new("nasm").arg("-v").status().map(|s| s.success()).unwrap_or(false) {
println!("cargo:warning=NASM does not seem to be installed, so turbojpeg will be compiled without \
SIMD extensions. Performance will suffer.");
}
}
#[cfg(not(feature = "cmake"))]
fn build_vendor(_link_kind: LinkKind) -> Result<Library> {
bail!("Trying to build turbojpeg from source, but the `cmake` feature is disabled.\
You have two options:\n\
- enable `cmake` feature of `turbojpeg-sys` crate\n\
- use TURBOJPEG_SOURCE to select other source for the library")
}
fn generate_or_copy_bindings(library: &Library) -> Result<()> {
match env("TURBOJPEG_BINDING") {
Some(binding) => {
if binding.eq_ignore_ascii_case("pregenerated") {
copy_pregenerated_bindings()
} else if binding.eq_ignore_ascii_case("bindgen") {
generate_bindings(library)
} else {
bail!("Unknown value of TURBOJPEG_BINDING, supported values are:\n\
- `pregenerated` to use our pregenerated Rust bindings,\n\
- `bindgen` to generate the bindings with bindgen")
}
},
None => {
if cfg!(feature = "bindgen") {
generate_bindings(library)
} else {
copy_pregenerated_bindings()
}
},
}
}
fn copy_pregenerated_bindings() -> Result<()> {
println!("Using pregenerated bindings");
let out_path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let crate_path = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
fs::copy(crate_path.join("bindings.rs"), out_path.join("bindings.rs"))?;
println!("cargo:rerun-if-changed={}", crate_path.join("bindings.rs").to_str().unwrap());
Ok(())
}
#[cfg(feature = "bindgen")]
fn generate_bindings(library: &Library) -> Result<()> {
println!("Generating bindings using bindgen");
let target = env::var("TARGET").unwrap();
let mut builder = bindgen::Builder::default()
.header("wrapper.h")
.use_core()
.ctypes_prefix("libc")
.clang_args(&["-target", &target]);
for path in library.include_paths.iter() {
let path = path.to_str().unwrap();
builder = builder.clang_arg(format!("-I{}", path));
println!("cargo:rerun-if-changed={}", path);
}
for (name, value) in library.defines.iter() {
if let Some(value) = value {
builder = builder.clang_arg(format!("-D{}={}", name, value));
} else {
builder = builder.clang_arg(format!("-D{}", name));
}
}
let bindings = builder.generate()
.map_err(|_| anyhow!("could not generate bindings"))?;
let out_file = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("bindings.rs");
bindings.write_to_file(&out_file)
.context("could not write bindings to OUT_DIR")?;
println!("Generated bindings are stored in {}", out_file.display());
Ok(())
}
#[cfg(not(feature = "bindgen"))]
fn generate_bindings(_: &Library) -> Result<()> {
bail!("Trying to build bindings with bindgen, but the `bindgen` feature is disabled. \
You have two options:\n\
- enable `bindgen` feature of `turbojpeg-sys` crate\n\
- use TURBOJPEG_BINDING to select other method to obtain the bindings")
}
fn env(name: &str) -> Option<OsString> {
fn env_inner(name: &str) -> Option<OsString> {
let value = env::var_os(name);
println!("cargo:rerun-if-env-changed={}", name);
match value {
Some(ref v) => println!("{} = {}", name, v.to_string_lossy()),
None => println!("{} unset", name),
}
value
}
let prefix = env::var("TARGET").unwrap().to_uppercase().replace('-', "_");
let prefixed = format!("{}_{}", prefix, name);
env_inner(&prefixed).or_else(|| env_inner(name))
}
fn env_bool(name: &str) -> Result<Option<bool>> {
match env(name) {
Some(value) => {
for v in ["", "1", "yes", "true", "on"].into_iter() {
if value.eq_ignore_ascii_case(v) { return Ok(Some(true)) }
}
for v in ["0", "no", "false", "off"].into_iter() {
if value.eq_ignore_ascii_case(v) { return Ok(Some(false)) }
}
bail!("Env variable {} has value {:?}, expected empty or boolean", name, value)
},
None => Ok(None),
}
}
fn env_path(name: &str) -> Option<PathBuf> {
env(name).map(|v| v.into())
}