Skip to content

Commit 1ed0aea

Browse files
committed
rust: Add cmake support for building rust apps
This provides the function `rust_cargo_application()` within Cmake to give applications written in Rust a simple way to start. This implements the basic functionality needed to build a rust application on Arm, including target mapping, and invoking cargo during the build. Most of the functionality is about passing the appropriate flags to `cargo`, which is used to perform the actual build of the rust code. Cargo generates `librustapp.a` which is added to the link dependencies for the application. The cargo rule is written such that cargo will always be built (provided `app` is being built), as there will be complex dependencies on the cargo side. Cargo will not modify the `librustapp.a` file if nothing needs to be build, so this will generally be fast if there are no changes to files that affect the Rust build. Signed-off-by: David Brown <[email protected]>
1 parent 5230bb4 commit 1ed0aea

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

cmake/modules/rust.cmake

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
# Rust make support
4+
5+
# Zephyr targets are defined through Kconfig. We need to map these to
6+
# an appropriate llvm target triple. This sets `RUST_TARGET` in the
7+
# parent scope, or an error if the target is not yet supported by
8+
# Rust.
9+
function(_rust_map_target)
10+
# Map Zephyr targets to LLVM targets.
11+
if(CONFIG_CPU_CORTEX_M)
12+
if(CONFIG_CPU_CORTEX_M0 OR CONFIG_CPU_CORTEX_M0PLUS OR CONFIG_CPU_CORTEX_M1)
13+
set(RUST_TARGET "thumbv6m-none-eabi" PARENT_SCOPE)
14+
elseif(CONFIG_CPU_CORTEX_M3)
15+
set(RUST_TARGET "thumbv7m-none-eabi" PARENT_SCOPE)
16+
elseif(CONFIG_CPU_CORTEX_M4)
17+
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
18+
set(RUST_TARGET "thumbv7em-none-eabihf" PARENT_SCOPE)
19+
else()
20+
set(RUST_TARGET "thumbv7em-none-eabi" PARENT_SCOPE)
21+
endif()
22+
elseif(CONFIG_CPU_CORTEX_M23)
23+
set(RUST_TARGET "thumbv8m.base-none-eabi" PARENT_SCOPE)
24+
elseif(CONFIG_CPU_CORTEX_M33 OR CONFIG_CPU_CORTEX_M55)
25+
# Not a typo, Zephyr, uses ARMV7_M_ARMV8_M_FP to select the FP even on v8m.
26+
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
27+
set(RUST_TARGET "thumbv8m.main-none-eabihf" PARENT_SCOPE)
28+
else()
29+
set(RUST_TARGET "thumbv8m.main-none-eabi" PARENT_SCOPE)
30+
endif()
31+
32+
# Todo: The M55 is thumbv8.1m.main-none-eabi, which can be added when Rust
33+
# gain support for this target.
34+
else()
35+
message(FATAL_ERROR "Unknown Cortex-M target.")
36+
endif()
37+
elseif(CONFIG_RISCV)
38+
if(CONFIG_RISCV_ISA_RV64I)
39+
# TODO: Should fail if the extensions don't match.
40+
set(RUST_TARGET "riscv64imac-unknown-none-elf" PARENT_SCOPE)
41+
elseif(CONFIG_RISCV_ISA_RV32I)
42+
# TODO: We have multiple choices, try to pick the best.
43+
set(RUST_TARGET "riscv32i-unknown-none-elf" PARENT_SCOPE)
44+
else()
45+
message(FATAL_ERROR "Rust: Unsupported riscv ISA")
46+
endif()
47+
else()
48+
message(FATAL_ERROR "Rust: Add support for other target")
49+
endif()
50+
endfunction()
51+
52+
function(rust_cargo_application)
53+
# For now, hard-code the Zephyr crate directly here. Once we have
54+
# more than one crate, these should be added by the modules
55+
# themselves.
56+
set(LIB_RUST_CRATES zephyr zephyr-build)
57+
58+
_rust_map_target()
59+
message(STATUS "Building Rust llvm target ${RUST_TARGET}")
60+
61+
# TODO: Make sure RUSTFLAGS is not set.
62+
63+
# TODO: Let this be configurable, or based on Kconfig debug?
64+
set(RUST_BUILD_TYPE debug)
65+
set(BUILD_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}")
66+
67+
set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust/target")
68+
set(RUST_LIBRARY "${CARGO_TARGET_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}/librustapp.a")
69+
set(SAMPLE_CARGO_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/rust/sample-cargo-config.toml")
70+
71+
# To get cmake to always invoke Cargo requires a bit of a trick. We make the output of the
72+
# command a file that never gets created. This will cause cmake to always rerun cargo. We
73+
# add the actual library as a BYPRODUCTS list of this command, otherwise, the first time the
74+
# link will fail because it doesn't think it knows how to build the library. This will also
75+
# cause the relink when the cargo command actually does rebuild the rust code.
76+
set(DUMMY_FILE "${CMAKE_BINARY_DIR}/always-run-cargo.dummy")
77+
78+
# For each module in zephyr-rs, add entry both to the .cargo/config template and for the
79+
# command line, since either invocation will need to see these.
80+
set(command_paths)
81+
set(config_paths "")
82+
message(STATUS "Processing crates: ${ZEPHYR_RS_MODULES}")
83+
foreach(module IN LISTS LIB_RUST_CRATES)
84+
message(STATUS "module: ${module}")
85+
set(config_paths
86+
"${config_paths}\
87+
${module}.path = \"${ZEPHYR_BASE}/lib/rust/${module}\"
88+
")
89+
list(APPEND command_paths
90+
"--config"
91+
"patch.crates-io.${module}.path=\\\"${ZEPHYR_BASE}/lib/rust/${module}\\\""
92+
)
93+
endforeach()
94+
95+
# Write out a cargo config file that can be copied into `.cargo/config.toml` (or made a
96+
# symlink) in the source directory to allow various IDE tools and such to work. The build we
97+
# invoke will override these settings, in case they are out of date. Everything set here
98+
# should match the arguments given to the cargo build command below.
99+
file(WRITE ${SAMPLE_CARGO_CONFIG} "
100+
# This is a generated sample .cargo/config.toml file from the Zephyr build.
101+
# At the time of generation, this represented the settings needed to allow
102+
# a `cargo build` command to compile the rust code using the current Zephyr build.
103+
# If any settings in the Zephyr build change, this could become out of date.
104+
[build]
105+
target = \"${RUST_TARGET}\"
106+
target-dir = \"${CARGO_TARGET_DIR}\"
107+
108+
[env]
109+
BUILD_DIR = \"${CMAKE_CURRENT_BINARY_DIR}\"
110+
DOTCONFIG = \"${DOTCONFIG}\"
111+
ZEPHYR_DTS = \"${ZEPHYR_DTS}\"
112+
113+
[patch.crates-io]
114+
${config_paths}
115+
")
116+
117+
# The library is built by invoking Cargo.
118+
add_custom_command(
119+
OUTPUT ${DUMMY_FILE}
120+
BYPRODUCTS ${RUST_LIBRARY}
121+
COMMAND
122+
${CMAKE_EXECUTABLE}
123+
env BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}
124+
DOTCONFIG=${DOTCONFIG}
125+
ZEPHYR_DTS=${ZEPHYR_DTS}
126+
cargo build
127+
# TODO: release flag if release build
128+
# --release
129+
130+
# Override the features according to the shield given. For a general case,
131+
# this will need to come from a variable or argument.
132+
# TODO: This needs to be passed in.
133+
# --no-default-features
134+
# --features ${SHIELD_FEATURE}
135+
136+
# Set a replacement so that packages can just use `zephyr-sys` as a package
137+
# name to find it.
138+
${command_paths}
139+
--target ${RUST_TARGET}
140+
--target-dir ${CARGO_TARGET_DIR}
141+
COMMENT "Building Rust application"
142+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
143+
)
144+
145+
add_custom_target(librustapp ALL
146+
DEPENDS ${DUMMY_FILE}
147+
)
148+
149+
target_link_libraries(app PUBLIC -Wl,--allow-multiple-definition ${RUST_LIBRARY})
150+
add_dependencies(app librustapp)
151+
152+
# Presumably, Rust applications will have no C source files, but cmake will require them.
153+
# Add an empty file so that this will build. The main will come from the rust library.
154+
target_sources(app PRIVATE ${ZEPHYR_BASE}/lib/rust/main.c)
155+
endfunction()

cmake/modules/zephyr_default.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ list(APPEND zephyr_cmake_modules kconfig)
112112
list(APPEND zephyr_cmake_modules arch_v2)
113113
list(APPEND zephyr_cmake_modules soc_v1)
114114
list(APPEND zephyr_cmake_modules soc_v2)
115+
list(APPEND zephyr_cmake_modules rust)
115116

116117
foreach(component ${SUB_COMPONENTS})
117118
if(NOT ${component} IN_LIST zephyr_cmake_modules)

0 commit comments

Comments
 (0)