lvgl / lv_binding_rust

LVGL 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).
MIT License
668 stars 69 forks source link

Cross compile fixes #153

Open madwizard-thomas opened 10 months ago

madwizard-thomas commented 10 months ago

Fixes #102

When cross-compiling this library (in my case for ESP32 but the fixes are generic) I came across several issues. This pull requests should fix these.

lvgl-sys is built twice, once as a normal dependency for lvgl (for the application) and once as build dependency (for the build script executable of lvgl crate). The normal dependency includes the automatically generated bindings (built by bindgen) and links the lvgl C library itself (using the cc crate). The build dependency is used to call _bindgen_raw_src to retrieve the raw source code of the generated bindings to further autogenerate rust code.

Both the library and the bindings are generated twice, for the application and build script. For the application the target (TARGET env) is set correctly, however the build script is a host targeted executable (TARGET == HOST), so the library and bindings are actually generated for the host machine. The library itself is not used for the build executable so this problem is silent, it does take unnecessary time to compile though. The bindings however are used in the build host executable, but here the bindings should be compiled for the target machine and not for the host. In most cases these bindings will be the same but there may be subtle differences leading to bugs. For both the application and the build script, the bindings need to be targeted to the embedded device.

Since HOST == TARGET in the build script, there is no way to tell the actual application target. The cc crate allows the environment variable CROSS_COMPILE to be set to specify a different compiler. It is only used by cc when host != target though. Bindgen does not use this variable, also it needs to cross-compile even when host == target so in this case target should always be CROSS_COMPILE.

While figuring this out I also came across a slightly unrelated issue with this line in lvlg-sys build.rs: add_c_files(&mut cfg, &lv_config_dir); This actually compiles all c files recursively starting at the config dir. Since the config dir is often the project dir this causes the script to possibly build all kinds of unrelated files. Especially with esp32 which stores the c source of esp-idf in a some directory in the project. I removed this line since the documentation says nothing about compiling C files from the config dir. If this is required for someone it should probably be a separate env var.

This PR consists of 3 commits: Remove compilation of all recursive C files in config dir Removes the add_c_files line for the config and adds a missing rerun-if-env-changed for the config env var.

Refactor build script: separate library and bindings in separate functions Lots of changed lines but actually changes no functionality, just refactors the build script in two separate steps and cleans it up a bit

Add library and raw-bindings features to lvgl-sys The library feature switches the compilation (cc) of the library on or off The raw-bindings feature toggles _bindgen_raw_src

This fixes the build for ESP32 (checked that it at least compiles for no-std and std esp templates) and it still works on linux with SDL (tested).

For ESP32 (and other embedded targets), the CROSS_COMPILE variable needs to be set, for example for esp32s3 you would need export CROSS_COMPILE=xtensa-esp32s3-elf, or better include it as [env] in .cargo/config.toml Another issue is that bindgen (at least for ESP32) does not have a sysroot by default. This can be set using export BINDGEN_EXTRA_CLANG_ARGS="--sysroot directoryhere". To find the correct sysroot you can run one of the gcc compiler tools with --print-sysroot, though cc does not work so use ld for example: xtensa-esp32s3-elf-ld --print-sysroot.

Or if CROSS_COMPILE is available in one go:

export CROSS_COMPILE=xtensa-esp32s3-elf
export BINDGEN_EXTRA_CLANG_ARGS="--sysroot "`$CROSS_COMPILE-ld --print-sysroot`

The bindgen sysroot could be done by the build.rs script as well but maybe that's a bit too specific.

enelson1001 commented 10 months ago

@madwizard-thomas I cloned the lv_binding_rust and applied your changes (At least I think I have all the changes incorporated). Everything builds and applications runs fine. The only thing I noticed was I get 2 sets of the same warnings which makes me think something is running twice. This is what I see during the cargo build.

 Compiling lvgl v0.6.2 (/home/ed/lv-binding-rust/lv_binding_rust_patch_madwizard/lvgl)
    Finished dev [optimized + debuginfo] target(s) in 2m 48s
warning: `extern` block uses type `[u32; 3]`, which is not FFI-safe
    --> /home/ed/esp-rust-projects/aliexpress-esp32s3/rust-esp32s3-lvgl-clickme/target/debug/build/lvgl-sys-23f05043d1aabbc6/out/bindings.rs:1482:13
     |
1482 |         va: va_list,
     |             ^^^^^^^ not FFI-safe
     |
     = help: consider passing a pointer to the array
     = note: passing raw arrays by value is not FFI-safe
     = note: `#[warn(improper_ctypes)]` on by default

warning: `extern` block uses type `[u32; 3]`, which is not FFI-safe
    --> /home/ed/esp-rust-projects/aliexpress-esp32s3/rust-esp32s3-lvgl-clickme/target/debug/build/lvgl-sys-23f05043d1aabbc6/out/bindings.rs:2223:63
     |
2223 |     pub fn _lv_txt_set_text_vfmt(fmt: *const cty::c_char, ap: va_list) -> *mut cty::c_char;
     |                                                               ^^^^^^^ not FFI-safe
     |
     = help: consider passing a pointer to the array
     = note: passing raw arrays by value is not FFI-safe

warning: 2 warnings emitted

warning: `extern` block uses type `[u32; 3]`, which is not FFI-safe
    --> /home/ed/esp-rust-projects/aliexpress-esp32s3/rust-esp32s3-lvgl-clickme/target/xtensa-esp32s3-espidf/debug/build/lvgl-sys-c6306817d27c4c31/out/bindings.rs:1482:13
     |
1482 |         va: va_list,
     |             ^^^^^^^ not FFI-safe
     |
     = help: consider passing a pointer to the array
     = note: passing raw arrays by value is not FFI-safe
     = note: `#[warn(improper_ctypes)]` on by default

warning: `extern` block uses type `[u32; 3]`, which is not FFI-safe
    --> /home/ed/esp-rust-projects/aliexpress-esp32s3/rust-esp32s3-lvgl-clickme/target/xtensa-esp32s3-espidf/debug/build/lvgl-sys-c6306817d27c4c31/out/bindings.rs:2223:63
     |
2223 |     pub fn _lv_txt_set_text_vfmt(fmt: *const cty::c_char, ap: va_list) -> *mut cty::c_char;
     |                                                               ^^^^^^^ not FFI-safe
     |
     = help: consider passing a pointer to the array
     = note: passing raw arrays by value is not FFI-safe

warning: 2 warnings emitted

Is this what you saw when you ran your tests?

madwizard-thomas commented 10 months ago

Yes, it generates the bindings twice, once for the host target (build.rs executable) and once for the embedded target. It needs them in both, I don't think there is a way around it. You can identify them by the target directories btw, target/debug -> host executable, target/xtensta-.. -> embedded target

nia-e commented 10 months ago

I am going to try to make it so only one version gets built in the first place - should be doable I think, possibly by splitting off another intermediary crate? This current approach is definitely "wrong" in that if C LVGL headers are materially different when cross-compiled it's going to just fail, but that's a more long term project once I get back to this proper. Until then this is very, very welcome

madwizard-thomas commented 10 months ago

I think you can do that if you split the binding and compile features into separate crates. Then you don’t need the features anymore. You probably would need to always add the raw bindings function as well, even for the embedded target library though hopefully it will be removed from the final binary (this is the library’s current situation anyway)

When cross compiling I think it will always need to generate the bindings twice since there the architectures are different.

Dominaezzz commented 3 weeks ago

Any chance this could land?