1# SPDX-License-Identifier: Apache-2.0 2# 3# Copyright (c) 2021, Nordic Semiconductor ASA 4 5# Validate board and setup boards target. 6# 7# This CMake module will validate the BOARD argument as well as splitting the 8# BOARD argument into <BOARD> and <BOARD_REVISION>. When BOARD_EXTENSIONS option 9# is enabled (default) this module will also take care of finding board 10# extension directories. 11# 12# If a board implementation is not found for the specified board an error will 13# be raised and list of valid boards will be printed. 14# 15# If user provided board is a board alias, the board will be adjusted to real 16# board name. 17# 18# If board name is deprecated, then board will be adjusted to new board name and 19# a deprecation warning will be printed to the user. 20# 21# Outcome: 22# The following variables will be defined when this CMake module completes: 23# 24# - BOARD: Board, without revision field. 25# - BOARD_REVISION: Board revision 26# - BOARD_QUALIFIERS: Board qualifiers 27# - NORMALIZED_BOARD_QUALIFIERS: Board qualifiers in lower-case format where slashes have been 28# replaced with underscores 29# - NORMALIZED_BOARD_TARGET: Board target in lower-case format where slashes have been 30# replaced with underscores 31# - BOARD_DIR: Board directory with the implementation for selected board 32# - ARCH_DIR: Arch dir for extracted from selected board 33# - BOARD_ROOT: BOARD_ROOT with ZEPHYR_BASE appended 34# - BOARD_EXTENSION_DIRS: List of board extension directories (If 35# BOARD_EXTENSIONS is not explicitly disabled) 36# 37# The following targets will be defined when this CMake module completes: 38# - board: when invoked, a list of valid boards will be printed 39# 40# Required variables: 41# - BOARD: Board name, including any optional revision field, for example: `foo` or `foo@1.0.0` 42# 43# Optional variables: 44# - BOARD_ROOT: CMake list of board roots containing board implementations 45# - ARCH_ROOT: CMake list of arch roots containing arch implementations 46# 47# Optional environment variables: 48# - ZEPHYR_BOARD_ALIASES: Environment setting pointing to a CMake file 49# containing board aliases. 50# 51# Variables set by this module and not mentioned above are for internal 52# use only, and may be removed, renamed, or re-purposed without prior notice. 53 54include_guard(GLOBAL) 55 56include(python) 57include(extensions) 58 59# Check that BOARD has been provided, and that it has not changed. 60# If user tries to change the BOARD, the BOARD value is reset to the BOARD_CACHED value. 61zephyr_check_cache(BOARD REQUIRED) 62 63# 'BOARD_ROOT' is a prioritized list of directories where boards may 64# be found. It always includes ${ZEPHYR_BASE} at the lowest priority (except for unittesting). 65if(NOT unittest IN_LIST Zephyr_FIND_COMPONENTS) 66 list(APPEND BOARD_ROOT ${ZEPHYR_BASE}) 67endif() 68 69# Helper function for parsing a board's name, revision, and qualifiers, 70# from one input variable to three separate output variables. 71function(parse_board_components board_in name_out revision_out qualifiers_out) 72 if(NOT "${${board_in}}" MATCHES "^([^@/]+)(@[^@/]+)?(/[^@]+)?$") 73 message(FATAL_ERROR 74 "Invalid revision / qualifiers format for ${board_in} (${${board_in}}). " 75 "Valid format is: <board>@<revision>/<qualifiers>" 76 ) 77 endif() 78 string(REPLACE "@" "" board_revision "${CMAKE_MATCH_2}") 79 80 set(${name_out} ${CMAKE_MATCH_1} PARENT_SCOPE) 81 set(${revision_out} ${board_revision} PARENT_SCOPE) 82 set(${qualifiers_out} ${CMAKE_MATCH_3} PARENT_SCOPE) 83endfunction() 84 85parse_board_components( 86 BOARD 87 BOARD BOARD_REVISION BOARD_QUALIFIERS 88) 89 90zephyr_get(ZEPHYR_BOARD_ALIASES) 91if(DEFINED ZEPHYR_BOARD_ALIASES) 92 include(${ZEPHYR_BOARD_ALIASES}) 93 if(${BOARD}_BOARD_ALIAS) 94 set(BOARD_ALIAS ${BOARD} CACHE STRING "Board alias, provided by user") 95 parse_board_components( 96 ${BOARD}_BOARD_ALIAS 97 BOARD BOARD_ALIAS_REVISION BOARD_ALIAS_QUALIFIERS 98 ) 99 message(STATUS "Aliased BOARD=${BOARD_ALIAS} changed to ${BOARD}") 100 if(NOT DEFINED BOARD_REVISION) 101 set(BOARD_REVISION ${BOARD_ALIAS_REVISION}) 102 endif() 103 set(BOARD_QUALIFIERS ${BOARD_ALIAS_QUALIFIERS}${BOARD_QUALIFIERS}) 104 endif() 105endif() 106 107include(${ZEPHYR_BASE}/boards/deprecated.cmake) 108if(${BOARD}${BOARD_QUALIFIERS}_DEPRECATED) 109 set(BOARD_DEPRECATED ${BOARD}${BOARD_QUALIFIERS} CACHE STRING "Deprecated BOARD, provided by user") 110 message(WARNING 111 "Deprecated BOARD=${BOARD_DEPRECATED} specified, " 112 "board automatically changed to: ${${BOARD}${BOARD_QUALIFIERS}_DEPRECATED}." 113 ) 114 parse_board_components( 115 ${BOARD}${BOARD_QUALIFIERS}_DEPRECATED 116 BOARD BOARD_DEPRECATED_REVISION BOARD_QUALIFIERS 117 ) 118 if(DEFINED BOARD_DEPRECATED_REVISION) 119 if(DEFINED BOARD_REVISION) 120 message(FATAL_ERROR 121 "Invalid board revision: ${BOARD_REVISION}\n" 122 "Deprecated board '${BOARD_DEPRECATED}' is now implemented as a revision of another board " 123 "(${BOARD}@${BOARD_DEPRECATED_REVISION}), so the specified revision does not apply. " 124 "Please consult the documentation for '${BOARD}' to see how to build for the new board." 125 ) 126 endif() 127 set(BOARD_REVISION ${BOARD_DEPRECATED_REVISION}) 128 endif() 129endif() 130 131zephyr_boilerplate_watch(BOARD) 132 133foreach(root ${BOARD_ROOT}) 134 # Check that the board root looks reasonable. 135 if(NOT IS_DIRECTORY "${root}/boards") 136 message(WARNING "BOARD_ROOT element without a 'boards' subdirectory: 137${root} 138Hints: 139 - if your board directory is '/foo/bar/boards/my_board' then add '/foo/bar' to BOARD_ROOT, not the entire board directory 140 - if in doubt, use absolute paths") 141 endif() 142endforeach() 143 144if((HWMv1 AND NOT EXISTS ${BOARD_DIR}/${BOARD}_defconfig) 145 OR (HWMv2 AND NOT EXISTS ${BOARD_DIR}/board.yml)) 146 message(WARNING "BOARD_DIR: ${BOARD_DIR} has been moved or deleted. " 147 "Trying to find new location." 148 ) 149 set(BOARD_DIR BOARD_DIR-NOTFOUND CACHE PATH "Path to a file." FORCE) 150endif() 151 152# Prepare list boards command. 153# This command is used for locating the board dir as well as printing all boards 154# in the system in the following cases: 155# - User specifies an invalid BOARD 156# - User invokes '<build-command> boards' target 157list(TRANSFORM ARCH_ROOT PREPEND "--arch-root=" OUTPUT_VARIABLE arch_root_args) 158list(TRANSFORM BOARD_ROOT PREPEND "--board-root=" OUTPUT_VARIABLE board_root_args) 159list(TRANSFORM SOC_ROOT PREPEND "--soc-root=" OUTPUT_VARIABLE soc_root_args) 160 161set(list_boards_commands 162 COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/list_boards.py 163 ${arch_root_args} ${board_root_args} --arch-root=${ZEPHYR_BASE} 164 ${soc_root_args} --soc-root=${ZEPHYR_BASE} 165) 166 167if(NOT BOARD_DIR) 168 if(BOARD_ALIAS) 169 execute_process(${list_boards_commands} --board=${BOARD_ALIAS} --cmakeformat={DIR} 170 OUTPUT_VARIABLE ret_board 171 ERROR_VARIABLE err_board 172 RESULT_VARIABLE ret_val 173 ) 174 string(STRIP "${ret_board}" ret_board) 175 cmake_parse_arguments(BOARD_HIDDEN "" "DIR" "" ${ret_board}) 176 set(BOARD_HIDDEN_DIR ${BOARD_HIDDEN_DIR} CACHE PATH "Path to a folder." FORCE) 177 178 if(BOARD_HIDDEN_DIR) 179 message("Board alias ${BOARD_ALIAS} is hiding the real board of same name") 180 endif() 181 endif() 182endif() 183 184set(format_str "{NAME}\;{DIR}\;{HWM}\;") 185set(format_str "${format_str}{REVISION_FORMAT}\;{REVISION_DEFAULT}\;{REVISION_EXACT}\;") 186set(format_str "${format_str}{REVISIONS}\;{SOCS}\;{QUALIFIERS}") 187 188list(TRANSFORM BOARD_DIRECTORIES PREPEND "--board-dir=" OUTPUT_VARIABLE board_dir_arg) 189execute_process(${list_boards_commands} --board=${BOARD} ${board_dir_arg} 190 --cmakeformat=${format_str} 191 OUTPUT_VARIABLE ret_board 192 ERROR_VARIABLE err_board 193 RESULT_VARIABLE ret_val 194) 195if(ret_val) 196 message(FATAL_ERROR "Error finding board: ${BOARD}\nError message: ${err_board}") 197endif() 198 199if(NOT "${ret_board}" STREQUAL "") 200 string(STRIP "${ret_board}" ret_board) 201 set(single_val "NAME;HWM;REVISION_FORMAT;REVISION_DEFAULT;REVISION_EXACT") 202 set(multi_val "DIR;REVISIONS;SOCS;QUALIFIERS") 203 cmake_parse_arguments(LIST_BOARD "" "${single_val}" "${multi_val}" ${ret_board}) 204 list(GET LIST_BOARD_DIR 0 BOARD_DIR) 205 set(BOARD_DIR ${BOARD_DIR} CACHE PATH "Main board directory for board (${BOARD})" FORCE) 206 set(BOARD_DIRECTORIES ${LIST_BOARD_DIR} CACHE INTERNAL "List of board directories for board (${BOARD})" FORCE) 207 foreach(dir ${BOARD_DIRECTORIES}) 208 set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${dir}/board.yml) 209 endforeach() 210 211 # Create two CMake variables identifying the hw model. 212 # CMake variable: HWM=[v1,v2] 213 # CMake variable: HWMv1=True, when HWMv1 is in use. 214 # CMake variable: HWMv2=True, when HWMv2 is in use. 215 set(HWM ${LIST_BOARD_HWM} CACHE INTERNAL "Zephyr hardware model version") 216 set(HWM${HWM} True CACHE INTERNAL "Zephyr hardware model") 217elseif(BOARD_DIR) 218 message(FATAL_ERROR "Error finding board: ${BOARD} in ${BOARD_DIR}.\n" 219 "This indicates the board has been removed, renamed, or placed at a new location.\n" 220 "Please run a pristine build." 221 ) 222else() 223 message("No board named '${BOARD}' found.\n\n" 224 "Please choose one of the following boards:\n" 225 ) 226 execute_process(${list_boards_commands}) 227 unset(CACHED_BOARD CACHE) 228 message(FATAL_ERROR "Invalid BOARD; see above.") 229endif() 230 231if(HWMv1 AND DEFINED BOARD_QUALIFIERS) 232 message(FATAL_ERROR 233 "Board '${BOARD}' does not support board qualifiers, ${BOARD}${BOARD_QUALIFIERS}.\n" 234 "Please specify board without qualifiers.\n" 235 ) 236endif() 237 238cmake_path(IS_PREFIX ZEPHYR_BASE "${BOARD_DIR}" NORMALIZE in_zephyr_tree) 239if(NOT in_zephyr_tree) 240 set(USING_OUT_OF_TREE_BOARD 1) 241endif() 242 243if(HWMv1) 244 if(EXISTS ${BOARD_DIR}/revision.cmake) 245 # Board provides revision handling. 246 include(${BOARD_DIR}/revision.cmake) 247 elseif(BOARD_REVISION) 248 message(WARNING "Board revision ${BOARD_REVISION} specified for ${BOARD}, \ 249 but board has no revision so revision will be ignored.") 250 endif() 251elseif(HWMv2) 252 if(LIST_BOARD_REVISION_FORMAT) 253 if(LIST_BOARD_REVISION_FORMAT STREQUAL "custom") 254 include(${BOARD_DIR}/revision.cmake) 255 else() 256 if(EXISTS ${BOARD_DIR}/revision.cmake) 257 message(WARNING 258 "revision.cmake ignored, revision.cmake is only used for revision format: 'custom'" 259 ) 260 endif() 261 262 string(TOUPPER "${LIST_BOARD_REVISION_FORMAT}" rev_format) 263 if(LIST_BOARD_REVISION_EXACT) 264 set(rev_exact EXACT) 265 endif() 266 267 board_check_revision( 268 FORMAT ${rev_format} 269 DEFAULT_REVISION ${LIST_BOARD_REVISION_DEFAULT} 270 VALID_REVISIONS ${LIST_BOARD_REVISIONS} 271 ${rev_exact} 272 ) 273 endif() 274 elseif(DEFINED BOARD_REVISION) 275 if(EXISTS ${BOARD_DIR}/revision.cmake) 276 message(WARNING 277 "revision.cmake is not used, revisions must be defined in '${BOARD_DIR}/board.yml'" 278 ) 279 endif() 280 281 message(FATAL_ERROR "Invalid board revision: ${BOARD_REVISION}\n" 282 "Board '${BOARD}' does not define any revisions." 283 ) 284 endif() 285 286 if(LIST_BOARD_QUALIFIERS) 287 # Allow users to omit the SoC when building for a board with a single SoC. 288 list(LENGTH LIST_BOARD_SOCS socs_length) 289 if(socs_length EQUAL 1) 290 set(BOARD_SINGLE_SOC TRUE) 291 set(BOARD_${BOARD}_SINGLE_SOC TRUE) 292 if(NOT DEFINED BOARD_QUALIFIERS) 293 set(BOARD_QUALIFIERS "/${LIST_BOARD_SOCS}") 294 elseif("${BOARD_QUALIFIERS}" MATCHES "^//.*") 295 string(REGEX REPLACE "^//" "/${LIST_BOARD_SOCS}/" BOARD_QUALIFIERS "${BOARD_QUALIFIERS}") 296 endif() 297 endif() 298 299 set(board_targets ${LIST_BOARD_QUALIFIERS}) 300 list(TRANSFORM board_targets PREPEND "${BOARD}/") 301 if(NOT ("${BOARD}${BOARD_QUALIFIERS}" IN_LIST board_targets)) 302 string(REPLACE ";" "\n" board_targets "${board_targets}") 303 unset(CACHED_BOARD CACHE) 304 message(FATAL_ERROR "Board qualifiers `${BOARD_QUALIFIERS}` for board \ 305 `${BOARD}` not found. Please specify a valid board target.\n" 306 "Valid board targets for ${LIST_BOARD_NAME} are:\n${board_targets}\n") 307 endif() 308 endif() 309else() 310 message(FATAL_ERROR "Unknown hw model (${HWM}) for board: ${BOARD}.") 311endif() 312 313set(board_message "Board: ${BOARD}") 314 315if(DEFINED BOARD_REVISION) 316 set(board_message "${board_message}, Revision: ${BOARD_REVISION}") 317 if(DEFINED ACTIVE_BOARD_REVISION) 318 set(board_message "${board_message} (Active: ${ACTIVE_BOARD_REVISION})") 319 set(BOARD_REVISION ${ACTIVE_BOARD_REVISION}) 320 endif() 321 322 string(REPLACE "." "_" BOARD_REVISION_STRING ${BOARD_REVISION}) 323endif() 324 325if(DEFINED BOARD_QUALIFIERS) 326 string(REGEX REPLACE "^/" "qualifiers: " board_message_qualifiers "${BOARD_QUALIFIERS}") 327 set(board_message "${board_message}, ${board_message_qualifiers}") 328 329 string(REPLACE "/" "_" NORMALIZED_BOARD_QUALIFIERS "${BOARD_QUALIFIERS}") 330endif() 331 332set(NORMALIZED_BOARD_TARGET "${BOARD}${BOARD_QUALIFIERS}") 333string(REPLACE "/" "_" NORMALIZED_BOARD_TARGET "${NORMALIZED_BOARD_TARGET}") 334 335message(STATUS "${board_message}") 336 337add_custom_target(boards ${list_boards_commands} USES_TERMINAL) 338 339# Board extensions are enabled by default 340set(BOARD_EXTENSIONS ON CACHE BOOL "Support board extensions") 341zephyr_get(BOARD_EXTENSIONS) 342 343# Process board extensions 344if(BOARD_EXTENSIONS) 345 get_filename_component(board_dir_name ${BOARD_DIR} NAME) 346 347 foreach(root ${BOARD_ROOT}) 348 set(board_extension_dir ${root}/boards/extensions/${board_dir_name}) 349 if(NOT EXISTS ${board_extension_dir}) 350 continue() 351 endif() 352 353 list(APPEND BOARD_EXTENSION_DIRS ${board_extension_dir}) 354 endforeach() 355endif() 356build_info(board name VALUE ${BOARD}) 357string(REGEX REPLACE "^/" "" qualifiers "${BOARD_QUALIFIERS}") 358build_info(board qualifiers VALUE ${qualifiers}) 359build_info(board revision VALUE ${BOARD_REVISION}) 360