windows-bindgen 0.52.0

Windows metadata compiler
Documentation
mod cfg;
mod classes;
mod com_methods;
mod constants;
mod delegates;
mod enums;
mod extensions;
mod functions;
mod handles;
mod implements;
mod interfaces;
mod iterators;
mod method_names;
mod standalone;
mod structs;
mod try_format;
mod winrt_methods;
mod writer;
use super::*;
use crate::{Error, Result, Tree};
use cfg::*;
use rayon::prelude::*;

pub fn from_reader(reader: &'static metadata::Reader, mut config: std::collections::BTreeMap<&str, &str>, output: &str) -> Result<()> {
    let mut writer = Writer::new(reader, output);
    writer.package = config.remove("package").is_some();
    writer.flatten = config.remove("flatten").is_some();
    writer.std = config.remove("std").is_some();
    writer.sys = writer.std || config.remove("sys").is_some();
    writer.implement = config.remove("implement").is_some();
    writer.minimal = config.remove("minimal").is_some();
    writer.no_inner_attributes = config.remove("no-inner-attributes").is_some();

    if writer.package && writer.flatten {
        return Err(Error::new("cannot combine `package` and `flatten` configuration values"));
    }

    if writer.implement && writer.sys {
        return Err(Error::new("cannot combine `implement` and `sys` configuration values"));
    }

    if let Some((key, _)) = config.first_key_value() {
        return Err(Error::new(&format!("invalid configuration value `{key}`")));
    }

    if writer.package {
        gen_package(&writer)
    } else {
        gen_file(&writer)
    }
}

fn gen_file(writer: &Writer) -> Result<()> {
    // TODO: harmonize this output code so we don't need these two wildly differnt code paths
    // there should be a simple way to generate the with or without namespaces.

    if writer.flatten {
        let tokens = standalone::standalone_imp(writer);
        crate::write_to_file(&writer.output, try_format(writer, &tokens))
    } else {
        let mut tokens = String::new();
        let root = Tree::new(writer.reader);

        for tree in root.nested.values() {
            tokens.push_str(&namespace(writer, tree));
        }

        crate::write_to_file(&writer.output, try_format(writer, &tokens))
    }
}

fn gen_package(writer: &Writer) -> Result<()> {
    let directory = crate::directory(&writer.output);
    let root = Tree::new(writer.reader);
    let mut root_len = 0;

    for tree in root.nested.values() {
        root_len = tree.namespace.len();
        _ = std::fs::remove_dir_all(format!("{directory}/{}", tree.namespace));
    }

    let trees = root.flatten();

    trees.par_iter().try_for_each(|tree| {
        let directory = format!("{directory}/{}", tree.namespace.replace('.', "/"));
        let mut tokens = namespace(writer, tree);

        let tokens_impl = if !writer.sys { namespace_impl(writer, tree) } else { String::new() };

        if !writer.sys && !tokens_impl.is_empty() {
            tokens.push_str("#[cfg(feature = \"implement\")]\n::core::include!(\"impl.rs\");\n");
        }

        let output = format!("{directory}/mod.rs");
        crate::write_to_file(&output, try_format(writer, &tokens))?;

        if !writer.sys && !tokens_impl.is_empty() {
            let output = format!("{directory}/impl.rs");
            crate::write_to_file(&output, try_format(writer, &tokens_impl))?;
        }

        Ok::<(), Error>(())
    })?;

    let cargo_toml = format!("{}/Cargo.toml", crate::directory(directory));
    let mut toml = String::new();

    for line in crate::read_file_lines(&cargo_toml)? {
        toml.push_str(&line);
        toml.push('\n');

        if line == "# generated features" {
            break;
        }
    }

    for tree in trees.iter().skip(1) {
        let feature = tree.namespace[root_len + 1..].replace('.', "_");

        if let Some(pos) = feature.rfind('_') {
            let dependency = &feature[..pos];

            toml.push_str(&format!("{feature} = [\"{dependency}\"]\n"));
        } else {
            toml.push_str(&format!("{feature} = []\n"));
        }
    }

    crate::write_to_file(&cargo_toml, toml)
}

use crate::tokens::*;
use metadata::*;
use method_names::*;
use std::collections::*;
use std::fmt::Write;
use try_format::*;
use writer::*;

