webview2-com-sys 0.8.0

Bindings generated with the windows crate for the WebView2 COM APIs
Documentation
fn main() -> webview2_nuget::Result<()> {
    if let Ok(package_root) = webview2_nuget::install() {
        webview2_nuget::update_windows(&package_root)?;
        webview2_nuget::update_browser_version(&package_root)?;
        webview2_nuget::update_callback_interfaces(&package_root)?;
    }

    windows::runtime::build! {
        Microsoft::Web::WebView2::Win32::*,
    };

    Ok(())
}

#[macro_use]
extern crate thiserror;

mod webview2_nuget {
    use std::{
        convert::From,
        env, fs,
        io::{self, Read, Write},
        path::{Path, PathBuf},
        process::Command,
    };

    use regex::Regex;

    include!("./src/browser_version.rs");
    include!("./src/callback_interfaces.rs");

    const WEBVIEW2_NAME: &str = "Microsoft.Web.WebView2";
    const WEBVIEW2_VERSION: &str = "1.0.1054.31";

    #[cfg(not(windows))]
    pub fn install() -> Result<PathBuf> {
        get_manifest_dir()
    }

    #[cfg(windows)]
    pub fn install() -> Result<PathBuf> {
        let out_dir = get_out_dir()?;
        let install_root = match out_dir.to_str() {
            Some(path) => path,
            None => return Err(Error::MissingPath(out_dir)),
        };

        let mut package_root = out_dir.clone();
        package_root.push(format!("{}.{}", WEBVIEW2_NAME, WEBVIEW2_VERSION));

        if !check_nuget_dir(install_root)? {
            let mut nuget_path = get_manifest_dir()?;
            nuget_path.push("tools");
            nuget_path.push("nuget.exe");

            let nuget_tool = match nuget_path.to_str() {
                Some(path) => path,
                None => return Err(Error::MissingPath(nuget_path)),
            };

            Command::new(nuget_tool)
                .args(&[
                    "install",
                    WEBVIEW2_NAME,
                    "-OutputDirectory",
                    install_root,
                    "-Version",
                    WEBVIEW2_VERSION,
                ])
                .output()?;

            if !check_nuget_dir(install_root)? {
                return Err(Error::MissingPath(package_root));
            }
        }

        Ok(package_root)
    }

    fn get_out_dir() -> Result<PathBuf> {
        Ok(PathBuf::from(env::var("OUT_DIR")?))
    }

    fn get_manifest_dir() -> Result<PathBuf> {
        Ok(PathBuf::from(env::var("CARGO_MANIFEST_DIR")?))
    }

    #[cfg(windows)]
    fn check_nuget_dir(install_root: &str) -> Result<bool> {
        let nuget_path = format!("{}.{}", WEBVIEW2_NAME, WEBVIEW2_VERSION);
        let mut dir_iter = fs::read_dir(install_root)?.filter(|dir| match dir {
            Ok(dir) => match dir.file_type() {
                Ok(file_type) => {
                    file_type.is_dir()
                        && match dir.file_name().to_str() {
                            Some(name) => name.eq_ignore_ascii_case(&nuget_path),
                            None => false,
                        }
                }
                Err(_) => false,
            },
            Err(_) => false,
        });
        Ok(dir_iter.next().is_some())
    }

    #[cfg(not(windows))]
    pub fn update_windows(_: &Path) -> Result<()> {
        Ok(())
    }

    #[cfg(windows)]
    pub fn update_windows(package_root: &Path) -> Result<()> {
        const WEBVIEW2_STATIC_LIB: &str = "WebView2LoaderStatic.lib";
        const WEBVIEW2_TARGETS: &[&str] = &["arm64", "x64", "x86"];

        let mut workspace_windows_dir = get_workspace_dir()?;
        workspace_windows_dir.push(".windows");

        let mut bindings_windows_dir = get_manifest_dir()?;
        bindings_windows_dir.push(".windows");

        let mut native_dir = package_root.to_path_buf();
        native_dir.push("build");
        native_dir.push("native");
        for target in WEBVIEW2_TARGETS {
            let mut lib_src = native_dir.clone();
            lib_src.push(target);
            lib_src.push(WEBVIEW2_STATIC_LIB);

            let mut lib_dest = workspace_windows_dir.clone();
            lib_dest.push(target);
            lib_dest.push(WEBVIEW2_STATIC_LIB);
            eprintln!("Copy from {:?} -> {:?}", lib_src, lib_dest);
            fs::copy(lib_src.as_path(), lib_dest.as_path())?;

            let mut lib_dest = bindings_windows_dir.clone();
            lib_dest.push(target);
            lib_dest.push(WEBVIEW2_STATIC_LIB);
            eprintln!("Copy from {:?} -> {:?}", lib_src, lib_dest);
            fs::copy(lib_src.as_path(), lib_dest.as_path())?;
        }

        Ok(())
    }

