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(HWMv2 AND NOT EXISTS ${BOARD_DIR}/board.yml)
145  message(WARNING "BOARD_DIR: ${BOARD_DIR} has been moved or deleted. "
146                  "Trying to find new location."
147  )
148  set(BOARD_DIR BOARD_DIR-NOTFOUND CACHE PATH "Path to a file." FORCE)
149endif()
150
151# Prepare list boards command.
152# This command is used for locating the board dir as well as printing all boards
153# in the system in the following cases:
154# - User specifies an invalid BOARD
155# - User invokes '<build-command> boards' target
156list(TRANSFORM ARCH_ROOT PREPEND "--arch-root=" OUTPUT_VARIABLE arch_root_args)
157list(TRANSFORM BOARD_ROOT PREPEND "--board-root=" OUTPUT_VARIABLE board_root_args)
158list(TRANSFORM SOC_ROOT PREPEND "--soc-root=" OUTPUT_VARIABLE soc_root_args)
159
160set(list_boards_commands
161    COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/list_boards.py
162            ${arch_root_args} ${board_root_args} --arch-root=${ZEPHYR_BASE}
163            ${soc_root_args} --soc-root=${ZEPHYR_BASE}
164)
165
166if(NOT BOARD_DIR)
167  if(BOARD_ALIAS)
168    execute_process(${list_boards_commands} --board=${BOARD_ALIAS} --cmakeformat={DIR}
169                    OUTPUT_VARIABLE ret_board
170                    ERROR_VARIABLE err_board
171                    RESULT_VARIABLE ret_val
172    )
173    string(STRIP "${ret_board}" ret_board)
174    cmake_parse_arguments(BOARD_HIDDEN "" "DIR" "" ${ret_board})
175    set(BOARD_HIDDEN_DIR ${BOARD_HIDDEN_DIR} CACHE PATH "Path to a folder." FORCE)
176
177    if(BOARD_HIDDEN_DIR)
178      message("Board alias ${BOARD_ALIAS} is hiding the real board of same name")
179    endif()
180  endif()
181endif()
182
183set(format_str "{NAME}\;{DIR}\;{HWM}\;")
184set(format_str "${format_str}{REVISION_FORMAT}\;{REVISION_DEFAULT}\;{REVISION_EXACT}\;")
185set(format_str "${format_str}{REVISIONS}\;{SOCS}\;{QUALIFIERS}")
186
187list(TRANSFORM BOARD_DIRECTORIES PREPEND "--board-dir=" OUTPUT_VARIABLE board_dir_arg)
188execute_process(${list_boards_commands} --board=${BOARD} ${board_dir_arg}
189  --cmakeformat=${format_str}
190                OUTPUT_VARIABLE ret_board
191                ERROR_VARIABLE err_board
192                RESULT_VARIABLE ret_val
193)
194if(ret_val)
195  message(FATAL_ERROR "Error finding board: ${BOARD}\nError message: ${err_board}")
196endif()
197
198if(NOT "${ret_board}" STREQUAL "")
199  string(STRIP "${ret_board}" ret_board)
200  set(single_val "NAME;HWM;REVISION_FORMAT;REVISION_DEFAULT;REVISION_EXACT")
201  set(multi_val  "DIR;REVISIONS;SOCS;QUALIFIERS")
202  cmake_parse_arguments(LIST_BOARD "" "${single_val}" "${multi_val}" ${ret_board})
203  list(GET LIST_BOARD_DIR 0 BOARD_DIR)
204  set(BOARD_DIR ${BOARD_DIR} CACHE PATH "Main board directory for board (${BOARD})" FORCE)
205  set(BOARD_DIRECTORIES ${LIST_BOARD_DIR} CACHE INTERNAL "List of board directories for board (${BOARD})" FORCE)
206  foreach(dir ${BOARD_DIRECTORIES})
207    set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${dir}/board.yml)
208  endforeach()
209
210  # Create two CMake variables identifying the hw model.
211  # CMake variable: HWM=v2
212  # CMake variable: HWMv2=True
213  set(HWM       ${LIST_BOARD_HWM} CACHE INTERNAL "Zephyr hardware model version")
214  set(HWM${HWM} True   CACHE INTERNAL "Zephyr hardware model")
215elseif(BOARD_DIR)
216  message(FATAL_ERROR "Error finding board: ${BOARD} in ${BOARD_DIR}.\n"
217          "This indicates the board has been removed, renamed, or placed at a new location.\n"
218	  "Please run a pristine build."
219  )
220else()
221  message("No board named '${BOARD}' found.\n\n"
222          "Please choose one of the following boards:\n"
223  )
224  execute_process(${list_boards_commands})
225  unset(CACHED_BOARD CACHE)
226  message(FATAL_ERROR "Invalid BOARD; see above.")
227endif()
228
229cmake_path(IS_PREFIX ZEPHYR_BASE "${BOARD_DIR}" NORMALIZE in_zephyr_tree)
230if(NOT in_zephyr_tree)
231  set(USING_OUT_OF_TREE_BOARD 1)
232endif()
233
234if(LIST_BOARD_REVISION_FORMAT)
235  if(LIST_BOARD_REVISION_FORMAT STREQUAL "custom")
236    include(${BOARD_DIR}/revision.cmake)
237  else()
238    if(EXISTS ${BOARD_DIR}/revision.cmake)
239      message(WARNING
240        "revision.cmake ignored, revision.cmake is only used for revision format: 'custom'"
241      )
242    endif()
243
244    string(TOUPPER "${LIST_BOARD_REVISION_FORMAT}" rev_format)
245    if(LIST_BOARD_REVISION_EXACT)
246      set(rev_exact EXACT)
247    endif()
248
249    board_check_revision(
250      FORMAT ${rev_format}
251      DEFAULT_REVISION ${LIST_BOARD_REVISION_DEFAULT}
252      VALID_REVISIONS ${LIST_BOARD_REVISIONS}
253      ${rev_exact}
254    )
255  endif()
256elseif(DEFINED BOARD_REVISION)
257  if(EXISTS ${BOARD_DIR}/revision.cmake)
258    message(WARNING
259      "revision.cmake is not used, revisions must be defined in '${BOARD_DIR}/board.yml'"
260    )
261  endif()
262
263  message(FATAL_ERROR "Invalid board revision: ${BOARD_REVISION}\n"
264                      "Board '${BOARD}' does not define any revisions."
265  )
266endif()
267
268if(LIST_BOARD_QUALIFIERS)
269  # Allow users to omit the SoC when building for a board with a single SoC.
270  list(LENGTH LIST_BOARD_SOCS socs_length)
271  if(socs_length EQUAL 1)
272    set(BOARD_SINGLE_SOC TRUE)
273    set(BOARD_${BOARD}_SINGLE_SOC TRUE)
274    if(NOT DEFINED BOARD_QUALIFIERS)
275      set(BOARD_QUALIFIERS "/${LIST_BOARD_SOCS}")
276    elseif("${BOARD_QUALIFIERS}" MATCHES "^//.*")
277      string(REGEX REPLACE "^//" "/${LIST_BOARD_SOCS}/" BOARD_QUALIFIERS "${BOARD_QUALIFIERS}")
278    endif()
279  endif()
280
281  set(board_targets ${LIST_BOARD_QUALIFIERS})
282  list(TRANSFORM board_targets PREPEND "${BOARD}/")
283  if(NOT ("${BOARD}${BOARD_QUALIFIERS}" IN_LIST board_targets))
284    string(REPLACE ";" "\n" board_targets "${board_targets}")
285    unset(CACHED_BOARD CACHE)
286    message(FATAL_ERROR "Board qualifiers `${BOARD_QUALIFIERS}` for board \
287          `${BOARD}` not found. Please specify a valid board target.\n"
288          "Valid board targets for ${LIST_BOARD_NAME} are:\n${board_targets}\n")
289  endif()
290endif()
291
292set(board_message "Board: ${BOARD}")
293
294if(DEFINED BOARD_REVISION)
295  set(board_message "${board_message}, Revision: ${BOARD_REVISION}")
296  if(DEFINED ACTIVE_BOARD_REVISION)
297    set(board_message "${board_message} (Active: ${ACTIVE_BOARD_REVISION})")
298    set(BOARD_REVISION ${ACTIVE_BOARD_REVISION})
299  endif()
300
301  string(REPLACE "." "_" BOARD_REVISION_STRING ${BOARD_REVISION})
302endif()
303
304if(DEFINED BOARD_QUALIFIERS)
305  string(REGEX REPLACE "^/" "qualifiers: " board_message_qualifiers "${BOARD_QUALIFIERS}")
306  set(board_message "${board_message}, ${board_message_qualifiers}")
307
308  string(REPLACE "/" "_" NORMALIZED_BOARD_QUALIFIERS "${BOARD_QUALIFIERS}")
309endif()
310
311set(NORMALIZED_BOARD_TARGET "${BOARD}${BOARD_QUALIFIERS}")
312string(REPLACE "/" "_" NORMALIZED_BOARD_TARGET "${NORMALIZED_BOARD_TARGET}")
313
314message(STATUS "${board_message}")
315
316add_custom_target(boards ${list_boards_commands} USES_TERMINAL)
317
318# Board extensions are enabled by default
319set(BOARD_EXTENSIONS ON CACHE BOOL "Support board extensions")
320zephyr_get(BOARD_EXTENSIONS)
321
322# Process board extensions
323if(BOARD_EXTENSIONS)
324  get_filename_component(board_dir_name ${BOARD_DIR} NAME)
325
326  foreach(root ${BOARD_ROOT})
327    set(board_extension_dir ${root}/boards/extensions/${board_dir_name})
328    if(NOT EXISTS ${board_extension_dir})
329      continue()
330    endif()
331
332    list(APPEND BOARD_EXTENSION_DIRS ${board_extension_dir})
333  endforeach()
334endif()
335build_info(board name VALUE ${BOARD})
336string(REGEX REPLACE "^/" "" qualifiers "${BOARD_QUALIFIERS}")
337build_info(board qualifiers VALUE ${qualifiers})
338build_info(board revision VALUE ${BOARD_REVISION})
339build_info(board path PATH ${BOARD_DIRECTORIES})
340