imgui-rs/xtask/src/bindgen.rs

152 lines
4.9 KiB
Rust

use crate::flags::Bindgen;
use anyhow::{anyhow, Context, Result};
use std::path::{Path, PathBuf};
impl Bindgen {
pub fn run(self) -> Result<()> {
let root = crate::project_root();
let output = self
.output_path
.map(PathBuf::from)
.unwrap_or_else(|| root.join("imgui-sys/src"));
let wasm_name = self
.wasm_import_name
.or_else(|| std::env::var("IMGUI_RS_WASM_IMPORT_NAME").ok())
.unwrap_or_else(|| "imgui-sys-v0".to_string());
for variant in ["master", "docking"] {
let cimgui_output = root.join(&format!("imgui-sys/third-party/imgui-{}", variant));
let types = get_types(&cimgui_output.join("structs_and_enums.json"))?;
let funcs = get_definitions(&cimgui_output.join("definitions.json"))?;
let header = cimgui_output.join("cimgui.h");
let output_name = if variant != "master" {
format!("{}_bindings.rs", variant)
} else {
"bindings.rs".into()
};
generate_binding_file(&header, &output.join(&output_name), &types, &funcs, None)?;
generate_binding_file(
&header,
&output.join(&format!("wasm_{}", &output_name)),
&types,
&funcs,
Some(&wasm_name),
)?;
}
Ok(())
}
}
fn get_types(structs_and_enums: &Path) -> Result<Vec<String>> {
let types_txt = std::fs::read_to_string(structs_and_enums)?;
let types_val = types_txt
.parse::<smoljson::ValOwn>()
.map_err(|e| anyhow!("Failed to parse {}: {:?}", structs_and_enums.display(), e))?;
let mut types: Vec<String> = types_val["enums"]
.as_object()
.ok_or_else(|| anyhow!("No `enums` in bindings file"))?
.keys()
.map(|k| format!("^{}", k))
.collect();
types.extend(
types_val["structs"]
.as_object()
.ok_or_else(|| anyhow!("No `structs` in bindings file"))?
.keys()
.map(|k| format!("^{}", k)),
);
Ok(types)
}
fn get_definitions(definitions: &Path) -> Result<Vec<String>> {
fn bad_arg_type(s: &str) -> bool {
s == "va_list" || s.starts_with("__")
}
let defs_txt = std::fs::read_to_string(definitions)?;
let defs_val = defs_txt
.parse::<smoljson::ValOwn>()
.map_err(|e| anyhow!("Failed to parse {}: {:?}", definitions.display(), e))?;
let definitions = defs_val
.into_object()
.ok_or_else(|| anyhow!("bad json data in defs file"))?;
let mut keep_defs = vec![];
for (name, def) in definitions {
let defs = def
.into_array()
.ok_or_else(|| anyhow!("def {} not an array", &name))?;
keep_defs.reserve(defs.len());
for func in defs {
let args = func["argsT"].as_array().unwrap();
if !args
.iter()
.any(|a| a["type"].as_str().map_or(false, bad_arg_type))
{
let name = func["ov_cimguiname"]
.as_str()
.ok_or_else(|| anyhow!("ov_cimguiname wasnt string..."))?;
keep_defs.push(format!("^{}", name));
}
}
}
Ok(keep_defs)
}
fn generate_binding_file(
header: &Path,
output: &Path,
types: &[String],
funcs: &[String],
wasm_import_mod: Option<&str>,
) -> Result<()> {
let mut cmd = std::process::Command::new("bindgen");
let a = &[
"--size_t-is-usize",
"--no-prepend-enum-name",
"--no-doc-comments",
// Layout tests aren't portable (they hardcode type sizes), and for
// our case they just serve to sanity check rustc's implementation of
// `#[repr(C)]`. If we bind directly to C++ ever, we should reconsider this.
"--no-layout-tests",
"--with-derive-default",
"--with-derive-partialeq",
"--with-derive-eq",
"--with-derive-hash",
"--impl-debug",
"--use-core",
];
cmd.args(a);
cmd.args(&["--blacklist-type", "__darwin_size_t"]);
cmd.args(&["--raw-line", "#![allow(nonstandard_style, clippy::all)]"]);
cmd.arg("--output").arg(output);
cmd.args(&["--ctypes-prefix", "cty"]);
if let Some(name) = wasm_import_mod {
cmd.args(&["--wasm-import-module-name", name]);
}
for t in types {
cmd.args(&["--whitelist-type", t]);
}
for f in funcs {
cmd.args(&["--whitelist-function", f]);
}
cmd.arg(header);
cmd.args(&["--", "-DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1"]);
eprintln!("Executing bindgen [output = {}]", output.display());
let status = cmd.status().context("Failed to execute bindgen")?;
if !status.success() {
anyhow!(
"Failed to execute bindgen: {}, see output for details",
status
);
}
eprintln!("Success [output = {}]", output.display());
Ok(())
}