    fn get_workspace_dir() -> Result<PathBuf> {
        use serde::Deserialize;

        #[derive(Deserialize)]
        struct CargoMetadata {
            workspace_root: String,
        }

        let output = Command::new(env::var("CARGO")?)
            .args(&["metadata", "--format-version=1", "--no-deps", "--offline"])
            .output()?;

        let metadata: CargoMetadata = serde_json::from_slice(&output.stdout)?;

        Ok(PathBuf::from(metadata.workspace_root))
    }

    #[cfg(not(windows))]
    pub fn update_browser_version(_: &PathBuf) -> Result<bool> {
        Ok(false)
    }

    #[cfg(windows)]
    pub fn update_browser_version(package_root: &Path) -> Result<bool> {
        let version = get_target_broweser_version(package_root)?;
        if version == CORE_WEBVIEW_TARGET_PRODUCT_VERSION {
            return Ok(false);
        }

        let mut source_path = get_manifest_dir()?;
        source_path.push("src");
        source_path.push("browser_version.rs");
        let mut source_file = fs::File::create(source_path)?;
        writeln!(
            source_file,
            r#"pub const CORE_WEBVIEW_TARGET_PRODUCT_VERSION: &str = "{}";"#,
            version
        )?;
        Ok(true)
    }

    #[cfg(windows)]
    fn get_target_broweser_version(package_root: &Path) -> Result<String> {
        let mut include_path = package_root.to_path_buf();
        include_path.push("build");
        include_path.push("native");
        include_path.push("include");
        include_path.push("WebView2EnvironmentOptions.h");
        let mut contents = String::new();
        fs::File::open(include_path)?.read_to_string(&mut contents)?;
        let pattern =
            Regex::new(r#"^\s*#define\s+CORE_WEBVIEW_TARGET_PRODUCT_VERSION\s+L"(.*)"\s*$"#)?;
        match contents
            .lines()
            .filter_map(|line| pattern.captures(line))
            .filter_map(|captures| captures.get(1))
            .next()
        {
            Some(capture) => Ok(capture.as_str().into()),
            None => Err(Error::MissingVersion),
        }
    }

    #[cfg(not(windows))]
    pub fn update_callback_interfaces(_: &PathBuf) -> Result<bool> {
        Ok(false)
    }

    #[cfg(windows)]
    pub fn update_callback_interfaces(package_root: &Path) -> Result<bool> {
        let interfaces = get_callback_interfaces(package_root)?;
        let declared = all_declared().into_iter().map(String::from).collect();
        if interfaces == declared {
            return Ok(false);
        }

        let mut source_path = get_manifest_dir()?;
        source_path.push("src");
        source_path.push("callback_interfaces.rs");
        let mut source_file = fs::File::create(source_path)?;
        writeln!(
            source_file,
            r#"use std::collections::BTreeSet;

/// Generate a list of all `ICoreWebView2...Handler` interfaces declared in `WebView2.h`. This is
/// for testing purposes to make sure they are all covered in [callback.rs](../../src/callback.rs).
pub fn all_declared() -> BTreeSet<&'static str> {{
    let mut interfaces = BTreeSet::new();
"#,
        )?;
        for interface in interfaces {
            writeln!(source_file, r#"    interfaces.insert("{}");"#, interface)?;
        }
        writeln!(
            source_file,
            r#"
    interfaces
}}"#
        )?;
        Ok(true)
    }

    #[cfg(windows)]
    fn get_callback_interfaces(package_root: &Path) -> Result<BTreeSet<String>> {
        let mut include_path = package_root.to_path_buf();
        include_path.push("build");
        include_path.push("native");
        include_path.push("include");
        include_path.push("WebView2.h");
        let mut contents = String::new();
        fs::File::open(include_path)?.read_to_string(&mut contents)?;
        let pattern =
            Regex::new(r#"^\s*typedef\s+interface\s+(ICoreWebView2[A-Za-z0-9]+Handler)\s+"#)?;
        let interfaces: BTreeSet<String> = contents
            .lines()
            .filter_map(|line| pattern.captures(line))
            .filter_map(|captures| captures.get(1))
            .map(|match_1| String::from(match_1.as_str()))
            .collect();
        if interfaces.is_empty() {
            Err(Error::MissingTypedef)
        } else {
            Ok(interfaces)
        }
    }

    #[derive(Debug, Error)]
    pub enum Error {
        #[error(transparent)]
        Io(#[from] io::Error),
        #[error(transparent)]
        Var(#[from] env::VarError),
        #[error(transparent)]
        Json(#[from] serde_json::Error),
        #[error(transparent)]
        Regex(#[from] regex::Error),
        #[error("Missing Version")]
        MissingVersion,
        #[error("Missing Typedef")]
        MissingTypedef,
        #[error("Missing Path")]
        MissingPath(PathBuf),
    }

    pub type Result<T> = std::result::Result<T, Error>;
}