use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-changed=libxaac");
let bundled = env::var_os("CARGO_FEATURE_BUNDLED").is_some();
let prefer_static = env::var_os("CARGO_FEATURE_STATIC").is_some();
let prefer_dynamic = env::var_os("CARGO_FEATURE_DYNAMIC").is_some();
assert!(
!(prefer_static && prefer_dynamic),
"`static` and `dynamic` features are mutually exclusive"
);
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("missing manifest dir"));
let source_dir = manifest_dir.join("libxaac");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("missing OUT_DIR"));
if bundled {
let processor = cmake_processor();
let build_dir = out_dir.join("cmake-build");
if build_dir.exists() {
std::fs::remove_dir_all(&build_dir)
.unwrap_or_else(|err| panic!("failed to clean {}: {err}", build_dir.display()));
}
run(
Command::new("cmake")
.arg("-S")
.arg(&source_dir)
.arg("-B")
.arg(&build_dir)
.arg(format!("-DCMAKE_SYSTEM_PROCESSOR={processor}"))
.arg("-DCMAKE_POSITION_INDEPENDENT_CODE=ON"),
);
run(
Command::new("cmake")
.arg("--build")
.arg(&build_dir)
.arg("--target")
.arg("libxaacenc")
.arg("libxaacdec"),
);
for lib_name in ["libxaacenc.a", "libxaacdec.a"] {
let lib_path = find_file(&build_dir, lib_name).unwrap_or_else(|| {
panic!("failed to locate {lib_name} under {}", build_dir.display())
});
let lib_dir = lib_path
.parent()
.expect("static library missing parent directory");
println!("cargo:rustc-link-search=native={}", lib_dir.display());
}
}
if bundled && prefer_dynamic {
println!("cargo:warning=`dynamic` requested with `bundled`, but vendored libxaac only builds static libraries; using static linking");
}
let link_kind = if bundled || prefer_static {
"static"
} else if prefer_dynamic {
"dylib"
} else {
"dylib"
};
println!("cargo:rustc-link-lib={link_kind}=xaacenc");
println!("cargo:rustc-link-lib={link_kind}=xaacdec");
println!("cargo:rustc-link-lib=m");
let bindings = bindgen::Builder::default()
.header(manifest_dir.join("wrapper.h").display().to_string())
.clang_arg(format!("-I{}", manifest_dir.display()))
.clang_arg(format!("-I{}", source_dir.join("common").display()))
.clang_arg(format!("-I{}", source_dir.join("decoder").display()))
.clang_arg(format!("-I{}", source_dir.join("decoder/drc_src").display()))
.clang_arg(format!("-I{}", source_dir.join("encoder").display()))
.clang_arg(format!("-I{}", source_dir.join("encoder/drc_src").display()))
.allowlist_function("ixheaace_(get_lib_id_strings|create|process|delete)")
.allowlist_function("ixheaacd_(get_lib_id_strings|dec_api|dec_main)")
.allowlist_function("ia_drc_dec_api")
.allowlist_type("ixheaace_.*")
.allowlist_type("ia_(mem_info_struct|lib_info_struct)")
.allowlist_var("(IA|IXHEAACE|AOT|DEFAULT_MEM_ALIGN_8).*")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("failed to generate bindings");
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("failed to write bindings");
}
fn cmake_processor() -> &'static str {
match env::var("CARGO_CFG_TARGET_ARCH").as_deref() {
Ok("x86_64") => "x86_64",
Ok("x86") => "i686",
Ok("aarch64") => "aarch64",
Ok("arm") => "aarch32",
Ok(other) => panic!("unsupported target arch: {other}"),
Err(_) => panic!("missing CARGO_CFG_TARGET_ARCH"),
}
}
fn find_file(dir: &Path, file_name: &str) -> Option<PathBuf> {
if !dir.exists() {
return None;
}
let entries = std::fs::read_dir(dir).ok()?;
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if let Some(found) = find_file(&path, file_name) {
return Some(found);
}
} else if path.file_name().and_then(|name| name.to_str()) == Some(file_name) {
return Some(path);
}
}
None
}
fn run(command: &mut Command) {
let status = command.status().expect("failed to spawn command");
assert!(status.success(), "command failed with status {status}");
}