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