Check & correct Kconfig files

This commit is contained in:
Roland Dobai 2018-12-06 15:08:18 +01:00
parent 065b99f28b
commit 00eefe0ef3
6 changed files with 1040 additions and 327 deletions

View File

@ -731,6 +731,23 @@ check_python_style:
# run it only under Python 3 (it is very slow under Python 2)
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh -p 3.4.8 python -m flake8 --config=$IDF_PATH/.flake8 --output-file=flake8_output.txt --tee --benchmark $IDF_PATH
check_kconfigs:
<<: *check_job_template
before_script: *do_nothing_before
artifacts:
when: on_failure
paths:
- components/*/Kconfig*.new
- examples/*/*/*/Kconfig*.new
- examples/*/*/*/*/Kconfig*.new
- tools/*/Kconfig*.new
- tools/*/*/Kconfig*.new
- tools/*/*/*/Kconfig*.new
expire_in: 1 week
script:
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ${IDF_PATH}/tools/test_check_kconfigs.py
- ${IDF_PATH}/tools/check_kconfigs.py
check_ut_cmake_make:
stage: check
image: $CI_DOCKER_REGISTRY/esp32-ci-env$BOT_DOCKER_IMAGE_TAG

95
Kconfig
View File

@ -4,35 +4,35 @@
#
mainmenu "Espressif IoT Development Framework Configuration"
config IDF_CMAKE
config IDF_CMAKE
bool
option env="IDF_CMAKE"
# A proxy to get environment variable $IDF_TARGET
config IDF_TARGET_ENV
config IDF_TARGET_ENV
# A proxy to get environment variable $IDF_TARGET
string
option env="IDF_TARGET"
# This option records the IDF target when sdkconfig is generated the first time.
# It is not updated if environment variable $IDF_TARGET changes later, and
# the build system is responsible for detecting the mismatch between
# CONFIG_IDF_TARGET and $IDF_TARGET.
config IDF_TARGET
config IDF_TARGET
# This option records the IDF target when sdkconfig is generated the first time.
# It is not updated if environment variable $IDF_TARGET changes later, and
# the build system is responsible for detecting the mismatch between
# CONFIG_IDF_TARGET and $IDF_TARGET.
string
default "IDF_TARGET_NOT_SET" if IDF_TARGET_ENV=""
default IDF_TARGET_ENV
menu "SDK tool configuration"
config TOOLPREFIX
menu "SDK tool configuration"
config TOOLPREFIX
string "Compiler toolchain path/prefix"
default "xtensa-esp32-elf-"
help
The prefix/path that is used to call the toolchain. The default setting assumes
a crosstool-ng gcc setup that is in your PATH.
config PYTHON
config PYTHON
string "Python 2 interpreter"
depends on !IDF_CMAKE
default "python"
@ -43,7 +43,7 @@ config PYTHON
(Note: This option is used with the GNU Make build system only, not idf.py
or CMake-based builds.)
config MAKE_WARN_UNDEFINED_VARIABLES
config MAKE_WARN_UNDEFINED_VARIABLES
bool "'make' warns on undefined variables"
default "y"
help
@ -54,13 +54,13 @@ config MAKE_WARN_UNDEFINED_VARIABLES
or otherwise missing, but it can be unwanted if you have Makefiles which
depend on undefined variables expanding to an empty string.
endmenu # SDK tool configuration
endmenu # SDK tool configuration
source "$COMPONENT_KCONFIGS_PROJBUILD"
source "$COMPONENT_KCONFIGS_PROJBUILD"
menu "Compiler options"
menu "Compiler options"
choice OPTIMIZATION_COMPILER
choice OPTIMIZATION_COMPILER
prompt "Optimization Level"
default OPTIMIZATION_LEVEL_DEBUG
help
@ -76,13 +76,13 @@ choice OPTIMIZATION_COMPILER
in project makefile, before including $(IDF_PATH)/make/project.mk. Note that
custom optimization levels may be unsupported.
config OPTIMIZATION_LEVEL_DEBUG
config OPTIMIZATION_LEVEL_DEBUG
bool "Debug (-Og)"
config OPTIMIZATION_LEVEL_RELEASE
config OPTIMIZATION_LEVEL_RELEASE
bool "Release (-Os)"
endchoice
endchoice
choice OPTIMIZATION_ASSERTION_LEVEL
choice OPTIMIZATION_ASSERTION_LEVEL
prompt "Assertion level"
default OPTIMIZATION_ASSERTIONS_ENABLED
help
@ -96,40 +96,40 @@ choice OPTIMIZATION_ASSERTION_LEVEL
- Disabled entirely (not recommended for most configurations.) -DNDEBUG is added
to CPPFLAGS in this case.
config OPTIMIZATION_ASSERTIONS_ENABLED
config OPTIMIZATION_ASSERTIONS_ENABLED
prompt "Enabled"
bool
help
Enable assertions. Assertion content and line number will be printed on failure.
config OPTIMIZATION_ASSERTIONS_SILENT
config OPTIMIZATION_ASSERTIONS_SILENT
prompt "Silent (saves code size)"
bool
help
Enable silent assertions. Failed assertions will abort(), user needs to
use the aborting address to find the line number with the failed assertion.
config OPTIMIZATION_ASSERTIONS_DISABLED
config OPTIMIZATION_ASSERTIONS_DISABLED
prompt "Disabled (sets -DNDEBUG)"
bool
help
If assertions are disabled, -DNDEBUG is added to CPPFLAGS.
endchoice # assertions
endchoice # assertions
menuconfig CXX_EXCEPTIONS
menuconfig CXX_EXCEPTIONS
bool "Enable C++ exceptions"
default n
help
Enabling this option compiles all IDF C++ files with exception support enabled.
Disabling this option disables C++ exception support in all compiled files, and any libstdc++ code which throws
an exception will abort instead.
Disabling this option disables C++ exception support in all compiled files, and any libstdc++ code
which throws an exception will abort instead.
Enabling this option currently adds an additional ~500 bytes of heap overhead
when an exception is thrown in user code for the first time.
config CXX_EXCEPTIONS_EMG_POOL_SIZE
config CXX_EXCEPTIONS_EMG_POOL_SIZE
int "Emergency Pool Size"
default 0
depends on CXX_EXCEPTIONS
@ -137,7 +137,7 @@ config CXX_EXCEPTIONS_EMG_POOL_SIZE
Size (in bytes) of the emergency memory pool for C++ exceptions. This pool will be used to allocate
memory for thrown exceptions when there is not enough memory on the heap.
choice STACK_CHECK_MODE
choice STACK_CHECK_MODE
prompt "Stack smashing protection mode"
default STACK_CHECK_NONE
help
@ -146,12 +146,12 @@ choice STACK_CHECK_MODE
The guards are initialized when a function is entered and then checked when the function exits.
If a guard check fails, program is halted. Protection has the following modes:
- In NORMAL mode (GCC flag: -fstack-protector) only functions that call alloca,
and functions with buffers larger than 8 bytes are protected.
- In NORMAL mode (GCC flag: -fstack-protector) only functions that call alloca, and functions with
buffers larger than 8 bytes are protected.
- STRONG mode (GCC flag: -fstack-protector-strong) is like NORMAL, but includes
additional functions to be protected -- those that have local array definitions,
or have references to local frame addresses.
- STRONG mode (GCC flag: -fstack-protector-strong) is like NORMAL, but includes additional functions
to be protected -- those that have local array definitions, or have references to local frame
addresses.
- In OVERALL mode (GCC flag: -fstack-protector-all) all functions are protected.
@ -162,23 +162,23 @@ choice STACK_CHECK_MODE
- coverage: NORMAL < STRONG < OVERALL
config STACK_CHECK_NONE
config STACK_CHECK_NONE
bool "None"
config STACK_CHECK_NORM
config STACK_CHECK_NORM
bool "Normal"
config STACK_CHECK_STRONG
config STACK_CHECK_STRONG
bool "Strong"
config STACK_CHECK_ALL
config STACK_CHECK_ALL
bool "Overall"
endchoice
endchoice
config STACK_CHECK
config STACK_CHECK
bool
default !STACK_CHECK_NONE
help
Stack smashing protection.
config WARN_WRITE_STRINGS
config WARN_WRITE_STRINGS
bool "Enable -Wwrite-strings warning flag"
default "n"
help
@ -192,15 +192,16 @@ config WARN_WRITE_STRINGS
For C++, this warns about the deprecated conversion from string
literals to ``char *``.
config DISABLE_GCC8_WARNINGS
config DISABLE_GCC8_WARNINGS
bool "Disable new warnings introduced in GCC 6 - 8"
default "n"
help
Enable this option if using GCC 6 or newer, and wanting to disable warnings which don't appear with GCC 5.
Enable this option if using GCC 6 or newer, and wanting to disable warnings which don't appear with
GCC 5.
endmenu # Compiler Options
endmenu # Compiler Options
menu "Component config"
source "$COMPONENT_KCONFIGS"
endmenu
menu "Component config"
source "$COMPONENT_KCONFIGS"
endmenu

