From 74ae3a8b5b3c16841924358bc20fa0c32b6c0852 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Sun, 14 Jun 2020 10:02:32 +0200 Subject: [PATCH] Use bindgen output to generate code --- .gitignore | 2 - lvgl-codegen/Cargo.toml | 8 +-- lvgl-codegen/build.rs | 70 ------------------ lvgl-codegen/src/lib.rs | 151 ++++++++++++++++++++++++--------------- lvgl-codegen/src/main.rs | 33 --------- lvgl-sys/src/lib.rs | 4 ++ lvgl/Cargo.toml | 6 ++ lvgl/build.rs | 56 +++++++-------- lvgl/src/widgets/mod.rs | 2 +- 9 files changed, 132 insertions(+), 200 deletions(-) delete mode 100644 lvgl-codegen/build.rs delete mode 100644 lvgl-codegen/src/main.rs diff --git a/.gitignore b/.gitignore index 03e3fc8..803b70e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,3 @@ Cargo.lock examples/demo/target/ lvgl-sys/target/ lvgl/target/ -lvgl-sys/src/bindings.rs -lvgl/src/widgets/generated.rs diff --git a/lvgl-codegen/Cargo.toml b/lvgl-codegen/Cargo.toml index d01fbf9..489478b 100644 --- a/lvgl-codegen/Cargo.toml +++ b/lvgl-codegen/Cargo.toml @@ -5,19 +5,13 @@ description = "Code generation based on LVGL source code" authors = ["Rafael Caricio "] edition = "2018" license = "MIT" -publish = false -build = "build.rs" [dependencies] regex = "1.3.9" quote = "1.0.7" lazy_static = "1.4.0" -clang = { git = "https://github.com/rafaelcaricio/clang-rs" } itertools = "0.9.0" proc-macro2 = "1.0.18" Inflector = "0.11.4" -lang-c = "0.8.1" -syn = "1.0.31" +syn = { version = "1.0.31", features = ["full"]} -[build-dependencies] -cc = "1.0.50" diff --git a/lvgl-codegen/build.rs b/lvgl-codegen/build.rs deleted file mode 100644 index 8a0be01..0000000 --- a/lvgl-codegen/build.rs +++ /dev/null @@ -1,70 +0,0 @@ -use cc::Build; -use std::ffi::OsStr; -use std::process::{Command, Stdio}; -use std::{env, fs, path::Path, path::PathBuf}; - -static CONFIG_NAME: &str = "DEP_LV_CONFIG_PATH"; - -fn main() { - let project_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) - .canonicalize() - .unwrap(); - let lvgl_sys = project_dir.join("..").join("lvgl-sys"); - let vendor = lvgl_sys.join("vendor"); - let lvgl_top_path = vendor.join("lvgl"); - let lvgl_src_path = lvgl_top_path.join("src"); - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - - let lv_config_dir = { - let raw_path = env::var(CONFIG_NAME).unwrap_or_else(|_| { - panic!( - "The environment variable {} is required to be defined", - CONFIG_NAME - ); - }); - let conf_path = PathBuf::from(raw_path); - - if !conf_path.exists() { - panic!(format!( - "Directory referenced by {} needs to exist", - CONFIG_NAME - )); - } - if !conf_path.is_dir() { - panic!(format!("{} needs to be a directory", CONFIG_NAME)); - } - if !conf_path.join("lv_conf.h").exists() { - panic!(format!( - "Directory referenced by {} needs to contain a file called lv_conf.h", - CONFIG_NAME - )); - } - - println!( - "cargo:rerun-if-changed={}", - conf_path.join("lv_conf.h").to_str().unwrap() - ); - conf_path - }; - - let build = Build::new(); - let tool = build.get_compiler(); - let preprocessed = Command::new(tool.path().to_str().unwrap()) - .args(&[ - "-E", - "-std=c99", - "-DLV_CONF_INCLUDE_SIMPLE", - "-I", - lvgl_top_path.to_string_lossy().as_ref(), - "-I", - lvgl_src_path.to_string_lossy().as_ref(), - "-I", - lv_config_dir.to_string_lossy().as_ref(), - lvgl_top_path.join("lvgl.h").to_string_lossy().as_ref(), - ]) - .output() - .unwrap(); - - let content = String::from_utf8(preprocessed.stdout).unwrap(); - fs::write(out_path.join("lvgl_full.c"), content).unwrap(); -} diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index f4e308e..2a16a14 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -1,4 +1,3 @@ -use clang::{Clang, Entity, EntityKind, Index, Linkage, Type}; use inflector::cases::pascalcase::to_pascal_case; use lazy_static::lazy_static; use proc_macro2::{Ident, TokenStream}; @@ -7,6 +6,8 @@ use quote::quote; use regex::Regex; use std::collections::HashMap; use std::error::Error; +use syn::export::ToTokens; +use syn::{FnArg, ForeignItem, ForeignItemFn, Item, ReturnType}; type CGResult = Result>; @@ -39,7 +40,7 @@ pub trait Rusty { fn code(&self, parent: &Self::Parent) -> WrapperResult; } -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct LvWidget { name: String, methods: Vec, @@ -66,7 +67,7 @@ impl Rusty for LvWidget { } } -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct LvFunc { name: String, args: Vec, @@ -81,7 +82,7 @@ impl LvFunc { pub fn is_method(&self) -> bool { if self.args.len() > 0 { let first_arg = &self.args[0]; - return first_arg.typ.typ.contains("lv_obj_t"); + return first_arg.typ.literal_name.contains("lv_obj_t"); } false } @@ -209,32 +210,44 @@ impl Rusty for LvFunc { } } -impl From> for LvFunc { - fn from(entity: Entity) -> Self { - let result = entity.get_result_type().unwrap().get_display_name(); - let ret_type = match result.as_str() { - "void" => None, - _ => Some(LvType::new(result)), +impl From for LvFunc { + fn from(ffi: ForeignItemFn) -> Self { + let ret = match ffi.sig.output { + ReturnType::Default => None, + ReturnType::Type(_, typ) => Some(typ.into()), }; Self::new( - entity.get_name().unwrap(), - entity - .get_arguments() - .unwrap() + ffi.sig.ident.to_string(), + ffi.sig + .inputs .iter() - .map(|e| e.into()) + .filter_map(|fa| { + // Since we know those are foreign functions, we only care about typed arguments + if let FnArg::Typed(tya) = fa { + Some(tya) + } else { + None + } + }) + .map(|a| a.clone().into()) .collect::>(), - ret_type, + ret, ) } } -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct LvArg { name: String, typ: LvType, } +impl From for LvArg { + fn from(fa: syn::PatType) -> Self { + Self::new(fa.pat.to_token_stream().to_string(), fa.ty.into()) + } +} + impl LvArg { pub fn new(name: String, typ: LvType) -> Self { Self { name, typ } @@ -290,31 +303,33 @@ impl Rusty for LvArg { } } -impl From<&Entity<'_>> for LvArg { - fn from(entity: &Entity) -> Self { - Self::new( - entity.get_name().unwrap(), - entity.get_type().unwrap().into(), - ) - } -} - -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct LvType { - typ: String, + literal_name: String, + r_type: Option>, } impl LvType { - pub fn new(typ: String) -> Self { - Self { typ } + pub fn new(literal_name: String) -> Self { + Self { + literal_name, + r_type: None, + } + } + + pub fn from(r_type: Box) -> Self { + Self { + literal_name: r_type.to_token_stream().to_string(), + r_type: Some(r_type), + } } pub fn is_const(&self) -> bool { - self.typ.starts_with("const ") + self.literal_name.starts_with("const ") } pub fn is_str(&self) -> bool { - self.typ.ends_with("char *") + self.literal_name.ends_with("char *") } } @@ -322,7 +337,7 @@ impl Rusty for LvType { type Parent = LvArg; fn code(&self, _parent: &Self::Parent) -> WrapperResult { - match TYPE_MAPPINGS.get(self.typ.as_str()) { + match TYPE_MAPPINGS.get(self.literal_name.as_str()) { Some(name) => { let val = if self.is_str() { quote!(&str) @@ -339,9 +354,9 @@ impl Rusty for LvType { } } -impl From> for LvType { - fn from(ty: Type) -> Self { - Self::new(ty.get_display_name()) +impl From> for LvType { + fn from(t: Box) -> Self { + Self::from(t) } } @@ -351,8 +366,8 @@ pub struct CodeGen { } impl CodeGen { - pub fn new() -> CGResult { - let functions = Self::load_function_definitions()?; + pub fn from(code: &str) -> CGResult { + let functions = Self::load_func_defs(code)?; let widgets = Self::extract_widgets(&functions)?; Ok(Self { functions, widgets }) } @@ -405,23 +420,31 @@ impl CodeGen { .collect::>() } - pub fn load_function_definitions() -> CGResult> { - let clang = Clang::new()?; - let index = Index::new(&clang, false, false); - let tu = index - .parser(concat!(env!("OUT_DIR"), "/lvgl_full.c")) - .parse()?; - let entities = tu - .get_entity() - .get_children() + pub fn load_func_defs(bindgen_code: &str) -> CGResult> { + let ast: syn::File = syn::parse_str(bindgen_code)?; + let fns = ast + .items .into_iter() - .filter(|e| e.get_kind() == EntityKind::FunctionDecl) - .filter(|e| e.get_name().is_some()) - .filter(|e| e.get_linkage().unwrap() != Linkage::Internal) - .map(|e| e.into()) - .filter(|e: &LvFunc| e.name.starts_with(LIB_PREFIX)) - .collect::>(); - Ok(entities) + .filter_map(|e| { + if let Item::ForeignMod(fm) = e { + Some(fm) + } else { + None + } + }) + .flat_map(|e| { + e.items.into_iter().filter_map(|it| { + if let ForeignItem::Fn(f) = it { + Some(f) + } else { + None + } + }) + }) + .filter(|ff| ff.sig.ident.to_string().starts_with(LIB_PREFIX)) + .map(|ff| ff.into()) + .collect::>(); + Ok(fns) } pub fn get_function_names(&self) -> CGResult> { @@ -435,11 +458,21 @@ mod test { use quote::quote; #[test] - fn can_list_functions() { - let lv = CodeGen::new().unwrap(); - let func = String::from("lv_obj_create"); - let func_names = lv.get_function_names().unwrap(); - assert!(func_names.contains(&func)); + fn can_load_bindgen_fns() { + let bindgen_code = quote! { + extern "C" { + #[doc = " Return with the screen of an object"] + #[doc = " @param obj pointer to an object"] + #[doc = " @return pointer to a screen"] + pub fn lv_obj_get_screen(obj: *const lv_obj_t) -> *mut lv_obj_t; + } + }; + + let cg = CodeGen::load_func_defs(bindgen_code.to_string().as_str()).unwrap(); + + let ffn = cg.get(0).unwrap(); + assert_eq!(ffn.name, "lv_obj_get_screen"); + assert_eq!(ffn.args[0].name, "obj"); } #[test] diff --git a/lvgl-codegen/src/main.rs b/lvgl-codegen/src/main.rs deleted file mode 100644 index 156d069..0000000 --- a/lvgl-codegen/src/main.rs +++ /dev/null @@ -1,33 +0,0 @@ -use lvgl_codegen::{CodeGen, Rusty}; -use proc_macro2::TokenStream; -use quote::quote; -use std::fs::File; -use std::io::prelude::*; -use std::path::Path; - -fn main() { - let rs = Path::new(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../lvgl/src/widgets/generated.rs" - )); - - let codegen = CodeGen::new().unwrap(); - - let widgets_impl: Vec = codegen - .get_widgets() - .iter() - .flat_map(|w| w.code(&())) - .collect(); - - let code = quote! { - #(#widgets_impl)* - }; - - let mut file = File::create(rs).unwrap(); - writeln!( - file, - "/* automatically generated by lvgl-codegen */\n{}", - code - ) - .unwrap(); -} diff --git a/lvgl-sys/src/lib.rs b/lvgl-sys/src/lib.rs index 7940650..6d0c9f7 100644 --- a/lvgl-sys/src/lib.rs +++ b/lvgl-sys/src/lib.rs @@ -7,6 +7,10 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +pub fn _bindgen_raw_src() -> &'static str { + include_str!(concat!(env!("OUT_DIR"), "/bindings.rs")) +} + #[cfg(test)] mod tests { use super::*; diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index d7e1544..41778cf 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -18,3 +18,9 @@ embedded-graphics = "0.6.2" cstr_core = { version = "0.2.0", default-features = false, features = ["alloc"] } bitflags = "1.2.1" +[build-dependencies] +quote = "1.0.7" +proc-macro2 = "1.0.18" +lvgl-codegen = { version = "0.1.0", path = "../lvgl-codegen" } +lvgl-sys = { path = "../lvgl-sys", version = "0.2.0" } + diff --git a/lvgl/build.rs b/lvgl/build.rs index da14cdb..0630c6a 100644 --- a/lvgl/build.rs +++ b/lvgl/build.rs @@ -1,33 +1,33 @@ +use lvgl_codegen::{CodeGen, Rusty}; +use proc_macro2::TokenStream; +use quote::quote; use std::env; -use std::path::Path; -use std::process::Command; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; fn main() { - let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let widgets_rs_path = manifest_dir.join("src/widgets/generated.rs"); - let codegen_bin = manifest_dir - .join("..") - .join("target") - .join("debug") - .join("lvgl-codegen") - .canonicalize() - .unwrap(); - println!("cargo:rerun-if-changed={}", codegen_bin.to_string_lossy()); + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let rs = out_path.join("generated.rs"); - if env::var("LVGL_FORCE_CODEGEN").is_ok() || !widgets_rs_path.exists() { - println!("Generating `src/widgets/generated.rs`"); - let status = Command::new(codegen_bin) - .spawn() - .unwrap_or_else(|_| { - panic!( - "Code generation failed because no codegen executable was found. \ - Please run `cargo build --package lvgl-codegen` and then try again.", - ) - }) - .wait() - .unwrap(); - if !status.success() { - panic!("Code generation failed"); - } - } + let widgets_impl = lvgl_sys::_bindgen_raw_src(); + + let codegen = CodeGen::from(widgets_impl).unwrap(); + let widgets_impl: Vec = codegen + .get_widgets() + .iter() + .flat_map(|w| w.code(&())) + .collect(); + + let code = quote! { + #(#widgets_impl)* + }; + + let mut file = File::create(rs).unwrap(); + writeln!( + file, + "/* automatically generated by lvgl-codegen */\n{}", + code + ) + .unwrap(); } diff --git a/lvgl/src/widgets/mod.rs b/lvgl/src/widgets/mod.rs index d4845eb..f5721ff 100644 --- a/lvgl/src/widgets/mod.rs +++ b/lvgl/src/widgets/mod.rs @@ -3,7 +3,7 @@ mod bar; mod gauge; mod label; -include!("generated.rs"); +include!(concat!(env!("OUT_DIR"), "/generated.rs")); use crate::{NativeObject, Widget}; pub use arc::*;