Browse Source

cmake: scripts: support board extension

Fixes: #69548

Support extending an existing board with new board variants.

This commit introduces the following changes to allow a board to be
extended out-of-tree.

The board yaml schema is extended to support an extend field which
will be used to identify the board to be extended.

A board 'plank' can be extended like this:
> board:
>   extend: plank
>   variants:
>     - name: ext
>       qualifier: soc1

For the rest of the build system this means that there is no longer a
single board directory.
The existing CMake variable BOARD_DIR is kept and reference the
directory which defines the board.
A new CMake variable BOARD_DIRECTORIES provides a list of all
directories which defines board targets for the board.
This means the directory which defines the board as well as all
directories that extends the board.

Signed-off-by: Torsten Rasmussen <Torsten.Rasmussen@nordicsemi.no>
pull/80202/head
Torsten Rasmussen 1 year ago committed by Henrik Brix Andersen
parent
commit
536d34fa7a
  1. 4
      Kconfig.zephyr
  2. 2
      boards/Kconfig
  3. 6
      boards/Kconfig.v1
  4. 2
      boards/Kconfig.v2
  5. 34
      cmake/modules/boards.cmake
  6. 40
      cmake/modules/dts.cmake
  7. 14
      cmake/modules/hwm_v2.cmake
  8. 7
      cmake/modules/kconfig.cmake
  9. 4
      cmake/modules/kernel.cmake
  10. 12
      doc/_extensions/zephyr/kconfig/__init__.py
  11. 4
      doc/_scripts/gen_boards_catalog.py
  12. 12
      scripts/ci/check_compliance.py
  13. 4
      scripts/ci/test_plan.py
  14. 2
      scripts/kconfig/lint.py
  15. 125
      scripts/list_boards.py
  16. 2
      scripts/pylib/twister/twisterlib/testplan.py
  17. 22
      scripts/schemas/board-schema.yml
  18. 4
      scripts/west_commands/boards.py
  19. 2
      share/sysbuild/Kconfig

4
Kconfig.zephyr

@ -17,13 +17,13 @@ osource "${APPLICATION_SOURCE_DIR}/VERSION"
# Shield defaults should have precedence over board defaults, which should have # Shield defaults should have precedence over board defaults, which should have
# precedence over SoC defaults, so include them in that order. # precedence over SoC defaults, so include them in that order.
# #
# $ARCH and $BOARD_DIR will be glob patterns when building documentation. # $ARCH and $KCONFIG_BOARD_DIR will be glob patterns when building documentation.
# This loads custom shields defconfigs (from BOARD_ROOT) # This loads custom shields defconfigs (from BOARD_ROOT)
osource "$(KCONFIG_BINARY_DIR)/Kconfig.shield.defconfig" osource "$(KCONFIG_BINARY_DIR)/Kconfig.shield.defconfig"
# This loads Zephyr base shield defconfigs # This loads Zephyr base shield defconfigs
source "boards/shields/*/Kconfig.defconfig" source "boards/shields/*/Kconfig.defconfig"
osource "$(BOARD_DIR)/Kconfig.defconfig" osource "$(KCONFIG_BOARD_DIR)/Kconfig.defconfig"
# This loads Zephyr specific SoC root defconfigs # This loads Zephyr specific SoC root defconfigs
source "$(KCONFIG_BINARY_DIR)/soc/Kconfig.defconfig" source "$(KCONFIG_BINARY_DIR)/soc/Kconfig.defconfig"

2
boards/Kconfig

@ -129,7 +129,7 @@ config QEMU_EXTRA_FLAGS
GDBstub over serial with `-serial tcp:127.0.0.1:5678,server` GDBstub over serial with `-serial tcp:127.0.0.1:5678,server`
# There might not be any board options, hence the optional source # There might not be any board options, hence the optional source
osource "$(BOARD_DIR)/Kconfig" osource "$(KCONFIG_BOARD_DIR)/Kconfig"
endmenu endmenu
config BOARD_HAS_TIMING_FUNCTIONS config BOARD_HAS_TIMING_FUNCTIONS

6
boards/Kconfig.v1

@ -2,9 +2,13 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# In HWMv1 the KCONFIG_BOARD_DIR points directly to the BOARD_DIR.
# Set the BOARD_DIR variable for backwards compatibility to legacy hardware model.
BOARD_DIR := $(KCONFIG_BOARD_DIR)
choice choice
prompt "Board Selection" prompt "Board Selection"
source "$(BOARD_DIR)/Kconfig.board" source "$(KCONFIG_BOARD_DIR)/Kconfig.board"
endchoice endchoice

2
boards/Kconfig.v2

@ -25,4 +25,4 @@ config BOARD_QUALIFIERS
For example, if building for ``nrf5340dk/nrf5340/cpuapp`` then this will contain the For example, if building for ``nrf5340dk/nrf5340/cpuapp`` then this will contain the
value ``nrf5340/cpuapp``. value ``nrf5340/cpuapp``.
osource "$(BOARD_DIR)/Kconfig.$(BOARD)" osource "$(KCONFIG_BOARD_DIR)/Kconfig.$(BOARD)"

34
cmake/modules/boards.cmake

