cmake: support versioning of board

This commit introduces support for versioning of boards.
The existing board handling is limited in such a way that it is not
possible to support a specific board in multiple variants.

This commit introduces versioning of board revisions so that it is
possible to support minor variations to a board without having to
defining a completely new board.

This can be done by adding a revision.cmake file in the board folder:
boards/<arch>/<board-dir>/revision.cmake

Depending on the revision format chosen, additional configuration files
for each revision available must also be added, those have the form:
boards/<arch>/<board-dir>/<board>_<revision>.conf

Examples:
boards/<arch>/<board-dir>/<board>_defconfig:  Common board settings

Revision format: MAJOR.MINOR.PATCH
boards/<arch>/<board-dir>/<board>_0_5_0.conf: Revision 0.5.0
boards/<arch>/<board-dir>/<board>_1_0_0.conf: Revision 1.0.0
boards/<arch>/<board-dir>/<board>_1_5_0.conf: Revision 1.5.0

Revision format: LETTER
boards/<arch>/<board-dir>/<board>_A.conf:     Revision A
boards/<arch>/<board-dir>/<board>_B.conf:     Revision B

The `board_check_revision` function is available in `extensions.cmake`
to facilitate board revision handling in `revision.cmake`.

User select the board revision using: `-DBOARD=<board>@<revision>`, as
example `-DBOARD=plank@0.5.0`.

If a shield, test, sample, or application needs to specify DTS overlay
or Kconfig fragments, this can be done by adding revision specific
configuration files in the sample/test/shield folder, like this:
<shield/sample-path>/boards/<board>.conf
<shield/sample-path>/boards/<board>_<revision>.conf

or if there is there is only a need for adjusting on a given board
revision:
<shield/sample-path>/boards/<board>_<revision>.conf

Similar for DTS overlay files:
<shield-path>/boards/<board>.overlay
<shield-path>/boards/<board>_<revision>.overlay

or:
<shield-path>/boards/<board>_<revision>.conf

For test/samples/apps:
<sample-path>/<board>.overlay
<sample-path>/<board>_<revision>.overlay

or:
<sample-path>/<board>_<revision>.overlay

Signed-off-by: Torsten Rasmussen <Torsten.Rasmussen@nordicsemi.no>
Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Torsten Rasmussen 2020-11-11 10:10:41 +01:00 committed by Anas Nashif
commit 77ecd6837d
4 changed files with 265 additions and 61 deletions

View file

