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)
|
||||
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]
|
||||
crate-type = ["staticlib"]
|
||||
#crate-type = ["rlib", "staticlib"]
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.58"
|
||||
cbindgen = "0.19"
|
||||
|
||||
[features]
|
||||
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 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());
|
||||
}
|
||||
|
|
@ -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 {
|
||||
unsafe {
|
||||
sys::validate_param_in_c(0, x);
|
||||
sys::validate_param_in_c(2, y);
|
||||
sys::validate_param_in_c(1, y);
|
||||
}
|
||||
x + y
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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_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;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
language = "C"
|
||||
Loading…
Reference in New Issue