@ -185,9 +185,7 @@ set(format_str "{NAME}\;{DIR}\;{HWM}\;")
set(format_str "${format_str}{REVISION_FORMAT}\;{REVISION_DEFAULT}\;{REVISION_EXACT}\;") set(format_str "${format_str}{REVISION_FORMAT}\;{REVISION_DEFAULT}\;{REVISION_EXACT}\;")
set(format_str "${format_str}{REVISIONS}\;{SOCS}\;{QUALIFIERS}") set(format_str "${format_str}{REVISIONS}\;{SOCS}\;{QUALIFIERS}")
if(BOARD_DIR) list(TRANSFORM BOARD_DIRECTORIES PREPEND "--board-dir=" OUTPUT_VARIABLE board_dir_arg)
set(board_dir_arg "--board-dir=${BOARD_DIR}")
endif()
execute_process(${list_boards_commands} --board=${BOARD} ${board_dir_arg} execute_process(${list_boards_commands} --board=${BOARD} ${board_dir_arg}
--cmakeformat=${format_str} --cmakeformat=${format_str}
OUTPUT_VARIABLE ret_board OUTPUT_VARIABLE ret_board
@ -200,29 +198,15 @@ endif()
if(NOT "${ret_board}" STREQUAL "") if(NOT "${ret_board}" STREQUAL "")
string(STRIP "${ret_board}" ret_board) string(STRIP "${ret_board}" ret_board)
string(FIND "${ret_board}" "\n" idx REVERSE) set(single_val "NAME;HWM;REVISION_FORMAT;REVISION_DEFAULT;REVISION_EXACT")
if(idx GREATER -1) set(multi_val "DIR;REVISIONS;SOCS;QUALIFIERS")
while(TRUE)
math(EXPR start "${idx} + 1")
string(SUBSTRING "${ret_board}" ${start} -1 line)
string(SUBSTRING "${ret_board}" 0 ${idx} ret_board)
cmake_parse_arguments(LIST_BOARD "" "DIR" "" ${line})
set(board_dirs "${board_dirs}\n${LIST_BOARD_DIR}")
if(idx EQUAL -1)
break()
endif()
string(FIND "${ret_board}" "\n" idx REVERSE)
endwhile()
message(FATAL_ERROR "Multiple boards named '${BOARD}' found in:${board_dirs}")
endif()
set(single_val "NAME;DIR;HWM;REVISION_FORMAT;REVISION_DEFAULT;REVISION_EXACT")
set(multi_val "REVISIONS;SOCS;QUALIFIERS")
cmake_parse_arguments(LIST_BOARD "" "${single_val}" "${multi_val}" ${ret_board}) cmake_parse_arguments(LIST_BOARD "" "${single_val}" "${multi_val}" ${ret_board})
set(BOARD_DIR ${LIST_BOARD_DIR} CACHE PATH "Board directory for board (${BOARD})" FORCE) list(GET LIST_BOARD_DIR 0 BOARD_DIR)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${BOARD_DIR}/board.yml) set(BOARD_DIR ${BOARD_DIR} CACHE PATH "Main board directory for board (${BOARD})" FORCE)
set(BOARD_DIRECTORIES ${LIST_BOARD_DIR} CACHE INTERNAL "List of board directories for board (${BOARD})" FORCE)
foreach(dir ${BOARD_DIRECTORIES})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${dir}/board.yml)
endforeach()
# Create two CMake variables identifying the hw model. # Create two CMake variables identifying the hw model.
# CMake variable: HWM=[v1,v2] # CMake variable: HWM=[v1,v2]

40
cmake/modules/dts.cmake