@ -190,9 +190,31 @@ add_custom_target(
# Dummy add to generate files.
zephyr_linker_sources(SECTIONS)
# 'BOARD_ROOT' is a prioritized list of directories where boards may
# be found. It always includes ${ZEPHYR_BASE} at the lowest priority.
zephyr_file(APPLICATION_ROOT BOARD_ROOT)
list(APPEND BOARD_ROOT ${ZEPHYR_BASE})
# 'SOC_ROOT' is a prioritized list of directories where socs may be
# found. It always includes ${ZEPHYR_BASE}/soc at the lowest priority.
zephyr_file(APPLICATION_ROOT SOC_ROOT)
list(APPEND SOC_ROOT ${ZEPHYR_BASE})
# 'ARCH_ROOT' is a prioritized list of directories where archs may be
# found. It always includes ${ZEPHYR_BASE} at the lowest priority.
zephyr_file(APPLICATION_ROOT ARCH_ROOT)
list(APPEND ARCH_ROOT ${ZEPHYR_BASE})
# Check that BOARD has been provided, and that it has not changed.
zephyr_check_cache(BOARD REQUIRED)
string(FIND "${BOARD}" "@" REVIVISION_SEPARATOR_INDEX)
if(NOT (REVIVISION_SEPARATOR_INDEX EQUAL -1))
math(EXPR BOARD_REVISION_INDEX "${REVIVISION_SEPARATOR_INDEX} + 1")
string(SUBSTRING ${BOARD} ${BOARD_REVISION_INDEX} -1 BOARD_REVISION)
string(SUBSTRING ${BOARD} 0 ${REVIVISION_SEPARATOR_INDEX} BOARD)
endif()
set(BOARD_MESSAGE "Board: ${BOARD}")
if(DEFINED ENV{ZEPHYR_BOARD_ALIASES})
@ -210,40 +232,6 @@ if(${BOARD}_DEPRECATED)
message(WARNING "Deprecated BOARD=${BOARD_DEPRECATED} name specified, board automatically changed to: ${BOARD}.")
endif()
# Check that SHIELD has not changed.
zephyr_check_cache(SHIELD)
if(SHIELD)
set(BOARD_MESSAGE "${BOARD_MESSAGE}, Shield(s): ${SHIELD}")
endif()
message(STATUS "${BOARD_MESSAGE}")
# 'BOARD_ROOT' is a prioritized list of directories where boards may
# be found. It always includes ${ZEPHYR_BASE} at the lowest priority.
zephyr_file(APPLICATION_ROOT BOARD_ROOT)
list(APPEND BOARD_ROOT ${ZEPHYR_BASE})
# 'SOC_ROOT' is a prioritized list of directories where socs may be
# found. It always includes ${ZEPHYR_BASE}/soc at the lowest priority.
zephyr_file(APPLICATION_ROOT SOC_ROOT)
list(APPEND SOC_ROOT ${ZEPHYR_BASE})
# 'ARCH_ROOT' is a prioritized list of directories where archs may be
# found. It always includes ${ZEPHYR_BASE} at the lowest priority.
zephyr_file(APPLICATION_ROOT ARCH_ROOT)
list(APPEND ARCH_ROOT ${ZEPHYR_BASE})
if(DEFINED SHIELD)
string(REPLACE " " ";" SHIELD_AS_LIST "${SHIELD}")
endif()
# SHIELD-NOTFOUND is a real CMake list, from which valid shields can be popped.
# After processing all shields, only invalid shields will be left in this list.
set(SHIELD-NOTFOUND ${SHIELD_AS_LIST})
# Use BOARD to search for a '_defconfig' file.
# e.g. zephyr/boards/arm/96b_carbon_nrf51/96b_carbon_nrf51_defconfig.
# When found, use that path to infer the ARCH we are building for.
foreach(root ${BOARD_ROOT})
# NB: find_path will return immediately if the output variable is
# already set
@ -265,7 +253,46 @@ foreach(root ${BOARD_ROOT})
if(BOARD_DIR AND NOT (${root} STREQUAL ${ZEPHYR_BASE}))
set(USING_OUT_OF_TREE_BOARD 1)
endif()
endforeach()
if(EXISTS ${BOARD_DIR}/revision.cmake)
# Board provides revision handling.
include(${BOARD_DIR}/revision.cmake)
elseif(BOARD_REVISION)
message(WARNING "Board revision ${BOARD_REVISION} specified for ${BOARD}, \
but board has no revision so revision will be ignored.")
endif()
if(DEFINED BOARD_REVISION)
set(BOARD_MESSAGE "${BOARD_MESSAGE}, Revision: ${BOARD_REVISION}")
if(DEFINED ACTIVE_BOARD_REVISION)
set(BOARD_MESSAGE "${BOARD_MESSAGE} (Active: ${ACTIVE_BOARD_REVISION})")
set(BOARD_REVISION ${ACTIVE_BOARD_REVISION})
endif()
string(REPLACE "." "_" BOARD_REVISION_STRING ${BOARD_REVISION})
endif()
# Check that SHIELD has not changed.
zephyr_check_cache(SHIELD)
if(SHIELD)
set(BOARD_MESSAGE "${BOARD_MESSAGE}, Shield(s): ${SHIELD}")
endif()
message(STATUS "${BOARD_MESSAGE}")
if(DEFINED SHIELD)
string(REPLACE " " ";" SHIELD_AS_LIST "${SHIELD}")
endif()
# SHIELD-NOTFOUND is a real CMake list, from which valid shields can be popped.
# After processing all shields, only invalid shields will be left in this list.
set(SHIELD-NOTFOUND ${SHIELD_AS_LIST})
# Use BOARD to search for a '_defconfig' file.
# e.g. zephyr/boards/arm/96b_carbon_nrf51/96b_carbon_nrf51_defconfig.
# When found, use that path to infer the ARCH we are building for.
foreach(root ${BOARD_ROOT})
set(shield_dir ${root}/boards/shields)
# Match the .overlay files in the shield directories to make sure we are
# finding shields, e.g. x_nucleo_iks01a1/x_nucleo_iks01a1.overlay
@ -380,10 +407,12 @@ if(DEFINED CONF_FILE)
get_filename_component(CONF_FILE_NAME ${CONF_FILE} NAME)
get_filename_component(CONF_FILE_DIR ${CONF_FILE} DIRECTORY)
if(${CONF_FILE_NAME} MATCHES "prj_(.*).conf")
set(CONF_FILE_BUILD_TYPE ${CMAKE_MATCH_1})
set(CONF_FILE_INCLUDE_FRAGMENTS true)
if(NOT IS_ABSOLUTE ${CONF_FILE_DIR})
set(CONF_FILE_DIR ${APPLICATION_SOURCE_DIR}/${CONF_FILE_DIR})
endif()
zephyr_file(CONF_FILES ${CONF_FILE_DIR}/boards KCONF CONF_FILE BUILD ${CMAKE_MATCH_1})
endif()
endif()
elseif(CACHED_CONF_FILE)
@ -401,11 +430,16 @@ elseif(COMMAND set_conf_file)
elseif(EXISTS ${APPLICATION_SOURCE_DIR}/prj_${BOARD}.conf)
set(CONF_FILE ${APPLICATION_SOURCE_DIR}/prj_${BOARD}.conf)
elseif(EXISTS ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.conf)
set(CONF_FILE ${APPLICATION_SOURCE_DIR}/prj.conf ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.conf)
elseif(EXISTS ${APPLICATION_SOURCE_DIR}/prj.conf)
set(CONF_FILE ${APPLICATION_SOURCE_DIR}/prj.conf)
set(CONF_FILE_INCLUDE_FRAGMENTS true)
endif()
if(CONF_FILE_INCLUDE_FRAGMENTS)
if(NOT CONF_FILE_DIR)
set(CONF_FILE_DIR ${APPLICATION_SOURCE_DIR})
endif()
zephyr_file(CONF_FILES ${CONF_FILE_DIR}/boards KCONF CONF_FILE BUILD ${CONF_FILE_BUILD_TYPE})
endif()
set(CACHED_CONF_FILE ${CONF_FILE} CACHE STRING "If desired, you can build the application using\
@ -424,6 +458,9 @@ elseif(DEFINED ENV{DTC_OVERLAY_FILE})
set(DTC_OVERLAY_FILE $ENV{DTC_OVERLAY_FILE})
elseif(EXISTS ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.overlay)
set(DTC_OVERLAY_FILE ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.overlay)
elseif((DEFINED BOARD_REVISION) AND
EXISTS ${APPLICATION_SOURCE_DIR}/${BOARD}_${BOARD_REVISION_STRING}.overlay)
set(DTC_OVERLAY_FILE ${APPLICATION_SOURCE_DIR}/${BOARD}_${BOARD_REVISION_STRING}.overlay)
elseif(EXISTS ${APPLICATION_SOURCE_DIR}/${BOARD}.overlay)
set(DTC_OVERLAY_FILE ${APPLICATION_SOURCE_DIR}/${BOARD}.overlay)
elseif(EXISTS ${APPLICATION_SOURCE_DIR}/app.overlay)

View file

@ -45,19 +45,22 @@ list(REMOVE_DUPLICATES
DTS_ROOT
)
set(dts_files
${DTS_SOURCE}
${shield_dts_files}
)
# TODO: What to do about non-posix platforms where NOT CONFIG_HAS_DTS (xtensa)?
# Drop support for NOT CONFIG_HAS_DTS perhaps?
if(EXISTS ${DTS_SOURCE})
set(SUPPORTS_DTS 1)
if(BOARD_REVISION AND EXISTS ${BOARD_DIR}/${BOARD}_${BOARD_REVISION_STRING}.overlay)
list(APPEND DTS_SOURCE ${BOARD_DIR}/${BOARD}_${BOARD_REVISION_STRING}.overlay)
endif()
else()
set(SUPPORTS_DTS 0)
endif()
set(dts_files
${DTS_SOURCE}
${shield_dts_files}
)
if(SUPPORTS_DTS)
if(DTC_OVERLAY_FILE)
# Convert from space-separated files into file list

View file

@ -630,10 +630,17 @@ endfunction()
# 1.4. board_*
#
# This section is for extensions which control Zephyr's board runners
# from the build system. The Zephyr build system has targets for
# flashing and debugging supported boards. These are wrappers around a
# "runner" Python subpackage that is part of Zephyr's "west" tool.
# This section is for extensions related to Zephyr board handling.
#
# Zephyr board extensions current contains:
# - Board runners
# - Board revision
# Zephyr board runners:
# Zephyr board runner extension functions control Zephyr's board runners
# from the build system. The Zephyr build system has targets for
# flashing and debugging supported boards. These are wrappers around a
# "runner" Python subpackage that is part of Zephyr's "west" tool.
#
# This section provides glue between CMake and the Python code that
# manages the runners.
@ -767,6 +774,148 @@ function(board_finalize_runner_args runner)
set_property(GLOBAL APPEND PROPERTY ZEPHYR_RUNNERS ${runner})
endfunction()
# Zephyr board revision:
#
# This section provides a function for revision checking.
# Usage:
# board_check_revision(FORMAT <LETTER | MAJOR.MINOR.PATCH>
# [EXACT]
# [DEFAULT_REVISION <revision>]
# [HIGHEST_REVISION <revision>]
# )
#
# Zephyr board extension function.
#
# This function can be used in `boards/<board>/revision.cmake` to check a user
# requested revision against available board revisions.
#
# The function will check the revision from `-DBOARD=<board>@<revision>` that
# is provided by the user according to the arguments.
# When `EXACT` is not specified, this function will set the Zephyr build system
# variable `ACTIVE_BOARD_REVISION` with the selected revision.
#
# FORMAT <LETTER | MAJOR.MINOR.PATCH>: Specify the revision format.
# LETTER: Revision format is a single letter from A - Z.
# MAJOR.MINOR.PATCH: Revision format is three digits, separated by `.`,
# `x.y.z`. Trailing zeroes may be omitted on the
# command line, which means:
# 1.0.0 == 1.0 == 1
#
# EXACT: Revision is required to be an exact match. As example, available revisions are:
# 0.1.0 and 0.3.0, and user provides 0.2.0, then an error is reported
# when `EXACT` is given.
# If `EXACT` is not provided, then closest lower revision will be selected
# as the active revision, which in the example will be `0.1.0`.
#
# DEFAULT_REVISION: Provides a default revision to use when user has not selected
# a revision number. If no default revision is provided then
# user will be printed with an error if no revision is given
# on the command line.
#
# HIGHEST_REVISION: Allows to specify highest valid revision for a board.
# This can be used to ensure that a newer board cannot be used
# with an older Zephyr. As example, if current board supports
# revisions 0.x.0-0.99.99 and 1.0.0-1.99.99, and it is expected
# that current board implementation will not work with board
# revision 2.0.0, then HIGHEST_REVISION can be set to 1.99.99,
# and user will be printed with an error if using
# `<board>@2.0.0` or higher.
# This field is not needed when `EXACT` is used.
#
function(board_check_revision)
set(options EXACT)
set(single_args FORMAT DEFAULT_REVISION HIGHEST_REVISION)
cmake_parse_arguments(BOARD_REV "${options}" "${single_args}" "" ${ARGN})
file(GLOB revision_candidates LIST_DIRECTORIES false RELATIVE ${BOARD_DIR}
${BOARD_DIR}/${BOARD}_*.conf
)
string(TOUPPER ${BOARD_REV_FORMAT} BOARD_REV_FORMAT)
if(NOT DEFINED BOARD_REVISION)
if(DEFINED BOARD_REV_DEFAULT_REVISION)
set(BOARD_REVISION ${BOARD_REV_DEFAULT_REVISION})
set(BOARD_REVISION ${BOARD_REVISION} PARENT_SCOPE)
else()
message(FATAL_ERROR "No board revision specified, Board: `${BOARD}` \
requires a revision. Please use: `-DBOARD=${BOARD}@<revision>`")
endif()
endif()
if(DEFINED BOARD_REV_HIGHEST_REVISION)
if(((BOARD_REV_FORMAT STREQUAL LETTER) AND
(BOARD_REVISION STRGREATER BOARD_REV_HIGHEST_REVISION)) OR
((BOARD_REV_FORMAT MATCHES "^MAJOR\.MINOR\.PATCH$") AND
(BOARD_REVISION VERSION_GREATER BOARD_REV_HIGHEST_REVISION))
)
message(FATAL_ERROR "Board revision `${BOARD_REVISION}` greater than \
highest supported revision `${BOARD_REV_HIGHEST_REVISION}`. \
Please specify a valid board revision.")
endif()
endif()
if(BOARD_REV_FORMAT STREQUAL LETTER)
set(revision_regex "([A-Z])")
elseif(BOARD_REV_FORMAT MATCHES "^MAJOR\.MINOR\.PATCH$")
set(revision_regex "((0|[1-9]+)(\.[0-9]+)(\.[0-9]+))")
# We allow loose <board>@<revision> typing on command line.
# so append missing zeroes.
if(BOARD_REVISION MATCHES "((0|[1-9]+)(\.[0-9]+)?(\.[0-9]+)?)")
if(NOT CMAKE_MATCH_3)
set(BOARD_REVISION ${BOARD_REVISION}.0)
set(BOARD_REVISION ${BOARD_REVISION} PARENT_SCOPE)
endif()
if(NOT CMAKE_MATCH_4)
set(BOARD_REVISION ${BOARD_REVISION}.0)
set(BOARD_REVISION ${BOARD_REVISION} PARENT_SCOPE)
endif()
endif()
else()
message(FATAL_ERROR "Invalid format specified for \
`zephyr_check_board_revision(FORMAT <LETTER | MAJOR.MINOR.PATCH>)`")
endif()
if(NOT (BOARD_REVISION MATCHES "^${revision_regex}$"))
message(FATAL_ERROR "Invalid revision format used for `${BOARD_REVISION}`. \
Board `${BOARD}` uses revision format: ${BOARD_REV_FORMAT}.")
endif()
string(REPLACE "." "_" underscore_revision_regex ${revision_regex})
set(file_revision_regex "${BOARD}_${underscore_revision_regex}.conf")
foreach(candidate ${revision_candidates})
if(${candidate} MATCHES "${file_revision_regex}")
string(REPLACE "_" "." FOUND_BOARD_REVISION ${CMAKE_MATCH_1})
if(${BOARD_REVISION} STREQUAL ${FOUND_BOARD_REVISION})
# Found exact match.
return()
endif()
if(NOT BOARD_REV_EXACT)
if((BOARD_REV_FORMAT MATCHES "^MAJOR\.MINOR\.PATCH$") AND
(${BOARD_REVISION} VERSION_GREATER_EQUAL ${FOUND_BOARD_REVISION}) AND
(${FOUND_BOARD_REVISION} VERSION_GREATER_EQUAL "${ACTIVE_BOARD_REVISION}")
)
set(ACTIVE_BOARD_REVISION ${FOUND_BOARD_REVISION})
elseif((BOARD_REV_FORMAT STREQUAL LETTER) AND
(${BOARD_REVISION} STRGREATER ${FOUND_BOARD_REVISION}) AND
(${FOUND_BOARD_REVISION} STRGREATER "${ACTIVE_BOARD_REVISION}")
)
set(ACTIVE_BOARD_REVISION ${FOUND_BOARD_REVISION})
endif()
endif()
endif()
endforeach()
if(BOARD_REV_EXACT OR NOT DEFINED ACTIVE_BOARD_REVISION)
message(FATAL_ERROR "Board revision `${BOARD_REVISION}` for board \
`${BOARD}` not found. Please specify a valid board revision.")
endif()
set(ACTIVE_BOARD_REVISION ${ACTIVE_BOARD_REVISION} PARENT_SCOPE)
endfunction()
# 1.5. Misc.
# zephyr_check_compiler_flag is a part of Zephyr's toolchain
@ -1796,25 +1945,36 @@ Relative paths are only allowed with `-D${ARGV1}=<path>`")
endif()
if(FILE_CONF_FILES)
set(FILENAMES ${BOARD})
if(DEFINED BOARD_REVISION)
list(APPEND FILENAMES "${BOARD}_${BOARD_REVISION_STRING}")
endif()
if(FILE_DTS)
if(EXISTS ${FILE_CONF_FILES}/${BOARD}.overlay)
list(APPEND ${FILE_DTS} ${FILE_CONF_FILES}/${BOARD}.overlay)
# This updates the provided list in parent scope (callers scope)
set(${FILE_DTS} ${${FILE_DTS}} PARENT_SCOPE)
endif()
foreach(filename ${FILENAMES})
if(EXISTS ${FILE_CONF_FILES}/${filename}.overlay)
list(APPEND ${FILE_DTS} ${FILE_CONF_FILES}/${filename}.overlay)
endif()
endforeach()
# This updates the provided list in parent scope (callers scope)
set(${FILE_DTS} ${${FILE_DTS}} PARENT_SCOPE)
endif()
if(FILE_KCONF)
set(FILENAME ${BOARD})
if(FILE_BUILD)
set(FILENAME "${FILENAME}_${FILE_BUILD}")
endif()
foreach(filename ${FILENAMES})
if(FILE_BUILD)
set(filename "${filename}_${FILE_BUILD}")
endif()
if(EXISTS ${FILE_CONF_FILES}/${FILENAME}.conf)
list(APPEND ${FILE_KCONF} ${FILE_CONF_FILES}/${FILENAME}.conf)
# This updates the provided list in parent scope (callers scope)
set(${FILE_KCONF} ${${FILE_KCONF}} PARENT_SCOPE)
endif()
if(EXISTS ${FILE_CONF_FILES}/${filename}.conf)
list(APPEND ${FILE_KCONF} ${FILE_CONF_FILES}/${filename}.conf)
endif()
endforeach()
# This updates the provided list in parent scope (callers scope)
set(${FILE_KCONF} ${${FILE_KCONF}} PARENT_SCOPE)
endif()
endif()
endfunction()

View file

@ -55,6 +55,10 @@ if(OVERLAY_CONFIG)
string(REPLACE " " ";" OVERLAY_CONFIG_AS_LIST "${OVERLAY_CONFIG}")
endif()
if((DEFINED BOARD_REVISION) AND EXISTS ${BOARD_DIR}/${BOARD}_${BOARD_REVISION_STRING}.conf)
list(INSERT CONF_FILE_AS_LIST 0 ${BOARD_DIR}/${BOARD}_${BOARD_REVISION_STRING}.conf)
endif()
# DTS_ROOT_BINDINGS is a semicolon separated list, this causes
# problems when invoking kconfig_target since semicolon is a special
# character in the C shell, so we make it into a question-mark