Skip to content

Commit 9b39673

Browse files
cmake: support building Rust libraries
This commit adds a new cmake function rust_cargo_library() to build a Rust staticlib library with cbindgen-generated header file, which can be linked into a C application. Signed-off-by: Matthew Richey <[email protected]>
1 parent a53e8f8 commit 9b39673

File tree

2 files changed

+141
-6
lines changed

2 files changed

+141
-6
lines changed

CMakeLists.txt

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,13 @@ function(rust_cargo_application)
8989

9090
# TODO: Make sure RUSTFLAGS is not set.
9191

92-
if(CONFIG_DEBUG)
93-
set(RUST_BUILD_TYPE "debug")
94-
set(rust_build_type_arg "")
92+
# choose debug/release build based on Kconfig choice
93+
if(CONFIG_RUST_CARGO_PROFILE_RELEASE)
94+
message(STATUS "Cargo build profile: release")
95+
set(RUST_BUILD_TYPE release)
9596
else()
96-
set(RUST_BUILD_TYPE "release")
97-
set(rust_build_type_arg "--release")
97+
message(STATUS "Cargo build profile: dev")
98+
set(RUST_BUILD_TYPE debug)
9899
endif()
99100
set(BUILD_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}")
100101

@@ -178,7 +179,7 @@ ${config_paths}
178179
DT_AUGMENTS="${DT_AUGMENTS}"
179180
BINARY_DIR_INCLUDE_GENERATED="${BINARY_DIR_INCLUDE_GENERATED}"
180181
cargo build
181-
${rust_build_type_arg}
182+
--profile $<IF:$<BOOL:${CONFIG_RUST_CARGO_PROFILE_RELEASE}>,release,dev>
182183

183184
# Override the features according to the shield given. For a general case,
184185
# this will need to come from a variable or argument.
@@ -194,6 +195,7 @@ ${config_paths}
194195
${CARGO_EXTRA_FLAGS}
195196
COMMENT "Building Rust application"
196197
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
198+
USES_TERMINAL
197199
)
198200