@ -76,9 +76,9 @@ find_package(Dtc 1.4.6)
# #
# Optional variables: # Optional variables:
# - BOARD: board name to use when looking for DTS_SOURCE # - BOARD: board name to use when looking for DTS_SOURCE
# - BOARD_DIR: board directory to use when looking for DTS_SOURCE # - BOARD_DIRECTORIES: list of board directories to use when looking for DTS_SOURCE
# - BOARD_REVISION_STRING: used when looking for a board revision's # - BOARD_REVISION_STRING: used when looking for a board revision's
# devicetree overlay file in BOARD_DIR # devicetree overlay file in one of the BOARD_DIRECTORIES
# - CMAKE_DTS_PREPROCESSOR: the path to the preprocessor to use # - CMAKE_DTS_PREPROCESSOR: the path to the preprocessor to use
# for devicetree files # for devicetree files
# - DTC_OVERLAY_FILE: list of devicetree overlay files which will be # - DTC_OVERLAY_FILE: list of devicetree overlay files which will be
@ -94,7 +94,7 @@ find_package(Dtc 1.4.6)
# C preprocessor when generating the devicetree from DTS_SOURCE # C preprocessor when generating the devicetree from DTS_SOURCE
# - DTS_SOURCE: the devicetree source file to use may be pre-set # - DTS_SOURCE: the devicetree source file to use may be pre-set
# with this variable; otherwise, it defaults to # with this variable; otherwise, it defaults to
# ${BOARD_DIR}/${BOARD}.dts # ${BOARD_DIRECTORIES}/<normalized_board_target>.dts
# #
# Variables set by this module and not mentioned above are for internal # Variables set by this module and not mentioned above are for internal
# use only, and may be removed, renamed, or re-purposed without prior notice. # use only, and may be removed, renamed, or re-purposed without prior notice.
@ -137,28 +137,30 @@ if(NOT DEFINED DTS_SOURCE)
zephyr_build_string(board_string SHORT shortened_board_string zephyr_build_string(board_string SHORT shortened_board_string
BOARD ${BOARD} BOARD_QUALIFIERS ${BOARD_QUALIFIERS} BOARD ${BOARD} BOARD_QUALIFIERS ${BOARD_QUALIFIERS}
) )
if(EXISTS ${BOARD_DIR}/${shortened_board_string}.dts AND NOT BOARD_${BOARD}_SINGLE_SOC) foreach(dir ${BOARD_DIRECTORIES})
message(FATAL_ERROR "Board ${ZFILE_BOARD} defines multiple SoCs.\nShortened file name " if(EXISTS ${dir}/${shortened_board_string}.dts AND NOT BOARD_${BOARD}_SINGLE_SOC)
"(${shortened_board_string}.dts) not allowed, use '<board>_<soc>.dts' naming" message(FATAL_ERROR "Board ${ZFILE_BOARD} defines multiple SoCs.\nShortened file name "
) "(${shortened_board_string}.dts) not allowed, use '<board>_<soc>.dts' naming"
elseif(EXISTS ${BOARD_DIR}/${board_string}.dts AND EXISTS ${BOARD_DIR}/${shortened_board_string}.dts) )
message(FATAL_ERROR "Conflicting file names discovered. Cannot use both " elseif(EXISTS ${dir}/${board_string}.dts AND EXISTS ${dir}/${shortened_board_string}.dts)
"${board_string}.dts and ${shortened_board_string}.dts. " message(FATAL_ERROR "Conflicting file names discovered. Cannot use both "
"Please choose one naming style, ${board_string}.dts is recommended." "${board_string}.dts and ${shortened_board_string}.dts. "
) "Please choose one naming style, ${board_string}.dts is recommended."
elseif(EXISTS ${BOARD_DIR}/${board_string}.dts) )
set(DTS_SOURCE ${BOARD_DIR}/${board_string}.dts) elseif(EXISTS ${dir}/${board_string}.dts)
elseif(EXISTS ${BOARD_DIR}/${shortened_board_string}.dts) set(DTS_SOURCE ${dir}/${board_string}.dts)
set(DTS_SOURCE ${BOARD_DIR}/${shortened_board_string}.dts) elseif(EXISTS ${dir}/${shortened_board_string}.dts)
endif() set(DTS_SOURCE ${dir}/${shortened_board_string}.dts)
endif()
endforeach()
endif() endif()
if(EXISTS ${DTS_SOURCE}) if(EXISTS ${DTS_SOURCE})
# We found a devicetree. Append all relevant dts overlays we can find... # We found a devicetree. Append all relevant dts overlays we can find...
zephyr_file(CONF_FILES ${BOARD_DIR} DTS DTS_SOURCE) zephyr_file(CONF_FILES ${BOARD_DIRECTORIES} DTS DTS_SOURCE)
zephyr_file( zephyr_file(
CONF_FILES ${BOARD_DIR} CONF_FILES ${BOARD_DIRECTORIES}
DTS no_rev_suffix_dts_board_overlays DTS no_rev_suffix_dts_board_overlays
BOARD ${BOARD} BOARD ${BOARD}
BOARD_QUALIFIERS ${BOARD_QUALIFIERS} BOARD_QUALIFIERS ${BOARD_QUALIFIERS}

14
cmake/modules/hwm_v2.cmake

@ -95,11 +95,15 @@ endwhile()
list(REMOVE_DUPLICATES kconfig_soc_source_dir) list(REMOVE_DUPLICATES kconfig_soc_source_dir)
# Support multiple ARCH_ROOT, SOC_ROOT and BOARD_ROOT # Support multiple ARCH_ROOT, SOC_ROOT and BOARD_ROOT
kconfig_gen("arch" "Kconfig" "${kconfig_arch_source_dir}" "Zephyr Arch Kconfig") kconfig_gen("arch" "Kconfig" "${kconfig_arch_source_dir}" "Zephyr Arch Kconfig")
kconfig_gen("soc" "Kconfig.defconfig" "${kconfig_soc_source_dir}" "Zephyr SoC defconfig") kconfig_gen("soc" "Kconfig.defconfig" "${kconfig_soc_source_dir}" "Zephyr SoC defconfig")
kconfig_gen("soc" "Kconfig" "${kconfig_soc_source_dir}" "Zephyr SoC Kconfig") kconfig_gen("soc" "Kconfig" "${kconfig_soc_source_dir}" "Zephyr SoC Kconfig")
kconfig_gen("soc" "Kconfig.soc" "${kconfig_soc_source_dir}" "SoC Kconfig") kconfig_gen("soc" "Kconfig.soc" "${kconfig_soc_source_dir}" "SoC Kconfig")
kconfig_gen("soc" "Kconfig.sysbuild" "${kconfig_soc_source_dir}" "Sysbuild SoC Kconfig") kconfig_gen("soc" "Kconfig.sysbuild" "${kconfig_soc_source_dir}" "Sysbuild SoC Kconfig")
kconfig_gen("boards" "Kconfig.defconfig" "${BOARD_DIRECTORIES}" "Zephyr board defconfig")
kconfig_gen("boards" "Kconfig.${BOARD}" "${BOARD_DIRECTORIES}" "board Kconfig")
kconfig_gen("boards" "Kconfig" "${BOARD_DIRECTORIES}" "Zephyr board Kconfig")
kconfig_gen("boards" "Kconfig.sysbuild" "${BOARD_DIRECTORIES}" "Sysbuild board Kconfig")
# Clear variables created by cmake_parse_arguments # Clear variables created by cmake_parse_arguments
unset(SOC_V2_NAME) unset(SOC_V2_NAME)

7
cmake/modules/kconfig.cmake

@ -21,9 +21,12 @@ file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/kconfig/include/config)
set_ifndef(KCONFIG_NAMESPACE "CONFIG") set_ifndef(KCONFIG_NAMESPACE "CONFIG")
set_ifndef(KCONFIG_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/Kconfig) set_ifndef(KCONFIG_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/Kconfig)
set(KCONFIG_BOARD_DIR ${KCONFIG_BINARY_DIR}/boards)
file(MAKE_DIRECTORY ${KCONFIG_BINARY_DIR}) file(MAKE_DIRECTORY ${KCONFIG_BINARY_DIR})
if(HWMv1) if(HWMv1)
# HWMv1 only supoorts a single board dir which points directly to the board dir.
set(KCONFIG_BOARD_DIR ${BOARD_DIR})
# Support multiple SOC_ROOT # Support multiple SOC_ROOT
file(MAKE_DIRECTORY ${KCONFIG_BINARY_DIR}/soc) file(MAKE_DIRECTORY ${KCONFIG_BINARY_DIR}/soc)
set(kconfig_soc_root ${SOC_ROOT}) set(kconfig_soc_root ${SOC_ROOT})
@ -73,7 +76,7 @@ else()
endif() endif()
if(NOT DEFINED BOARD_DEFCONFIG) if(NOT DEFINED BOARD_DEFCONFIG)
zephyr_file(CONF_FILES ${BOARD_DIR} DEFCONFIG BOARD_DEFCONFIG) zephyr_file(CONF_FILES ${BOARD_DIRECTORIES} DEFCONFIG BOARD_DEFCONFIG)
endif() endif()
if(DEFINED BOARD_REVISION) if(DEFINED BOARD_REVISION)
@ -157,7 +160,7 @@ set(COMMON_KCONFIG_ENV_SETTINGS
APP_VERSION_TWEAK_STRING=${APP_VERSION_TWEAK_STRING} APP_VERSION_TWEAK_STRING=${APP_VERSION_TWEAK_STRING}
CONFIG_=${KCONFIG_NAMESPACE}_ CONFIG_=${KCONFIG_NAMESPACE}_
KCONFIG_CONFIG=${DOTCONFIG} KCONFIG_CONFIG=${DOTCONFIG}
BOARD_DIR=${BOARD_DIR} KCONFIG_BOARD_DIR=${KCONFIG_BOARD_DIR}
BOARD=${BOARD} BOARD=${BOARD}
BOARD_REVISION=${BOARD_REVISION} BOARD_REVISION=${BOARD_REVISION}
BOARD_QUALIFIERS=${BOARD_QUALIFIERS} BOARD_QUALIFIERS=${BOARD_QUALIFIERS}

4
cmake/modules/kernel.cmake

@ -173,7 +173,9 @@ if(CONFIG_LLEXT AND CONFIG_LLEXT_TYPE_ELF_SHAREDLIB)
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
endif() endif()
include(${BOARD_DIR}/board.cmake OPTIONAL) foreach(dir ${BOARD_DIRECTORIES})
include(${dir}/board.cmake OPTIONAL)
endforeach()
# If we are using a suitable ethernet driver inside qemu, then these options # If we are using a suitable ethernet driver inside qemu, then these options
# must be set, otherwise a zephyr instance cannot receive any network packets. # must be set, otherwise a zephyr instance cannot receive any network packets.

12
doc/_extensions/zephyr/kconfig/__init__.py

@ -91,7 +91,7 @@ def kconfig_load(app: Sphinx) -> Tuple[kconfiglib.Kconfig, Dict[str, str]]:
root_args = argparse.Namespace(**{'soc_roots': [Path(ZEPHYR_BASE)]}) root_args = argparse.Namespace(**{'soc_roots': [Path(ZEPHYR_BASE)]})
v2_systems = list_hardware.find_v2_systems(root_args) v2_systems = list_hardware.find_v2_systems(root_args)
soc_folders = {soc.folder for soc in v2_systems.get_socs()} soc_folders = {soc.folder[0] for soc in v2_systems.get_socs()}
with open(Path(td) / "soc" / "Kconfig.defconfig", "w") as f: with open(Path(td) / "soc" / "Kconfig.defconfig", "w") as f:
f.write('') f.write('')
@ -114,8 +114,9 @@ def kconfig_load(app: Sphinx) -> Tuple[kconfiglib.Kconfig, Dict[str, str]]:
(Path(td) / 'boards').mkdir(exist_ok=True) (Path(td) / 'boards').mkdir(exist_ok=True)
root_args = argparse.Namespace(**{'board_roots': [Path(ZEPHYR_BASE)], root_args = argparse.Namespace(**{'board_roots': [Path(ZEPHYR_BASE)],
'soc_roots': [Path(ZEPHYR_BASE)], 'board': None}) 'soc_roots': [Path(ZEPHYR_BASE)], 'board': None,
v2_boards = list_boards.find_v2_boards(root_args) 'board_dir': []})
v2_boards = list_boards.find_v2_boards(root_args).values()
with open(Path(td) / "boards" / "Kconfig.boards", "w") as f: with open(Path(td) / "boards" / "Kconfig.boards", "w") as f:
for board in v2_boards: for board in v2_boards:
@ -126,7 +127,8 @@ def kconfig_load(app: Sphinx) -> Tuple[kconfiglib.Kconfig, Dict[str, str]]:
board_str = 'BOARD_' + re.sub(r"[^a-zA-Z0-9_]", "_", qualifier).upper() board_str = 'BOARD_' + re.sub(r"[^a-zA-Z0-9_]", "_", qualifier).upper()
f.write('config ' + board_str + '\n') f.write('config ' + board_str + '\n')
f.write('\t bool\n') f.write('\t bool\n')
f.write('source "' + (board.dir / ('Kconfig.' + board.name)).as_posix() + '"\n\n') f.write('source "' +
(board.directories[0] / ('Kconfig.' + board.name)).as_posix() + '"\n\n')
# base environment # base environment
os.environ["ZEPHYR_BASE"] = str(ZEPHYR_BASE) os.environ["ZEPHYR_BASE"] = str(ZEPHYR_BASE)
@ -140,7 +142,7 @@ def kconfig_load(app: Sphinx) -> Tuple[kconfiglib.Kconfig, Dict[str, str]]:
os.environ["HWM_SCHEME"] = "v2" os.environ["HWM_SCHEME"] = "v2"
os.environ["BOARD"] = "boards" os.environ["BOARD"] = "boards"
os.environ["BOARD_DIR"] = str(Path(td) / "boards") os.environ["KCONFIG_BOARD_DIR"] = str(Path(td) / "boards")
# insert external Kconfigs to the environment # insert external Kconfigs to the environment
module_paths = dict() module_paths = dict()

4
doc/_scripts/gen_boards_catalog.py

@ -70,7 +70,7 @@ def get_catalog():
arch_roots=module_settings["arch_root"], arch_roots=module_settings["arch_root"],
board_roots=module_settings["board_root"], board_roots=module_settings["board_root"],
soc_roots=module_settings["soc_root"], soc_roots=module_settings["soc_root"],
board_dir=ZEPHYR_BASE / "boards", board_dir=[],
board=None, board=None,
) )
@ -78,7 +78,7 @@ def get_catalog():
systems = list_hardware.find_v2_systems(args_find_boards) systems = list_hardware.find_v2_systems(args_find_boards)
board_catalog = {} board_catalog = {}
for board in boards: for board in boards.values():
# We could use board.vendor but it is often incorrect. Instead, deduce vendor from # We could use board.vendor but it is often incorrect. Instead, deduce vendor from
# containing folder. There are a few exceptions, like the "native" and "others" folders # containing folder. There are a few exceptions, like the "native" and "others" folders
# which we know are not actual vendors so treat them as such. # which we know are not actual vendors so treat them as such.

