vacc-libva 0.1.0

Safe bindings over libva: cros-libva with VAAPI-decoder fixes (zero-initialized decode parameter buffers, HEVC extension buffer wrappers); interim until the fixes land upstream
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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;

/// Environment variable that can be set to point to the directory containing the `va.h`, `va_drm.h` and `va_drmcommon.h`
/// files to use to generate the bindings.
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";

/// Wrapper file to use as input of bindgen.
const WRAPPER_PATH: &str = "libva-wrapper.h";

// Return VA_MAJOR_VERSION and VA_MINOR_VERSION from va_version.h.
fn get_va_version(va_h_path: &str) -> (u32, u32) {
    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 = Regex::new(VERSION_REGEX_STRINGS[i]).unwrap();
        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 = Regex::new(r"[0-9]+")
            .unwrap()
            .find(match_line[0])
            .unwrap()
            .as_str();
        numbers[i] = number_str.parse::<u32>().unwrap();
    }

    (numbers[0], numbers[1])
}

fn main() {
    // Do not require dependencies when generating docs.
    if std::env::var("CARGO_DOC").is_ok() || std::env::var("DOCS_RS").is_ok() {
        return;
    }

    let va_h_path = env::var(CROS_LIBVA_H_PATH_ENV)
        .or_else(|e| {
            if let VarError::NotPresent = e {
                let libva_library = pkg_config::probe_library("libva");
                match libva_library {
                    Ok(_) => Ok(libva_library.unwrap().include_paths[0]
                        .clone()
                        .into_os_string()
                        .into_string()
                        .unwrap()),
                    Err(e) => panic!("libva is not found in system: {}", e),
                }
            } else {
                Err(e)
            }
        })
        .expect("libva header location is unknown");

    let va_lib_path = env::var(CROS_LIBVA_LIB_PATH_ENV).unwrap_or_default();
    // Check the path exists.
    if !va_h_path.is_empty() {
        assert!(
            Path::new(&va_h_path).exists(),
            "{} doesn't exist",
            va_h_path
        );
    }

    let (major, minor) = get_va_version(&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)
    };

    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_lib_path.is_empty() {
        assert!(
            Path::new(&va_lib_path).exists(),
            "{} doesn't exist",
            va_lib_path
        );
        println!("cargo:rustc-link-arg=-Wl,-rpath={}", va_lib_path);
    }

    // Tell cargo to link va and va-drm objects dynamically.
    println!("cargo:rustc-link-lib=dylib=va");
    println!("cargo:rustc-link-lib=dylib=va-drm"); // for the vaGetDisplayDRM entrypoint

    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 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");
        let va_p_h_path_env = env::var(CROS_LIBVA_PROTECTED_CONTENT_H_PATH_ENV);
        if va_p_h_path_env.is_ok() {
            let va_p_h_path = va_p_h_path_env.unwrap();
            bindings_builder = bindings_builder.clang_arg(format!("-I{}", va_p_h_path));
        }
    }

    let bindings = bindings_builder
        .generate()
        .expect("unable to generate bindings");

    let out_path = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is not set"));

    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}