Add scripts to extract PICO_CMAKE_CONFIG and PICO_BUILD_DEFINE entries (#1708)
Tidy up a couple of PICO_CMAKE_CONFIG and PICO_BUILD_DEFINE entries
diff --git a/cmake/pico_pre_load_platform.cmake b/cmake/pico_pre_load_platform.cmake
index 479eedf..3bce8e6 100644
--- a/cmake/pico_pre_load_platform.cmake
+++ b/cmake/pico_pre_load_platform.cmake
@@ -1,4 +1,4 @@
-# PICO_CMAKE_CONFIG: PICO_PLATFORM, platform to build for e.g. rp2040/host, default=rp2040 or environment value, group=build
+# PICO_CMAKE_CONFIG: PICO_PLATFORM, platform to build for e.g. rp2040/host, type=string, default=rp2040 or environment value, group=build
if (DEFINED ENV{PICO_PLATFORM} AND (NOT PICO_PLATFORM))
set(PICO_PLATFORM $ENV{PICO_PLATFORM})
message("Using PICO_PLATFORM from environment ('${PICO_PLATFORM}')")
@@ -13,7 +13,7 @@
set(PICO_PLATFORM ${PICO_PLATFORM} CACHE STRING "PICO Build platform (e.g. rp2040, host)")
-# PICO_CMAKE_CONFIG: PICO_CMAKE_PRELOAD_PLATFORM_FILE, custom CMake file to use to set up the platform environment, default=none, group=build
+# PICO_CMAKE_CONFIG: PICO_CMAKE_PRELOAD_PLATFORM_FILE, custom CMake file to use to set up the platform environment, type=string, group=build
set(PICO_CMAKE_PRELOAD_PLATFORM_FILE "" CACHE INTERNAL "")
set(PICO_CMAKE_PRELOAD_PLATFORM_DIR "${CMAKE_CURRENT_LIST_DIR}/preload/platforms" CACHE INTERNAL "")
diff --git a/cmake/pico_pre_load_toolchain.cmake b/cmake/pico_pre_load_toolchain.cmake
index b77f421..a8fbbdb 100644
--- a/cmake/pico_pre_load_toolchain.cmake
+++ b/cmake/pico_pre_load_toolchain.cmake
@@ -1,4 +1,4 @@
-# PICO_CMAKE_CONFIG: PICO_TOOLCHAIN_PATH, Path to search for compiler, default=none (i.e. search system paths), group=build
+# PICO_CMAKE_CONFIG: PICO_TOOLCHAIN_PATH, Path to search for compiler, type=string, default=none (i.e. search system paths), group=build
set(PICO_TOOLCHAIN_PATH "${PICO_TOOLCHAIN_PATH}" CACHE INTERNAL "")
# Set a default build type if none was specified
@@ -16,7 +16,7 @@
error("Default build type is NOT supported")
endif()
-# PICO_CMAKE_CONFIG: PICO_COMPILER, Optionally specifies a different compiler (other than pico_arm_gcc.cmake) - this is not yet fully supported, default=none, group=build
+# PICO_CMAKE_CONFIG: PICO_COMPILER, Optionally specifies a different compiler (other than pico_arm_gcc.cmake) - this is not yet fully supported, type=string, group=build
# If PICO_COMPILER is specified, set toolchain file to ${PICO_COMPILER}.cmake.
if (DEFINED PICO_COMPILER)
if (DEFINED CMAKE_TOOLCHAIN_FILE)
diff --git a/src/board_setup.cmake b/src/board_setup.cmake
index 153af4e..48839f7 100644
--- a/src/board_setup.cmake
+++ b/src/board_setup.cmake
@@ -12,7 +12,7 @@
endif()
set(PICO_BOARD ${PICO_BOARD} CACHE STRING "PICO target board (e.g. pico)" FORCE)
-# PICO_CMAKE_CONFIG: PICO_BOARD_CMAKE_DIRS, Directories to look for <PICO_BOARD>.cmake in. This is overridable from the user environment, type=list, default="", group=build
+# PICO_CMAKE_CONFIG: PICO_BOARD_CMAKE_DIRS, Directories to look for <PICO_BOARD>.cmake in. This is overridable from the user environment, type=list, group=build
if (DEFINED ENV{PICO_BOARD_CMAKE_DIRS})
set(PICO_BOARD_CMAKE_DIRS $ENV{PICO_BOARD_CMAKE_DIRS})
message("Using PICO_BOARD_CMAKE_DIRS from environment ('${PICO_BOARD_CMAKE_DIRS}')")
diff --git a/src/boards/generic_board.cmake b/src/boards/generic_board.cmake
index 3dd5292..3307e78 100644
--- a/src/boards/generic_board.cmake
+++ b/src/boards/generic_board.cmake
@@ -1,6 +1,6 @@
# For boards without their own cmake file, simply include a header
-# PICO_CMAKE_CONFIG: PICO_BOARD_HEADER_DIRS, Directories to look for <PICO_BOARD>.h in. This is overridable from the user environment, type=list, default="", group=build
+# PICO_CMAKE_CONFIG: PICO_BOARD_HEADER_DIRS, Directories to look for <PICO_BOARD>.h in. This is overridable from the user environment, type=list, group=build
if (DEFINED ENV{PICO_BOARD_HEADER_DIRS})
set(PICO_BOARD_HEADER_DIRS $ENV{PICO_BOARD_HEADER_DIRS})
message("Using PICO_BOARD_HEADER_DIRS from environment ('${PICO_BOARD_HEADER_DIRS}')")
diff --git a/src/common/pico_base/generate_config_header.cmake b/src/common/pico_base/generate_config_header.cmake
index 9840105..5cc6aff 100644
--- a/src/common/pico_base/generate_config_header.cmake
+++ b/src/common/pico_base/generate_config_header.cmake
@@ -10,11 +10,11 @@
endforeach()
endmacro()
-# PICO_CMAKE_CONFIG: PICO_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for all platforms, type=list, default="", group=pico_base
+# PICO_CMAKE_CONFIG: PICO_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for all platforms, type=list, group=pico_base
add_header_content_from_var(PICO_CONFIG_HEADER_FILES)
-# PICO_CMAKE_CONFIG: PICO_RP2040_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for rp2040 platform, type=list, default="", group=pico_base
-# PICO_CMAKE_CONFIG: PICO_HOST_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for host platform, type=list, default="", group=pico_base
+# PICO_CMAKE_CONFIG: PICO_RP2040_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for rp2040 platform, type=list, group=pico_base
+# PICO_CMAKE_CONFIG: PICO_HOST_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for host platform, type=list, group=pico_base
add_header_content_from_var(PICO_${PICO_PLATFORM_UPPER}_CONFIG_HEADER_FILES)
file(GENERATE
diff --git a/src/common/pico_binary_info/CMakeLists.txt b/src/common/pico_binary_info/CMakeLists.txt
index eb0c3f6..bcaad6f 100644
--- a/src/common/pico_binary_info/CMakeLists.txt
+++ b/src/common/pico_binary_info/CMakeLists.txt
@@ -11,7 +11,7 @@
target_link_libraries(pico_binary_info INTERFACE pico_binary_info_headers)
function(pico_set_program_name TARGET name)
- # PICO_BUILD_DEFINE: PICO_PROGRAM_NAME, value passed to pico_set_program_name, type=string, default=none, group=pico_binary_info
+ # PICO_BUILD_DEFINE: PICO_PROGRAM_NAME, value passed to pico_set_program_name, type=string, group=pico_binary_info
target_compile_definitions(${TARGET} PRIVATE -DPICO_PROGRAM_NAME="${name}")
endfunction()
@@ -19,16 +19,16 @@
# since this is the command line, we will remove newlines
string(REPLACE "\n" " " description ${description})
string(REPLACE "\"" "\\\"" description ${description})
- # PICO_BUILD_DEFINE: PICO_PROGRAM_DESCRIPTION, value passed to pico_set_program_description, type=string, default=none, group=pico_binary_info
+ # PICO_BUILD_DEFINE: PICO_PROGRAM_DESCRIPTION, value passed to pico_set_program_description, type=string, group=pico_binary_info
target_compile_definitions(${TARGET} PRIVATE -DPICO_PROGRAM_DESCRIPTION="${description}")
endfunction()
function(pico_set_program_url TARGET url)
- # PICO_BUILD_DEFINE: PICO_PROGRAM_URL, value passed to pico_set_program_url, type=string, default=none, group=pico_binary_info
+ # PICO_BUILD_DEFINE: PICO_PROGRAM_URL, value passed to pico_set_program_url, type=string, group=pico_binary_info
target_compile_definitions(${TARGET} PRIVATE -DPICO_PROGRAM_URL="${url}")
endfunction()
function(pico_set_program_version TARGET version)
- # PICO_BUILD_DEFINE: PICO_PROGRAM_VERSION_STRING, value passed to pico_set_program_version, type=string, default=none, group=pico_binary_info
+ # PICO_BUILD_DEFINE: PICO_PROGRAM_VERSION_STRING, value passed to pico_set_program_version, type=string, group=pico_binary_info
target_compile_definitions(${TARGET} PRIVATE -DPICO_PROGRAM_VERSION_STRING="${version}")
endfunction()
diff --git a/src/rp2_common/boot_stage2/CMakeLists.txt b/src/rp2_common/boot_stage2/CMakeLists.txt
index 97c8e01..2636373 100644
--- a/src/rp2_common/boot_stage2/CMakeLists.txt
+++ b/src/rp2_common/boot_stage2/CMakeLists.txt
@@ -1,5 +1,5 @@
-# PICO_CMAKE_CONFIG: PICO_DEFAULT_BOOT_STAGE2_FILE, Default boot stage 2 file to use unless overridden by pico_set_boot_stage2 on the TARGET; this setting is useful when explicitly setting the default build from a per board CMake file, group=build
-# PICO_CMAKE_CONFIG: PICO_DEFAULT_BOOT_STAGE2, Simpler alternative to specifying PICO_DEFAULT_BOOT_STAGE2_FILE where the file is src/rp2_common/boot_stage2/{PICO_DEFAULT_BOOT_STAGE2}.S, default=compile_time_choice, group=build
+# PICO_CMAKE_CONFIG: PICO_DEFAULT_BOOT_STAGE2_FILE, Default boot stage 2 file to use unless overridden by pico_set_boot_stage2 on the TARGET; this setting is useful when explicitly setting the default build from a per board CMake file, type=string, group=build
+# PICO_CMAKE_CONFIG: PICO_DEFAULT_BOOT_STAGE2, Simpler alternative to specifying PICO_DEFAULT_BOOT_STAGE2_FILE where the file is src/rp2_common/boot_stage2/{PICO_DEFAULT_BOOT_STAGE2}.S, type=string, default=compile_time_choice, group=build
if (DEFINED ENV{PICO_DEFAULT_BOOT_STAGE2_FILE})
set(PICO_DEFAULT_BOOT_STAGE2_FILE $ENV{PICO_DEFAULT_BOOT_STAGE2_FILE})
@@ -105,4 +105,4 @@
pico_define_boot_stage2(${NAME} ${PICO_DEFAULT_BOOT_STAGE2_FILE})
endfunction()
-pico_promote_common_scope_vars()
\ No newline at end of file
+pico_promote_common_scope_vars()
diff --git a/src/rp2_common/pico_stdio_usb/CMakeLists.txt b/src/rp2_common/pico_stdio_usb/CMakeLists.txt
index 9113c71..bca4d35 100644
--- a/src/rp2_common/pico_stdio_usb/CMakeLists.txt
+++ b/src/rp2_common/pico_stdio_usb/CMakeLists.txt
@@ -18,7 +18,7 @@
target_link_libraries(pico_stdio_usb INTERFACE
tinyusb_device_unmarked
)
- # PICO_CMAKE_CONFIG: PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS, Maximum number of milliseconds to wait during initialization for a CDC connection from the host (negative means indefinite) during initialization, default=0, group=pico_stdio_usb
+ # PICO_CMAKE_CONFIG: PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS, Maximum number of milliseconds to wait during initialization for a CDC connection from the host (negative means indefinite) during initialization, type=int, default=0, group=pico_stdio_usb
if (PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS)
target_compile_definitions(pico_stdio_usb INTERFACE
PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS=${PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS}
diff --git a/src/rp2_common/pico_stdlib/CMakeLists.txt b/src/rp2_common/pico_stdlib/CMakeLists.txt
index 183ca52..b5ad182 100644
--- a/src/rp2_common/pico_stdlib/CMakeLists.txt
+++ b/src/rp2_common/pico_stdlib/CMakeLists.txt
@@ -1,8 +1,8 @@
-# PICO_CMAKE_CONFIG: PICO_STDIO_UART, OPTION: Globally enable stdio UART, default=1, group=pico_stdlib
+# PICO_CMAKE_CONFIG: PICO_STDIO_UART, OPTION: Globally enable stdio UART, type=bool, default=1, group=pico_stdlib
option(PICO_STDIO_UART "Globally enable stdio UART" 1)
-# PICO_CMAKE_CONFIG: PICO_STDIO_USB, OPTION: Globally enable stdio USB, default=0, group=pico_stdlib
+# PICO_CMAKE_CONFIG: PICO_STDIO_USB, OPTION: Globally enable stdio USB, type=bool, default=0, group=pico_stdlib
option(PICO_STDIO_USB "Globally enable stdio USB" 0)
-# PICO_CMAKE_CONFIG: PICO_STDIO_SEMIHOSTING, OPTION: Globally enable stdio semihosting, default=0, group=pico_stdlib
+# PICO_CMAKE_CONFIG: PICO_STDIO_SEMIHOSTING, OPTION: Globally enable stdio semihosting, type=bool, default=0, group=pico_stdlib
option(PICO_STDIO_SEMIHOSTING "Globally enable stdio semi-hosting" 0)
if (NOT TARGET pico_stdlib)
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index b358142..9082a53 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -12,7 +12,7 @@
endif()
endfunction()
-# PICO_CMAKE_CONFIG: PICO_DEFAULT_PIOASM_OUTPUT_FORMAT, default output format used by pioasm when using pico_generate_pio_header, default=c-sdk, group=build
+# PICO_CMAKE_CONFIG: PICO_DEFAULT_PIOASM_OUTPUT_FORMAT, default output format used by pioasm when using pico_generate_pio_header, type=string, default=c-sdk, group=build
function(pico_generate_pio_header TARGET PIO)
_pico_init_pioasm()
cmake_parse_arguments(pico_generate_pio_header "" "OUTPUT_FORMAT;OUTPUT_DIR" "" ${ARGN} )
diff --git a/tools/extract_build_defines.py b/tools/extract_build_defines.py
new file mode 100755
index 0000000..221a255
--- /dev/null
+++ b/tools/extract_build_defines.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#
+# Script to scan the Raspberry Pi Pico SDK tree searching for CMake build defines
+# Outputs a tab separated file of the configuration item:
+# name location description type default group
+#
+# Usage:
+#
+# tools/extract_build_defines.py <root of repo> [output file]
+#
+# If not specified, output file will be `pico_build_defines.tsv`
+
+
+import os
+import sys
+import re
+import csv
+import logging
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+scandir = sys.argv[1]
+outfile = sys.argv[2] if len(sys.argv) > 2 else 'pico_build_defines.tsv'
+
+BUILD_DEFINE_RE = re.compile(r'#\s+PICO_BUILD_DEFINE:\s+(\w+),\s+([^,]+)(?:,\s+(.*))?$')
+
+all_configs = {}
+all_attrs = set()
+all_descriptions = {}
+
+
+
+def ValidateAttrs(config_attrs, file_path, linenum):
+ _type = config_attrs.get('type')
+
+ # Validate attrs
+ if _type == 'int':
+ _min = _max = _default = None
+ if config_attrs.get('min', None) is not None:
+ value = config_attrs['min']
+ m = re.match(r'^(\d+)e(\d+)$', value.lower())
+ if m:
+ _min = int(m.group(1)) * 10**int(m.group(2))
+ else:
+ _min = int(value, 0)
+ if config_attrs.get('max', None) is not None:
+ value = config_attrs['max']
+ m = re.match(r'^(\d+)e(\d+)$', value.lower())
+ if m:
+ _max = int(m.group(1)) * 10**int(m.group(2))
+ else:
+ _max = int(value, 0)
+ if config_attrs.get('default', None) is not None:
+ if '/' not in config_attrs['default']:
+ try:
+ value = config_attrs['default']
+ m = re.match(r'^(\d+)e(\d+)$', value.lower())
+ if m:
+ _default = int(m.group(1)) * 10**int(m.group(2))
+ else:
+ _default = int(value, 0)
+ except ValueError:
+ pass
+ if _min is not None and _max is not None:
+ if _min > _max:
+ raise Exception('{} at {}:{} has min {} > max {}'.format(config_name, file_path, linenum, config_attrs['min'], config_attrs['max']))
+ if _min is not None and _default is not None:
+ if _min > _default:
+ raise Exception('{} at {}:{} has min {} > default {}'.format(config_name, file_path, linenum, config_attrs['min'], config_attrs['default']))
+ if _default is not None and _max is not None:
+ if _default > _max:
+ raise Exception('{} at {}:{} has default {} > max {}'.format(config_name, file_path, linenum, config_attrs['default'], config_attrs['max']))
+ elif _type == 'bool':
+ assert 'min' not in config_attrs
+ assert 'max' not in config_attrs
+ _default = config_attrs.get('default', None)
+ if _default is not None:
+ if '/' not in _default:
+ if (_default.lower() != '0') and (config_attrs['default'].lower() != '1') and ( _default not in all_configs):
+ logger.info('{} at {}:{} has non-integer default value "{}"'.format(config_name, file_path, linenum, config_attrs['default']))
+
+ elif _type == 'string':
+ assert 'min' not in config_attrs
+ assert 'max' not in config_attrs
+ _default = config_attrs.get('default', None)
+ elif _type == 'list':
+ assert 'min' not in config_attrs
+ assert 'max' not in config_attrs
+ _default = config_attrs.get('default', None)
+ else:
+ raise Exception("Found unknown PICO_BUILD_DEFINE type {} at {}:{}".format(_type, file_path, linenum))
+
+
+
+
+# Scan all CMakeLists.txt and .cmake files in the specific path, recursively.
+
+for dirpath, dirnames, filenames in os.walk(scandir):
+ for filename in filenames:
+ file_ext = os.path.splitext(filename)[1]
+ if filename == 'CMakeLists.txt' or file_ext == '.cmake':
+ file_path = os.path.join(dirpath, filename)
+
+ with open(file_path, encoding="ISO-8859-1") as fh:
+ linenum = 0
+ for line in fh.readlines():
+ linenum += 1
+ line = line.strip()
+ m = BUILD_DEFINE_RE.match(line)
+ if m:
+ config_name = m.group(1)
+ config_description = m.group(2)
+ _attrs = m.group(3)
+ # allow commas to appear inside brackets by converting them to and from NULL chars
+ _attrs = re.sub(r'(\(.+\))', lambda m: m.group(1).replace(',', '\0'), _attrs)
+
+ if '=' in config_description:
+ raise Exception("For {} at {}:{} the description was set to '{}' - has the description field been omitted?".format(config_name, file_path, linenum, config_description))
+ if config_description in all_descriptions:
+ raise Exception("Found description {} at {}:{} but it was already used at {}:{}".format(config_description, file_path, linenum, os.path.join(scandir, all_descriptions[config_description]['filename']), all_descriptions[config_description]['line_number']))
+ else:
+ all_descriptions[config_description] = {'config_name': config_name, 'filename': os.path.relpath(file_path, scandir), 'line_number': linenum}
+
+ config_attrs = {}
+ prev = None
+ # Handle case where attr value contains a comma
+ for item in _attrs.split(','):
+ if "=" not in item:
+ assert(prev)
+ item = prev + "," + item
+ try:
+ k, v = (i.strip() for i in item.split('='))
+ except ValueError:
+ raise Exception('{} at {}:{} has malformed value {}'.format(config_name, file_path, linenum, item))
+ config_attrs[k] = v.replace('\0', ',')
+ all_attrs.add(k)
+ prev = item
+ #print(file_path, config_name, config_attrs)
+
+ if 'group' not in config_attrs:
+ raise Exception('{} at {}:{} has no group attribute'.format(config_name, file_path, linenum))
+
+ #print(file_path, config_name, config_attrs)
+ if config_name in all_configs:
+ raise Exception("Found {} at {}:{} but it was already declared at {}:{}".format(config_name, file_path, linenum, os.path.join(scandir, all_configs[config_name]['filename']), all_configs[config_name]['line_number']))
+ else:
+ all_configs[config_name] = {'attrs': config_attrs, 'filename': os.path.relpath(file_path, scandir), 'line_number': linenum, 'description': config_description}
+
+
+for config_name, config_obj in all_configs.items():
+ file_path = os.path.join(scandir, config_obj['filename'])
+ linenum = config_obj['line_number']
+
+ ValidateAttrs(config_obj['attrs'], file_path, linenum)
+
+with open(outfile, 'w', newline='') as csvfile:
+ fieldnames = ('name', 'location', 'description', 'type') + tuple(sorted(all_attrs - set(['type'])))
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames, extrasaction='ignore', dialect='excel-tab')
+
+ writer.writeheader()
+ for config_name, config_obj in sorted(all_configs.items()):
+ writer.writerow({'name': config_name, 'location': '/{}:{}'.format(config_obj['filename'], config_obj['line_number']), 'description': config_obj['description'], **config_obj['attrs']})
diff --git a/tools/extract_cmake_configs.py b/tools/extract_cmake_configs.py
new file mode 100755
index 0000000..9c4f194
--- /dev/null
+++ b/tools/extract_cmake_configs.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#
+# Script to scan the Raspberry Pi Pico SDK tree searching for CMake configuration items
+# Outputs a tab separated file of the configuration item:
+# name location description type advanced default group
+#
+# Usage:
+#
+# tools/extract_cmake_configs.py <root of repo> [output file]
+#
+# If not specified, output file will be `pico_cmake_configs.tsv`
+
+
+import os
+import sys
+import re
+import csv
+import logging
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+scandir = sys.argv[1]
+outfile = sys.argv[2] if len(sys.argv) > 2 else 'pico_cmake_configs.tsv'
+
+CMAKE_CONFIG_RE = re.compile(r'#\s+PICO_CMAKE_CONFIG:\s+(\w+),\s+([^,]+)(?:,\s+(.*))?$')
+
+all_configs = {}
+all_attrs = set()
+all_descriptions = {}
+
+
+
+def ValidateAttrs(config_attrs, file_path, linenum):
+ _type = config_attrs.get('type')
+
+ # Validate attrs
+ if _type == 'int':
+ _min = _max = _default = None
+ if config_attrs.get('min', None) is not None:
+ value = config_attrs['min']
+ m = re.match(r'^(\d+)e(\d+)$', value.lower())
+ if m:
+ _min = int(m.group(1)) * 10**int(m.group(2))
+ else:
+ _min = int(value, 0)
+ if config_attrs.get('max', None) is not None:
+ value = config_attrs['max']
+ m = re.match(r'^(\d+)e(\d+)$', value.lower())
+ if m:
+ _max = int(m.group(1)) * 10**int(m.group(2))
+ else:
+ _max = int(value, 0)
+ if config_attrs.get('default', None) is not None:
+ if '/' not in config_attrs['default']:
+ try:
+ value = config_attrs['default']
+ m = re.match(r'^(\d+)e(\d+)$', value.lower())
+ if m:
+ _default = int(m.group(1)) * 10**int(m.group(2))
+ else:
+ _default = int(value, 0)
+ except ValueError:
+ pass
+ if _min is not None and _max is not None:
+ if _min > _max:
+ raise Exception('{} at {}:{} has min {} > max {}'.format(config_name, file_path, linenum, config_attrs['min'], config_attrs['max']))
+ if _min is not None and _default is not None:
+ if _min > _default:
+ raise Exception('{} at {}:{} has min {} > default {}'.format(config_name, file_path, linenum, config_attrs['min'], config_attrs['default']))
+ if _default is not None and _max is not None:
+ if _default > _max:
+ raise Exception('{} at {}:{} has default {} > max {}'.format(config_name, file_path, linenum, config_attrs['default'], config_attrs['max']))
+ elif _type == 'bool':
+ assert 'min' not in config_attrs
+ assert 'max' not in config_attrs
+ _default = config_attrs.get('default', None)
+ if _default is not None:
+ if '/' not in _default:
+ if (_default.lower() != '0') and (config_attrs['default'].lower() != '1') and ( _default not in all_configs):
+ logger.info('{} at {}:{} has non-integer default value "{}"'.format(config_name, file_path, linenum, config_attrs['default']))
+
+ elif _type == 'string':
+ assert 'min' not in config_attrs
+ assert 'max' not in config_attrs
+ _default = config_attrs.get('default', None)
+ elif _type == 'list':
+ assert 'min' not in config_attrs
+ assert 'max' not in config_attrs
+ _default = config_attrs.get('default', None)
+ else:
+ raise Exception("Found unknown PICO_CMAKE_CONFIG type {} at {}:{}".format(_type, file_path, linenum))
+
+
+
+
+# Scan all CMakeLists.txt and .cmake files in the specific path, recursively.
+
+for dirpath, dirnames, filenames in os.walk(scandir):
+ for filename in filenames:
+ file_ext = os.path.splitext(filename)[1]
+ if filename == 'CMakeLists.txt' or file_ext == '.cmake':
+ file_path = os.path.join(dirpath, filename)
+
+ with open(file_path, encoding="ISO-8859-1") as fh:
+ linenum = 0
+ for line in fh.readlines():
+ linenum += 1
+ line = line.strip()
+ m = CMAKE_CONFIG_RE.match(line)
+ if m:
+ config_name = m.group(1)
+ config_description = m.group(2)
+ _attrs = m.group(3)
+ # allow commas to appear inside brackets by converting them to and from NULL chars
+ _attrs = re.sub(r'(\(.+\))', lambda m: m.group(1).replace(',', '\0'), _attrs)
+
+ if '=' in config_description:
+ raise Exception("For {} at {}:{} the description was set to '{}' - has the description field been omitted?".format(config_name, file_path, linenum, config_description))
+ if config_description in all_descriptions:
+ raise Exception("Found description {} at {}:{} but it was already used at {}:{}".format(config_description, file_path, linenum, os.path.join(scandir, all_descriptions[config_description]['filename']), all_descriptions[config_description]['line_number']))
+ else:
+ all_descriptions[config_description] = {'config_name': config_name, 'filename': os.path.relpath(file_path, scandir), 'line_number': linenum}
+
+ config_attrs = {}
+ prev = None
+ # Handle case where attr value contains a comma
+ for item in _attrs.split(','):
+ if "=" not in item:
+ assert(prev)
+ item = prev + "," + item
+ try:
+ k, v = (i.strip() for i in item.split('='))
+ except ValueError:
+ raise Exception('{} at {}:{} has malformed value {}'.format(config_name, file_path, linenum, item))
+ config_attrs[k] = v.replace('\0', ',')
+ all_attrs.add(k)
+ prev = item
+ #print(file_path, config_name, config_attrs)
+
+ if 'group' not in config_attrs:
+ raise Exception('{} at {}:{} has no group attribute'.format(config_name, file_path, linenum))
+
+ #print(file_path, config_name, config_attrs)
+ if config_name in all_configs:
+ raise Exception("Found {} at {}:{} but it was already declared at {}:{}".format(config_name, file_path, linenum, os.path.join(scandir, all_configs[config_name]['filename']), all_configs[config_name]['line_number']))
+ else:
+ all_configs[config_name] = {'attrs': config_attrs, 'filename': os.path.relpath(file_path, scandir), 'line_number': linenum, 'description': config_description}
+
+
+for config_name, config_obj in all_configs.items():
+ file_path = os.path.join(scandir, config_obj['filename'])
+ linenum = config_obj['line_number']
+
+ ValidateAttrs(config_obj['attrs'], file_path, linenum)
+
+with open(outfile, 'w', newline='') as csvfile:
+ fieldnames = ('name', 'location', 'description', 'type') + tuple(sorted(all_attrs - set(['type'])))
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames, extrasaction='ignore', dialect='excel-tab')
+
+ writer.writeheader()
+ for config_name, config_obj in sorted(all_configs.items()):
+ writer.writerow({'name': config_name, 'location': '/{}:{}'.format(config_obj['filename'], config_obj['line_number']), 'description': config_obj['description'], **config_obj['attrs']})