fn namespace(writer: &Writer, tree: &Tree) -> String {
    let writer = &mut writer.clone();
    writer.namespace = tree.namespace;
    let mut tokens = TokenStream::new();

    for (name, tree) in &tree.nested {
        let name = to_ident(name);
        let feature = tree.namespace[tree.namespace.find('.').unwrap() + 1..].replace('.', "_");
        let doc = format!(r#"Required features: `\"{feature}\"`"#);
        if writer.package {
            tokens.combine(&quote! {
                #[cfg(feature = #feature)]
                #[doc = #doc]
                pub mod #name;
            });
        } else {
            tokens.combine(&quote! { pub mod #name });
            tokens.push_str("{");
            tokens.push_str(&namespace(writer, tree));
            tokens.push_str("}");
        }
    }

    let mut functions = BTreeMap::<&str, TokenStream>::new();
    let mut types = BTreeMap::<TypeKind, BTreeMap<&str, TokenStream>>::new();

    for item in writer.reader.namespace_items(writer.namespace) {
        match item {
            Item::Type(def) => {
                let type_name = def.type_name();
                if REMAP_TYPES.iter().any(|(x, _)| x == &type_name) {
                    continue;
                }
                if CORE_TYPES.iter().any(|(x, _)| x == &type_name) {
                    continue;
                }
                let name = type_name.name;
                let kind = def.kind();
                match kind {
                    TypeKind::Class => {
                        if def.flags().contains(TypeAttributes::WindowsRuntime) {
                            types.entry(kind).or_default().insert(name, classes::writer(writer, def));
                        }
                    }
                    TypeKind::Interface => types.entry(kind).or_default().entry(name).or_default().combine(&interfaces::writer(writer, def)),
                    TypeKind::Enum => types.entry(kind).or_default().entry(name).or_default().combine(&enums::writer(writer, def)),
                    TypeKind::Struct => {
                        if def.fields().next().is_none() {
                            if let Some(guid) = type_def_guid(def) {
                                let ident = to_ident(name);
                                let value = writer.guid(&guid);
                                let guid = writer.type_name(&Type::GUID);
                                let cfg = type_def_cfg(def, &[]);
                                let doc = writer.cfg_doc(&cfg);
                                let constant = quote! {
                                    #doc
                                    pub const #ident: #guid = #value;
                                };
                                types.entry(TypeKind::Class).or_default().entry(name).or_default().combine(&constant);
                                continue;
                            }
                        }
                        types.entry(kind).or_default().entry(name).or_default().combine(&structs::writer(writer, def));
                    }
                    TypeKind::Delegate => types.entry(kind).or_default().entry(name).or_default().combine(&delegates::writer(writer, def)),
                }
            }
            Item::Fn(def, namespace) => {
                let name = def.name();
                functions.entry(name).or_default().combine(&functions::writer(writer, namespace, def));
            }
            Item::Const(def) => {
                let name = def.name();
                types.entry(TypeKind::Class).or_default().entry(name).or_default().combine(&constants::writer(writer, def));
            }
        }
    }

    for function in functions.values() {
        tokens.combine(function);
    }

    for ty in types.values().flat_map(|v| v.values()) {
        tokens.combine(ty);
    }

    tokens.combine(&extensions::gen_mod(writer, tree.namespace));

    if writer.implement {
        tokens.push_str(&namespace_impl(writer, tree));
    }

    tokens.into_string()
}

fn namespace_impl(writer: &Writer, tree: &Tree) -> String {
    let writer = &mut writer.clone();
    writer.namespace = tree.namespace;
    let mut types = BTreeMap::<&str, TokenStream>::new();

    for item in writer.reader.namespace_items(tree.namespace) {
        if let Item::Type(def) = item {
            let type_name = def.type_name();
            if CORE_TYPES.iter().any(|(x, _)| x == &type_name) {
                continue;
            }
            if def.kind() != TypeKind::Interface {
                continue;
            }
            let tokens = implements::writer(writer, def);

            if !tokens.is_empty() {
                types.insert(type_name.name, tokens);
            }
        }
    }

    let types = types.values();

    let mut tokens = quote! {
        #(#types)*
    };

    tokens.combine(&extensions::gen_impl(tree.namespace));
    tokens.into_string()
}

/// Expand a possibly empty generics list with a new generic
fn expand_generics(generics: TokenStream, new: TokenStream) -> TokenStream {
    if generics.is_empty() {
        quote!(#new)
    } else {
        quote!(#generics, #new)
    }
}

/// Expand a possibly emppty where clause with a new generic constraint
fn expand_where_clause(where_clause: TokenStream, generic: TokenStream) -> TokenStream {
    if where_clause.is_empty() {
        quote!(where #generic)
    } else {
        quote!(#where_clause #generic)
    }
}