12
scripts/ci/check_compliance.py

@ -511,8 +511,9 @@ class KconfigCheck(ComplianceTest):
soc_roots = self.get_module_setting_root('soc', settings_file) soc_roots = self.get_module_setting_root('soc', settings_file)
soc_roots.insert(0, Path(ZEPHYR_BASE)) soc_roots.insert(0, Path(ZEPHYR_BASE))
root_args = argparse.Namespace(**{'board_roots': board_roots, root_args = argparse.Namespace(**{'board_roots': board_roots,
'soc_roots': soc_roots, 'board': None}) 'soc_roots': soc_roots, 'board': None,
v2_boards = list_boards.find_v2_boards(root_args) 'board_dir': []})
v2_boards = list_boards.find_v2_boards(root_args).values()
with open(kconfig_defconfig_file, 'w') as fp: with open(kconfig_defconfig_file, 'w') as fp:
for board in v2_boards: for board in v2_boards:
@ -546,7 +547,7 @@ class KconfigCheck(ComplianceTest):
root_args = argparse.Namespace(**{'soc_roots': soc_roots}) root_args = argparse.Namespace(**{'soc_roots': soc_roots})
v2_systems = list_hardware.find_v2_systems(root_args) v2_systems = list_hardware.find_v2_systems(root_args)
soc_folders = {soc.folder for soc in v2_systems.get_socs()} soc_folders = {soc.folder[0] for soc in v2_systems.get_socs()}
with open(kconfig_defconfig_file, 'w') as fp: with open(kconfig_defconfig_file, 'w') as fp:
for folder in soc_folders: for folder in soc_folders:
fp.write('osource "' + (Path(folder) / 'Kconfig.defconfig').as_posix() + '"\n') fp.write('osource "' + (Path(folder) / 'Kconfig.defconfig').as_posix() + '"\n')
@ -616,7 +617,7 @@ class KconfigCheck(ComplianceTest):
os.makedirs(os.path.join(kconfiglib_dir, 'soc'), exist_ok=True) os.makedirs(os.path.join(kconfiglib_dir, 'soc'), exist_ok=True)
os.makedirs(os.path.join(kconfiglib_dir, 'arch'), exist_ok=True) os.makedirs(os.path.join(kconfiglib_dir, 'arch'), exist_ok=True)
os.environ["BOARD_DIR"] = kconfiglib_boards_dir os.environ["KCONFIG_BOARD_DIR"] = kconfiglib_boards_dir
self.get_v2_model(kconfiglib_dir, os.path.join(kconfiglib_dir, "settings_file.txt")) self.get_v2_model(kconfiglib_dir, os.path.join(kconfiglib_dir, "settings_file.txt"))
# Tells Kconfiglib to generate warnings for all references to undefined # Tells Kconfiglib to generate warnings for all references to undefined
@ -920,6 +921,9 @@ flagged.
# Zephyr toolchain variant and therefore not # Zephyr toolchain variant and therefore not
# visible to compliance. # visible to compliance.
"BOARD_", # Used as regex in scripts/utils/board_v1_to_v2.py "BOARD_", # Used as regex in scripts/utils/board_v1_to_v2.py
"BOARD_MPS2_AN521_CPUTEST", # Used for board and SoC extension feature tests
"BOARD_NATIVE_SIM_NATIVE_64_TWO", # Used for board and SoC extension feature tests
"BOARD_NATIVE_SIM_NATIVE_ONE", # Used for board and SoC extension feature tests
"BOOT_DIRECT_XIP", # Used in sysbuild for MCUboot configuration "BOOT_DIRECT_XIP", # Used in sysbuild for MCUboot configuration
"BOOT_DIRECT_XIP_REVERT", # Used in sysbuild for MCUboot configuration "BOOT_DIRECT_XIP_REVERT", # Used in sysbuild for MCUboot configuration
"BOOT_FIRMWARE_LOADER", # Used in sysbuild for MCUboot configuration "BOOT_FIRMWARE_LOADER", # Used in sysbuild for MCUboot configuration