View File

@ -1,19 +1,19 @@
menu "Application Level Tracing"
choice ESP32_APPTRACE_DESTINATION
choice ESP32_APPTRACE_DESTINATION
prompt "Data Destination"
default ESP32_APPTRACE_DEST_NONE
help
Select destination for application trace: trace memory or none (to disable).
config ESP32_APPTRACE_DEST_TRAX
config ESP32_APPTRACE_DEST_TRAX
bool "Trace memory"
select ESP32_APPTRACE_ENABLE
config ESP32_APPTRACE_DEST_NONE
config ESP32_APPTRACE_DEST_NONE
bool "None"
endchoice
endchoice
config ESP32_APPTRACE_ENABLE
config ESP32_APPTRACE_ENABLE
bool
depends on !ESP32_TRAX
select MEMMAP_TRACEMEM
@ -22,13 +22,13 @@ config ESP32_APPTRACE_ENABLE
help
Enables/disable application tracing module.
config ESP32_APPTRACE_LOCK_ENABLE
config ESP32_APPTRACE_LOCK_ENABLE
bool
default !SYSVIEW_ENABLE
help
Enables/disable application tracing module internal sync lock.
config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO
config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO
int "Timeout for flushing last trace data to host on panic"
depends on ESP32_APPTRACE_ENABLE
range -1 5000
@ -37,7 +37,7 @@ config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO
Timeout for flushing last trace data to host in case of panic. In ms.
Use -1 to disable timeout and wait forever.
config ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH
config ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH
int "Threshold for flushing last trace data to host on panic"
depends on ESP32_APPTRACE_DEST_TRAX
range 0 16384
@ -46,7 +46,7 @@ config ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH
Threshold for flushing last trace data to host on panic in post-mortem mode.
This is minimal amount of data needed to perform flush. In bytes.
config ESP32_APPTRACE_PENDING_DATA_SIZE_MAX
config ESP32_APPTRACE_PENDING_DATA_SIZE_MAX
int "Size of the pending data buffer"
depends on ESP32_APPTRACE_DEST_TRAX
default 0
@ -55,16 +55,16 @@ config ESP32_APPTRACE_PENDING_DATA_SIZE_MAX
the time critical code (scheduler, ISRs etc). If this parameter is 0 then
events will be discarded when main HW buffer is full.
menu "FreeRTOS SystemView Tracing"
menu "FreeRTOS SystemView Tracing"
depends on ESP32_APPTRACE_ENABLE
config SYSVIEW_ENABLE
config SYSVIEW_ENABLE
bool "SystemView Tracing Enable"
depends on ESP32_APPTRACE_ENABLE
default n
help
Enables supporrt for SEGGER SystemView tracing functionality.
choice SYSVIEW_TS_SOURCE
choice SYSVIEW_TS_SOURCE
prompt "Timer to use as timestamp source"
depends on SYSVIEW_ENABLE
default SYSVIEW_TS_SOURCE_CCOUNT if FREERTOS_UNICORE && !PM_ENABLE
@ -74,125 +74,125 @@ choice SYSVIEW_TS_SOURCE
SystemView needs to use a hardware timer as the source of timestamps
when tracing. This option selects the timer for it.
config SYSVIEW_TS_SOURCE_CCOUNT
config SYSVIEW_TS_SOURCE_CCOUNT
bool "CPU cycle counter (CCOUNT)"
depends on FREERTOS_UNICORE && !PM_ENABLE
config SYSVIEW_TS_SOURCE_TIMER_00
config SYSVIEW_TS_SOURCE_TIMER_00
bool "Timer 0, Group 0"
depends on !PM_ENABLE
config SYSVIEW_TS_SOURCE_TIMER_01
config SYSVIEW_TS_SOURCE_TIMER_01
bool "Timer 1, Group 0"
depends on !PM_ENABLE
config SYSVIEW_TS_SOURCE_TIMER_10
config SYSVIEW_TS_SOURCE_TIMER_10
bool "Timer 0, Group 1"
depends on !PM_ENABLE
config SYSVIEW_TS_SOURCE_TIMER_11
config SYSVIEW_TS_SOURCE_TIMER_11
bool "Timer 1, Group 1"
depends on !PM_ENABLE
config SYSVIEW_TS_SOURCE_ESP_TIMER
config SYSVIEW_TS_SOURCE_ESP_TIMER
bool "esp_timer high resolution timer"
endchoice
endchoice
config SYSVIEW_EVT_OVERFLOW_ENABLE
config SYSVIEW_EVT_OVERFLOW_ENABLE
bool "Trace Buffer Overflow Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Trace Buffer Overflow" event.
config SYSVIEW_EVT_ISR_ENTER_ENABLE
config SYSVIEW_EVT_ISR_ENTER_ENABLE
bool "ISR Enter Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "ISR Enter" event.
config SYSVIEW_EVT_ISR_EXIT_ENABLE
config SYSVIEW_EVT_ISR_EXIT_ENABLE
bool "ISR Exit Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "ISR Exit" event.
config SYSVIEW_EVT_ISR_TO_SCHEDULER_ENABLE
config SYSVIEW_EVT_ISR_TO_SCHEDULER_ENABLE
bool "ISR Exit to Scheduler Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "ISR to Scheduler" event.
config SYSVIEW_EVT_TASK_START_EXEC_ENABLE
config SYSVIEW_EVT_TASK_START_EXEC_ENABLE
bool "Task Start Execution Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Task Start Execution" event.
config SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE
config SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE
bool "Task Stop Execution Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Task Stop Execution" event.
config SYSVIEW_EVT_TASK_START_READY_ENABLE
config SYSVIEW_EVT_TASK_START_READY_ENABLE
bool "Task Start Ready State Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Task Start Ready State" event.
config SYSVIEW_EVT_TASK_STOP_READY_ENABLE
config SYSVIEW_EVT_TASK_STOP_READY_ENABLE
bool "Task Stop Ready State Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Task Stop Ready State" event.
config SYSVIEW_EVT_TASK_CREATE_ENABLE
config SYSVIEW_EVT_TASK_CREATE_ENABLE
bool "Task Create Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Task Create" event.
config SYSVIEW_EVT_TASK_TERMINATE_ENABLE
config SYSVIEW_EVT_TASK_TERMINATE_ENABLE
bool "Task Terminate Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Task Terminate" event.
config SYSVIEW_EVT_IDLE_ENABLE
config SYSVIEW_EVT_IDLE_ENABLE
bool "System Idle Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "System Idle" event.
config SYSVIEW_EVT_TIMER_ENTER_ENABLE
config SYSVIEW_EVT_TIMER_ENTER_ENABLE
bool "Timer Enter Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Timer Enter" event.
config SYSVIEW_EVT_TIMER_EXIT_ENABLE
config SYSVIEW_EVT_TIMER_EXIT_ENABLE
bool "Timer Exit Event"
depends on SYSVIEW_ENABLE
default y
help
Enables "Timer Exit" event.
endmenu
endmenu
config ESP32_GCOV_ENABLE
config ESP32_GCOV_ENABLE
bool "GCOV to Host Enable"
depends on ESP32_DEBUG_STUBS_ENABLE && ESP32_APPTRACE_ENABLE && !SYSVIEW_ENABLE
default y

