From 115eae7c66c0c0d7182262395bf106f860cfc199 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Wed, 5 Jun 2024 01:59:28 +0100 Subject: [PATCH] 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 --- cmake/pico_pre_load_platform.cmake | 4 +- cmake/pico_pre_load_toolchain.cmake | 4 +- src/board_setup.cmake | 2 +- src/boards/generic_board.cmake | 2 +- .../pico_base/generate_config_header.cmake | 6 +- src/common/pico_binary_info/CMakeLists.txt | 8 +- src/rp2_common/boot_stage2/CMakeLists.txt | 6 +- src/rp2_common/pico_stdio_usb/CMakeLists.txt | 2 +- src/rp2_common/pico_stdlib/CMakeLists.txt | 6 +- tools/CMakeLists.txt | 2 +- tools/extract_build_defines.py | 168 ++++++++++++++++++ tools/extract_cmake_configs.py | 168 ++++++++++++++++++ 12 files changed, 357 insertions(+), 21 deletions(-) create mode 100755 tools/extract_build_defines.py create mode 100755 tools/extract_cmake_configs.py diff --git a/cmake/pico_pre_load_platform.cmake b/cmake/pico_pre_load_platform.cmake index 479eedf3b..3bce8e6b4 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 @@ endif () 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 b77f42148..a8fbbdbeb 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 @@ if (CMAKE_BUILD_TYPE STREQUAL "Default") 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 153af4e24..48839f737 100644 --- a/src/board_setup.cmake +++ b/src/board_setup.cmake @@ -12,7 +12,7 @@ else() 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 .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 .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 3dd52922b..3307e78c1 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 .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 .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 98401057c..5cc6aff69 100644 --- a/src/common/pico_base/generate_config_header.cmake +++ b/src/common/pico_base/generate_config_header.cmake @@ -10,11 +10,11 @@ macro(add_header_content_from_var VAR) 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 eb0c3f66b..bcaad6f9e 100644 --- a/src/common/pico_binary_info/CMakeLists.txt +++ b/src/common/pico_binary_info/CMakeLists.txt @@ -11,7 +11,7 @@ endif() 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 @@ function(pico_set_program_description TARGET description) # 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 97c8e0127..26363734d 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 @@ function(pico_clone_default_boot_stage2 NAME) 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 9113c716e..bca4d3533 100644 --- a/src/rp2_common/pico_stdio_usb/CMakeLists.txt +++ b/src/rp2_common/pico_stdio_usb/CMakeLists.txt @@ -18,7 +18,7 @@ if (TARGET tinyusb_device_unmarked) 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 183ca52b5..b5ad182f1 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 b358142cc..9082a539d 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -12,7 +12,7 @@ function(_pico_init_pioasm) 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 000000000..221a25568 --- /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 [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 000000000..9c4f194fa --- /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 [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']})