diff --git a/CMakeLists.txt b/CMakeLists.txt index 763f07c..87e1e18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,3 @@ cmake_minimum_required(VERSION 3.5) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(esp32-hello-rust) - -# The Xtensa Rust build includes compiler intrinsics that conflict with names -# in GCC. To avoid multiple definition errors, allow the linker to pick one of -# the available options and ignore the others. -if((${IDF_TARGET} MATCHES "esp32") OR (${IDF_TARGET} MATCHES "esp32s2")) - target_link_options(${CMAKE_PROJECT_NAME}.elf PRIVATE "-Wl,--allow-multiple-definition") -endif() diff --git a/components/clib/CMakeLists.txt b/components/clib/CMakeLists.txt new file mode 100644 index 0000000..9c134f5 --- /dev/null +++ b/components/clib/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "clib.c" + INCLUDE_DIRS "include") diff --git a/components/clib/README.md b/components/clib/README.md new file mode 100644 index 0000000..672b81c --- /dev/null +++ b/components/clib/README.md @@ -0,0 +1,5 @@ +# clib + +This component exposes functions and data defined in C that will accessed from Rust. + +The Rust build will automatically generate bindings to these functions by processing the exported header files with bindgen. \ No newline at end of file diff --git a/components/clib/clib.c b/components/clib/clib.c new file mode 100644 index 0000000..e9ab258 --- /dev/null +++ b/components/clib/clib.c @@ -0,0 +1,17 @@ +/* Hello World Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include + +#include "CApi.h" + +bool validate_param_in_c(int param, int value) +{ + printf("C validated param #%d = %d\n", param, value); + return true; +} \ No newline at end of file diff --git a/main/CApi.h b/components/clib/include/CApi.h similarity index 100% rename from main/CApi.h rename to components/clib/include/CApi.h diff --git a/components/rustlib/CMakeLists.txt b/components/rustlib/CMakeLists.txt new file mode 100644 index 0000000..dcf8ecf --- /dev/null +++ b/components/rustlib/CMakeLists.txt @@ -0,0 +1,57 @@ +set(RUST_DEPS "clib") + +idf_component_register( + SRCS "placeholder.c" + INCLUDE_DIRS "" + PRIV_REQUIRES "${RUST_DEPS}" +) + +set(CARGO_BUILD_TYPE "release") +set(CARGO_BUILD_ARG "--release") +if(CONFIG_IDF_TARGET_ARCH_RISCV) + set(CARGO_TARGET "riscv32i-unknown-none-elf") + set(CARGO_FEATURES_ARG "") +elseif(CONFIG_IDF_TARGET_ARCH_XTENSA) + set(CARGO_TARGET "xtensa-esp32-none-elf") + set(CARGO_FEATURES_ARG "--features=std") +endif() + +set(RUST_PROJECT_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(RUST_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}") +set(RUST_TARGET_DIR "${RUST_BUILD_DIR}/target") +set(RUST_INCLUDE_DIR "${RUST_TARGET_DIR}") +set(RUST_INCLUDE_HEADER "${RUST_INCLUDE_DIR}/RustApi.h") +set(RUST_STATIC_LIBRARY "${RUST_TARGET_DIR}/${CARGO_TARGET}/${CARGO_BUILD_TYPE}/librustlib.a") + +ExternalProject_Add( + rustlib_project + PREFIX "${RUST_PROJECT_DIR}" + DOWNLOAD_COMMAND "" + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env + CARGO_BUILD_TARGET=${CARGO_TARGET} + CARGO_BUILD_TARGET_DIR=${RUST_TARGET_DIR} + cargo clean + BUILD_COMMAND ${CMAKE_COMMAND} -E env + CARGO_BUILD_TARGET=${CARGO_TARGET} + CARGO_BUILD_TARGET_DIR=${RUST_TARGET_DIR} + cargo build ${CARGO_BUILD_ARG} ${CARGO_FEATURES_ARG} + INSTALL_COMMAND "" + BUILD_ALWAYS TRUE + TMP_DIR "${RUST_BUILD_DIR}/tmp" + STAMP_DIR "${RUST_BUILD_DIR}/stamp" + DOWNLOAD_DIR "${RUST_BUILD_DIR}" + SOURCE_DIR "${RUST_PROJECT_DIR}" + BINARY_DIR "${RUST_PROJECT_DIR}" + INSTALL_DIR "${RUST_BUILD_DIR}" + BUILD_BYPRODUCTS + "${RUST_INCLUDE_HEADER}" + "${RUST_STATIC_LIBRARY}" +) + +set_source_files_properties("${RUST_INCLUDE_HEADER}" PROPERTIES GENERATED true) + +add_prebuilt_library(rustlib_lib "${RUST_STATIC_LIBRARY}" PRIV_REQUIRES "${RUST_DEPS}") +add_dependencies(rustlib_lib rustlib_project) + +target_include_directories(${COMPONENT_LIB} PUBLIC "${RUST_INCLUDE_DIR}") +target_link_libraries(${COMPONENT_LIB} PRIVATE rustlib_lib) diff --git a/rustlib/Cargo.toml b/components/rustlib/Cargo.toml similarity index 93% rename from rustlib/Cargo.toml rename to components/rustlib/Cargo.toml index ddf3f35..968595e 100644 --- a/rustlib/Cargo.toml +++ b/components/rustlib/Cargo.toml @@ -6,10 +6,10 @@ edition = "2018" [lib] crate-type = ["staticlib"] -#crate-type = ["rlib", "staticlib"] [build-dependencies] bindgen = "0.58" +cbindgen = "0.19" [features] std = [] diff --git a/components/rustlib/README.md b/components/rustlib/README.md new file mode 100644 index 0000000..da414e5 --- /dev/null +++ b/components/rustlib/README.md @@ -0,0 +1,13 @@ +# rustlib + +This component generates a static library from Rust source code and exposes functions that will be callable from the main IDF application from C. + +This component is responsible for three separate functions: + +* Export C bindings for Rust functions with `cbindgen`. +* Import Rust binding for C functions with `bindgen`. +* Build a top level static library from Rust code. The `cargo` build command is wrapped in a CMake `ExternalProject_Add` command. + +Currently the `build.rs` script expects the `clib` component to export a header with the name `CApi.h`. If the name of the include file changes or if more files are added, the build script must be updated to process the latest headers. + +Depending on the complexity of the project, the `cbindgen` and `bindgen` steps could be broken out into one or more IDF components or Rust crates. However, to prevent redundant duplication of the Rust runtime, all Rust crates should be compiled into a single static library that will be linked into the final IDF application. \ No newline at end of file diff --git a/rustlib/build.rs b/components/rustlib/build.rs similarity index 59% rename from rustlib/build.rs rename to components/rustlib/build.rs index af64de6..ddae318 100644 --- a/rustlib/build.rs +++ b/components/rustlib/build.rs @@ -5,19 +5,32 @@ fn main() { let target = env::var("TARGET").unwrap(); let cargo_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let target_dir = PathBuf::from(env::var("CARGO_BUILD_TARGET_DIR").unwrap()); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - run_cbindgen(&cargo_dir, &out_dir); + run_cbindgen(&cargo_dir, &target_dir); run_bindgen(&target, &out_dir); } -fn run_cbindgen(_cargo_dir: &Path, _out_dir: &Path) { - // TODO: Run cbindgen +fn run_cbindgen(cargo_dir: &Path, target_dir: &Path) { + let out = target_dir.join("RustApi.h"); + + cbindgen::Builder::new() + .with_crate(cargo_dir) + .with_language(cbindgen::Language::C) + .generate() + .expect("Unable to generate bindings") + .write_to_file(&out); + + println!("cargo:rerun-if-changed={}", out.display()); } fn run_bindgen(target: &str, out_dir: &Path) { + let header = "../clib/include/CApi.h"; + let out = out_dir.join("bindings.rs"); + let mut builder = bindgen::Builder::default(); - builder = builder.header("../main/CApi.h"); + builder = builder.header(header); match target { "riscv32i-unknown-none-elf" => { builder = builder.clang_arg("--target=riscv32"); @@ -36,6 +49,9 @@ fn run_bindgen(target: &str, out_dir: &Path) { let bindings = builder.generate().expect("Couldn't generate bindings!"); bindings - .write_to_file(out_dir.join("bindings.rs")) + .write_to_file(&out) .expect("Couldn't save bindings!"); + + println!("cargo:rerun-if-changed={}", header); + println!("cargo:rerun-if-changed={}", out.display()); } diff --git a/components/rustlib/placeholder.c b/components/rustlib/placeholder.c new file mode 100644 index 0000000..de0472a --- /dev/null +++ b/components/rustlib/placeholder.c @@ -0,0 +1,13 @@ +/* Hello World Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* This is an empty source file to force the build of a CMake library that + can participate in the CMake dependency graph. This placeholder library + will depend on the actual library generated by external Rust build. +*/ \ No newline at end of file diff --git a/rustlib/src/lib.rs b/components/rustlib/src/lib.rs similarity index 95% rename from rustlib/src/lib.rs rename to components/rustlib/src/lib.rs index 952604e..ad14f10 100644 --- a/rustlib/src/lib.rs +++ b/components/rustlib/src/lib.rs @@ -26,7 +26,7 @@ pub mod sys { pub extern "C" fn add_in_rust(x: i32, y: i32) -> i32 { unsafe { sys::validate_param_in_c(0, x); - sys::validate_param_in_c(2, y); + sys::validate_param_in_c(1, y); } x + y } diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 8aea823..8a3ab69 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,46 +1,2 @@ idf_component_register(SRCS "main.c" INCLUDE_DIRS "") - -set(CARGO_BUILD_TYPE "release") -set(CARGO_BUILD_ARG "--release") -if(CONFIG_IDF_TARGET_ARCH_RISCV) - set(CARGO_TARGET "riscv32i-unknown-none-elf") - set(CARGO_FEATURES_ARG "") -elseif(CONFIG_IDF_TARGET_ARCH_XTENSA) - set(CARGO_TARGET "xtensa-esp32-none-elf") - set(CARGO_FEATURES_ARG "--features=std") -endif() - -set(RUST_PROJECT_DIR "${CMAKE_CURRENT_LIST_DIR}/../rustlib") -set(RUST_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/rustlib") -set(RUST_STATIC_LIBRARY "${RUST_BUILD_DIR}/${CARGO_TARGET}/${CARGO_BUILD_TYPE}/librustlib.a") - -ExternalProject_Add( - rustlib_project - PREFIX "${RUST_PROJECT_DIR}" - BUILD_COMMAND ${CMAKE_COMMAND} -E env - CARGO_BUILD_TARGET=${CARGO_TARGET} - CARGO_BUILD_TARGET_DIR=${RUST_BUILD_DIR} - cargo build ${CARGO_BUILD_ARG} ${CARGO_FEATURES_ARG} - DOWNLOAD_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - BUILD_ALWAYS TRUE - BUILD_DIR "${RUST_BUILD_DIR}" - INSTALL_DIR "${RUST_BUILD_DIR}" - STAMP_DIR "${RUST_BUILD_DIR}/stamp" - TMP_DIR "${RUST_BUILD_DIR}/tmp" - DOWNLOAD_DIR "${RUST_BUILD_DIR}" - SOURCE_DIR "${RUST_PROJECT_DIR}" - BUILD_BYPRODUCTS "${RUST_STATIC_LIBRARY}" -) - -# this adds "rustlib" static library target. -# PRIV_REQUIRES tells CMake that librustlib.a itself depends on libmain.a. -add_prebuilt_library(rustlib "${RUST_STATIC_LIBRARY}" PRIV_REQUIRES main) - -# rustlib will be produced by building rustlib_project target -add_dependencies(rustlib rustlib_project) - -# 'main' calls a function from the library, so link it to 'main' -target_link_libraries(${COMPONENT_LIB} PRIVATE rustlib) diff --git a/main/RustApi.h b/main/RustApi.h deleted file mode 100644 index 3b211fe..0000000 --- a/main/RustApi.h +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Rust functions exposed to C. - */ - -extern int add_in_rust(int x, int y); \ No newline at end of file diff --git a/main/component.mk b/main/component.mk deleted file mode 100644 index 0b9d758..0000000 --- a/main/component.mk +++ /dev/null @@ -1,5 +0,0 @@ -# -# "main" pseudo-component makefile. -# -# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) - diff --git a/main/main.c b/main/main.c index f1140f2..92e6838 100644 --- a/main/main.c +++ b/main/main.c @@ -13,7 +13,6 @@ #include "esp_system.h" #include "esp_spi_flash.h" -#include "CApi.h" #include "RustApi.h" void app_main(void) @@ -49,16 +48,3 @@ void app_main(void) fflush(stdout); esp_restart(); } - -// bool validate_param_in_c(const char *param, int value) -// { -// printf("C validated %s = %d\n", param, value); -// return true; -// } - - -bool validate_param_in_c(int param, int value) -{ - printf("C validated %d = %d\n", param, value); - return true; -} \ No newline at end of file diff --git a/rustlib/cbindgen.toml b/rustlib/cbindgen.toml deleted file mode 100644 index 08094f2..0000000 --- a/rustlib/cbindgen.toml +++ /dev/null @@ -1 +0,0 @@ -language = "C"