484
tools/check_kconfigs.py Executable file
View File

@ -0,0 +1,484 @@
#!/usr/bin/env python
#
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from __future__ import unicode_literals
import os
import sys
import re
import argparse
from io import open
# regular expression search object for matching Kconfig files
RE_KCONFIG = re.compile(r'^Kconfig(?:\.projbuild)?$')
# ouput file with suggestions will get this suffix
OUTPUT_SUFFIX = '.new'
# ignored directories (makes sense only when run on IDF_PATH)
# Note: IGNORE_DIRS is a tuple in order to be able to use it directly with the startswith() built-in function which
# accepts tuples but no lists.
IGNORE_DIRS = (
# Kconfigs from submodules need to be ignored:
os.path.join('components', 'mqtt', 'esp-mqtt'),
# Temporary (incompatibility) list:
os.path.join('components', 'app_update'),
os.path.join('components', 'aws_iot'),
os.path.join('components', 'bootloader'),
os.path.join('components', 'bt'),
os.path.join('components', 'driver'),
os.path.join('components', 'esp32'),
os.path.join('components', 'esp_adc_cal'),
os.path.join('components', 'esp_event'),
os.path.join('components', 'esp_http_client'),
os.path.join('components', 'esp_http_server'),
os.path.join('components', 'esptool_py'),
os.path.join('components', 'ethernet'),
os.path.join('components', 'fatfs'),
os.path.join('components', 'freemodbus'),
os.path.join('components', 'freertos'),
os.path.join('components', 'heap'),
os.path.join('components', 'libsodium'),
os.path.join('components', 'log'),
os.path.join('components', 'lwip'),
os.path.join('components', 'mbedtls'),
os.path.join('components', 'mdns'),
os.path.join('components', 'mqtt'),
os.path.join('components', 'nvs_flash'),
os.path.join('components', 'openssl'),
os.path.join('components', 'partition_table'),
os.path.join('components', 'pthread'),
os.path.join('components', 'spi_flash'),
os.path.join('components', 'spiffs'),
os.path.join('components', 'tcpip_adapter'),
os.path.join('components', 'unity'),
os.path.join('components', 'vfs'),
os.path.join('components', 'wear_levelling'),
os.path.join('examples', 'bluetooth', 'a2dp_gatts_coex', 'main'),
os.path.join('examples', 'bluetooth', 'a2dp_sink', 'main'),
os.path.join('examples', 'bluetooth', 'ble_ibeacon', 'main'),
os.path.join('examples', 'bluetooth', 'ble_throughput', 'throughput_client', 'main'),
os.path.join('examples', 'bluetooth', 'ble_throughput', 'throughput_server', 'main'),
os.path.join('examples', 'bluetooth', 'gatt_server', 'main'),
os.path.join('examples', 'ethernet', 'ethernet', 'main'),
os.path.join('examples', 'ethernet', 'iperf', 'main'),
os.path.join('examples', 'get-started', 'blink', 'main'),
os.path.join('examples', 'mesh', 'internal_communication', 'main'),
os.path.join('examples', 'mesh', 'manual_networking', 'main'),
os.path.join('examples', 'peripherals', 'adc2', 'main'),
os.path.join('examples', 'peripherals', 'i2c', 'i2c_self_test', 'main'),
os.path.join('examples', 'peripherals', 'i2c', 'i2c_tools', 'main'),
os.path.join('examples', 'peripherals', 'sdio', 'host', 'main'),
os.path.join('examples', 'peripherals', 'sdio', 'slave', 'main'),
os.path.join('examples', 'peripherals', 'spi_master', 'main'),
os.path.join('examples', 'peripherals', 'uart', 'nmea0183_parser', 'main'),
os.path.join('examples', 'protocols', 'asio', 'chat_client', 'main'),
os.path.join('examples', 'protocols', 'asio', 'chat_server', 'main'),
os.path.join('examples', 'protocols', 'asio', 'tcp_echo_server', 'main'),
os.path.join('examples', 'protocols', 'asio', 'udp_echo_server', 'main'),
os.path.join('examples', 'protocols', 'aws_iot', 'subscribe_publish', 'main'),
os.path.join('examples', 'protocols', 'aws_iot', 'thing_shadow', 'main'),
os.path.join('examples', 'protocols', 'coap_client', 'main'),
os.path.join('examples', 'protocols', 'coap_server', 'main'),
os.path.join('examples', 'protocols', 'esp_http_client', 'main'),
os.path.join('examples', 'protocols', 'http2_request', 'main'),
os.path.join('examples', 'protocols', 'http_request', 'main'),
os.path.join('examples', 'protocols', 'http_server', 'advanced_tests', 'main'),
os.path.join('examples', 'protocols', 'http_server', 'persistent_sockets', 'main'),
os.path.join('examples', 'protocols', 'http_server', 'simple', 'main'),
os.path.join('examples', 'protocols', 'https_mbedtls', 'main'),
os.path.join('examples', 'protocols', 'https_request', 'main'),
os.path.join('examples', 'protocols', 'https_server', 'main'),
os.path.join('examples', 'protocols', 'mdns', 'main'),
os.path.join('examples', 'protocols', 'modbus_slave', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'ssl', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'ssl_mutual_auth', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'tcp', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'ws', 'main'),
os.path.join('examples', 'protocols', 'mqtt', 'wss', 'main'),
os.path.join('examples', 'protocols', 'openssl_client', 'main'),
os.path.join('examples', 'protocols', 'openssl_server', 'main'),
os.path.join('examples', 'protocols', 'pppos_client', 'main'),
os.path.join('examples', 'protocols', 'sntp', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'tcp_client', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'tcp_server', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'udp_client', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'udp_multicast', 'main'),
os.path.join('examples', 'protocols', 'sockets', 'udp_server', 'main'),
os.path.join('examples', 'provisioning', 'ble_prov', 'main'),
os.path.join('examples', 'provisioning', 'console_prov', 'main'),
os.path.join('examples', 'provisioning', 'custom_config', 'main'),
os.path.join('examples', 'provisioning', 'softap_prov', 'main'),
os.path.join('examples', 'system', 'app_trace_to_host', 'main'),
os.path.join('examples', 'system', 'base_mac_address', 'main'),
os.path.join('examples', 'system', 'console', 'main'),
os.path.join('examples', 'system', 'deep_sleep', 'main'),
os.path.join('examples', 'system', 'gcov', 'main'),
os.path.join('examples', 'system', 'ota', 'native_ota_example', 'main'),
os.path.join('examples', 'system', 'ota', 'simple_ota_example', 'main'),
os.path.join('examples', 'system', 'sysview_tracing', 'main'),
os.path.join('examples', 'wifi', 'espnow', 'main'),
os.path.join('examples', 'wifi', 'getting_started', 'softAP', 'main'),
os.path.join('examples', 'wifi', 'getting_started', 'station', 'main'),
os.path.join('examples', 'wifi', 'power_save', 'main'),
os.path.join('examples', 'wifi', 'scan', 'main'),
os.path.join('examples', 'wifi', 'simple_sniffer', 'main'),
os.path.join('examples', 'wifi', 'wpa2_enterprise', 'main'),
os.path.join('examples', 'wifi', 'wps', 'main'),
os.path.join('tools', 'kconfig'),
os.path.join('tools', 'kconfig_new', 'test'),
os.path.join('tools', 'ldgen', 'test', 'data'),
os.path.join('tools', 'unit-test-app', 'components', 'test_utils'),
)
SPACES_PER_INDENT = 4
CONFIG_NAME_MAX_LENGTH = 50
# TODO increase prefix length (after the names have been refactored)
CONFIG_NAME_MIN_PREFIX_LENGTH = 0
# list or rules for lines
LINE_ERROR_RULES = [
# (regular expression for finding, error message, correction)
(re.compile(r'\t'), 'tabulators should be replaced by spaces', r' ' * SPACES_PER_INDENT),
(re.compile(r'\s+\n'), 'trailing whitespaces should be removed', r'\n'),
(re.compile(r'.{120}'), 'line should be shorter than 120 characters', None),
]
class InputError(RuntimeError):
"""
Represents and error on the input
"""
def __init__(self, path, line_number, error_msg, suggested_line):
super(InputError, self).__init__('{}:{}: {}'.format(path, line_number, error_msg))
self.suggested_line = suggested_line
class BaseChecker(object):
"""
Base class for all checker objects
"""
def __init__(self, path_in_idf):
self.path_in_idf = path_in_idf
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
pass
class LineRuleChecker(BaseChecker):
"""
checks LINE_ERROR_RULES for each line
"""
def process_line(self, line, line_number):
errors = []
for rule in LINE_ERROR_RULES:
m = rule[0].search(line)
if m:
errors.append(rule[1])
if rule[2]:
line = rule[0].sub(rule[2], line)
if len(errors) > 0:
raise InputError(self.path_in_idf, line_number, "; ".join(errors), line)
class IndentAndNameChecker(BaseChecker):
"""
checks the indentation of each line and configuration names
"""
def __init__(self, path_in_idf, debug=False):
super(IndentAndNameChecker, self).__init__(path_in_idf)
self.debug = debug
self.min_prefix_length = CONFIG_NAME_MIN_PREFIX_LENGTH
# stack of the nested menuconfig items, e.g. ['mainmenu', 'menu', 'config']
self.level_stack = []
# stack common prefixes of configs
self.prefix_stack = []
# menu items which increase the indentation of the next line
self.re_increase_level = re.compile(r'''^\s*
(
(menu(?!config))
|(mainmenu)
|(choice)
|(config)
|(menuconfig)
|(help)
|(if)
)
''', re.X)
# closing menu items which decrease the indentation
self.re_decrease_level = re.compile(r'''^\s*
(
(endmenu)
|(endchoice)
|(endif)
)
''', re.X)
# matching beginning of the closing menuitems
self.pair_dic = {'endmenu': 'menu',
'endchoice': 'choice',
'endif': 'if',
}
# regex for config names
self.re_name = re.compile(r'''^
(
(?:config)
|(?:menuconfig)
|(?:choice)
)\s+
(\w+)
''', re.X)
# regex for new prefix stack
self.re_new_stack = re.compile(r'''^
(
(?:menu(?!config))
|(?:mainmenu)
|(?:choice)
)
''', re.X)
def __exit__(self, type, value, traceback):
super(IndentAndNameChecker, self).__exit__(type, value, traceback)
if len(self.prefix_stack) > 0:
self.check_common_prefix('', 'EOF')
if len(self.prefix_stack) != 0:
if self.debug:
print(self.prefix_stack)
raise RuntimeError("Prefix stack should be empty. Perhaps a menu/choice hasn't been closed")
def del_from_level_stack(self, count):
""" delete count items from the end of the level_stack """
if count > 0:
# del self.level_stack[-0:] would delete everything and we expect not to delete anything for count=0
del self.level_stack[-count:]
def update_level_for_inc_pattern(self, new_item):
if self.debug:
print('level+', new_item, ': ', self.level_stack, end=' -> ')
# "config" and "menuconfig" don't have a closing pair. So if new_item is an item which need to be indented
# outside the last "config" or "menuconfig" then we need to find to a parent where it belongs
if new_item in ['config', 'menuconfig', 'menu', 'choice', 'if']:
# item is not belonging to a previous "config" or "menuconfig" so need to indent to parent
for i, item in enumerate(reversed(self.level_stack)):
if item in ['menu', 'mainmenu', 'choice', 'if']:
# delete items ("config", "menuconfig", "help") until the appropriate parent
self.del_from_level_stack(i)
break
self.level_stack.append(new_item)
if self.debug:
print(self.level_stack)
# The new indent is for the next line. Use the old one for the current line:
return len(self.level_stack) - 1
def update_level_for_dec_pattern(self, new_item):
if self.debug:
print('level-', new_item, ': ', self.level_stack, end=' -> ')
target = self.pair_dic[new_item]
for i, item in enumerate(reversed(self.level_stack)):
# find the matching beginning for the closing item in reverse-order search
# Note: "menuconfig", "config" and "help" don't have closing pairs and they are also on the stack. Now they
# will be deleted together with the "menu" or "choice" we are closing.
if item == target:
i += 1 # delete also the matching beginning
if self.debug:
print('delete ', i, end=' -> ')
self.del_from_level_stack(i)
break
if self.debug:
print(self.level_stack)
return len(self.level_stack)
def check_name_and_update_prefix(self, line, line_number):
m = self.re_name.search(line)
if m:
name = m.group(2)
name_length = len(name)
if name_length > CONFIG_NAME_MAX_LENGTH:
raise InputError(self.path_in_idf, line_number,
'{} is {} characters long and it should be {} at most'
''.format(name, name_length, CONFIG_NAME_MAX_LENGTH),
line) # no suggested correction for this
if self.prefix_stack[-1] is None:
self.prefix_stack[-1] = name
else:
# this has nothing common with paths but the algorithm can be used for this also
self.prefix_stack[-1] = os.path.commonprefix([self.prefix_stack[-1], name])
if self.debug:
print('prefix+', self.prefix_stack)
m = self.re_new_stack.search(line)
if m:
self.prefix_stack.append(None)
if self.debug:
print('prefix+', self.prefix_stack)
def check_common_prefix(self, line, line_number):
common_prefix = self.prefix_stack.pop()
if self.debug:
print('prefix-', self.prefix_stack)
if common_prefix is None:
return
common_prefix_len = len(common_prefix)
if common_prefix_len < self.min_prefix_length:
raise InputError(self.path_in_idf, line_number,
'Common prefix "{}" length is {} and should be at least {} characters long'
''.format(common_prefix, common_prefix_len, self.min_prefix_length),
line) # no suggested correction for this
if len(self.prefix_stack) > 0:
parent_prefix = self.prefix_stack[-1]
if parent_prefix is None:
# propagate to parent level where it will influence the prefix checking with the rest which might
# follow later on that level
self.prefix_stack[-1] = common_prefix
else:
if len(self.level_stack) > 0 and self.level_stack[-1] in ['mainmenu', 'menu']:
# the prefix from menu is not required to propagate to the children
return
if not common_prefix.startswith(parent_prefix):
raise InputError(self.path_in_idf, line_number,
'Common prefix "{}" should start with {}'
''.format(common_prefix, parent_prefix),
line) # no suggested correction for this
def process_line(self, line, line_number):
stripped_line = line.strip()
if len(stripped_line) == 0:
return
current_level = len(self.level_stack)
m = re.search(r'\S', line) # indent found as the first non-space character
if m:
current_indent = m.start()
else:
current_indent = 0
if current_level > 0 and self.level_stack[-1] == 'help':
if current_indent >= current_level * SPACES_PER_INDENT:
# this line belongs to 'help'
return
self.check_name_and_update_prefix(stripped_line, line_number)
m = self.re_increase_level.search(line)
if m:
current_level = self.update_level_for_inc_pattern(m.group(1))
else:
m = self.re_decrease_level.search(line)
if m:
new_item = m.group(1)
current_level = self.update_level_for_dec_pattern(new_item)
if new_item not in ['endif']:
# endif doesn't require to check the prefix because the items in inside if/endif belong to the
# same prefix level
self.check_common_prefix(line, line_number)
expected_indent = current_level * SPACES_PER_INDENT
if current_indent != expected_indent:
raise InputError(self.path_in_idf, line_number,
'Indentation consists of {} spaces instead of {}'.format(current_indent, expected_indent),
(' ' * expected_indent) + line.lstrip())
def valid_directory(path):
if not os.path.isdir(path):
raise argparse.ArgumentTypeError("{} is not a valid directory!".format(path))
return path
def main():
default_path = os.getenv('IDF_PATH', None)
parser = argparse.ArgumentParser(description='Kconfig style checker')
parser.add_argument('--verbose', '-v', help='Print more information (useful for debugging)',
action='store_true', default=False)
parser.add_argument('--directory', '-d', help='Path to directory where Kconfigs should be recursively checked '
'(for example $IDF_PATH)',
type=valid_directory,
required=default_path is None,
default=default_path)
args = parser.parse_args()
success_couter = 0
ignore_counter = 0
failure = False
# IGNORE_DIRS makes sense when the required directory is IDF_PATH
check_ignore_dirs = default_path is not None and os.path.abspath(args.directory) == os.path.abspath(default_path)
for root, dirnames, filenames in os.walk(args.directory):
for filename in [f for f in filenames if RE_KCONFIG.search(f)]:
full_path = os.path.join(root, filename)
path_in_idf = os.path.relpath(full_path, args.directory)
if check_ignore_dirs and path_in_idf.startswith(IGNORE_DIRS):
print('{}: Ignored'.format(path_in_idf))
ignore_counter += 1
continue
suggestions_full_path = full_path + OUTPUT_SUFFIX
with open(full_path, 'r', encoding='utf-8') as f, \
open(suggestions_full_path, 'w', encoding='utf-8', newline='\n') as f_o, \
LineRuleChecker(path_in_idf) as line_checker, \
IndentAndNameChecker(path_in_idf, debug=args.verbose) as indent_and_name_checker:
try:
for line_number, line in enumerate(f, start=1):
try:
for checker in [line_checker, indent_and_name_checker]:
checker.process_line(line, line_number)
# The line is correct therefore we echo it to the output file
f_o.write(line)
except InputError as e:
print(e)
failure = True
f_o.write(e.suggested_line)
except UnicodeDecodeError:
raise ValueError("The encoding of {} is not Unicode.".format(path_in_idf))
if failure:
print('{} has been saved with suggestions for resolving the issues. Please note that the suggestions '
'can be wrong and you might need to re-run the checker several times for solving all issues.'
''.format(path_in_idf + OUTPUT_SUFFIX))
print('Please fix the errors and run {} for checking the correctness of '
'Kconfigs.'.format(os.path.relpath(os.path.abspath(__file__), args.directory)))
sys.exit(1)
else:
success_couter += 1
print('{}: OK'.format(path_in_idf))
try:
os.remove(suggestions_full_path)
except Exception:
# not a serious error is when the file cannot be deleted
print('{} cannot be deleted!'.format(suggestions_full_path))
if ignore_counter > 0:
print('{} files have been ignored.'.format(ignore_counter))
if success_couter > 0:
print('{} files have been successfully checked.'.format(success_couter))
if __name__ == "__main__":
main()

