Replace imgui-sys-bindgen with a newly-added xtask setup

This commit is contained in:
Thom Chiovoloni 2020-12-06 18:51:28 -08:00
parent 60847630df
commit 4f5eba718f
11 changed files with 263 additions and 178 deletions

2
.cargo/config Normal file
View File

@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"

View File

@ -7,5 +7,5 @@ members = [
"imgui-winit-support",
"imgui-examples",
"imgui-gfx-examples",
"imgui-sys-bindgen",
"xtask",
]

View File

@ -1,16 +0,0 @@
[package]
name = "imgui-sys-bindgen"
version = "0.0.0"
authors = ["The imgui-rs Developers"]
edition = "2018"
description = "imgui-sys bindings updater"
homepage = "https://github.com/imgui-rs/imgui-rs"
repository = "https://github.com/imgui-rs/imgui-rs"
license = "MIT/Apache-2.0"
publish = false
[dependencies]
bindgen = "0.56"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"

View File

@ -1,123 +0,0 @@
use bindgen::{Bindings, EnumVariation, RustTarget};
use serde_derive::Deserialize;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::fs::{read_to_string, File};
use std::io::Read;
use std::path::Path;
#[derive(Debug)]
struct BindgenError;
impl fmt::Display for BindgenError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Failed to generate bindings")
}
}
impl Error for BindgenError {}
#[derive(Deserialize)]
struct StructsAndEnums {
enums: HashMap<String, serde_json::Value>,
structs: HashMap<String, serde_json::Value>,
}
#[derive(Deserialize)]
struct DefinitionArg {
#[serde(rename = "type")]
type_: String,
}
#[derive(Deserialize)]
struct Definition {
#[serde(rename = "argsT")]
args_t: Vec<DefinitionArg>,
ov_cimguiname: String,
}
#[derive(Debug, Clone)]
struct Whitelist {
enums: Vec<String>,
structs: Vec<String>,
definitions: Vec<String>,
}
fn parse_whitelist<R: Read>(
structs_and_enums: R,
definitions: R,
) -> Result<Whitelist, serde_json::Error> {
let StructsAndEnums { enums, structs } = serde_json::from_reader(structs_and_enums)?;
let enums = enums.keys().cloned().collect();
let structs = structs.keys().cloned().collect();
let definitions: HashMap<String, Vec<Definition>> = serde_json::from_reader(definitions)?;
let definitions = definitions
.into_iter()
.flat_map(|(_, defs)| defs.into_iter())
.filter_map(|d| {
let uses_va_list = d.args_t.iter().any(|a| a.type_ == "va_list");
if uses_va_list {
None
} else {
Some(d.ov_cimguiname)
}
})
.collect();
Ok(Whitelist {
enums,
structs,
definitions,
})
}
pub fn generate_bindings<P: AsRef<Path>>(
path: &P,
wasm_import_name: Option<String>,
) -> Result<Bindings, Box<dyn Error>> {
let path = path.as_ref();
let structs_and_enums = File::open(path.join("structs_and_enums.json"))?;
let definitions = File::open(path.join("definitions.json"))?;
let header = read_to_string(path.join("cimgui.h"))?;
let whitelist = parse_whitelist(structs_and_enums, definitions)?;
let mut builder = bindgen::builder()
.raw_line("#![allow(non_upper_case_globals)]")
.raw_line("#![allow(non_camel_case_types)]")
.raw_line("#![allow(non_snake_case)]")
.raw_line("#![allow(clippy::all)]")
.header_contents("cimgui.h", &header)
.rust_target(RustTarget::Stable_1_40)
.default_enum_style(EnumVariation::Consts)
.size_t_is_usize(true)
.prepend_enum_name(false)
.generate_comments(false)
.layout_tests(true)
.derive_copy(true)
.derive_debug(true)
.derive_default(true)
.derive_hash(true)
.derive_partialeq(true)
.derive_eq(true)
.impl_debug(true)
.rustfmt_bindings(true)
.clang_arg("-DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1");
if let Some(name) = wasm_import_name {
builder = builder.wasm_import_module_name(name);
}
for e in whitelist.structs {
builder = builder.whitelist_type(format!("^{}", e));
}
for e in whitelist.enums {
builder = builder.whitelist_type(format!("^{}", e));
}
for e in whitelist.definitions {
builder = builder.whitelist_function(format!("^{}", e));
}
let bindings = builder.generate().map_err(|_| BindgenError)?;
Ok(bindings)
}

View File

@ -1,28 +0,0 @@
use imgui_sys_bindgen::generate_bindings;
use std::env;
fn main() {
let cwd = env::current_dir().expect("Failed to read current directory");
let sys_path = cwd
.join("..")
.join("imgui-sys")
.canonicalize()
.expect("Failed to find imgui-sys directory");
let bindings = generate_bindings(&sys_path.join("third-party"), None)
.expect("Failed to generate bindings");
let output_path = sys_path.join("src").join("bindings.rs");
bindings
.write_to_file(&output_path)
.expect("Failed to write bindings");
let wasm_ffi_import_name = option_env!("IMGUI_RS_WASM_IMPORT_NAME")
.map(|s| s.to_string())
.or_else(|| Some("imgui-sys-v0".to_string()));
let wasm_bindings = generate_bindings(&sys_path.join("third-party"), wasm_ffi_import_name)
.expect("Failed to generate bindings");
let output_path = sys_path.join("src").join("wasm_bindings.rs");
wasm_bindings
.write_to_file(&output_path)
.expect("Failed to write wasm bindings");
}

