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/README.md b/README.md index 6b1777c..2513cdd 100644 --- a/README.md +++ b/README.md @@ -62,14 +62,6 @@ $ git submodule init $ git submodule update ``` -Some parts of `lvgl-rs` are auto-generated. The package `lvgl-codegen` is responsible for the generated code. -When building from source, we need to first compile the `lvgl-codegen` tool. The `lvgl-codegen` tool is used -automatically in the [`lvgl` package build](./lvgl/build.rs). - -```bash -$ DEP_LV_CONFIG_PATH=`pwd`/examples/include cargo build --package lvgl-codegen -``` - Then run the `demo` example: ```shell diff --git a/lvgl-codegen/Cargo.toml b/lvgl-codegen/Cargo.toml index d01fbf9..adfed16 100644 --- a/lvgl-codegen/Cargo.toml +++ b/lvgl-codegen/Cargo.toml @@ -1,23 +1,17 @@ [package] name = "lvgl-codegen" -version = "0.1.0" +version = "0.3.0" 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..260761d 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>; @@ -14,12 +15,11 @@ const LIB_PREFIX: &str = "lv_"; lazy_static! { static ref TYPE_MAPPINGS: HashMap<&'static str, &'static str> = [ - ("uint16_t", "u16"), - ("int32_t", "i32"), - ("uint8_t", "u8"), + ("u16", "u16"), + ("i32", "i32"), + ("u8", "u8"), ("bool", "bool"), - ("_Bool", "bool"), - ("const char *", "&str"), + ("* const cty :: c_char", "&str"), ] .iter() .cloned() @@ -39,7 +39,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 +66,7 @@ impl Rusty for LvWidget { } } -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct LvFunc { name: String, args: Vec, @@ -81,7 +81,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 +209,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 +302,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("* const cty :: c_char") } } @@ -322,7 +336,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 +353,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 +365,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 +419,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 +457,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] @@ -494,12 +526,12 @@ mod test { #[test] fn generate_method_wrapper() { - // void lv_arc_set_bg_end_angle(lv_obj_t * arc, uint16_t end); + // pub fn lv_arc_set_bg_end_angle(arc: *mut lv_obj_t, end: u16); let arc_set_bg_end_angle = LvFunc::new( "lv_arc_set_bg_end_angle".to_string(), vec![ - LvArg::new("arc".to_string(), LvType::new("lv_obj_t *".to_string())), - LvArg::new("end".to_string(), LvType::new("uint16_t".to_string())), + LvArg::new("arc".to_string(), LvType::new("*mut lv_obj_t".to_string())), + LvArg::new("end".to_string(), LvType::new("u16".to_string())), ], None, ); @@ -523,15 +555,17 @@ mod test { #[test] fn generate_method_wrapper_for_str_types_as_argument() { - // void lv_label_set_text(lv_obj_t * label, const char * text) - let label_set_text = LvFunc::new( - "lv_label_set_text".to_string(), - vec![ - LvArg::new("label".to_string(), LvType::new("lv_obj_t *".to_string())), - LvArg::new("text".to_string(), LvType::new("const char *".to_string())), - ], - None, - ); + let bindgen_code = quote! { + extern "C" { + #[doc = " Set a new text for a label. Memory will be allocated to store the text by the label."] + #[doc = " @param label pointer to a label object"] + #[doc = " @param text '\\0' terminated character string. NULL to refresh with the current text."] + pub fn lv_label_set_text(label: *mut lv_obj_t, text: *const cty::c_char); + } + }; + let cg = CodeGen::load_func_defs(bindgen_code.to_string().as_str()).unwrap(); + + let label_set_text = cg.get(0).unwrap().clone(); let parent_widget = LvWidget { name: "label".to_string(), methods: vec![], @@ -575,17 +609,17 @@ mod test { #[test] fn generate_widget_with_constructor_code() { - // lv_obj_t * lv_arc_create(lv_obj_t * par, const lv_obj_t * copy); + // pub fn lv_arc_create(par: *mut lv_obj_t, copy: *const lv_obj_t) -> *mut lv_obj_t; let arc_create = LvFunc::new( "lv_arc_create".to_string(), vec![ - LvArg::new("par".to_string(), LvType::new("lv_obj_t *".to_string())), + LvArg::new("par".to_string(), LvType::new("*mut lv_obj_t".to_string())), LvArg::new( "copy".to_string(), - LvType::new("const lv_obj_t *".to_string()), + LvType::new("*const lv_obj_t".to_string()), ), ], - Some(LvType::new("lv_obj_t *".to_string())), + Some(LvType::new("*mut lv_obj_t".to_string())), ); let arc_widget = LvWidget { 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/Cargo.toml b/lvgl-sys/Cargo.toml index b3a9479..6e1d72f 100644 --- a/lvgl-sys/Cargo.toml +++ b/lvgl-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lvgl-sys" description = "Raw bindings to the LittlevGL C library." -version = "0.2.0" +version = "0.3.0" authors = ["Rafael Caricio "] edition = "2018" license = "MIT" 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..b6c250e 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lvgl" description = "LittlevGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash)." -version = "0.2.1" +version = "0.3.0" authors = ["Rafael Caricio "] edition = "2018" repository = "https://github.com/rafaelcaricio/lvgl-rs" @@ -12,9 +12,15 @@ keywords = ["littlevgl", "lvgl", "graphical_interfaces"] include = ["Cargo.toml", "src/**/*", "src/widgets/generated.rs"] [dependencies] -lvgl-sys = { path = "../lvgl-sys", version = "0.2.0" } +lvgl-sys = { version = "0.3.0", path = "../lvgl-sys" } cty = "0.2.1" 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.3.0", path = "../lvgl-codegen" } +lvgl-sys = { version = "0.3.0", path = "../lvgl-sys" } + 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::*;