diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..35049cb --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/Cargo.toml b/Cargo.toml index 28afb10..3c77bf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,5 @@ members = [ "imgui-winit-support", "imgui-examples", "imgui-gfx-examples", - "imgui-sys-bindgen", + "xtask", ] diff --git a/imgui-sys-bindgen/Cargo.toml b/imgui-sys-bindgen/Cargo.toml deleted file mode 100644 index 5e03045..0000000 --- a/imgui-sys-bindgen/Cargo.toml +++ /dev/null @@ -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" diff --git a/imgui-sys-bindgen/src/lib.rs b/imgui-sys-bindgen/src/lib.rs deleted file mode 100644 index 5404385..0000000 --- a/imgui-sys-bindgen/src/lib.rs +++ /dev/null @@ -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, - structs: HashMap, -} - -#[derive(Deserialize)] -struct DefinitionArg { - #[serde(rename = "type")] - type_: String, -} - -#[derive(Deserialize)] -struct Definition { - #[serde(rename = "argsT")] - args_t: Vec, - ov_cimguiname: String, -} - -#[derive(Debug, Clone)] -struct Whitelist { - enums: Vec, - structs: Vec, - definitions: Vec, -} - -fn parse_whitelist( - structs_and_enums: R, - definitions: R, -) -> Result { - 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> = 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>( - path: &P, - wasm_import_name: Option, -) -> Result> { - 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) -} diff --git a/imgui-sys-bindgen/src/main.rs b/imgui-sys-bindgen/src/main.rs deleted file mode 100644 index 0d9010d..0000000 --- a/imgui-sys-bindgen/src/main.rs +++ /dev/null @@ -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"); -} diff --git a/imgui-sys/src/bindings.rs b/imgui-sys/src/bindings.rs index f1ef1e9..2239053 100644 --- a/imgui-sys/src/bindings.rs +++ b/imgui-sys/src/bindings.rs @@ -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)] diff --git a/imgui-sys/src/wasm_bindings.rs b/imgui-sys/src/wasm_bindings.rs index 62523cf..ed66c84 100644 --- a/imgui-sys/src/wasm_bindings.rs +++ b/imgui-sys/src/wasm_bindings.rs @@ -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)] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..675f279 --- /dev/null +++ b/xtask/Cargo.toml @@ -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 "] +edition = "2018" +publish = false + +[dependencies] +anyhow = "1" +pico-args = "0.3" +smoljson = "0.1" diff --git a/xtask/src/bindgen.rs b/xtask/src/bindgen.rs new file mode 100644 index 0000000..d08f7b5 --- /dev/null +++ b/xtask/src/bindgen.rs @@ -0,0 +1,155 @@ +use anyhow::{anyhow, Context, Result}; +use std::path::{Path, PathBuf}; + +pub struct GenBindings { + pub bindings_path: Option, + pub output_path: Option, + pub wasm_import_name: Option, +} + +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> { + let types_txt = std::fs::read_to_string(structs_and_enums)?; + let types_val = types_txt + .parse::() + .map_err(|e| anyhow!("Failed to parse {}: {:?}", structs_and_enums.display(), e))?; + let mut types: Vec = 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> { + 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::() + .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 +// } +// } diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs new file mode 100644 index 0000000..cb0d688 --- /dev/null +++ b/xtask/src/lib.rs @@ -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) -> std::io::Result { + 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); + } + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..e6aa30f --- /dev/null +++ b/xtask/src/main.rs @@ -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 + +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 /imgui-sys/third-party + bindings_path: args.opt_value_from_str("--cimgui-dir")?, + // defaults to /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(()) +}