bevy-overlay-wrapper 0.1.4

CEF bindings for the Bevy Overlay plugin
use std::{
    fs::OpenOptions,
    io::{Cursor, Read, Seek, SeekFrom, Write},
    path::Path,
};

use regex::Regex;

fn main() {
    download_cef();
    compile_lib();
}

fn download_cef() {
    let out_dir = std::env::var("OUT_DIR").unwrap();
    let lib_dir = Path::new(out_dir.as_str()).join("libcef");

    if lib_dir.exists() {
        return;
    }

    let opt_level = "Debug".to_string();
    let target_os = std::env::var("CARGO_CFG_TARGET_OS");
    let target_os = target_os.as_ref().map(|x| &**x);
    let version = "119.4.7+g55e15c8+chromium-119.0.6045.199";

    let mut mappings: Vec<(Regex, String)> = Vec::new();
    let maps = match target_os {
        Ok("macos") => vec![
            (
                format!(
                    r"^[^/]+/{}/(Chromium Embedded Framework\.framework/.+)$",
                    opt_level
                ),
                "${1}",
            ),
            (r"^[^/]+/include/(.+\.h)".to_owned(), "include/${1}"),
            (r#"^[^/]+/libcef_dll/(.+)"#.to_owned(), "libcef_dll/${1}"),
            (r#"^[^/]+/cmake/(.+)"#.to_owned(), "cmake/${1}"),
            (format!(r"^[^/]+/{}/(cef_sandbox.a)$", opt_level), "${1}"),
        ],
        Ok("windows") => vec![
            (
                format!(r"^[^/]+/{}/([^/]+\.(lib|dll|bin))$", opt_level),
                "${1}",
            ),
            (
                format!(r"^[^/]+/{}/(swiftshader/[^/]+\.dll)$", opt_level),
                "${1}",
            ),
            (r"^[^/]+/Resources/icudtl\.dat$".to_owned(), "icudtl.dat"),
            (
                r"^[^/]+/Resources/((locales/)?[^/]+\.pak)$".to_owned(),
                "${1}",
            ),
        ],
        _ => panic!("unsupported platform"),
    };

    mappings.extend(maps.into_iter().map(|(src, dest)| {
        (
            Regex::new(&src).unwrap(),
            format!("{}/{}", lib_dir.display(), dest),
        )
    }));

    match target_os {
        Ok("macos") => {
            let reader: Box<dyn Read> = {
                let link = format!(
                    "https://cef-builds.spotifycdn.com/cef_binary_{version}_macosarm64.tar.bz2"
                );
                let response = ureq::get(link.as_str()).call();

                let mut buf = Vec::new();
                let _ = response.into_reader().read_to_end(&mut buf); //TODO
                Box::new(Cursor::new(buf))
            };

            let tar_file = bzip2::read::BzDecoder::new(reader);
            let mut archive = tar::Archive::new(tar_file);

            for mut entry in archive.entries().unwrap().flatten() {
                if let Ok(path) = entry.path() {
                    let path_string = path.to_string_lossy().to_string();
                    for (regex, destination) in mappings.iter() {
                        if regex.is_match(&path_string) {
                            let filename = regex
                                .replace(&path_string, destination.as_str())
                                .to_string();
                            let path = std::path::Path::new(&filename);
                            if let Some(folder) = path.parent() {
                                let _ = std::fs::create_dir_all(folder);
                            }
                            if !path.exists() {
                                let _ = entry.unpack(&filename); //TODO
                            }
                            break;
                        }
                    }
                }
            }
        }
        Ok("windows") => {
            let reader: Box<dyn Read> = {
                let link = format!(
                    "https://cef-builds.spotifycdn.com/cef_binary_{version}_windows64.tar.bz2"
                );
                let response = ureq::get(link.as_str()).call();

                let mut buf = Vec::new();
                let _ = response.into_reader().read_to_end(&mut buf); //TODO
                Box::new(Cursor::new(buf))
            };

            let tar_file = bzip2::read::BzDecoder::new(reader);
            let mut archive = tar::Archive::new(tar_file);

            for mut entry in archive.entries().unwrap().flatten() {
                if let Ok(path) = entry.path() {
                    let path_string = path.to_string_lossy().to_string();
                    for (regex, destination) in mappings.iter() {
                        if regex.is_match(&path_string) {
                            let filename = regex
                                .replace(&path_string, destination.as_str())
                                .to_string();
                            let path = std::path::Path::new(&filename);
                            if let Some(folder) = path.parent() {
                                let _ = std::fs::create_dir_all(folder);
                            }
                            if !path.exists() {
                                let _ = entry.unpack(&filename); //TODO
                            }
                            break;
                        }
                    }
                }
            }
        }
        _ => panic!("unsupported platform"),
    }
}

fn compile_lib() {
    let target_os = std::env::var("CARGO_CFG_TARGET_OS");
    let target_os = target_os.as_ref().map(|x| &**x);
    let out_dir = std::env::var("OUT_DIR").unwrap();
    let lib_dir = Path::new(out_dir.as_str()).join("libcef");

    match target_os {
        Ok("macos") => {
            remove_find_package_dep(&lib_dir.join("cmake").join("cef_macros.cmake"));
            remove_find_package_dep(&lib_dir.join("cmake").join("cef_variables.cmake"));

            let cmake_file = "
                cmake_minimum_required(VERSION 3.0)
                project(dll_wrapper)
                set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"./\")
				set(CEF_USE_SANDBOX TRUE)
                include(\"./cmake/cef_macros.cmake\")
                include(\"./cmake/cef_variables.cmake\")
                include_directories(
					\"./include/\"
                	\"./libcef_dll/\"
					\".\"
				)
				add_subdirectory(\"./libcef_dll/\")
                install(TARGETS libcef_dll_wrapper DESTINATION .)
				configure_file(cef_sandbox.a ../libcef_sandbox.a COPYONLY)
            "
            .to_string();
            std::fs::write(lib_dir.join("CMakeLists.txt"), cmake_file).unwrap();

            let dst = cmake::Config::new(&lib_dir).generator("Ninja").build();
            println!(
                "cargo:warning=compiled CEF DLL wrapper to {}",
                dst.display()
            );
            println!("cargo:rustc-link-search=native={}", dst.display());
            println!("cargo:rustc-link-lib=static=cef_dll_wrapper");
            // println!("cargo:rustc-link-arg=-lc++");
            println!("cargo:rustc-link-lib=static=cef_sandbox");
            println!("cargo:rustc-link-lib=sandbox");

            let framework_dir = lib_dir
                .join("Chromium Embedded Framework.framework")
                .canonicalize()
                .unwrap();
            assert!(framework_dir.exists());
            println!(
                "cargo:rustc-env=CEF_SYS_FRAMEWORK_PATH={}",
                framework_dir.display()
            );
        }
        Ok("windows") => {
            println!("cargo:rustc-link-lib=libcef");
            println!("cargo:rustc-link-search={}", lib_dir.display());
        }
        _ => panic!("unsupported OS"),
    }
}

fn remove_find_package_dep(path: &Path) {
    let mut cmake_macros_file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(path)
        .expect("cef_macros.cmake not found in expected location");
    let mut cmake_macros_str = String::new();
    cmake_macros_file
        .read_to_string(&mut cmake_macros_str)
        .unwrap();

    #[cfg(target_os = "windows")]
    {
        cmake_macros_str = cmake_macros_str.replace("\r\n", "\n");
    }

    let new_str = cmake_macros_str.replace(CEF_MACROS_REMOVE, "");
    cmake_macros_str = new_str;

    #[cfg(target_os = "windows")]
    {
        cmake_macros_str = cmake_macros_str.replace('\n', "\r\n");
    }

    cmake_macros_file.seek(SeekFrom::Start(0)).unwrap();
    cmake_macros_file
        .set_len(cmake_macros_str.len() as u64)
        .unwrap();
    cmake_macros_file
        .write_all(cmake_macros_str.as_bytes())
        .unwrap();
}

const CEF_MACROS_REMOVE: &str = "if(NOT DEFINED _CEF_ROOT_EXPLICIT)
  message(FATAL_ERROR \"Use find_package(CEF) to load this file.\")
endif()";