199201
# Be sure we don't try building this until all of the generated headers have been generated.
@@ -261,3 +263,118 @@ ${config_paths}
261263
# Add an empty file so that this will build. The main will come from the rust library.
262264
target_sources(app PRIVATE $CACHE{RUST_MODULE_DIR}/main.c ${WRAPPER_FILE})
263265
endfunction()
266+
267+
# Function to build a Rust library using cargo, to be integrated into a C-based
268+
# Zephyr application.
269+
#
270+
# In contrast to the rust_cargo_application function, which builds an entire
271+
# Zephyr application from primarily Rust source files, this function is designed
272+
# to build a standalone Rust crate into a library that can be integrated into a
273+
# Zephyr application whose source code base is primarily written in C.
274+
#
275+
# A library built with this function will typically provide a C API through a
276+
# Foreign Function Interface (FFI) whose C header files will be automatically
277+
# generated from the Rust source code by the cbindgen tool, therefore use of
278+
# this function requires the cbindgen tool to be installed on the build host.
279+
#
280+
# The caller must specify the following arguments:
281+
#
282+
# LIBRARY_NAME <library-name> - the name of the library
283+
# CRATE_PATH <crate-path> - path to the root of the Rust crate to build
284+
#
285+
# The caller may optionally specify the following argument:
286+
#
287+
# MEMORY_PARTITION <memory-partition-name>
288+
#
289+
# which, if specified, will place any static global symbols built from the Rust
290+
# sources into the specified memory partition, which can allow the symbols to be
291+
# used from user mode threads.
292+
#
293+
# The function will add the specified library name into the build environment as
294+
# a symbol that can be linked into the target Zephyr application using e.g.
295+
# target_link_libraries.
296+
#
297+
# Usage:
298+
# rust_cargo_library(LIBRARY_NAME <library-name>
299+
# CRATE_PATH <crate-path>
300+
# |MEMORY_PARTITION <memory-partition-name>|
301+
# )
302+
#
303+
function(rust_cargo_library)
304+
# parse function arguments
305+
set(flags)
306+
set(args LIBRARY_NAME CRATE_PATH MEMORY_PARTITION)
307+
set(listArgs)
308+
cmake_parse_arguments(arg "${flags}" "${args}" "${listArgs}" ${ARGN})
309+
310+
# verify required arguments
311+
if (NOT arg_LIBRARY_NAME)
312+
message(FATAL_ERROR "[rust_cargo_library]: LIBRARY_NAME is a required argument")
313+
endif()
314+
if (NOT arg_CRATE_PATH)
315+
message(FATAL_ERROR "[rust_cargo_library]: CRATE_PATH is a required argument")
316+
endif()
317+
318+
# locate cbindgen executable
319+
find_program(CBINDGEN_EXE cbindgen REQUIRED)
320+
321+
_rust_map_target()
322+
message(STATUS "Building Rust library ${arg_LIBRARY_NAME} for llvm target ${RUST_TARGET}")
323+
324+
# choose debug/release build based on Kconfig choice
325+
if(CONFIG_RUST_CARGO_PROFILE_RELEASE)
326+
message(STATUS "Cargo build profile: release")
327+
set(RUST_BUILD_TYPE release)
328+
else()
329+
message(STATUS "Cargo build profile: dev")
330+
set(RUST_BUILD_TYPE debug)
331+
endif()
332+
set(BUILD_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE})
333+
334+
# library filename is based on arg_LIBRARY_NAME
335+
set(CARGO_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR}/rust/target)
336+
set(RUST_LIBRARY ${CARGO_TARGET_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}/lib${arg_LIBRARY_NAME}.a)
337+
338+
# cbindgen header filename is based on arg_LIBRARY_NAME with _ replaced by -
339+
string(REPLACE "_" "-" CBINDGEN_FILENAME "${arg_LIBRARY_NAME}-cbindgen.h")
340+
set(CBINDGEN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/${CBINDGEN_FILENAME})
341+
342+
# The library is built by invoking Cargo.
343+
add_custom_target(
344+
${arg_LIBRARY_NAME}_builder
345+
BYPRODUCTS ${RUST_LIBRARY} ${CBINDGEN_HEADER}
346+
347+
# build the library
348+
COMMAND
349+
cargo build
350+
--profile $<IF:$<STREQUAL:${CONFIG_RUST_CARGO_PROFILE_RELEASE},y>,release,dev>
351+
--target ${RUST_TARGET}
352+
--target-dir ${CARGO_TARGET_DIR}
353+
${CARGO_EXTRA_FLAGS}
354+
355+
# autogenerate the C header file using cbindgen
356+
COMMAND
357+
${CMAKE_COMMAND}
358+
-E env ${CBINDGEN_EXE}
359+
--config cbindgen.toml
360+
--output ${CBINDGEN_HEADER}
361+
--lang c
362+
363+
WORKING_DIRECTORY ${arg_CRATE_PATH}
364+
USES_TERMINAL
365+
)
366+
367+
# Create a library target and add the location of the generated header file
368+
# to its include path.
369+
add_library(${arg_LIBRARY_NAME} STATIC IMPORTED GLOBAL)
370+
add_dependencies(${arg_LIBRARY_NAME} ${arg_LIBRARY_NAME}_builder)
371+
set_target_properties(${arg_LIBRARY_NAME} PROPERTIES IMPORTED_LOCATION ${RUST_LIBRARY})
372+
set_target_properties(${arg_LIBRARY_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR})
373+
374+
# Place any statics from the rust library into the specified memory partition.
375+
if (arg_MEMORY_PARTITION)
376+
zephyr_append_cmake_library(${arg_LIBRARY_NAME})
377+
set(ZEPHYR_CURRENT_LIBRARY ${arg_LIBRARY_NAME})
378+
zephyr_library_app_memory(${arg_MEMORY_PARTITION})
379+
endif()
380+
endfunction()

Kconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,24 @@ config RUST_ALLOC
3131
Zephyr allocator (malloc/free). This this enabled, Rust
3232
applications can use the `alloc` crate.
3333

34+
choice RUST_CARGO_PROFILE
35+
prompt "Cargo profile"
36+
default RUST_CARGO_PROFILE_DEV
37+
help
38+
Select the profile to use when invoking cargo.
39+
40+
config RUST_CARGO_PROFILE_DEV
41+
bool "Development profile"
42+
help
43+
Cargo profile suitable for development and debugging builds.
44+
45+
config RUST_CARGO_PROFILE_RELEASE
46+
bool "Release profile"
47+
help
48+
Cargo profile suitable for release builds.
49+
50+
endchoice # RUST_BUILD_TYPE
51+
3452
endif # RUST
3553

3654
endmenu

0 commit comments

Comments
 (0)