Use bindgen output to generate code #22

Merged
rafaelcaricio merged 3 commits from syn-codegen into master 2020-06-14 09:10:30 +00:00
11 changed files with 158 additions and 233 deletions

2
.gitignore vendored
View file

@ -12,5 +12,3 @@ Cargo.lock
examples/demo/target/
lvgl-sys/target/
lvgl/target/
lvgl-sys/src/bindings.rs
lvgl/src/widgets/generated.rs

View file

@ -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

View file

@ -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 <rafael@caricio.com>"]
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"

View file

@ -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();
}

View file

@ -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<T> = Result<T, Box<dyn Error>>;
@ -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<TokenStream>;
}
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone)]
pub struct LvWidget {
name: String,
methods: Vec<LvFunc>,
@ -66,7 +66,7 @@ impl Rusty for LvWidget {
}
}
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone)]
pub struct LvFunc {
name: String,
args: Vec<LvArg>,
@ -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<Entity<'_>> 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<ForeignItemFn> 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::<Vec<LvArg>>(),
ret_type,
ret,
)
}
}
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone)]
pub struct LvArg {
name: String,
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 {
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<Box<syn::Type>>,
}
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<syn::Type>) -> 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<TokenStream> {
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<Type<'_>> for LvType {
fn from(ty: Type) -> Self {
Self::new(ty.get_display_name())
impl From<Box<syn::Type>> for LvType {
fn from(t: Box<syn::Type>) -> Self {
Self::from(t)
}
}
@ -351,8 +365,8 @@ pub struct CodeGen {
}
impl CodeGen {
pub fn new() -> CGResult<Self> {
let functions = Self::load_function_definitions()?;
pub fn from(code: &str) -> CGResult<Self> {
let functions = Self::load_func_defs(code)?;
let widgets = Self::extract_widgets(&functions)?;
Ok(Self { functions, widgets })
}
@ -405,23 +419,31 @@ impl CodeGen {
.collect::<Vec<_>>()
}
pub fn load_function_definitions() -> CGResult<Vec<LvFunc>> {
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<Vec<LvFunc>> {
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::<Vec<_>>();
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::<Vec<LvFunc>>();
Ok(fns)
}
pub fn get_function_names(&self) -> CGResult<Vec<String>> {
@ -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 {

View file

@ -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();
}

View file

@ -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 <crates.lvgl-sys@caric.io>"]
edition = "2018"
license = "MIT"

View file

@ -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::*;

View file

@ -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 <crates.lvgl@caric.io>"]
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" }

View file

@ -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<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();
}

View file

@ -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::*;