4
scripts/ci/test_plan.py

@ -239,12 +239,12 @@ class Filters:
# Look for boards in monitored repositories # Look for boards in monitored repositories
lb_args = argparse.Namespace(**{'arch_roots': roots, 'board_roots': roots, 'board': None, 'soc_roots':roots, lb_args = argparse.Namespace(**{'arch_roots': roots, 'board_roots': roots, 'board': None, 'soc_roots':roots,
'board_dir': None}) 'board_dir': None})
known_boards = list_boards.find_v2_boards(lb_args) known_boards = list_boards.find_v2_boards(lb_args).values()
for changed in changed_boards: for changed in changed_boards:
for board in known_boards: for board in known_boards:
c = (zephyr_base / changed).resolve() c = (zephyr_base / changed).resolve()
if c.is_relative_to(board.dir.resolve()): if c.is_relative_to(board.directories[0].resolve()):
for file in glob.glob(os.path.join(board.dir, f"{board.name}*.yaml")): for file in glob.glob(os.path.join(board.dir, f"{board.name}*.yaml")):
with open(file, 'r', encoding='utf-8') as f: with open(file, 'r', encoding='utf-8') as f:
b = yaml.load(f.read(), Loader=SafeLoader) b = yaml.load(f.read(), Loader=SafeLoader)

