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>
diff --git a/cmake/app/boilerplate.cmake b/cmake/app/boilerplate.cmake
index ab30149..5ff6a19 100644
--- a/cmake/app/boilerplate.cmake
+++ b/cmake/app/boilerplate.cmake
@@ -190,9 +190,31 @@
# 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 @@
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 @@
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 @@
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(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 @@
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)
diff --git a/cmake/dts.cmake b/cmake/dts.cmake
index 48394d0..d3f0b34 100644
--- a/cmake/dts.cmake
+++ b/cmake/dts.cmake
@@ -45,19 +45,22 @@
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
diff --git a/cmake/extensions.cmake b/cmake/extensions.cmake
index 6d515ea..1b95b40 100644
--- a/cmake/extensions.cmake
+++ b/cmake/extensions.cmake
@@ -630,10 +630,17 @@
# 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 @@
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 @@
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()
diff --git a/cmake/kconfig.cmake b/cmake/kconfig.cmake
index 03aab1a..defec28 100644
--- a/cmake/kconfig.cmake
+++ b/cmake/kconfig.cmake
@@ -55,6 +55,10 @@
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