Create components for Rust and C integration (#10)
* Create components for Rust and C integration * Create component for C code that will be callable from Rust * Create component for Rust code that will be callable from C * Remove circular dependencies between C and Rust code * Run cbindgen to dynamically generate C headers for Rust functions
This commit is contained in:
parent
b57a505379
commit
8c94733638
|
|
@ -4,10 +4,3 @@ cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
project(esp32-hello-rust)
|
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()
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
idf_component_register(SRCS "clib.c"
|
||||||
|
INCLUDE_DIRS "include")
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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 <stdio.h>
|
||||||
|
|
||||||
|
#include "CApi.h"
|
||||||
|
|
||||||
|
bool validate_param_in_c(int param, int value)
|
||||||
|
{
|
||||||
|
printf("C validated param #%d = %d\n", param, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -6,10 +6,10 @@ edition = "2018"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib"]
|
crate-type = ["staticlib"]
|
||||||
#crate-type = ["rlib", "staticlib"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.58"
|
bindgen = "0.58"
|
||||||
|
cbindgen = "0.19"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = []
|
std = []
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -5,19 +5,32 @@ fn main() {
|
||||||
let target = env::var("TARGET").unwrap();
|
let target = env::var("TARGET").unwrap();
|
||||||
|
|
||||||
let cargo_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").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());
|
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);
|
run_bindgen(&target, &out_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_cbindgen(_cargo_dir: &Path, _out_dir: &Path) {
|
fn run_cbindgen(cargo_dir: &Path, target_dir: &Path) {
|
||||||
// TODO: Run cbindgen
|
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) {
|
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();
|
let mut builder = bindgen::Builder::default();
|
||||||
builder = builder.header("../main/CApi.h");
|
builder = builder.header(header);
|
||||||
match target {
|
match target {
|
||||||
"riscv32i-unknown-none-elf" => {
|
"riscv32i-unknown-none-elf" => {
|
||||||
builder = builder.clang_arg("--target=riscv32");
|
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!");
|
let bindings = builder.generate().expect("Couldn't generate bindings!");
|
||||||
bindings
|
bindings
|
||||||
.write_to_file(out_dir.join("bindings.rs"))
|
.write_to_file(&out)
|
||||||
.expect("Couldn't save bindings!");
|
.expect("Couldn't save bindings!");
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed={}", header);
|
||||||
|
println!("cargo:rerun-if-changed={}", out.display());
|
||||||
}
|
}
|
||||||
|
|
@ -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.
|
||||||
|
*/
|
||||||
|
|
@ -26,7 +26,7 @@ pub mod sys {
|
||||||
pub extern "C" fn add_in_rust(x: i32, y: i32) -> i32 {
|
pub extern "C" fn add_in_rust(x: i32, y: i32) -> i32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
sys::validate_param_in_c(0, x);
|
sys::validate_param_in_c(0, x);
|
||||||
sys::validate_param_in_c(2, y);
|
sys::validate_param_in_c(1, y);
|
||||||
}
|
}
|
||||||
x + y
|
x + y
|
||||||
}
|
}
|
||||||
|
|
@ -1,46 +1,2 @@
|
||||||
idf_component_register(SRCS "main.c"
|
idf_component_register(SRCS "main.c"
|
||||||
INCLUDE_DIRS "")
|
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)
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/*
|
|
||||||
* Rust functions exposed to C.
|
|
||||||
*/
|
|
||||||
|
|
||||||
extern int add_in_rust(int x, int y);
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
#
|
|
||||||
# "main" pseudo-component makefile.
|
|
||||||
#
|
|
||||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
|
||||||
|
|
||||||
14
main/main.c
14
main/main.c
|
|
@ -13,7 +13,6 @@
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
#include "esp_spi_flash.h"
|
#include "esp_spi_flash.h"
|
||||||
|
|
||||||
#include "CApi.h"
|
|
||||||
#include "RustApi.h"
|
#include "RustApi.h"
|
||||||
|
|
||||||
void app_main(void)
|
void app_main(void)
|
||||||
|
|
@ -49,16 +48,3 @@ void app_main(void)
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
esp_restart();
|
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
language = "C"
|
|
||||||
Loading…
Reference in New Issue