View File

@ -1,9 +1,6 @@
/* automatically generated by rust-bindgen 0.55.1 */
/* automatically generated by rust-bindgen 0.56.0 */
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::all)]
#![allow(nonstandard_style, clippy::all)]
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]

View File

@ -1,9 +1,6 @@
/* automatically generated by rust-bindgen 0.55.1 */
/* automatically generated by rust-bindgen 0.56.0 */
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::all)]
#![allow(nonstandard_style, clippy::all)]
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]

12
xtask/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
# See https://github.com/matklad/cargo-xtask for more on this pattern.
[package]
name = "xtask"
version = "0.1.0"
authors = ["Thom Chiovoloni <chiovolonit@gmail.com>"]
edition = "2018"
publish = false
[dependencies]
anyhow = "1"
pico-args = "0.3"
smoljson = "0.1"

155
xtask/src/bindgen.rs Normal file
View File

@ -0,0 +1,155 @@
use anyhow::{anyhow, Context, Result};
use std::path::{Path, PathBuf};
pub struct GenBindings {
pub bindings_path: Option<String>,
pub output_path: Option<String>,
pub wasm_import_name: Option<String>,
}
impl GenBindings {
pub fn run(self) -> Result<()> {
let root = crate::project_root();
let bindings = self
.bindings_path
.map(PathBuf::from)
.unwrap_or_else(|| root.join("imgui-sys/third-party"));
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());
let types = get_types(&bindings.join("structs_and_enums.json"))?;
let funcs = get_definitions(&bindings.join("definitions.json"))?;
let header = bindings.join("cimgui.h");
generate_binding_file(&header, &output.join("bindings.rs"), &types, &funcs, None)?;
generate_binding_file(
&header,
&output.join("wasm_bindings.rs"),
&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",
"--with-derive-default",
"--with-derive-partialeq",
"--with-derive-eq",
"--with-derive-hash",
"--impl-debug",
];
cmd.args(a);
cmd.args(&["--blacklist-type", "__darwin_size_t"]);
cmd.args(&["--raw-line", "#![allow(nonstandard_style, clippy::all)]"]);
cmd.arg("--output").arg(output);
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 exacute bindgen")?;
if !status.success() {
anyhow!(
"Failed to execute bindgen: {}, see output for details",
status
);
}
eprintln!("Success [output = {}]", output.display());
Ok(())
}
// impl Deref for CountingCommand {
// type Target = std::process::Command;
// fn deref(&self) -> &Self::Target {
// &self.c
// }
// }
// impl std::ops::DerefMut for CountingCommand {
// fn deref_mut(&mut self) -> &mut Self::Target {
// &mut self.c
// }
// }

30
xtask/src/lib.rs Normal file
View File

@ -0,0 +1,30 @@
use std::{
env,
path::{Path, PathBuf},
};
pub mod bindgen;
pub fn project_root() -> PathBuf {
Path::new(
&env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()),
)
.ancestors()
.nth(1)
.unwrap()
.to_path_buf()
}
pub fn pushd(p: impl AsRef<Path>) -> std::io::Result<Pushd> {
let cwd = std::env::current_dir()?;
std::env::set_current_dir(p.as_ref())?;
Ok(Pushd(cwd))
}
pub struct Pushd(PathBuf);
impl Drop for Pushd {
fn drop(&mut self) {
if let Err(e) = std::env::set_current_dir(&self.0) {
eprintln!("warning: popd failed: {:?}", e);
}
}
}

59
xtask/src/main.rs Normal file
View File

@ -0,0 +1,59 @@
use anyhow::Result;
use pico_args::Arguments;
use xtask::bindgen::GenBindings;
use xtask::{project_root, pushd};
fn main() {
if let Err(e) = try_main() {
eprintln!("{}", e);
for cause in e.chain().skip(1) {
eprintln!("Caused By: {}", cause);
}
std::process::exit(-1);
}
}
const HELP: &str = "\
cargo xtask
Run custom build command.
USAGE:
cargo xtask <SUBCOMMAND>
SUBCOMMANDS:
bindgen - produce bindings using bindgen \
must have bindgen installed, may require unix \
as we pass `bindgen` very many CLI args
TODO:
run - run or list examples
lint-all - run clippy as we would in CI
test-all - run the tests we'd run in CI
";
fn try_main() -> Result<()> {
let _g = pushd(project_root())?;
let mut args = Arguments::from_env();
let subcommand = args.subcommand()?.unwrap_or_default();
match subcommand.as_str() {
"bindgen" => {
// none of these are required.
let cmd = GenBindings {
// defaults to <project>/imgui-sys/third-party
bindings_path: args.opt_value_from_str("--cimgui-dir")?,
// defaults to <project>/imgui-sys/src
output_path: args.opt_value_from_str("--output-dir")?,
// defaults to "imgui-sys-v0", but can be set in
// env("IMGUI_RS_WASM_IMPORT_NAME")
wasm_import_name: args.opt_value_from_str("--wasm-import-name")?,
};
args.finish()?;
cmd.run()?;
}
_ => {
eprintln!("{}", HELP);
}
}
Ok(())
}