Use bindgen output to generate code #22
9 changed files with 132 additions and 200 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,5 +12,3 @@ Cargo.lock
|
||||||
examples/demo/target/
|
examples/demo/target/
|
||||||
lvgl-sys/target/
|
lvgl-sys/target/
|
||||||
lvgl/target/
|
lvgl/target/
|
||||||
lvgl-sys/src/bindings.rs
|
|
||||||
lvgl/src/widgets/generated.rs
|
|
||||||
|
|
|
@ -5,19 +5,13 @@ description = "Code generation based on LVGL source code"
|
||||||
authors = ["Rafael Caricio <rafael@caricio.com>"]
|
authors = ["Rafael Caricio <rafael@caricio.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
publish = false
|
|
||||||
build = "build.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "1.3.9"
|
regex = "1.3.9"
|
||||||
quote = "1.0.7"
|
quote = "1.0.7"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
clang = { git = "https://github.com/rafaelcaricio/clang-rs" }
|
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
proc-macro2 = "1.0.18"
|
proc-macro2 = "1.0.18"
|
||||||
Inflector = "0.11.4"
|
Inflector = "0.11.4"
|
||||||
lang-c = "0.8.1"
|
syn = { version = "1.0.31", features = ["full"]}
|
||||||
syn = "1.0.31"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
cc = "1.0.50"
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
use clang::{Clang, Entity, EntityKind, Index, Linkage, Type};
|
|
||||||
use inflector::cases::pascalcase::to_pascal_case;
|
use inflector::cases::pascalcase::to_pascal_case;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use proc_macro2::{Ident, TokenStream};
|
use proc_macro2::{Ident, TokenStream};
|
||||||
|
@ -7,6 +6,8 @@ use quote::quote;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use syn::export::ToTokens;
|
||||||
|
use syn::{FnArg, ForeignItem, ForeignItemFn, Item, ReturnType};
|
||||||
|
|
||||||
type CGResult<T> = Result<T, Box<dyn Error>>;
|
type CGResult<T> = Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ pub trait Rusty {
|
||||||
fn code(&self, parent: &Self::Parent) -> WrapperResult<TokenStream>;
|
fn code(&self, parent: &Self::Parent) -> WrapperResult<TokenStream>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone)]
|
||||||
pub struct LvWidget {
|
pub struct LvWidget {
|
||||||
name: String,
|
name: String,
|
||||||
methods: Vec<LvFunc>,
|
methods: Vec<LvFunc>,
|
||||||
|
@ -66,7 +67,7 @@ impl Rusty for LvWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone)]
|
||||||
pub struct LvFunc {
|
pub struct LvFunc {
|
||||||
name: String,
|
name: String,
|
||||||
args: Vec<LvArg>,
|
args: Vec<LvArg>,
|
||||||
|
@ -81,7 +82,7 @@ impl LvFunc {
|
||||||
pub fn is_method(&self) -> bool {
|
pub fn is_method(&self) -> bool {
|
||||||
if self.args.len() > 0 {
|
if self.args.len() > 0 {
|
||||||
let first_arg = &self.args[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
|
false
|
||||||
}
|
}
|
||||||
|
@ -209,32 +210,44 @@ impl Rusty for LvFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Entity<'_>> for LvFunc {
|
impl From<ForeignItemFn> for LvFunc {
|
||||||
fn from(entity: Entity) -> Self {
|
fn from(ffi: ForeignItemFn) -> Self {
|
||||||
let result = entity.get_result_type().unwrap().get_display_name();
|
let ret = match ffi.sig.output {
|
||||||
let ret_type = match result.as_str() {
|
ReturnType::Default => None,
|
||||||
"void" => None,
|
ReturnType::Type(_, typ) => Some(typ.into()),
|
||||||
_ => Some(LvType::new(result)),
|
|
||||||
};
|
};
|
||||||
Self::new(
|
Self::new(
|
||||||
entity.get_name().unwrap(),
|
ffi.sig.ident.to_string(),
|
||||||
entity
|
ffi.sig
|
||||||
.get_arguments()
|
.inputs
|
||||||
.unwrap()
|
|
||||||
.iter()
|
.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::<Vec<LvArg>>(),
|
.collect::<Vec<LvArg>>(),
|
||||||
ret_type,
|
ret,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone)]
|
||||||
pub struct LvArg {
|
pub struct LvArg {
|
||||||
name: String,
|
name: String,
|
||||||
typ: LvType,
|
typ: LvType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<syn::PatType> for LvArg {
|
||||||
|
fn from(fa: syn::PatType) -> Self {
|
||||||
|
Self::new(fa.pat.to_token_stream().to_string(), fa.ty.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LvArg {
|
impl LvArg {
|
||||||
pub fn new(name: String, typ: LvType) -> Self {
|
pub fn new(name: String, typ: LvType) -> Self {
|
||||||
Self { name, typ }
|
Self { name, typ }
|
||||||
|
@ -290,31 +303,33 @@ impl Rusty for LvArg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Entity<'_>> for LvArg {
|
#[derive(Clone)]
|
||||||
fn from(entity: &Entity) -> Self {
|
|
||||||
Self::new(
|
|
||||||
entity.get_name().unwrap(),
|
|
||||||
entity.get_type().unwrap().into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
|
||||||
pub struct LvType {
|
pub struct LvType {
|
||||||
typ: String,
|
literal_name: String,
|
||||||
|
r_type: Option<Box<syn::Type>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LvType {
|
impl LvType {
|
||||||
pub fn new(typ: String) -> Self {
|
pub fn new(literal_name: String) -> Self {
|
||||||
Self { typ }
|
Self {
|
||||||
|
literal_name,
|
||||||
|
r_type: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(r_type: Box<syn::Type>) -> Self {
|
||||||
|
Self {
|
||||||
|
literal_name: r_type.to_token_stream().to_string(),
|
||||||
|
r_type: Some(r_type),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_const(&self) -> bool {
|
pub fn is_const(&self) -> bool {
|
||||||
self.typ.starts_with("const ")
|
self.literal_name.starts_with("const ")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_str(&self) -> bool {
|
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;
|
type Parent = LvArg;
|
||||||
|
|
||||||
fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
|
fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
|
||||||
match TYPE_MAPPINGS.get(self.typ.as_str()) {
|
match TYPE_MAPPINGS.get(self.literal_name.as_str()) {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let val = if self.is_str() {
|
let val = if self.is_str() {
|
||||||
quote!(&str)
|
quote!(&str)
|
||||||
|
@ -339,9 +354,9 @@ impl Rusty for LvType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Type<'_>> for LvType {
|
impl From<Box<syn::Type>> for LvType {
|
||||||
fn from(ty: Type) -> Self {
|
fn from(t: Box<syn::Type>) -> Self {
|
||||||
Self::new(ty.get_display_name())
|
Self::from(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,8 +366,8 @@ pub struct CodeGen {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeGen {
|
impl CodeGen {
|
||||||
pub fn new() -> CGResult<Self> {
|
pub fn from(code: &str) -> CGResult<Self> {
|
||||||
let functions = Self::load_function_definitions()?;
|
let functions = Self::load_func_defs(code)?;
|
||||||
let widgets = Self::extract_widgets(&functions)?;
|
let widgets = Self::extract_widgets(&functions)?;
|
||||||
Ok(Self { functions, widgets })
|
Ok(Self { functions, widgets })
|
||||||
}
|
}
|
||||||
|
@ -405,23 +420,31 @@ impl CodeGen {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_function_definitions() -> CGResult<Vec<LvFunc>> {
|
pub fn load_func_defs(bindgen_code: &str) -> CGResult<Vec<LvFunc>> {
|
||||||
let clang = Clang::new()?;
|
let ast: syn::File = syn::parse_str(bindgen_code)?;
|
||||||
let index = Index::new(&clang, false, false);
|
let fns = ast
|
||||||
let tu = index
|
.items
|
||||||
.parser(concat!(env!("OUT_DIR"), "/lvgl_full.c"))
|
|
||||||
.parse()?;
|
|
||||||
let entities = tu
|
|
||||||
.get_entity()
|
|
||||||
.get_children()
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|e| e.get_kind() == EntityKind::FunctionDecl)
|
.filter_map(|e| {
|
||||||
.filter(|e| e.get_name().is_some())
|
if let Item::ForeignMod(fm) = e {
|
||||||
.filter(|e| e.get_linkage().unwrap() != Linkage::Internal)
|
Some(fm)
|
||||||
.map(|e| e.into())
|
} else {
|
||||||
.filter(|e: &LvFunc| e.name.starts_with(LIB_PREFIX))
|
None
|
||||||
.collect::<Vec<_>>();
|
}
|
||||||
Ok(entities)
|
})
|
||||||
|
.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::<Vec<LvFunc>>();
|
||||||
|
Ok(fns)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_function_names(&self) -> CGResult<Vec<String>> {
|
pub fn get_function_names(&self) -> CGResult<Vec<String>> {
|
||||||
|
@ -435,11 +458,21 @@ mod test {
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_list_functions() {
|
fn can_load_bindgen_fns() {
|
||||||
let lv = CodeGen::new().unwrap();
|
let bindgen_code = quote! {
|
||||||
let func = String::from("lv_obj_create");
|
extern "C" {
|
||||||
let func_names = lv.get_function_names().unwrap();
|
#[doc = " Return with the screen of an object"]
|
||||||
assert!(func_names.contains(&func));
|
#[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]
|
#[test]
|
||||||
|
|
|
@ -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<TokenStream> = 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();
|
|
||||||
}
|
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||||
|
|
||||||
|
pub fn _bindgen_raw_src() -> &'static str {
|
||||||
|
include_str!(concat!(env!("OUT_DIR"), "/bindings.rs"))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -18,3 +18,9 @@ embedded-graphics = "0.6.2"
|
||||||
cstr_core = { version = "0.2.0", default-features = false, features = ["alloc"] }
|
cstr_core = { version = "0.2.0", default-features = false, features = ["alloc"] }
|
||||||
bitflags = "1.2.1"
|
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" }
|
||||||
|
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
|
use lvgl_codegen::{CodeGen, Rusty};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::Path;
|
use std::fs::File;
|
||||||
use std::process::Command;
|
use std::io::prelude::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||||
let widgets_rs_path = manifest_dir.join("src/widgets/generated.rs");
|
let rs = out_path.join("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());
|
|
||||||
|
|
||||||
if env::var("LVGL_FORCE_CODEGEN").is_ok() || !widgets_rs_path.exists() {
|
let widgets_impl = lvgl_sys::_bindgen_raw_src();
|
||||||
println!("Generating `src/widgets/generated.rs`");
|
|
||||||
let status = Command::new(codegen_bin)
|
let codegen = CodeGen::from(widgets_impl).unwrap();
|
||||||
.spawn()
|
let widgets_impl: Vec<TokenStream> = codegen
|
||||||
.unwrap_or_else(|_| {
|
.get_widgets()
|
||||||
panic!(
|
.iter()
|
||||||
"Code generation failed because no codegen executable was found. \
|
.flat_map(|w| w.code(&()))
|
||||||
Please run `cargo build --package lvgl-codegen` and then try again.",
|
.collect();
|
||||||
)
|
|
||||||
})
|
let code = quote! {
|
||||||
.wait()
|
#(#widgets_impl)*
|
||||||
.unwrap();
|
};
|
||||||
if !status.success() {
|
|
||||||
panic!("Code generation failed");
|
let mut file = File::create(rs).unwrap();
|
||||||
}
|
writeln!(
|
||||||
}
|
file,
|
||||||
|
"/* automatically generated by lvgl-codegen */\n{}",
|
||||||
|
code
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ mod bar;
|
||||||
mod gauge;
|
mod gauge;
|
||||||
mod label;
|
mod label;
|
||||||
|
|
||||||
include!("generated.rs");
|
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||||
|
|
||||||
use crate::{NativeObject, Widget};
|
use crate::{NativeObject, Widget};
|
||||||
pub use arc::*;
|
pub use arc::*;
|
||||||
|
|
Loading…
Reference in a new issue