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