libde265-sys2 0.1.0

libde265 bindings
Documentation
use std::io;
use std::path::{Path, PathBuf};

fn main() {
    if std::env::var("DOCS_RS").is_ok() {
        // Don't link with libde265 in case of building documentation for docs.rs.
        return;
    }

    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=wrapper.h");

    // Tell cargo to tell rustc to link the libde265 library.

    #[allow(unused_assignments, unused_variables)]
    let include_paths;

    #[cfg(not(target_os = "windows"))]
    #[allow(unused_assignments)]
    {
        include_paths = find_libde265();
    }

    #[cfg(target_os = "windows")]
    #[allow(unused_assignments)]
    {
        include_paths = install_libde265_by_vcpkg();
    }

    #[cfg(feature = "use-bindgen")]
    run_bindgen(include_paths);
}

#[allow(dead_code)]
fn prepare_libde265_src() -> PathBuf {
    let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
    let crate_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
    let libde265_dir = crate_dir.join("vendor/libde265");
    let dst_dir = out_path.join("libde265");
    copy_dir_all(libde265_dir, &dst_dir).unwrap();
    dst_dir
}

#[cfg(feature = "embedded-libde265")]
fn compile_libde265() -> String {
    use std::path::PathBuf;

    let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
    let libde265_dir = prepare_libde265_src();

    let mut build_config = cmake::Config::new(libde265_dir);
    build_config.out_dir(out_path.join("libde265_build"));
    build_config.define("CMAKE_INSTALL_LIBDIR", "lib");

    // Disable some options
    for key in [
        "BUILD_SHARED_LIBS",
        "ENABLE_SDL",
        "ENABLE_DECODER",
        "ENABLE_ENCODER",
    ] {
        build_config.define(key, "OFF");
    }

    let libde265_build = build_config.build();

    libde265_build
        .join("lib/pkgconfig")
        .to_string_lossy()
        .to_string()
}

#[cfg(not(target_os = "windows"))]
fn find_libde265() -> Vec<String> {
    #[allow(unused_mut)]
    let mut config = system_deps::Config::new();

    #[cfg(feature = "embedded-libde265")]
    {
        unsafe {
            std::env::set_var("SYSTEM_DEPS_LIBDE265_BUILD_INTERNAL", "always");
        }
        config = config.add_build_internal("libde265", |lib, version| {
            let pc_file_path = compile_libde265();
            system_deps::Library::from_internal_pkg_config(pc_file_path, lib, version)
        });
    }

    use system_deps::Error;

    match config.probe() {
        Ok(dependencies) => {
            let lib = dependencies.get_by_name("libde265").unwrap();
            lib.include_paths
                .iter()
                .map(|p| p.to_string_lossy().to_string())
                .collect()
        }
        Err(err) => {
            let err_msg = match &err {
                Error::InvalidMetadata(msg) => {
                    if msg.contains("No version") && msg.contains("libde265") {
                        "You MUST enable one of the crate features to specify \
                    minimal supported version of 'libde265' API (e.g. v1_0)."
                            .to_string()
                    } else {
                        err.to_string()
                    }
                }
                _ => err.to_string(),
            };
            println!("cargo:error={}", err_msg);
            std::process::exit(1)
        }
    }
}

#[cfg(target_os = "windows")]
fn install_libde265_by_vcpkg() -> Vec<String> {
    let vcpkg_lib = vcpkg::Config::new()
        .emit_includes(true)
        .find_package("libde265");
    match vcpkg_lib {
        Ok(lib) => lib
            .include_paths
            .iter()
            .map(|p| p.to_string_lossy().to_string())
            .collect(),
        Err(err) => {
            println!("cargo:warning={}", err);
            std::process::exit(1)
        }
    }
}

#[cfg(feature = "use-bindgen")]
fn run_bindgen(include_paths: Vec<String>) {
    #[derive(Debug)]
    struct ParseFixer;

    impl bindgen::callbacks::ParseCallbacks for ParseFixer {
        fn process_comment(&self, _comment: &str) -> Option<String> {
            const REMOVE: &[&str] = &[
                "=== error codes ===",
                "--- warnings ---",
                "obsolet",
                "currently: 1",
            ];
            for substr in REMOVE {
                if _comment.contains(substr) {
                    return Some("".to_string());
                }
            }
            None
        }
    }

    let mut base_builder = bindgen::Builder::default()
        .header("wrapper.h")
        .generate_comments(true)
        .formatter(bindgen::Formatter::Prettyplease)
        .wrap_unsafe_ops(true)
        .generate_cstr(true)
        .disable_name_namespacing()
        .array_pointers_in_arguments(true)
        .allowlist_function("(de|en)265_.*")
        .allowlist_type("(de|en)265_.*")
        .allowlist_var("LIBDE265_.*")
        .size_t_is_usize(true)
        .clang_args([
            "-fparse-all-comments",
            "-fretain-comments-from-system-headers",
        ])
        .default_enum_style(bindgen::EnumVariation::ModuleConsts)
        .prepend_enum_name(false)
        .clang_arg("-std=c++14")
        .clang_arg("-x")
        .clang_arg("c++")
        .parse_callbacks(Box::new(ParseFixer));

    for path in include_paths {
        base_builder = base_builder.clang_arg(format!("-I{}", path));
    }

    // Don't derive Copy and Clone for structures with pointers.
    for struct_name in ["de265_image", "en265_packet"] {
        base_builder = base_builder.no_copy(struct_name);
    }

    // Finish the builder and generate the bindings.
    let bindings = base_builder
        .clone()
        .generate()
        .expect("Unable to generate bindings");

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings.rs!");

    // Create linker_test.rs module for testing cases when not all
    // functions from *.h files are really available in libde265.
    let code = bindings.to_string();
    let mut func_names = Vec::new();
    for line in code.lines() {
        if !line.contains("pub fn de265_") && !line.contains("pub fn en265_") {
            continue;
        }
        let line = line.trim();
        let res: Vec<&str> = line.split(&[' ', '(']).collect();
        if res.len() > 3 {
            if let &["pub", "fn", name] = &res[..3] {
                func_names.push(name)
            }
        }
    }

    let mut result = vec![
        "use super::*;\n\n",
        "#[test]\n",
        "fn is_all_functions_exists_in_libde265() {\n",
        "    let fn_pointers = [\n",
    ];
    for name in func_names {
        result.push("        ");
        result.push(name);
        result.push(" as *const fn(),\n")
    }
    result.extend(vec![
        "    ];\n",
        "    for pointer in fn_pointers.iter() {\n",
        "        assert!(!pointer.is_null());\n",
        "    }\n",
        "}\n",
    ]);
    let test_module = result.join("");
    let test_path = out_path.join("linker_test.rs");
    std::fs::write(&test_path, test_module).expect("Couldn't write test module!");

    let bindings = base_builder
        .layout_tests(false)
        .generate()
        .expect("Unable to generate bindings without tests");
    bindings
        .write_to_file(out_path.join("bindings_wo_tests.rs"))
        .expect("Couldn't write bindings_wo_tests.rs!");
}

fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
    std::fs::create_dir_all(&dst)?;
    for entry in std::fs::read_dir(src)? {
        let entry = entry?;
        let ty = entry.file_type()?;
        if ty.is_dir() {
            copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
        } else {
            std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
        }
    }
    Ok(())
}