use regex::Regex;
use std::env::VarError;
use std::env::{self};
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
mod bindgen_gen;
use bindgen_gen::vaapi_gen_builder;
const CROS_LIBVA_H_PATH_ENV: &str = "CROS_LIBVA_H_PATH";
const CROS_LIBVA_LIB_PATH_ENV: &str = "CROS_LIBVA_LIB_PATH";
const CROS_LIBVA_PROTECTED_CONTENT_H_PATH_ENV: &str = "CROS_LIBVA_PROTECTED_CONTENT_H_PATH";
const WRAPPER_PATH: &str = "libva-wrapper.h";
fn get_va_version(va_h_path: &str) -> (u32, u32) {
use std::sync::LazyLock;
static VERSION_RES: LazyLock<[Regex; 2]> = LazyLock::new(|| {
[
Regex::new(r"#define VA_MAJOR_VERSION\s*[0-9]+").unwrap(),
Regex::new(r"#define VA_MINOR_VERSION\s*[0-9]+").unwrap(),
]
});
static NUMBER_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[0-9]+").unwrap());
let va_version_h_path = Path::new(va_h_path).join("va/va_version.h");
assert!(
va_version_h_path.exists(),
"{} doesn't exist",
va_version_h_path.display()
);
let header_content = read_to_string(va_version_h_path).unwrap();
let lines = header_content.lines();
const VERSION_REGEX_STRINGS: [&str; 2] = [
r"#define VA_MAJOR_VERSION\s*[0-9]+",
r"#define VA_MINOR_VERSION\s*[0-9]+",
];
let mut numbers: [u32; 2] = [0; 2];
for i in 0..2 {
let re = &VERSION_RES[i];
let match_line = lines
.clone()
.filter(|&s| re.is_match(s))
.collect::<Vec<_>>();
assert_eq!(
match_line.len(),
1,
"unexpected match for {}: {:?}",
VERSION_REGEX_STRINGS[i],
match_line
);
let number_str = NUMBER_RE.find(match_line[0]).unwrap().as_str();
numbers[i] = number_str.parse::<u32>().unwrap();
}
(numbers[0], numbers[1])
}
fn generate_vendored_version_header(out_dir: &Path, manifest_dir: &Path) -> (u32, u32) {
let meson_build = read_to_string(manifest_dir.join("libva/meson.build"))
.expect("failed to read libva/meson.build — is the submodule initialized?");
let extract = |var_name: &str| -> String {
let re = Regex::new(&format!(r"{}\s*=\s*(\d+)", var_name)).unwrap();
re.captures(&meson_build)
.unwrap_or_else(|| panic!("{} not found in libva/meson.build", var_name))[1]
.to_string()
};
let major = extract("va_api_major_version");
let minor = extract("va_api_minor_version");
let micro = extract("va_api_micro_version");
let version = format!("{}.{}.{}", major, minor, micro);
let template = read_to_string(manifest_dir.join("libva/va/va_version.h.in"))
.expect("failed to read libva/va/va_version.h.in");
let generated = template
.replace("@VA_API_MAJOR_VERSION@", &major)
.replace("@VA_API_MINOR_VERSION@", &minor)
.replace("@VA_API_MICRO_VERSION@", µ)
.replace("@VA_API_VERSION@", &version);
let va_dir = out_dir.join("va");
std::fs::create_dir_all(&va_dir).expect("failed to create va dir in OUT_DIR");
std::fs::write(va_dir.join("va_version.h"), generated).expect("failed to write va_version.h");
(
major.parse().expect("invalid major version"),
minor.parse().expect("invalid minor version"),
)
}
fn main() {
if std::env::var("CARGO_DOC").is_ok() || std::env::var("DOCS_RS").is_ok() {
return;
}
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is not set"));
let manifest_dir =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` is not set"));
let mut use_vendored_headers = cfg!(feature = "vendored");
let (va_h_path, major, minor) = if cfg!(feature = "vendored") {
let (major, minor) = generate_vendored_version_header(&out_dir, &manifest_dir);
println!("cargo:rerun-if-changed=libva/meson.build");
println!("cargo:rerun-if-changed=libva/va/va_version.h.in");
let va_h_path = manifest_dir
.join("libva")
.into_os_string()
.into_string()
.unwrap();
(va_h_path, major, minor)
} else {
let explicit = env::var(CROS_LIBVA_H_PATH_ENV);
match explicit {
Ok(p) => {
let (major, minor) = get_va_version(&p);
(p, major, minor)
}
Err(VarError::NotPresent) => {
let mut config = pkg_config::Config::new();
config.cargo_metadata(false);
match config.probe("libva") {
Ok(lib) => {
let va_h_path = lib.include_paths[0]
.clone()
.into_os_string()
.into_string()
.unwrap();
let (major, minor) = get_va_version(&va_h_path);
(va_h_path, major, minor)
}
Err(e) => {
if manifest_dir.join("libva/va/va.h").exists() {
println!(
"cargo:warning=libva not found via pkg-config ({}), falling back to vendored headers in lib/libva",
e
);
use_vendored_headers = true;
let (major, minor) = generate_vendored_version_header(&out_dir, &manifest_dir);
println!("cargo:rerun-if-changed=libva/meson.build");
println!("cargo:rerun-if-changed=libva/va/va_version.h.in");
let va_h_path = manifest_dir
.join("libva")
.into_os_string()
.into_string()
.unwrap();
(va_h_path, major, minor)
} else {
panic!("libva is not found in system: {}", e)
}
}
}
}
Err(e) => panic!("{}: {}", CROS_LIBVA_H_PATH_ENV, e),
}
};
if !va_h_path.is_empty() {
assert!(
Path::new(&va_h_path).exists(),
"{} doesn't exist",
va_h_path
);
}
println!("libva {}.{} is used to generate bindings", major, minor);
let va_check_version = |desired_major: u32, desired_minor: u32| {
major > desired_major || (major == desired_major && minor >= desired_minor)
};
println!("cargo:rerun-if-env-changed={}", CROS_LIBVA_H_PATH_ENV);
println!("cargo:rerun-if-env-changed={}", CROS_LIBVA_LIB_PATH_ENV);
println!(
"cargo:rerun-if-env-changed={}",
CROS_LIBVA_PROTECTED_CONTENT_H_PATH_ENV
);
println!("cargo::rustc-check-cfg=cfg(libva_1_23_or_higher)");
println!("cargo::rustc-check-cfg=cfg(libva_1_22_or_higher)");
println!("cargo::rustc-check-cfg=cfg(libva_1_21_or_higher)");
println!("cargo::rustc-check-cfg=cfg(libva_1_20_or_higher)");
println!("cargo::rustc-check-cfg=cfg(libva_1_19_or_higher)");
println!("cargo::rustc-check-cfg=cfg(libva_1_16_or_higher)");
println!("cargo::rustc-check-cfg=cfg(libva_1_15_or_higher)");
println!("cargo::rustc-check-cfg=cfg(libva_1_14_or_higher)");
println!("cargo::rustc-check-cfg=cfg(libva_1_10_or_higher)");
if va_check_version(1, 23) {
println!("cargo::rustc-cfg=libva_1_23_or_higher");
}
if va_check_version(1, 22) {
println!("cargo::rustc-cfg=libva_1_22_or_higher");
}
if va_check_version(1, 21) {
println!("cargo::rustc-cfg=libva_1_21_or_higher");
}
if va_check_version(1, 20) {
println!("cargo::rustc-cfg=libva_1_20_or_higher")
}
if va_check_version(1, 19) {
println!("cargo::rustc-cfg=libva_1_19_or_higher")
}
if va_check_version(1, 16) {
println!("cargo::rustc-cfg=libva_1_16_or_higher")
}
if va_check_version(1, 15) {
println!("cargo::rustc-cfg=libva_1_15_or_higher");
}
if va_check_version(1, 14) {
println!("cargo::rustc-cfg=libva_1_14_or_higher");
}
if va_check_version(1, 10) {
println!("cargo::rustc-cfg=libva_1_10_or_higher");
}
if !cfg!(feature = "dlopen") {
let va_lib_path = env::var(CROS_LIBVA_LIB_PATH_ENV).unwrap_or_default();
if !va_lib_path.is_empty() {
assert!(
Path::new(&va_lib_path).exists(),
"{} doesn't exist",
va_lib_path
);
println!("cargo:rustc-link-search=native={}", va_lib_path);
println!("cargo:rustc-link-arg=-Wl,-rpath={}", va_lib_path);
println!("cargo:rustc-link-lib=dylib=va");
println!("cargo:rustc-link-lib=dylib=va-drm");
} else {
let mut libva = pkg_config::Config::new();
libva.cargo_metadata(true);
if let Err(e) = libva.probe("libva") {
if use_vendored_headers {
println!(
"cargo:warning=libva not found for linking ({}), skipping link (dlopen fallback/vendors headers)",
e
);
} else {
panic!("libva not found via pkg-config: {}", e);
}
} else {
let mut libva_drm = pkg_config::Config::new();
libva_drm.cargo_metadata(true);
if let Err(e) = libva_drm.probe("libva-drm") {
if use_vendored_headers {
println!(
"cargo:warning=libva-drm not found for linking ({}), skipping",
e
);
} else {
panic!("libva-drm not found via pkg-config: {}", e);
}
}
}
}
}
let mut bindings_builder = vaapi_gen_builder(bindgen::builder()).header(WRAPPER_PATH);
if !va_h_path.is_empty() {
bindings_builder = bindings_builder.clang_arg(format!("-I{}", va_h_path));
}
if use_vendored_headers {
bindings_builder = bindings_builder.clang_arg(format!("-I{}", out_dir.display()));
}
if std::env::var("CARGO_FEATURE_INTEL_PROTECTED_CONTENT_HEADERS").is_ok() {
println!("cargo:warning=Building with intel protected content headers!");
bindings_builder = bindings_builder.clang_arg("-DINTEL_PROTECTED_CONTENT_HEADERS");
if let Ok(va_p_h_path) = env::var(CROS_LIBVA_PROTECTED_CONTENT_H_PATH_ENV) {
bindings_builder = bindings_builder.clang_arg(format!("-I{}", va_p_h_path));
}
}
println!("cargo:rerun-if-changed={}", WRAPPER_PATH);
println!("cargo:rerun-if-changed=bindgen_gen.rs");
let bindings = bindings_builder
.generate()
.expect("unable to generate bindings");
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings!");
}