2
scripts/kconfig/lint.py

@ -209,7 +209,7 @@ def init_kconfig():
ZEPHYR_BASE=TOP_DIR, ZEPHYR_BASE=TOP_DIR,
SOC_DIR="soc", SOC_DIR="soc",
ARCH_DIR="arch", ARCH_DIR="arch",
BOARD_DIR="boards/*/*", KCONFIG_BOARD_DIR="boards/*/*",
ARCH="*") ARCH="*")
kconf = kconfiglib.Kconfig(suppress_traceback=True) kconf = kconfiglib.Kconfig(suppress_traceback=True)

125
scripts/list_boards.py

@ -4,13 +4,13 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import argparse import argparse
from collections import defaultdict from collections import defaultdict, Counter
from dataclasses import dataclass, field from dataclasses import dataclass, field
import itertools import itertools
from pathlib import Path from pathlib import Path
import pykwalify.core import pykwalify.core
import sys import sys
from typing import List from typing import List, Union
import yaml import yaml
import list_hardware import list_hardware
from list_hardware import unique_paths from list_hardware import unique_paths
@ -91,7 +91,8 @@ class Soc:
@dataclass(frozen=True) @dataclass(frozen=True)
class Board: class Board:
name: str name: str
dir: Path # HWMv1 only supports a single Path, and requires Board dataclass to be hashable.
directories: Union[Path, List[Path]]
hwm: str hwm: str
full_name: str = None full_name: str = None
arch: str = None arch: str = None
@ -103,6 +104,41 @@ class Board:
socs: List[Soc] = field(default_factory=list, compare=False) socs: List[Soc] = field(default_factory=list, compare=False)
variants: List[str] = field(default_factory=list, compare=False) variants: List[str] = field(default_factory=list, compare=False)
def from_qualifier(self, qualifiers):
qualifiers_list = qualifiers.split('/')
node = Soc(None)
n = len(qualifiers_list)
if n > 0:
soc_qualifier = qualifiers_list.pop(0)
for s in self.socs:
if s.name == soc_qualifier:
node = s
break
if n > 1:
if node.cpuclusters:
cpu_qualifier = qualifiers_list.pop(0)
for c in node.cpuclusters:
if c.name == cpu_qualifier:
node = c
break
else:
node = Variant(None)
for q in qualifiers_list:
for v in node.variants:
if v.name == q:
node = v
break
else:
node = Variant(None)
if node in (Soc(None), Variant(None)):
sys.exit(f'ERROR: qualifiers {qualifiers} not found when extending board {self.name}')
return node
def board_key(board): def board_key(board):
return board.name return board.name
@ -165,11 +201,10 @@ def find_arch2board_set_in(root, arches, board_dir):
for arch in arches: for arch in arches:
if not (boards / arch).is_dir(): if not (boards / arch).is_dir():
continue continue
for maybe_board in (boards / arch).iterdir(): for maybe_board in (boards / arch).iterdir():
if not maybe_board.is_dir(): if not maybe_board.is_dir():
continue continue
if board_dir is not None and board_dir != maybe_board: if board_dir and maybe_board not in board_dir:
continue continue
for maybe_defconfig in maybe_board.iterdir(): for maybe_defconfig in maybe_board.iterdir():
file_name = maybe_defconfig.name file_name = maybe_defconfig.name
@ -181,7 +216,8 @@ def find_arch2board_set_in(root, arches, board_dir):
def load_v2_boards(board_name, board_yml, systems): def load_v2_boards(board_name, board_yml, systems):
boards = [] boards = {}
board_extensions = []
if board_yml.is_file(): if board_yml.is_file():
with board_yml.open('r', encoding='utf-8') as f: with board_yml.open('r', encoding='utf-8') as f:
b = yaml.load(f.read(), Loader=SafeLoader) b = yaml.load(f.read(), Loader=SafeLoader)
@ -199,6 +235,18 @@ def load_v2_boards(board_name, board_yml, systems):
board_array = b.get('boards', [b.get('board', None)]) board_array = b.get('boards', [b.get('board', None)])
for board in board_array: for board in board_array:
mutual_exclusive = {'name', 'extend'}
if len(mutual_exclusive - board.keys()) < 1:
sys.exit(f'ERROR: Malformed "board" section in file: {board_yml.as_posix()}\n'
f'{mutual_exclusive} are mutual exclusive at this level.')
# This is a extending an existing board, place in array to allow later processing.
if 'extend' in board:
board.update({'dir': board_yml.parent})
board_extensions.append(board)
continue
# Create board
if board_name is not None: if board_name is not None:
if board['name'] != board_name: if board['name'] != board_name:
# Not the board we're looking for, ignore. # Not the board we're looking for, ignore.
@ -220,9 +268,9 @@ def load_v2_boards(board_name, board_yml, systems):
socs = [Soc.from_soc(systems.get_soc(s['name']), s.get('variants', [])) socs = [Soc.from_soc(systems.get_soc(s['name']), s.get('variants', []))
for s in board.get('socs', {})] for s in board.get('socs', {})]
board = Board( boards[board['name']] = Board(
name=board['name'], name=board['name'],
dir=board_yml.parent, directories=[board_yml.parent],
vendor=board.get('vendor'), vendor=board.get('vendor'),
full_name=board.get('full_name'), full_name=board.get('full_name'),
revision_format=board.get('revision', {}).get('format'), revision_format=board.get('revision', {}).get('format'),
@ -234,8 +282,28 @@ def load_v2_boards(board_name, board_yml, systems):
variants=[Variant.from_dict(v) for v in board.get('variants', [])], variants=[Variant.from_dict(v) for v in board.get('variants', [])],
hwm='v2', hwm='v2',
) )
boards.append(board) board_qualifiers = board_v2_qualifiers(boards[board['name']])
return boards duplicates = [q for q, n in Counter(board_qualifiers).items() if n > 1]
if duplicates:
sys.exit(f'ERROR: Duplicated board qualifiers detected {duplicates} for board: '
f'{board["name"]}.\nPlease check content of: {board_yml.as_posix()}\n')
return boards, board_extensions
def extend_v2_boards(boards, board_extensions):
for e in board_extensions:
board = boards.get(e['extend'])
if board is None:
continue
board.directories.append(e['dir'])
for v in e.get('variants', []):
node = board.from_qualifier(v['qualifier'])
if str(v['qualifier'] + '/' + v['name']) in board_v2_qualifiers(board):
board_yml = e['dir'] / BOARD_YML
sys.exit(f'ERROR: Variant: {v["name"]}, defined multiple times for board: '
f'{board.name}.\nLast defined in {board_yml}')
node.variants.append(Variant.from_dict(v))
# Note that this does not share the args.board functionality of find_v2_boards # Note that this does not share the args.board functionality of find_v2_boards
@ -253,14 +321,25 @@ def find_v2_boards(args):
root_args = argparse.Namespace(**{'soc_roots': args.soc_roots}) root_args = argparse.Namespace(**{'soc_roots': args.soc_roots})
systems = list_hardware.find_v2_systems(root_args) systems = list_hardware.find_v2_systems(root_args)
boards = [] boards = {}
board_extensions = []
board_files = [] board_files = []
for root in unique_paths(args.board_roots): if args.board_dir:
board_files.extend((root / 'boards').rglob(BOARD_YML)) board_files = [d / BOARD_YML for d in args.board_dir]
else:
for root in unique_paths(args.board_roots):
board_files.extend((root / 'boards').rglob(BOARD_YML))
for board_yml in board_files: for board_yml in board_files:
b = load_v2_boards(args.board, board_yml, systems) b, e = load_v2_boards(args.board, board_yml, systems)
boards.extend(b) conflict_boards = set(boards.keys()).intersection(b.keys())
if conflict_boards:
sys.exit(f'ERROR: Board(s): {conflict_boards}, defined multiple times.\n'
f'Last defined in {board_yml}')
boards.update(b)
board_extensions.extend(e)
extend_v2_boards(boards, board_extensions)
return boards return boards
@ -285,7 +364,7 @@ def add_args(parser):
help='add a soc root, may be given more than once') help='add a soc root, may be given more than once')
parser.add_argument("--board", dest='board', default=None, parser.add_argument("--board", dest='board', default=None,
help='lookup the specific board, fail if not found') help='lookup the specific board, fail if not found')
parser.add_argument("--board-dir", default=None, type=Path, parser.add_argument("--board-dir", default=[], type=Path, action='append',
help='Only look for boards at the specific location') help='Only look for boards at the specific location')
@ -327,20 +406,16 @@ def board_v2_qualifiers_csv(board):
def dump_v2_boards(args): def dump_v2_boards(args):
if args.board_dir: boards = find_v2_boards(args)
root_args = argparse.Namespace(**{'soc_roots': args.soc_roots})
systems = list_hardware.find_v2_systems(root_args)
boards = load_v2_boards(args.board, args.board_dir / BOARD_YML, systems)
else:
boards = find_v2_boards(args)
for b in boards: for b in boards.values():
qualifiers_list = board_v2_qualifiers(b) qualifiers_list = board_v2_qualifiers(b)
if args.cmakeformat is not None: if args.cmakeformat is not None:
notfound = lambda x: x or 'NOTFOUND' notfound = lambda x: x or 'NOTFOUND'
info = args.cmakeformat.format( info = args.cmakeformat.format(
NAME='NAME;' + b.name, NAME='NAME;' + b.name,
DIR='DIR;' + str(b.dir.as_posix()), DIR='DIR;' + ';'.join(
[str(x.as_posix()) for x in b.directories]),
VENDOR='VENDOR;' + notfound(b.vendor), VENDOR='VENDOR;' + notfound(b.vendor),
HWM='HWM;' + b.hwm, HWM='HWM;' + b.hwm,
REVISION_DEFAULT='REVISION_DEFAULT;' + notfound(b.revision_default), REVISION_DEFAULT='REVISION_DEFAULT;' + notfound(b.revision_default),
@ -365,7 +440,7 @@ def dump_boards(args):
if args.cmakeformat is not None: if args.cmakeformat is not None:
info = args.cmakeformat.format( info = args.cmakeformat.format(
NAME='NAME;' + board.name, NAME='NAME;' + board.name,
DIR='DIR;' + str(board.dir.as_posix()), DIR='DIR;' + str(board.directories.as_posix()),
HWM='HWM;' + board.hwm, HWM='HWM;' + board.hwm,
VENDOR='VENDOR;NOTFOUND', VENDOR='VENDOR;NOTFOUND',
REVISION_DEFAULT='REVISION_DEFAULT;NOTFOUND', REVISION_DEFAULT='REVISION_DEFAULT;NOTFOUND',

2
scripts/pylib/twister/twisterlib/testplan.py

@ -442,7 +442,7 @@ class TestPlan:
logger.debug(f"Adding platform {platform.name} with aliases {platform.aliases}") logger.debug(f"Adding platform {platform.name} with aliases {platform.aliases}")
self.platforms.append(platform) self.platforms.append(platform)
for board in known_boards: for board in known_boards.values():
new_config_found = False new_config_found = False
# don't load the same board data twice # don't load the same board data twice
if not bdirs.get(board.dir): if not bdirs.get(board.dir):

22
scripts/schemas/board-schema.yml

@ -23,17 +23,33 @@ schema;variant-schema:
required: false required: false
include: variant-schema include: variant-schema
schema;extend-variant-schema:
required: false
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
qualifier:
required: true
type: str
schema;board-schema: schema;board-schema:
type: map type: map
mapping: mapping:
name: name:
required: true required: false # Note: either name or extend is required, but that is handled in python
type: str type: str
desc: Name of the board desc: Name of the board
full_name: full_name:
required: false required: false
type: str type: str
desc: Full name of the board. Typically set to the commercial name of the board. desc: Full name of the board. Typically set to the commercial name of the board.
extend:
required: false # Note: either name or extend is required, but that is handled in python
type: str
vendor: vendor:
required: false required: false
type: str type: str
@ -63,7 +79,7 @@ schema;board-schema:
required: true required: true
type: str type: str
socs: socs:
required: true required: false # Required for name:, but not for extend.
type: seq type: seq
sequence: sequence:
- type: map - type: map
@ -73,6 +89,8 @@ schema;board-schema:
type: str type: str
variants: variants:
include: variant-schema include: variant-schema
variants:
include: extend-variant-schema
type: map type: map
mapping: mapping:

4
scripts/west_commands/boards.py

@ -97,14 +97,14 @@ class Boards(WestCommand):
log.inf(args.format.format(name=board.name, arch=board.arch, log.inf(args.format.format(name=board.name, arch=board.arch,
dir=board.dir, hwm=board.hwm, qualifiers='')) dir=board.dir, hwm=board.hwm, qualifiers=''))
for board in list_boards.find_v2_boards(args): for board in list_boards.find_v2_boards(args).values():
if name_re is not None and not name_re.search(board.name): if name_re is not None and not name_re.search(board.name):
continue continue
log.inf( log.inf(
args.format.format( args.format.format(
name=board.name, name=board.name,
full_name=board.full_name, full_name=board.full_name,
dir=board.dir, dir=board.directories[0],
hwm=board.hwm, hwm=board.hwm,
vendor=board.vendor, vendor=board.vendor,
qualifiers=list_boards.board_v2_qualifiers_csv(board), qualifiers=list_boards.board_v2_qualifiers_csv(board),

2
share/sysbuild/Kconfig

@ -6,7 +6,7 @@ rsource "Kconfig.$(HWM_SCHEME)"
comment "Sysbuild image configuration" comment "Sysbuild image configuration"
osource "$(BOARD_DIR)/Kconfig.sysbuild" osource "$(KCONFIG_BOARD_DIR)/Kconfig.sysbuild"
osource "$(KCONFIG_BINARY_DIR)/soc/Kconfig.sysbuild" osource "$(KCONFIG_BINARY_DIR)/soc/Kconfig.sysbuild"
menu "Modules" menu "Modules"

Loading…
Cancel
Save