-
Notifications
You must be signed in to change notification settings - Fork 9
/
CMakeLists.txt
244 lines (215 loc) · 9.7 KB
/
CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# SPDX-License-Identifier: Apache-2.0
# Rust make support
set(RUST_MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "")
# Zephyr targets are defined through Kconfig. We need to map these to
# an appropriate llvm target triple. This sets `RUST_TARGET` in the
# parent scope, or an error if the target is not yet supported by
# Rust.
function(_rust_map_target)
# Map Zephyr targets to LLVM targets.
if(CONFIG_CPU_CORTEX_M)
if(CONFIG_CPU_CORTEX_M0 OR CONFIG_CPU_CORTEX_M0PLUS OR CONFIG_CPU_CORTEX_M1)
set(RUST_TARGET "thumbv6m-none-eabi" PARENT_SCOPE)
elseif(CONFIG_CPU_CORTEX_M3)
set(RUST_TARGET "thumbv7m-none-eabi" PARENT_SCOPE)
elseif(CONFIG_CPU_CORTEX_M4 OR CONFIG_CPU_CORTEX_M7)
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
set(RUST_TARGET "thumbv7em-none-eabihf" PARENT_SCOPE)
else()
set(RUST_TARGET "thumbv7em-none-eabi" PARENT_SCOPE)
endif()
elseif(CONFIG_CPU_CORTEX_M23)
set(RUST_TARGET "thumbv8m.base-none-eabi" PARENT_SCOPE)
elseif(CONFIG_CPU_CORTEX_M33 OR CONFIG_CPU_CORTEX_M55)
# Not a typo, Zephyr, uses ARMV7_M_ARMV8_M_FP to select the FP even on v8m.
if(CONFIG_FP_HARDABI OR FORCE_FP_HARDABI)
set(RUST_TARGET "thumbv8m.main-none-eabihf" PARENT_SCOPE)
else()
set(RUST_TARGET "thumbv8m.main-none-eabi" PARENT_SCOPE)
endif()
# Todo: The M55 is thumbv8.1m.main-none-eabi, which can be added when Rust
# gain support for this target.
else()
message(FATAL_ERROR "Unknown Cortex-M target.")
endif()
elseif(CONFIG_RISCV)
if(CONFIG_RISCV_ISA_RV64I)
# TODO: Should fail if the extensions don't match.
set(RUST_TARGET "riscv64imac-unknown-none-elf" PARENT_SCOPE)
elseif(CONFIG_RISCV_ISA_RV32I)
# TODO: We have multiple choices, try to pick the best.
set(RUST_TARGET "riscv32i-unknown-none-elf" PARENT_SCOPE)
else()
message(FATAL_ERROR "Rust: Unsupported riscv ISA")
endif()
else()
message(FATAL_ERROR "Rust: Add support for other target")
endif()
endfunction()
function(get_include_dirs target dirs)
get_target_property(include_dirs ${target} INTERFACE_INCLUDE_DIRECTORIES)
if(include_dirs)
set(${dirs} ${include_dirs} PARENT_SCOPE)
else()
set(${dirs} "" PARENT_SCOPE)
endif()
endfunction()
function(rust_cargo_application)
# For now, hard-code the Zephyr crate directly here. Once we have
# more than one crate, these should be added by the modules
# themselves.
set(LIB_RUST_CRATES zephyr zephyr-build zephyr-sys)
get_include_dirs(zephyr_interface include_dirs)
get_property(include_defines TARGET zephyr_interface PROPERTY INTERFACE_COMPILE_DEFINITIONS)
message(STATUS "Includes: ${include_dirs}")
message(STATUS "Defines: ${include_defines}")
_rust_map_target()
message(STATUS "Building Rust llvm target ${RUST_TARGET}")
# TODO: Make sure RUSTFLAGS is not set.
if(CONFIG_DEBUG)
set(RUST_BUILD_TYPE "debug")
set(rust_build_type_arg "")
else()
set(RUST_BUILD_TYPE "release")
set(rust_build_type_arg "--release")
endif()
set(BUILD_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}")
set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust/target")
set(RUST_LIBRARY "${CARGO_TARGET_DIR}/${RUST_TARGET}/${RUST_BUILD_TYPE}/librustapp.a")
set(SAMPLE_CARGO_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/rust/sample-cargo-config.toml")
# The generated C binding wrappers. These are bindgen-generated wrappers for the inline functions
# within Zephyr.
set(WRAPPER_FILE "${CMAKE_CURRENT_BINARY_DIR}/rust/wrapper.c")
# To get cmake to always invoke Cargo requires a bit of a trick. We make the output of the
# command a file that never gets created. This will cause cmake to always rerun cargo. We
# add the actual library as a BYPRODUCTS list of this command, otherwise, the first time the
# link will fail because it doesn't think it knows how to build the library. This will also
# cause the relink when the cargo command actually does rebuild the rust code.
set(DUMMY_FILE "${CMAKE_BINARY_DIR}/always-run-cargo.dummy")
# For each module in zephyr-rs, add entry both to the .cargo/config template and for the
# command line, since either invocation will need to see these.
set(command_paths)
set(config_paths "")
message(STATUS "Processing crates: ${ZEPHYR_RS_MODULES}")
foreach(module IN LISTS LIB_RUST_CRATES)
message(STATUS "module: ${module}")
set(config_paths
"${config_paths}\
${module}.path = \"$CACHE{RUST_MODULE_DIR}/${module}\"
")
list(APPEND command_paths
"--config"
"patch.crates-io.${module}.path=\\\"$CACHE{RUST_MODULE_DIR}/${module}\\\""
)
endforeach()
# Write out a cargo config file that can be copied into `.cargo/config.toml` (or made a
# symlink) in the source directory to allow various IDE tools and such to work. The build we
# invoke will override these settings, in case they are out of date. Everything set here
# should match the arguments given to the cargo build command below.
file(WRITE ${SAMPLE_CARGO_CONFIG} "
# This is a generated sample .cargo/config.toml file from the Zephyr build.
# At the time of generation, this represented the settings needed to allow
# a `cargo build` command to compile the rust code using the current Zephyr build.
# If any settings in the Zephyr build change, this could become out of date.
[build]
target = \"${RUST_TARGET}\"
target-dir = \"${CARGO_TARGET_DIR}\"
[env]
BUILD_DIR = \"${CMAKE_CURRENT_BINARY_DIR}\"
DOTCONFIG = \"${DOTCONFIG}\"
ZEPHYR_DTS = \"${ZEPHYR_DTS}\"
INCLUDE_DIRS = \"${include_dirs}\"
INCLUDE_DEFINES = \"${include_defines}\"
WRAPPER_FILE = \"${WRAPPER_FILE}\"
[patch.crates-io]
${config_paths}
")
# The block of environment variables below could theoretically be captured in a variable, but this
# seems "challenging" in CMake (to be polite), as many of these contain spaces, and the quoting
# rules in CMake are inconsistent, at best.
# TODO: Figure out how to factor these out.
# The library is built by invoking Cargo.
add_custom_command(
OUTPUT ${DUMMY_FILE}
BYPRODUCTS ${RUST_LIBRARY} ${WRAPPER_FILE}
COMMAND
${CMAKE_COMMAND} -E
env BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}
ZEPHYR_BASE=${ZEPHYR_BASE}
DOTCONFIG=${DOTCONFIG}
ZEPHYR_DTS=${ZEPHYR_DTS}
INCLUDE_DIRS="${include_dirs}"
INCLUDE_DEFINES="${include_defines}"
WRAPPER_FILE="${WRAPPER_FILE}"
cargo build
${rust_build_type_arg}
# Override the features according to the shield given. For a general case,
# this will need to come from a variable or argument.
# TODO: This needs to be passed in.
# --no-default-features
# --features ${SHIELD_FEATURE}
# Set a replacement so that packages can just use `zephyr-sys` as a package
# name to find it.
${command_paths}
--target ${RUST_TARGET}
--target-dir ${CARGO_TARGET_DIR}
COMMENT "Building Rust application"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
# Be sure we don't try building this until all of the generated headers have been generated.
add_custom_target(librustapp ALL
DEPENDS ${DUMMY_FILE}
# The variables, defined at the top level, don't seem to be accessible here.
syscall_list_h_target
driver_validation_h_target
kobj_types_h_target
)
# Command to generate the rust docs. As mentioned above, the whole environment is duplicated, so
# it is important to keep this in sync with the above.
add_custom_command(
OUTPUT generate_rust_docs
COMMAND
${CMAKE_COMMAND} -E
env BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}
ZEPHYR_BASE=${ZEPHYR_BASE}
DOTCONFIG=${DOTCONFIG}
ZEPHYR_DTS=${ZEPHYR_DTS}
INCLUDE_DIRS="${include_dirs}"
INCLUDE_DEFINES="${include_defines}"
WRAPPER_FILE="${WRAPPER_FILE}"
cargo doc
${rust_build_type_arg}
# Override the features according to the shield given. For a general case,
# this will need to come from a variable or argument.
# TODO: This needs to be passed in.
# --no-default-features
# --features ${SHIELD_FEATURE}
# Set a replacement so that packages can just use `zephyr-sys` as a package
# name to find it.
${command_paths}
--target ${RUST_TARGET}
--target-dir ${CARGO_TARGET_DIR}
COMMENT "Building Rust documentation"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(rustdoc
DEPENDS generate_rust_docs
# The variables, defined at the top level, don't seem to be accessible here.
syscall_list_h_target
driver_validation_h_target
kobj_types_h_target
)
# Linking with the <rt_library> (`$<TARGET_PROPERTY:linker,rt_library>`).
# -lgcc / -lcompiler_rt depending on toolchain, linker, and runtime library configuration.
# In general this shouldn't be needed, as the runtime libary is generally linked late, but
# librustapp.a includes it's own runtime functions, and on riscv (and potentially others) an
# unrecognized / unknown type is used in the relocation section for clzsi2 object.
# Thus we must for current time ensure that the runtime library is before librustapp.a.
# Example of warning reported by ld when this fix is not in place:
# <path>/ld.bfd: rust/target/riscv64imac-unknown-none-elf/debug/librustapp.a(45c91108d938afe8-clzdi2.o): unsupported relocation type 0x3d
target_link_libraries(app PUBLIC $<TARGET_PROPERTY:linker,rt_library> -Wl,--allow-multiple-definition ${RUST_LIBRARY})
add_dependencies(app librustapp)
# Presumably, Rust applications will have no C source files, but cmake will require them.
# Add an empty file so that this will build. The main will come from the rust library.
target_sources(app PRIVATE $CACHE{RUST_MODULE_DIR}/main.c ${WRAPPER_FILE})
endfunction()