View File

@ -71,3 +71,5 @@ tools/ldgen/test/test_generation.py
examples/build_system/cmake/idf_as_lib/build.sh
examples/storage/parttool/parttool_example.py
examples/system/ota/otatool/otatool_example.py
tools/check_kconfigs.py
tools/test_check_kconfigs.py

209
tools/test_check_kconfigs.py Executable file
View File

@ -0,0 +1,209 @@
#!/usr/bin/env python
#
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import unittest
from check_kconfigs import LineRuleChecker
from check_kconfigs import InputError
from check_kconfigs import IndentAndNameChecker
class ApplyLine(object):
def apply_line(self, string):
self.checker.process_line(string + '\n', 0)
def expect_error(self, string, expect, cleanup=None):
try:
with self.assertRaises(InputError) as cm:
self.apply_line(string)
if expect:
self.assertEqual(cm.exception.suggested_line, expect + '\n')
finally:
if cleanup:
# cleanup of the previous failure
self.apply_line(cleanup)
def expt_success(self, string):
self.apply_line(string)
class TestLineRuleChecker(unittest.TestCase, ApplyLine):
def setUp(self):
self.checker = LineRuleChecker('Kconfig')
def tearDown(self):
pass
def test_tabulators(self):
self.expect_error('\ttest', expect=' test')
self.expect_error('\t test', expect=' test')
self.expect_error(' \ttest', expect=' test')
self.expect_error(' \t test', expect=' test')
self.expt_success(' test')
self.expt_success('test')
def test_trailing_whitespaces(self):
self.expect_error(' ', expect='')
self.expect_error(' ', expect='')
self.expect_error('test ', expect='test')
self.expt_success('test')
self.expt_success('')
def test_line_length(self):
self.expect_error('x' * 120, expect=None)
self.expt_success('x' * 119)
self.expt_success('')
class TestIndentAndNameChecker(unittest.TestCase, ApplyLine):
def setUp(self):
self.checker = IndentAndNameChecker('Kconfig')
self.checker.min_prefix_length = 4
def tearDown(self):
self.checker.__exit__('Kconfig', None, None)
class TestIndent(TestIndentAndNameChecker):
def setUp(self):
super(TestIndent, self).setUp()
self.checker.min_prefix_length = 0 # prefixes are ignored in this test case
def test_indent_characters(self):
self.expt_success('menu "test"')
self.expect_error(' test', expect=' test')
self.expect_error(' test', expect=' test')
self.expect_error(' test', expect=' test')
self.expect_error(' test', expect=' test')
self.expt_success(' test')
self.expt_success(' test2')
self.expt_success(' config')
self.expect_error(' default', expect=' default')
self.expt_success(' help')
self.expect_error(' text', expect=' text')
self.expt_success(' help text')
self.expt_success(' menu')
self.expt_success(' endmenu')
self.expect_error(' choice', expect=' choice', cleanup=' endchoice')
self.expect_error(' choice', expect=' choice', cleanup=' endchoice')
self.expt_success(' choice')
self.expt_success(' endchoice')
self.expt_success(' config')
self.expt_success('endmenu')
def test_help_content(self):
self.expt_success('menu "test"')
self.expt_success(' config')
self.expt_success(' help')
self.expt_success(' description')
self.expt_success(' config keyword in the help')
self.expt_success(' menu keyword in the help')
self.expt_success(' menuconfig keyword in the help')
self.expt_success(' endmenu keyword in the help')
self.expt_success(' endmenu keyword in the help')
self.expt_success(' endmenu keyword in the help')
self.expect_error(' menu "real menu with wrong indent"',
expect=' menu "real menu with wrong indent"', cleanup=' endmenu')
self.expt_success('endmenu')
def test_mainmenu(self):
self.expt_success('mainmenu "test"')
self.expect_error('test', expect=' test')
self.expt_success(' not_a_keyword')
self.expt_success(' config')
self.expt_success(' menuconfig')
self.expect_error('test', expect=' test')
self.expect_error(' test', expect=' test')
self.expt_success(' menu')
self.expt_success(' endmenu')
def test_ifendif(self):
self.expt_success('menu "test"')
self.expt_success(' config')
self.expt_success(' help')
self.expect_error(' if', expect=' if', cleanup=' endif')
self.expt_success(' if')
self.expect_error(' config', expect=' config')
self.expt_success(' config')
self.expt_success(' help')
self.expt_success(' endif')
self.expt_success(' config')
self.expt_success('endmenu')
class TestName(TestIndentAndNameChecker):
def setUp(self):
super(TestName, self).setUp()
self.checker.min_prefix_length = 0 # prefixes are ignored in this test case
def test_name_length(self):
self.expt_success('menu "test"')
self.expt_success(' config ABC')
self.expt_success(' config ' + ('X' * 50))
self.expect_error(' config ' + ('X' * 51), expect=None)
self.expt_success(' menuconfig ' + ('X' * 50))
self.expect_error(' menuconfig ' + ('X' * 51), expect=None)
self.expt_success(' choice ' + ('X' * 50))
self.expect_error(' choice ' + ('X' * 51), expect=None)
self.expt_success('endmenu')
class TestPrefix(TestIndentAndNameChecker):
def test_prefix_len(self):
self.expt_success('menu "test"')
self.expt_success(' config ABC_1')
self.expt_success(' config ABC_2')
self.expt_success(' config ABC_DEBUG')
self.expt_success(' config ABC_ANOTHER')
self.expt_success('endmenu')
self.expt_success('menu "test2"')
self.expt_success(' config A')
self.expt_success(' config B')
self.expect_error('endmenu', expect=None)
def test_choices(self):
self.expt_success('menu "test"')
self.expt_success(' choice ASSERTION_LEVEL')
self.expt_success(' config ASSERTION_DEBUG')
self.expt_success(' config ASSERTION_RELEASE')
self.expt_success(' menuconfig ASSERTION_XY')
self.expt_success(' endchoice')
self.expt_success(' choice DEBUG')
self.expt_success(' config DE_1')
self.expt_success(' config DE_2')
self.expect_error(' endchoice', expect=None)
self.expect_error('endmenu', expect=None)
def test_nested_menu(self):
self.expt_success('menu "test"')
self.expt_success(' config DOESNT_MATTER')
self.expt_success(' menu "inner menu"')
self.expt_success(' config MENUOP_1')
self.expt_success(' config MENUOP_2')
self.expt_success(' config MENUOP_3')
self.expt_success(' endmenu')
self.expt_success('endmenu')
def test_nested_ifendif(self):
self.expt_success('menu "test"')
self.expt_success(' config MENUOP_1')
self.expt_success(' if MENUOP_1')
self.expt_success(' config MENUOP_2')
self.expt_success(' endif')
self.expt_success('endmenu')
if __name__ == '__main__':
unittest.main()