├── CMakeLists.txt ├── Kconfig ├── README.md ├── cmake ├── backports │ └── FindPythonInterp.cmake ├── extensions.cmake ├── kconfig.cmake ├── python.cmake └── top.cmake ├── configs └── test_defconfig └── scripts └── kconfig ├── diffconfig ├── guiconfig.py ├── hardenconfig.py ├── hardened.csv ├── kconfig.py ├── kconfigfunctions.py ├── kconfiglib.py ├── lint.py └── menuconfig.py /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | project(kconfig-test 4 | VERSION 1.0 5 | DESCRIPTION "kconfig test project" 6 | LANGUAGES C 7 | ) 8 | 9 | include(cmake/top.cmake) 10 | 11 | if(CONFIG_TEST_OPTION) 12 | 13 | message("Config test_option enabled") 14 | 15 | endif() 16 | 17 | 18 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | mainmenu "Project Configuration" 2 | 3 | config TEST_OPTION 4 | bool "Test Option" 5 | help 6 | Test option description 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmake-kconfig 2 | 3 | Minimal cmake project with kconfig integration adapted from Zephyr. 4 | 5 | # Example 6 | 7 | Default build using a provided configurations called `test`. 8 | 9 | ``` 10 | mkdir build 11 | cd build 12 | cmake -GNinja -DBOARD=test .. 13 | ninja 14 | ``` 15 | 16 | Note the above uses the config provided by: 17 | ``` 18 | configs/test_defconfig 19 | ``` 20 | 21 | Updating the configuration: 22 | 23 | ``` 24 | ninja menuconfig 25 | ``` 26 | 27 | This will bring up an interactive menu to turn options on/off and it will 28 | save a .config file in the build directory. 29 | 30 | The test_defconfig can be updated by copying the build/.config file to 31 | configs/test_defconfig and committing. 32 | 33 | Before any targets are built an autoconf.h header file is generated under: 34 | 35 | ``` 36 | build/kconfig/include/generate/autoconf.h 37 | ``` 38 | 39 | This is allows everything to have a common configuration. 40 | 41 | ## Cmake 42 | ``` 43 | if(CONFIG_TEST_OPTION) 44 | message("Config test_option is enabled") 45 | endif() 46 | ``` 47 | 48 | ## Make 49 | ``` 50 | -include build/.config 51 | 52 | ifeq ($(CONFIG_TEST_OPTION),y) 53 | objs += src/test_option.o 54 | endif 55 | ``` 56 | 57 | ## C/C++ ... 58 | 59 | ``` 60 | #include 61 | 62 | #ifdef CONFIG_TEST_OPTION 63 | // Code built for option. 64 | #endif 65 | ``` 66 | # Kconfig 67 | 68 | Kconfig is Brilliant! It manages a unified configuration separately from 69 | the main source code that can be used with the build system and source code. 70 | 71 | It is the best-in-class configuration management tool that exists for embedded 72 | C code, period. 73 | 74 | It allows dependencies to be defined between different config options. 75 | And the best thing is, some really smart people have worked all this out before, 76 | so we get a really powerful system for little effort/cost. 77 | 78 | -------------------------------------------------------------------------------- /cmake/backports/FindPythonInterp.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #.rst: 5 | # FindPythonInterp 6 | # ---------------- 7 | # 8 | # Find python interpreter 9 | # 10 | # This module finds if Python interpreter is installed and determines 11 | # where the executables are. This code sets the following variables: 12 | # 13 | # :: 14 | # 15 | # PYTHONINTERP_FOUND - Was the Python executable found 16 | # PYTHON_EXECUTABLE - path to the Python interpreter 17 | # 18 | # 19 | # 20 | # :: 21 | # 22 | # PYTHON_VERSION_STRING - Python version found e.g. 2.5.2 23 | # PYTHON_VERSION_MAJOR - Python major version found e.g. 2 24 | # PYTHON_VERSION_MINOR - Python minor version found e.g. 5 25 | # PYTHON_VERSION_PATCH - Python patch version found e.g. 2 26 | # 27 | # 28 | # 29 | # The Python_ADDITIONAL_VERSIONS variable can be used to specify a list 30 | # of version numbers that should be taken into account when searching 31 | # for Python. You need to set this variable before calling 32 | # find_package(PythonInterp). 33 | # 34 | # If calling both ``find_package(PythonInterp)`` and 35 | # ``find_package(PythonLibs)``, call ``find_package(PythonInterp)`` first to 36 | # get the currently active Python version by default with a consistent version 37 | # of PYTHON_LIBRARIES. 38 | 39 | function(determine_python_version exe result) 40 | execute_process(COMMAND "${exe}" -c 41 | "import sys; sys.stdout.write(';'.join([str(x) for x in sys.version_info[:3]]))" 42 | OUTPUT_VARIABLE _VERSION 43 | RESULT_VARIABLE _PYTHON_VERSION_RESULT 44 | ERROR_QUIET) 45 | if(_PYTHON_VERSION_RESULT) 46 | # sys.version predates sys.version_info, so use that 47 | execute_process(COMMAND "${exe}" -c "import sys; sys.stdout.write(sys.version)" 48 | OUTPUT_VARIABLE _VERSION 49 | RESULT_VARIABLE _PYTHON_VERSION_RESULT 50 | ERROR_QUIET) 51 | if(_PYTHON_VERSION_RESULT) 52 | # sys.version was first documented for Python 1.5, so assume 53 | # this is older. 54 | set(ver "1.4") 55 | else() 56 | string(REGEX REPLACE " .*" "" ver "${_VERSION}") 57 | endif() 58 | else() 59 | string(REPLACE ";" "." ver "${_VERSION}") 60 | endif() 61 | set(${result} ${ver} PARENT_SCOPE) 62 | endfunction() 63 | 64 | # Find out if the 'python' executable on path has the correct version, 65 | # and choose it if it does. This gives this executable the highest 66 | # priority, which is expected behaviour. 67 | find_program(PYTHON_EXECUTABLE python) 68 | if(NOT (${PYTHON_EXECUTABLE} STREQUAL PYTHON_EXECUTABLE-NOTFOUND)) 69 | determine_python_version(${PYTHON_EXECUTABLE} ver) 70 | if(${ver} VERSION_LESS PythonInterp_FIND_VERSION) 71 | # We didn't find the correct version on path, so forget about it 72 | # and continue looking. 73 | unset(PYTHON_EXECUTABLE) 74 | unset(PYTHON_EXECUTABLE CACHE) 75 | endif() 76 | endif() 77 | 78 | unset(_Python_NAMES) 79 | 80 | set(_PYTHON1_VERSIONS 1.6 1.5) 81 | set(_PYTHON2_VERSIONS 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0) 82 | set(_PYTHON3_VERSIONS 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0) 83 | 84 | if(PythonInterp_FIND_VERSION) 85 | if(PythonInterp_FIND_VERSION_COUNT GREATER 1) 86 | set(_PYTHON_FIND_MAJ_MIN "${PythonInterp_FIND_VERSION_MAJOR}.${PythonInterp_FIND_VERSION_MINOR}") 87 | list(APPEND _Python_NAMES 88 | python${_PYTHON_FIND_MAJ_MIN} 89 | python${PythonInterp_FIND_VERSION_MAJOR}) 90 | unset(_PYTHON_FIND_OTHER_VERSIONS) 91 | if(NOT PythonInterp_FIND_VERSION_EXACT) 92 | foreach(_PYTHON_V ${_PYTHON${PythonInterp_FIND_VERSION_MAJOR}_VERSIONS}) 93 | if(NOT _PYTHON_V VERSION_LESS _PYTHON_FIND_MAJ_MIN) 94 | list(APPEND _PYTHON_FIND_OTHER_VERSIONS ${_PYTHON_V}) 95 | endif() 96 | endforeach() 97 | endif() 98 | unset(_PYTHON_FIND_MAJ_MIN) 99 | else() 100 | list(APPEND _Python_NAMES python${PythonInterp_FIND_VERSION_MAJOR}) 101 | set(_PYTHON_FIND_OTHER_VERSIONS ${_PYTHON${PythonInterp_FIND_VERSION_MAJOR}_VERSIONS}) 102 | endif() 103 | else() 104 | set(_PYTHON_FIND_OTHER_VERSIONS ${_PYTHON3_VERSIONS} ${_PYTHON2_VERSIONS} ${_PYTHON1_VERSIONS}) 105 | endif() 106 | find_program(PYTHON_EXECUTABLE NAMES ${_Python_NAMES}) 107 | 108 | # Set up the versions we know about, in the order we will search. Always add 109 | # the user supplied additional versions to the front. 110 | set(_Python_VERSIONS ${Python_ADDITIONAL_VERSIONS}) 111 | # If FindPythonInterp has already found the major and minor version, 112 | # insert that version next to get consistent versions of the interpreter and 113 | # library. 114 | if(DEFINED PYTHONLIBS_VERSION_STRING) 115 | string(REPLACE "." ";" _PYTHONLIBS_VERSION "${PYTHONLIBS_VERSION_STRING}") 116 | list(GET _PYTHONLIBS_VERSION 0 _PYTHONLIBS_VERSION_MAJOR) 117 | list(GET _PYTHONLIBS_VERSION 1 _PYTHONLIBS_VERSION_MINOR) 118 | list(APPEND _Python_VERSIONS ${_PYTHONLIBS_VERSION_MAJOR}.${_PYTHONLIBS_VERSION_MINOR}) 119 | endif() 120 | 121 | # Search for the current active python version first on Linux, and last on Windows 122 | if(NOT WIN32) 123 | list(APPEND _Python_VERSIONS ";") 124 | endif() 125 | 126 | list(APPEND _Python_VERSIONS ${_PYTHON_FIND_OTHER_VERSIONS}) 127 | 128 | if(WIN32) 129 | list(APPEND _Python_VERSIONS ";") 130 | endif() 131 | 132 | unset(_PYTHON_FIND_OTHER_VERSIONS) 133 | unset(_PYTHON1_VERSIONS) 134 | unset(_PYTHON2_VERSIONS) 135 | unset(_PYTHON3_VERSIONS) 136 | 137 | # Search for newest python version if python executable isn't found 138 | if(NOT PYTHON_EXECUTABLE) 139 | foreach(_CURRENT_VERSION IN LISTS _Python_VERSIONS) 140 | set(_Python_NAMES python${_CURRENT_VERSION}) 141 | if(WIN32) 142 | list(APPEND _Python_NAMES python) 143 | endif() 144 | 145 | if(WIN32) 146 | find_program(PYTHON_EXECUTABLE 147 | NAMES ${_Python_NAMES} 148 | HINTS [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath] 149 | ) 150 | endif() 151 | 152 | find_program(PYTHON_EXECUTABLE 153 | NAMES ${_Python_NAMES} 154 | PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath] 155 | ) 156 | endforeach() 157 | endif() 158 | 159 | # determine python version string 160 | if(PYTHON_EXECUTABLE) 161 | determine_python_version(${PYTHON_EXECUTABLE} res) 162 | set(PYTHON_VERSION_STRING ${res}) 163 | 164 | if(PYTHON_VERSION_STRING MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") 165 | set(PYTHON_VERSION_PATCH "${CMAKE_MATCH_3}") 166 | else() 167 | set(PYTHON_VERSION_PATCH "0") 168 | endif() 169 | set(PYTHON_VERSION_MAJOR "${CMAKE_MATCH_1}") 170 | set(PYTHON_VERSION_MINOR "${CMAKE_MATCH_2}") 171 | endif() 172 | 173 | include(FindPackageHandleStandardArgs) 174 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(PythonInterp REQUIRED_VARS PYTHON_EXECUTABLE VERSION_VAR PYTHON_VERSION_STRING) 175 | 176 | mark_as_advanced(PYTHON_EXECUTABLE) 177 | -------------------------------------------------------------------------------- /cmake/extensions.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | ######################################################## 4 | # Table of contents 5 | ######################################################## 6 | # 1. Zephyr-aware extensions 7 | # 1.1. zephyr_* 8 | # 1.2. zephyr_library_* 9 | # 1.2.1 zephyr_interface_library_* 10 | # 1.3. generate_inc_* 11 | # 1.4. board_* 12 | # 1.5. Misc. 13 | # 2. Kconfig-aware extensions 14 | # 2.1 *_if_kconfig 15 | # 2.2 Misc 16 | # 3. CMake-generic extensions 17 | # 3.1. *_ifdef 18 | # 3.2. *_ifndef 19 | # 3.3. *_option compiler compatibility checks 20 | # 3.3.1 Toolchain integration 21 | # 3.4. Debugging CMake 22 | # 3.5. File system management 23 | 24 | ######################################################## 25 | # 1. Zephyr-aware extensions 26 | ######################################################## 27 | # 1.1. zephyr_* 28 | # 29 | # The following methods are for modifying the CMake library[0] called 30 | # "zephyr". zephyr is a catch-all CMake library for source files that 31 | # can be built purely with the include paths, defines, and other 32 | # compiler flags that all zephyr source files use. 33 | # [0] https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html 34 | # 35 | # Example usage: 36 | # zephyr_sources( 37 | # random_esp32.c 38 | # utils.c 39 | # ) 40 | # 41 | # Is short for: 42 | # target_sources(zephyr PRIVATE 43 | # ${CMAKE_CURRENT_SOURCE_DIR}/random_esp32.c 44 | # ${CMAKE_CURRENT_SOURCE_DIR}/utils.c 45 | # ) 46 | # 47 | # As a very high-level introduction here are two call graphs that are 48 | # purposely minimalistic and incomplete. 49 | # 50 | # zephyr_library_cc_option() 51 | # | 52 | # v 53 | # zephyr_library_compile_options() --> target_compile_options() 54 | # 55 | # 56 | # zephyr_cc_option() ---> target_cc_option() 57 | # | 58 | # v 59 | # zephyr_cc_option_fallback() ---> target_cc_option_fallback() 60 | # | 61 | # v 62 | # zephyr_compile_options() ---> target_compile_options() 63 | # 64 | 65 | 66 | # https://cmake.org/cmake/help/latest/command/target_sources.html 67 | function(zephyr_sources) 68 | foreach(arg ${ARGV}) 69 | if(IS_DIRECTORY ${arg}) 70 | message(FATAL_ERROR "zephyr_sources() was called on a directory") 71 | endif() 72 | target_sources(zephyr PRIVATE ${arg}) 73 | endforeach() 74 | endfunction() 75 | 76 | # https://cmake.org/cmake/help/latest/command/target_include_directories.html 77 | function(zephyr_include_directories) 78 | foreach(arg ${ARGV}) 79 | if(IS_ABSOLUTE ${arg}) 80 | set(path ${arg}) 81 | else() 82 | set(path ${CMAKE_CURRENT_SOURCE_DIR}/${arg}) 83 | endif() 84 | target_include_directories(zephyr_interface INTERFACE ${path}) 85 | endforeach() 86 | endfunction() 87 | 88 | # https://cmake.org/cmake/help/latest/command/target_include_directories.html 89 | function(zephyr_system_include_directories) 90 | foreach(arg ${ARGV}) 91 | if(IS_ABSOLUTE ${arg}) 92 | set(path ${arg}) 93 | else() 94 | set(path ${CMAKE_CURRENT_SOURCE_DIR}/${arg}) 95 | endif() 96 | target_include_directories(zephyr_interface SYSTEM INTERFACE ${path}) 97 | endforeach() 98 | endfunction() 99 | 100 | # https://cmake.org/cmake/help/latest/command/target_compile_definitions.html 101 | function(zephyr_compile_definitions) 102 | target_compile_definitions(zephyr_interface INTERFACE ${ARGV}) 103 | endfunction() 104 | 105 | # https://cmake.org/cmake/help/latest/command/target_compile_options.html 106 | function(zephyr_compile_options) 107 | target_compile_options(zephyr_interface INTERFACE ${ARGV}) 108 | endfunction() 109 | 110 | # https://cmake.org/cmake/help/latest/command/target_link_libraries.html 111 | function(zephyr_link_libraries) 112 | target_link_libraries(zephyr_interface INTERFACE ${ARGV}) 113 | endfunction() 114 | 115 | # See this file section 3.1. target_cc_option 116 | function(zephyr_cc_option) 117 | foreach(arg ${ARGV}) 118 | target_cc_option(zephyr_interface INTERFACE ${arg}) 119 | endforeach() 120 | endfunction() 121 | 122 | function(zephyr_cc_option_fallback option1 option2) 123 | target_cc_option_fallback(zephyr_interface INTERFACE ${option1} ${option2}) 124 | endfunction() 125 | 126 | function(zephyr_ld_options) 127 | target_ld_options(zephyr_interface INTERFACE ${ARGV}) 128 | endfunction() 129 | 130 | # Getter functions for extracting build information from 131 | # zephyr_interface. Returning lists, and strings is supported, as is 132 | # requesting specific categories of build information (defines, 133 | # includes, options). 134 | # 135 | # The naming convention follows: 136 | # zephyr_get_${build_information}_for_lang${format}(lang x [STRIP_PREFIX]) 137 | # Where 138 | # the argument 'x' is written with the result 139 | # and 140 | # ${build_information} can be one of 141 | # - include_directories # -I directories 142 | # - system_include_directories # -isystem directories 143 | # - compile_definitions # -D'efines 144 | # - compile_options # misc. compiler flags 145 | # and 146 | # ${format} can be 147 | # - the empty string '', signifying that it should be returned as a list 148 | # - _as_string signifying that it should be returned as a string 149 | # and 150 | # ${lang} can be one of 151 | # - C 152 | # - CXX 153 | # - ASM 154 | # 155 | # STRIP_PREFIX 156 | # 157 | # By default the result will be returned ready to be passed directly 158 | # to a compiler, e.g. prefixed with -D, or -I, but it is possible to 159 | # omit this prefix by specifying 'STRIP_PREFIX' . This option has no 160 | # effect for 'compile_options'. 161 | # 162 | # e.g. 163 | # zephyr_get_include_directories_for_lang(ASM x) 164 | # writes "-Isome_dir;-Isome/other/dir" to x 165 | 166 | function(zephyr_get_include_directories_for_lang_as_string lang i) 167 | zephyr_get_include_directories_for_lang(${lang} list_of_flags ${ARGN}) 168 | 169 | convert_list_of_flags_to_string_of_flags(list_of_flags str_of_flags) 170 | 171 | set(${i} ${str_of_flags} PARENT_SCOPE) 172 | endfunction() 173 | 174 | function(zephyr_get_system_include_directories_for_lang_as_string lang i) 175 | zephyr_get_system_include_directories_for_lang(${lang} list_of_flags ${ARGN}) 176 | 177 | convert_list_of_flags_to_string_of_flags(list_of_flags str_of_flags) 178 | 179 | set(${i} ${str_of_flags} PARENT_SCOPE) 180 | endfunction() 181 | 182 | function(zephyr_get_compile_definitions_for_lang_as_string lang i) 183 | zephyr_get_compile_definitions_for_lang(${lang} list_of_flags ${ARGN}) 184 | 185 | convert_list_of_flags_to_string_of_flags(list_of_flags str_of_flags) 186 | 187 | set(${i} ${str_of_flags} PARENT_SCOPE) 188 | endfunction() 189 | 190 | function(zephyr_get_compile_options_for_lang_as_string lang i) 191 | zephyr_get_compile_options_for_lang(${lang} list_of_flags) 192 | 193 | convert_list_of_flags_to_string_of_flags(list_of_flags str_of_flags) 194 | 195 | set(${i} ${str_of_flags} PARENT_SCOPE) 196 | endfunction() 197 | 198 | function(zephyr_get_include_directories_for_lang lang i) 199 | get_property_and_add_prefix(flags zephyr_interface INTERFACE_INCLUDE_DIRECTORIES 200 | "-I" 201 | ${ARGN} 202 | ) 203 | 204 | process_flags(${lang} flags output_list) 205 | 206 | set(${i} ${output_list} PARENT_SCOPE) 207 | endfunction() 208 | 209 | function(zephyr_get_system_include_directories_for_lang lang i) 210 | get_property_and_add_prefix(flags zephyr_interface INTERFACE_SYSTEM_INCLUDE_DIRECTORIES 211 | "-isystem" 212 | ${ARGN} 213 | ) 214 | 215 | process_flags(${lang} flags output_list) 216 | 217 | set(${i} ${output_list} PARENT_SCOPE) 218 | endfunction() 219 | 220 | function(zephyr_get_compile_definitions_for_lang lang i) 221 | get_property_and_add_prefix(flags zephyr_interface INTERFACE_COMPILE_DEFINITIONS 222 | "-D" 223 | ${ARGN} 224 | ) 225 | 226 | process_flags(${lang} flags output_list) 227 | 228 | set(${i} ${output_list} PARENT_SCOPE) 229 | endfunction() 230 | 231 | function(zephyr_get_compile_options_for_lang lang i) 232 | get_property(flags TARGET zephyr_interface PROPERTY INTERFACE_COMPILE_OPTIONS) 233 | 234 | process_flags(${lang} flags output_list) 235 | 236 | set(${i} ${output_list} PARENT_SCOPE) 237 | endfunction() 238 | 239 | # This function writes a dict to it's output parameter 240 | # 'return_dict'. The dict has information about the parsed arguments, 241 | # 242 | # Usage: 243 | # zephyr_get_parse_args(foo ${ARGN}) 244 | # print(foo_STRIP_PREFIX) # foo_STRIP_PREFIX might be set to 1 245 | function(zephyr_get_parse_args return_dict) 246 | foreach(x ${ARGN}) 247 | if(x STREQUAL STRIP_PREFIX) 248 | set(${return_dict}_STRIP_PREFIX 1 PARENT_SCOPE) 249 | endif() 250 | endforeach() 251 | endfunction() 252 | 253 | function(process_flags lang input output) 254 | # The flags might contains compile language generator expressions that 255 | # look like this: 256 | # $<$:-fno-exceptions> 257 | # 258 | # Flags that don't specify a language like this apply to all 259 | # languages. 260 | # 261 | # See COMPILE_LANGUAGE in 262 | # https://cmake.org/cmake/help/v3.3/manual/cmake-generator-expressions.7.html 263 | # 264 | # To deal with this, we apply a regex to extract the flag and also 265 | # to find out if the language matches. 266 | # 267 | # If this doesn't work out we might need to ban the use of 268 | # COMPILE_LANGUAGE and instead partition C, CXX, and ASM into 269 | # different libraries 270 | set(languages C CXX ASM) 271 | 272 | set(tmp_list "") 273 | 274 | foreach(flag ${${input}}) 275 | set(is_compile_lang_generator_expression 0) 276 | foreach(l ${languages}) 277 | if(flag MATCHES ":([^>]+)>") 278 | set(is_compile_lang_generator_expression 1) 279 | if(${l} STREQUAL ${lang}) 280 | list(APPEND tmp_list ${CMAKE_MATCH_1}) 281 | break() 282 | endif() 283 | endif() 284 | endforeach() 285 | 286 | if(NOT is_compile_lang_generator_expression) 287 | list(APPEND tmp_list ${flag}) 288 | endif() 289 | endforeach() 290 | 291 | set(${output} ${tmp_list} PARENT_SCOPE) 292 | endfunction() 293 | 294 | function(convert_list_of_flags_to_string_of_flags ptr_list_of_flags string_of_flags) 295 | # Convert the list to a string so we can do string replace 296 | # operations on it and replace the ";" list separators with a 297 | # whitespace so the flags are spaced out 298 | string(REPLACE ";" " " locally_scoped_string_of_flags "${${ptr_list_of_flags}}") 299 | 300 | # Set the output variable in the parent scope 301 | set(${string_of_flags} ${locally_scoped_string_of_flags} PARENT_SCOPE) 302 | endfunction() 303 | 304 | macro(get_property_and_add_prefix result target property prefix) 305 | zephyr_get_parse_args(args ${ARGN}) 306 | 307 | if(args_STRIP_PREFIX) 308 | set(maybe_prefix "") 309 | else() 310 | set(maybe_prefix ${prefix}) 311 | endif() 312 | 313 | get_property(target_property TARGET ${target} PROPERTY ${property}) 314 | foreach(x ${target_property}) 315 | list(APPEND ${result} ${maybe_prefix}${x}) 316 | endforeach() 317 | endmacro() 318 | 319 | # 1.2 zephyr_library_* 320 | # 321 | # Zephyr libraries use CMake's library concept and a set of 322 | # assumptions about how zephyr code is organized to cut down on 323 | # boilerplate code. 324 | # 325 | # A Zephyr library can be constructed by the function zephyr_library 326 | # or zephyr_library_named. The constructors create a CMake library 327 | # with a name accessible through the variable ZEPHYR_CURRENT_LIBRARY. 328 | # 329 | # The variable ZEPHYR_CURRENT_LIBRARY should seldom be needed since 330 | # the zephyr libraries have methods that modify the libraries. These 331 | # methods have the signature: zephyr_library_ 332 | # 333 | # The methods are wrappers around the CMake target_* functions. See 334 | # https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html for 335 | # documentation on the underlying target_* functions. 336 | # 337 | # The methods modify the CMake target_* API to reduce boilerplate; 338 | # PRIVATE is assumed 339 | # The target is assumed to be ZEPHYR_CURRENT_LIBRARY 340 | # 341 | # When a flag that is given through the zephyr_* API conflicts with 342 | # the zephyr_library_* API then precedence will be given to the 343 | # zephyr_library_* API. In other words, local configuration overrides 344 | # global configuration. 345 | 346 | # Constructor with a directory-inferred name 347 | macro(zephyr_library) 348 | zephyr_library_get_current_dir_lib_name(${ZEPHYR_BASE} lib_name) 349 | zephyr_library_named(${lib_name}) 350 | endmacro() 351 | 352 | # Determines what the current directory's lib name would be according to the 353 | # provided base and writes it to the argument "lib_name" 354 | macro(zephyr_library_get_current_dir_lib_name base lib_name) 355 | # Remove the prefix (/home/sebo/zephyr/driver/serial/CMakeLists.txt => driver/serial/CMakeLists.txt) 356 | file(RELATIVE_PATH name ${base} ${CMAKE_CURRENT_LIST_FILE}) 357 | 358 | # Remove the filename (driver/serial/CMakeLists.txt => driver/serial) 359 | get_filename_component(name ${name} DIRECTORY) 360 | 361 | # Replace / with __ (driver/serial => driver__serial) 362 | string(REGEX REPLACE "/" "__" name ${name}) 363 | 364 | set(${lib_name} ${name}) 365 | endmacro() 366 | 367 | # Constructor with an explicitly given name. 368 | macro(zephyr_library_named name) 369 | # This is a macro because we need add_library() to be executed 370 | # within the scope of the caller. 371 | set(ZEPHYR_CURRENT_LIBRARY ${name}) 372 | add_library(${name} STATIC "") 373 | 374 | zephyr_append_cmake_library(${name}) 375 | 376 | target_link_libraries(${name} PUBLIC zephyr_interface) 377 | endmacro() 378 | 379 | # Provides amend functionality to a Zephyr library for out-of-tree usage. 380 | # 381 | # When called from a Zephyr module, the corresponding zephyr library defined 382 | # within Zephyr will be looked up. 383 | # 384 | # Note, in order to ensure correct library when amending, the folder structure in the 385 | # Zephyr module must resemble the structure used in Zephyr, as example: 386 | # 387 | # Example: to amend the zephyr library created in 388 | # ZEPHYR_BASE/drivers/entropy/CMakeLists.txt 389 | # add the following file: 390 | # ZEPHYR_MODULE/drivers/entropy/CMakeLists.txt 391 | # with content: 392 | # zephyr_library_amend() 393 | # zephyr_libray_add_sources(...) 394 | # 395 | macro(zephyr_library_amend) 396 | # This is a macro because we need to ensure the ZEPHYR_CURRENT_LIBRARY and 397 | # following zephyr_library_* calls are executed within the scope of the 398 | # caller. 399 | if(NOT ZEPHYR_CURRENT_MODULE_DIR) 400 | message(FATAL_ERROR "Function only available for Zephyr modules.") 401 | endif() 402 | 403 | zephyr_library_get_current_dir_lib_name(${ZEPHYR_CURRENT_MODULE_DIR} lib_name) 404 | 405 | set(ZEPHYR_CURRENT_LIBRARY ${lib_name}) 406 | endmacro() 407 | 408 | function(zephyr_link_interface interface) 409 | target_link_libraries(${interface} INTERFACE zephyr_interface) 410 | endfunction() 411 | 412 | # 413 | # zephyr_library versions of normal CMake target_ functions 414 | # 415 | function(zephyr_library_sources source) 416 | target_sources(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${source} ${ARGN}) 417 | endfunction() 418 | 419 | function(zephyr_library_include_directories) 420 | target_include_directories(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${ARGN}) 421 | endfunction() 422 | 423 | function(zephyr_library_link_libraries item) 424 | target_link_libraries(${ZEPHYR_CURRENT_LIBRARY} PUBLIC ${item} ${ARGN}) 425 | endfunction() 426 | 427 | function(zephyr_library_compile_definitions item) 428 | target_compile_definitions(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${item} ${ARGN}) 429 | endfunction() 430 | 431 | function(zephyr_library_compile_options item) 432 | # The compiler is relied upon for sane behaviour when flags are in 433 | # conflict. Compilers generally give precedence to flags given late 434 | # on the command line. So to ensure that zephyr_library_* flags are 435 | # placed late on the command line we create a dummy interface 436 | # library and link with it to obtain the flags. 437 | # 438 | # Linking with a dummy interface library will place flags later on 439 | # the command line than the the flags from zephyr_interface because 440 | # zephyr_interface will be the first interface library that flags 441 | # are taken from. 442 | 443 | string(MD5 uniqueness ${item}) 444 | set(lib_name options_interface_lib_${uniqueness}) 445 | 446 | if (TARGET ${lib_name}) 447 | # ${item} already added, ignoring duplicate just like CMake does 448 | return() 449 | endif() 450 | 451 | add_library( ${lib_name} INTERFACE) 452 | target_compile_options(${lib_name} INTERFACE ${item} ${ARGN}) 453 | 454 | target_link_libraries(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${lib_name}) 455 | endfunction() 456 | 457 | function(zephyr_library_cc_option) 458 | foreach(option ${ARGV}) 459 | string(MAKE_C_IDENTIFIER check${option} check) 460 | zephyr_check_compiler_flag(C ${option} ${check}) 461 | 462 | if(${check}) 463 | zephyr_library_compile_options(${option}) 464 | endif() 465 | endforeach() 466 | endfunction() 467 | 468 | # Add the existing CMake library 'library' to the global list of 469 | # Zephyr CMake libraries. This is done automatically by the 470 | # constructor but must called explicitly on CMake libraries that do 471 | # not use a zephyr library constructor. 472 | function(zephyr_append_cmake_library library) 473 | set_property(GLOBAL APPEND PROPERTY ZEPHYR_LIBS ${library}) 474 | endfunction() 475 | 476 | # Add the imported library 'library_name', located at 'library_path' to the 477 | # global list of Zephyr CMake libraries. 478 | function(zephyr_library_import library_name library_path) 479 | add_library(${library_name} STATIC IMPORTED GLOBAL) 480 | set_target_properties(${library_name} 481 | PROPERTIES IMPORTED_LOCATION 482 | ${library_path} 483 | ) 484 | zephyr_append_cmake_library(${library_name}) 485 | endfunction() 486 | 487 | # Place the current zephyr library in the application memory partition. 488 | # 489 | # The partition argument is the name of the partition where the library shall 490 | # be placed. 491 | # 492 | # Note: Ensure the given partition has been define using 493 | # K_APPMEM_PARTITION_DEFINE in source code. 494 | function(zephyr_library_app_memory partition) 495 | set_property(TARGET zephyr_property_target 496 | APPEND PROPERTY COMPILE_OPTIONS 497 | "-l" $ "${partition}") 498 | endfunction() 499 | 500 | # 1.2.1 zephyr_interface_library_* 501 | # 502 | # A Zephyr interface library is a thin wrapper over a CMake INTERFACE 503 | # library. The most important responsibility of this abstraction is to 504 | # ensure that when a user KConfig-enables a library then the header 505 | # files of this library will be accessible to the 'app' library. 506 | # 507 | # This is done because when a user uses Kconfig to enable a library he 508 | # expects to be able to include it's header files and call it's 509 | # functions out-of-the box. 510 | # 511 | # A Zephyr interface library should be used when there exists some 512 | # build information (include directories, defines, compiler flags, 513 | # etc.) that should be applied to a set of Zephyr libraries and 'app' 514 | # might be one of these libraries. 515 | # 516 | # Zephyr libraries must explicitly call 517 | # zephyr_library_link_libraries() to use this build 518 | # information. 'app' is treated as a special case for usability 519 | # reasons; a Kconfig option (CONFIG_APP_LINK_WITH_) 520 | # should exist for each interface_library and will determine if 'app' 521 | # links with the interface_library. 522 | # 523 | # This API has a constructor like the zephyr_library API has, but it 524 | # does not have wrappers over the other cmake target functions. 525 | macro(zephyr_interface_library_named name) 526 | add_library(${name} INTERFACE) 527 | set_property(GLOBAL APPEND PROPERTY ZEPHYR_INTERFACE_LIBS ${name}) 528 | endmacro() 529 | 530 | # 1.3 generate_inc_* 531 | 532 | # These functions are useful if there is a need to generate a file 533 | # that can be included into the application at build time. The file 534 | # can also be compressed automatically when embedding it. 535 | # 536 | # See tests/application_development/gen_inc_file for an example of 537 | # usage. 538 | function(generate_inc_file 539 | source_file # The source file to be converted to hex 540 | generated_file # The generated file 541 | ) 542 | add_custom_command( 543 | OUTPUT ${generated_file} 544 | COMMAND 545 | ${PYTHON_EXECUTABLE} 546 | ${ZEPHYR_BASE}/scripts/file2hex.py 547 | ${ARGN} # Extra arguments are passed to file2hex.py 548 | --file ${source_file} 549 | > ${generated_file} # Does pipe redirection work on Windows? 550 | DEPENDS ${source_file} 551 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 552 | ) 553 | endfunction() 554 | 555 | function(generate_inc_file_for_gen_target 556 | target # The cmake target that depends on the generated file 557 | source_file # The source file to be converted to hex 558 | generated_file # The generated file 559 | gen_target # The generated file target we depend on 560 | # Any additional arguments are passed on to file2hex.py 561 | ) 562 | generate_inc_file(${source_file} ${generated_file} ${ARGN}) 563 | 564 | # Ensure 'generated_file' is generated before 'target' by creating a 565 | # dependency between the two targets 566 | 567 | add_dependencies(${target} ${gen_target}) 568 | endfunction() 569 | 570 | function(generate_inc_file_for_target 571 | target # The cmake target that depends on the generated file 572 | source_file # The source file to be converted to hex 573 | generated_file # The generated file 574 | # Any additional arguments are passed on to file2hex.py 575 | ) 576 | # Ensure 'generated_file' is generated before 'target' by creating a 577 | # 'custom_target' for it and setting up a dependency between the two 578 | # targets 579 | 580 | # But first create a unique name for the custom target 581 | generate_unique_target_name_from_filename(${generated_file} generated_target_name) 582 | 583 | add_custom_target(${generated_target_name} DEPENDS ${generated_file}) 584 | generate_inc_file_for_gen_target(${target} ${source_file} ${generated_file} ${generated_target_name} ${ARGN}) 585 | endfunction() 586 | 587 | # 1.4. board_* 588 | # 589 | # This section is for extensions which control Zephyr's board runners 590 | # from the build system. The Zephyr build system has targets for 591 | # flashing and debugging supported boards. These are wrappers around a 592 | # "runner" Python subpackage that is part of Zephyr's "west" tool. 593 | # 594 | # This section provides glue between CMake and the Python code that 595 | # manages the runners. 596 | 597 | function(_board_check_runner_type type) # private helper 598 | if (NOT (("${type}" STREQUAL "FLASH") OR ("${type}" STREQUAL "DEBUG"))) 599 | message(FATAL_ERROR "invalid type ${type}; should be FLASH or DEBUG") 600 | endif() 601 | endfunction() 602 | 603 | # This function sets the runner for the board unconditionally. It's 604 | # meant to be used from application CMakeLists.txt files. 605 | # 606 | # NOTE: Usually board_set_xxx_ifnset() is best in board.cmake files. 607 | # This lets the user set the runner at cmake time, or in their 608 | # own application's CMakeLists.txt. 609 | # 610 | # Usage: 611 | # board_set_runner(FLASH pyocd) 612 | # 613 | # This would set the board's flash runner to "pyocd". 614 | # 615 | # In general, "type" is FLASH or DEBUG, and "runner" is the name of a 616 | # runner. 617 | function(board_set_runner type runner) 618 | _board_check_runner_type(${type}) 619 | if (DEFINED BOARD_${type}_RUNNER) 620 | message(STATUS "overriding ${type} runner ${BOARD_${type}_RUNNER}; it's now ${runner}") 621 | endif() 622 | set(BOARD_${type}_RUNNER ${runner} PARENT_SCOPE) 623 | endfunction() 624 | 625 | # This macro is like board_set_runner(), but will only make a change 626 | # if that runner is currently not set. 627 | # 628 | # See also board_set_flasher_ifnset() and board_set_debugger_ifnset(). 629 | macro(board_set_runner_ifnset type runner) 630 | _board_check_runner_type(${type}) 631 | # This is a macro because set_ifndef() works at parent scope. 632 | # If this were a function, that would be this function's scope, 633 | # which wouldn't work. 634 | set_ifndef(BOARD_${type}_RUNNER ${runner}) 635 | endmacro() 636 | 637 | # A convenience macro for board_set_runner(FLASH ${runner}). 638 | macro(board_set_flasher runner) 639 | board_set_runner(FLASH ${runner}) 640 | endmacro() 641 | 642 | # A convenience macro for board_set_runner(DEBUG ${runner}). 643 | macro(board_set_debugger runner) 644 | board_set_runner(DEBUG ${runner}) 645 | endmacro() 646 | 647 | # A convenience macro for board_set_runner_ifnset(FLASH ${runner}). 648 | macro(board_set_flasher_ifnset runner) 649 | board_set_runner_ifnset(FLASH ${runner}) 650 | endmacro() 651 | 652 | # A convenience macro for board_set_runner_ifnset(DEBUG ${runner}). 653 | macro(board_set_debugger_ifnset runner) 654 | board_set_runner_ifnset(DEBUG ${runner}) 655 | endmacro() 656 | 657 | # This function is intended for board.cmake files and application 658 | # CMakeLists.txt files. 659 | # 660 | # Usage from board.cmake files: 661 | # board_runner_args(runner "--some-arg=val1" "--another-arg=val2") 662 | # 663 | # The build system will then ensure the command line used to 664 | # create the runner contains: 665 | # --some-arg=val1 --another-arg=val2 666 | # 667 | # Within application CMakeLists.txt files, ensure that all calls to 668 | # board_runner_args() are part of a macro named app_set_runner_args(), 669 | # like this, which is defined before including the boilerplate file: 670 | # macro(app_set_runner_args) 671 | # board_runner_args(runner "--some-app-setting=value") 672 | # endmacro() 673 | # 674 | # The build system tests for the existence of the macro and will 675 | # invoke it at the appropriate time if it is defined. 676 | # 677 | # Any explicitly provided settings given by this function override 678 | # defaults provided by the build system. 679 | function(board_runner_args runner) 680 | string(MAKE_C_IDENTIFIER ${runner} runner_id) 681 | # Note the "_EXPLICIT_" here, and see below. 682 | set_property(GLOBAL APPEND PROPERTY BOARD_RUNNER_ARGS_EXPLICIT_${runner_id} ${ARGN}) 683 | endfunction() 684 | 685 | # This function is intended for internal use by 686 | # boards/common/runner.board.cmake files. 687 | # 688 | # Basic usage: 689 | # board_finalize_runner_args(runner) 690 | # 691 | # This ensures the build system captures all arguments added in any 692 | # board_runner_args() calls, and otherwise finishes registering a 693 | # runner for use. 694 | # 695 | # Extended usage: 696 | # board_runner_args(runner "--some-arg=default-value") 697 | # 698 | # This provides common or default values for arguments. These are 699 | # placed before board_runner_args() calls, so they generally take 700 | # precedence, except for arguments which can be given multiple times 701 | # (use these with caution). 702 | function(board_finalize_runner_args runner) 703 | # If the application provided a macro to add additional runner 704 | # arguments, handle them. 705 | if(COMMAND app_set_runner_args) 706 | app_set_runner_args() 707 | endif() 708 | 709 | # Retrieve the list of explicitly set arguments. 710 | string(MAKE_C_IDENTIFIER ${runner} runner_id) 711 | get_property(explicit GLOBAL PROPERTY "BOARD_RUNNER_ARGS_EXPLICIT_${runner_id}") 712 | 713 | # Note no _EXPLICIT_ here. This property contains the final list. 714 | set_property(GLOBAL APPEND PROPERTY BOARD_RUNNER_ARGS_${runner_id} 715 | # Default arguments from the common runner file come first. 716 | ${ARGN} 717 | # Arguments explicitly given with board_runner_args() come 718 | # last, so they take precedence. 719 | ${explicit} 720 | ) 721 | 722 | # Add the finalized runner to the global property list. 723 | set_property(GLOBAL APPEND PROPERTY ZEPHYR_RUNNERS ${runner}) 724 | endfunction() 725 | 726 | # 1.5. Misc. 727 | 728 | # zephyr_check_compiler_flag is a part of Zephyr's toolchain 729 | # infrastructure. It should be used when testing toolchain 730 | # capabilities and it should normally be used in place of the 731 | # functions: 732 | # 733 | # check_compiler_flag 734 | # check_c_compiler_flag 735 | # check_cxx_compiler_flag 736 | # 737 | # See check_compiler_flag() for API documentation as it has the same 738 | # API. 739 | # 740 | # It is implemented as a wrapper on top of check_compiler_flag, which 741 | # again wraps the CMake-builtin's check_c_compiler_flag and 742 | # check_cxx_compiler_flag. 743 | # 744 | # It takes time to check for compatibility of flags against toolchains 745 | # so we cache the capability test results in USER_CACHE_DIR (This 746 | # caching comes in addition to the caching that CMake does in the 747 | # build folder's CMakeCache.txt) 748 | function(zephyr_check_compiler_flag lang option check) 749 | # Check if the option is covered by any hardcoded check before doing 750 | # an automated test. 751 | zephyr_check_compiler_flag_hardcoded(${lang} "${option}" check exists) 752 | if(exists) 753 | set(check ${check} PARENT_SCOPE) 754 | return() 755 | endif() 756 | 757 | # Locate the cache directory 758 | set_ifndef( 759 | ZEPHYR_TOOLCHAIN_CAPABILITY_CACHE_DIR 760 | ${USER_CACHE_DIR}/ToolchainCapabilityDatabase 761 | ) 762 | 763 | # The toolchain capability database/cache is maintained as a 764 | # directory of files. The filenames in the directory are keys, and 765 | # the file contents are the values in this key-value store. 766 | 767 | # We need to create a unique key wrt. testing the toolchain 768 | # capability. This key must include everything that can affect the 769 | # toolchain test. 770 | # 771 | # Also, to fit the key into a filename we calculate the MD5 sum of 772 | # the key. 773 | 774 | # The 'cacheformat' must be bumped if a bug in the caching mechanism 775 | # is detected and all old keys must be invalidated. 776 | set(cacheformat 3) 777 | 778 | set(key_string "") 779 | set(key_string "${key_string}${cacheformat}_") 780 | set(key_string "${key_string}${TOOLCHAIN_SIGNATURE}_") 781 | set(key_string "${key_string}${lang}_") 782 | set(key_string "${key_string}${option}_") 783 | set(key_string "${key_string}${CMAKE_REQUIRED_FLAGS}_") 784 | 785 | string(MD5 key ${key_string}) 786 | 787 | # Check the cache 788 | set(key_path ${ZEPHYR_TOOLCHAIN_CAPABILITY_CACHE_DIR}/${key}) 789 | if(EXISTS ${key_path}) 790 | file(READ 791 | ${key_path} # File to be read 792 | key_value # Output variable 793 | LIMIT 1 # Read at most 1 byte ('0' or '1') 794 | ) 795 | 796 | set(${check} ${key_value} PARENT_SCOPE) 797 | return() 798 | endif() 799 | 800 | # Flags that start with -Wno- can not be tested by 801 | # check_compiler_flag, they will always pass, but -W can be 802 | # tested, so to test -Wno- flags we test -W 803 | # instead. 804 | if("${option}" MATCHES "-Wno-(.*)") 805 | set(possibly_translated_option -W${CMAKE_MATCH_1}) 806 | else() 807 | set(possibly_translated_option ${option}) 808 | endif() 809 | 810 | check_compiler_flag(${lang} "${possibly_translated_option}" inner_check) 811 | 812 | set(${check} ${inner_check} PARENT_SCOPE) 813 | 814 | # Populate the cache 815 | if(NOT (EXISTS ${key_path})) 816 | 817 | # This is racy. As often with race conditions, this one can easily be 818 | # made worse and demonstrated with a simple delay: 819 | # execute_process(COMMAND "sleep" "5") 820 | # Delete the cache, add the sleep above and run sanitycheck with a 821 | # large number of JOBS. Once it's done look at the log.txt file 822 | # below and you will see that concurrent cmake processes created the 823 | # same files multiple times. 824 | 825 | # While there are a number of reasons why this race seems both very 826 | # unlikely and harmless, let's play it safe anyway and write to a 827 | # private, temporary file first. All modern filesystems seem to 828 | # support at least one atomic rename API and cmake's file(RENAME 829 | # ...) officially leverages that. 830 | string(RANDOM LENGTH 8 tempsuffix) 831 | 832 | file( 833 | WRITE 834 | "${key_path}_tmp_${tempsuffix}" 835 | ${inner_check} 836 | ) 837 | file( 838 | RENAME 839 | "${key_path}_tmp_${tempsuffix}" "${key_path}" 840 | ) 841 | 842 | # Populate a metadata file (only intended for trouble shooting) 843 | # with information about the hash, the toolchain capability 844 | # result, and the toolchain test. 845 | file( 846 | APPEND 847 | ${ZEPHYR_TOOLCHAIN_CAPABILITY_CACHE_DIR}/log.txt 848 | "${inner_check} ${key} ${key_string}\n" 849 | ) 850 | endif() 851 | endfunction() 852 | 853 | function(zephyr_check_compiler_flag_hardcoded lang option check exists) 854 | # Various flags that are not supported for CXX may not be testable 855 | # because they would produce a warning instead of an error during 856 | # the test. Exclude them by toolchain-specific blacklist. 857 | if((${lang} STREQUAL CXX) AND ("${option}" IN_LIST CXX_EXCLUDED_OPTIONS)) 858 | set(check 0 PARENT_SCOPE) 859 | set(exists 1 PARENT_SCOPE) 860 | else() 861 | # There does not exist a hardcoded check for this option. 862 | set(exists 0 PARENT_SCOPE) 863 | endif() 864 | endfunction(zephyr_check_compiler_flag_hardcoded) 865 | 866 | # zephyr_linker_sources( [SORT_KEY ] ) 867 | # 868 | # is one or more .ld formatted files whose contents will be 869 | # copied/included verbatim into the given in the global linker.ld. 870 | # Preprocessor directives work inside . Relative paths are resolved 871 | # relative to the calling file, like zephyr_sources(). 872 | # is one of 873 | # NOINIT Inside the noinit output section. 874 | # RWDATA Inside the data output section. 875 | # RODATA Inside the rodata output section. 876 | # ROM_START Inside the first output section of the image. This option is 877 | # currently only available on ARM Cortex-M, ARM Cortex-R, 878 | # x86, ARC, and openisa_rv32m1. 879 | # RAM_SECTIONS Inside the RAMABLE_REGION GROUP. 880 | # SECTIONS Near the end of the file. Don't use this when linking into 881 | # RAMABLE_REGION, use RAM_SECTIONS instead. 882 | # is an optional key to sort by inside of each location. The key must 883 | # be alphanumeric, and the keys are sorted alphabetically. If no key is 884 | # given, the key 'default' is used. Keys are case-sensitive. 885 | # 886 | # Use NOINIT, RWDATA, and RODATA unless they don't work for your use case. 887 | # 888 | # When placing into NOINIT, RWDATA, RODATA, ROM_START, the contents of the files 889 | # will be placed inside an output section, so assume the section definition is 890 | # already present, e.g.: 891 | # _mysection_start = .; 892 | # KEEP(*(.mysection)); 893 | # _mysection_end = .; 894 | # _mysection_size = ABSOLUTE(_mysection_end - _mysection_start); 895 | # 896 | # When placing into SECTIONS or RAM_SECTIONS, the files must instead define 897 | # their own output sections to achieve the same thing: 898 | # SECTION_PROLOGUE(.mysection,,) 899 | # { 900 | # _mysection_start = .; 901 | # KEEP(*(.mysection)) 902 | # _mysection_end = .; 903 | # } GROUP_LINK_IN(ROMABLE_REGION) 904 | # _mysection_size = _mysection_end - _mysection_start; 905 | # 906 | # Note about the above examples: If the first example was used with RODATA, and 907 | # the second with SECTIONS, the two examples do the same thing from a user 908 | # perspective. 909 | # 910 | # Friendly reminder: Beware of the different ways the location counter ('.') 911 | # behaves inside vs. outside section definitions. 912 | function(zephyr_linker_sources location) 913 | # Set up the paths to the destination files. These files are #included inside 914 | # the global linker.ld. 915 | set(snippet_base "${__build_dir}/include/generated") 916 | set(sections_path "${snippet_base}/snippets-sections.ld") 917 | set(ram_sections_path "${snippet_base}/snippets-ram-sections.ld") 918 | set(rom_start_path "${snippet_base}/snippets-rom-start.ld") 919 | set(noinit_path "${snippet_base}/snippets-noinit.ld") 920 | set(rwdata_path "${snippet_base}/snippets-rwdata.ld") 921 | set(rodata_path "${snippet_base}/snippets-rodata.ld") 922 | 923 | # Clear destination files if this is the first time the function is called. 924 | get_property(cleared GLOBAL PROPERTY snippet_files_cleared) 925 | if (NOT DEFINED cleared) 926 | file(WRITE ${sections_path} "") 927 | file(WRITE ${ram_sections_path} "") 928 | file(WRITE ${rom_start_path} "") 929 | file(WRITE ${noinit_path} "") 930 | file(WRITE ${rwdata_path} "") 931 | file(WRITE ${rodata_path} "") 932 | set_property(GLOBAL PROPERTY snippet_files_cleared true) 933 | endif() 934 | 935 | # Choose destination file, based on the argument. 936 | if ("${location}" STREQUAL "SECTIONS") 937 | set(snippet_path "${sections_path}") 938 | elseif("${location}" STREQUAL "RAM_SECTIONS") 939 | set(snippet_path "${ram_sections_path}") 940 | elseif("${location}" STREQUAL "ROM_START") 941 | set(snippet_path "${rom_start_path}") 942 | elseif("${location}" STREQUAL "NOINIT") 943 | set(snippet_path "${noinit_path}") 944 | elseif("${location}" STREQUAL "RWDATA") 945 | set(snippet_path "${rwdata_path}") 946 | elseif("${location}" STREQUAL "RODATA") 947 | set(snippet_path "${rodata_path}") 948 | else() 949 | message(fatal_error "Must choose valid location for linker snippet.") 950 | endif() 951 | 952 | cmake_parse_arguments(L "" "SORT_KEY" "" ${ARGN}) 953 | set(SORT_KEY default) 954 | if(DEFINED L_SORT_KEY) 955 | set(SORT_KEY ${L_SORT_KEY}) 956 | endif() 957 | 958 | foreach(file IN ITEMS ${L_UNPARSED_ARGUMENTS}) 959 | # Resolve path. 960 | if(IS_ABSOLUTE ${file}) 961 | set(path ${file}) 962 | else() 963 | set(path ${CMAKE_CURRENT_SOURCE_DIR}/${file}) 964 | endif() 965 | 966 | if(IS_DIRECTORY ${path}) 967 | message(FATAL_ERROR "zephyr_linker_sources() was called on a directory") 968 | endif() 969 | 970 | # Find the relative path to the linker file from the include folder. 971 | file(RELATIVE_PATH relpath ${ZEPHYR_BASE}/include ${path}) 972 | 973 | # Create strings to be written into the file 974 | set (include_str "/* Sort key: \"${SORT_KEY}\" */#include \"${relpath}\"") 975 | 976 | # Add new line to existing lines, sort them, and write them back. 977 | file(STRINGS ${snippet_path} lines) # Get current lines (without newlines). 978 | list(APPEND lines ${include_str}) 979 | list(SORT lines) 980 | string(REPLACE ";" "\n;" lines "${lines}") # Add newline to each line. 981 | file(WRITE ${snippet_path} ${lines} "\n") 982 | endforeach() 983 | endfunction(zephyr_linker_sources) 984 | 985 | 986 | # Helper function for CONFIG_CODE_DATA_RELOCATION 987 | # Call this function with 2 arguments file and then memory location 988 | function(zephyr_code_relocate file location) 989 | set_property(TARGET code_data_relocation_target 990 | APPEND PROPERTY COMPILE_DEFINITIONS 991 | "${location}:${CMAKE_CURRENT_SOURCE_DIR}/${file}") 992 | endfunction() 993 | 994 | # Usage: 995 | # check_dtc_flag("-Wtest" DTC_WARN_TEST) 996 | # 997 | # Writes 1 to the output variable 'ok' if 998 | # the flag is supported, otherwise writes 0. 999 | # 1000 | # using 1001 | function(check_dtc_flag flag ok) 1002 | execute_process( 1003 | COMMAND 1004 | ${DTC} ${flag} -v 1005 | ERROR_QUIET 1006 | OUTPUT_QUIET 1007 | RESULT_VARIABLE dtc_check_ret 1008 | ) 1009 | if (dtc_check_ret EQUAL 0) 1010 | set(${ok} 1 PARENT_SCOPE) 1011 | else() 1012 | set(${ok} 0 PARENT_SCOPE) 1013 | endif() 1014 | endfunction() 1015 | 1016 | ######################################################## 1017 | # 2. Kconfig-aware extensions 1018 | ######################################################## 1019 | # 1020 | # Kconfig is a configuration language developed for the Linux 1021 | # kernel. The below functions integrate CMake with Kconfig. 1022 | # 1023 | # 2.1 *_if_kconfig 1024 | # 1025 | # Functions for conditionally including directories and source files 1026 | # that have matching KConfig values. 1027 | # 1028 | # zephyr_library_sources_if_kconfig(fft.c) 1029 | # is the same as 1030 | # zephyr_library_sources_ifdef(CONFIG_FFT fft.c) 1031 | # 1032 | # add_subdirectory_if_kconfig(serial) 1033 | # is the same as 1034 | # add_subdirectory_ifdef(CONFIG_SERIAL serial) 1035 | function(add_subdirectory_if_kconfig dir) 1036 | string(TOUPPER config_${dir} UPPER_CASE_CONFIG) 1037 | add_subdirectory_ifdef(${UPPER_CASE_CONFIG} ${dir}) 1038 | endfunction() 1039 | 1040 | function(target_sources_if_kconfig target scope item) 1041 | get_filename_component(item_basename ${item} NAME_WE) 1042 | string(TOUPPER CONFIG_${item_basename} UPPER_CASE_CONFIG) 1043 | target_sources_ifdef(${UPPER_CASE_CONFIG} ${target} ${scope} ${item}) 1044 | endfunction() 1045 | 1046 | function(zephyr_library_sources_if_kconfig item) 1047 | get_filename_component(item_basename ${item} NAME_WE) 1048 | string(TOUPPER CONFIG_${item_basename} UPPER_CASE_CONFIG) 1049 | zephyr_library_sources_ifdef(${UPPER_CASE_CONFIG} ${item}) 1050 | endfunction() 1051 | 1052 | function(zephyr_sources_if_kconfig item) 1053 | get_filename_component(item_basename ${item} NAME_WE) 1054 | string(TOUPPER CONFIG_${item_basename} UPPER_CASE_CONFIG) 1055 | zephyr_sources_ifdef(${UPPER_CASE_CONFIG} ${item}) 1056 | endfunction() 1057 | 1058 | # 2.2 Misc 1059 | # 1060 | # import_kconfig( []) 1061 | # 1062 | # Parse a KConfig fragment (typically with extension .config) and 1063 | # introduce all the symbols that are prefixed with 'prefix' into the 1064 | # CMake namespace. List all created variable names in the 'keys' 1065 | # output variable if present. 1066 | function(import_kconfig prefix kconfig_fragment) 1067 | # Parse the lines prefixed with 'prefix' in ${kconfig_fragment} 1068 | file( 1069 | STRINGS 1070 | ${kconfig_fragment} 1071 | DOT_CONFIG_LIST 1072 | REGEX "^${prefix}" 1073 | ENCODING "UTF-8" 1074 | ) 1075 | 1076 | foreach (CONFIG ${DOT_CONFIG_LIST}) 1077 | # CONFIG could look like: CONFIG_NET_BUF=y 1078 | 1079 | # Match the first part, the variable name 1080 | string(REGEX MATCH "[^=]+" CONF_VARIABLE_NAME ${CONFIG}) 1081 | 1082 | # Match the second part, variable value 1083 | string(REGEX MATCH "=(.+$)" CONF_VARIABLE_VALUE ${CONFIG}) 1084 | # The variable name match we just did included the '=' symbol. To just get the 1085 | # part on the RHS we use match group 1 1086 | set(CONF_VARIABLE_VALUE ${CMAKE_MATCH_1}) 1087 | 1088 | if("${CONF_VARIABLE_VALUE}" MATCHES "^\"(.*)\"$") # Is surrounded by quotes 1089 | set(CONF_VARIABLE_VALUE ${CMAKE_MATCH_1}) 1090 | endif() 1091 | 1092 | set("${CONF_VARIABLE_NAME}" "${CONF_VARIABLE_VALUE}" PARENT_SCOPE) 1093 | list(APPEND keys "${CONF_VARIABLE_NAME}") 1094 | endforeach() 1095 | 1096 | foreach(outvar ${ARGN}) 1097 | set(${outvar} "${keys}" PARENT_SCOPE) 1098 | endforeach() 1099 | endfunction() 1100 | 1101 | ######################################################## 1102 | # 3. CMake-generic extensions 1103 | ######################################################## 1104 | # 1105 | # These functions extend the CMake API in a way that is not particular 1106 | # to Zephyr. Primarily they work around limitations in the CMake 1107 | # language to allow cleaner build scripts. 1108 | 1109 | # 3.1. *_ifdef 1110 | # 1111 | # Functions for conditionally executing CMake functions with oneliners 1112 | # e.g. 1113 | # 1114 | # if(CONFIG_FFT) 1115 | # zephyr_library_source( 1116 | # fft_32.c 1117 | # fft_utils.c 1118 | # ) 1119 | # endif() 1120 | # 1121 | # Becomes 1122 | # 1123 | # zephyr_source_ifdef( 1124 | # CONFIG_FFT 1125 | # fft_32.c 1126 | # fft_utils.c 1127 | # ) 1128 | # 1129 | # More Generally 1130 | # "_ifdef(CONDITION args)" 1131 | # Becomes 1132 | # """ 1133 | # if(CONDITION) 1134 | # (args) 1135 | # endif() 1136 | # """ 1137 | # 1138 | # ifdef functions are added on an as-need basis. See 1139 | # https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html for 1140 | # a list of available functions. 1141 | function(add_subdirectory_ifdef feature_toggle dir) 1142 | if(${${feature_toggle}}) 1143 | add_subdirectory(${dir}) 1144 | endif() 1145 | endfunction() 1146 | 1147 | function(target_sources_ifdef feature_toggle target scope item) 1148 | if(${${feature_toggle}}) 1149 | target_sources(${target} ${scope} ${item} ${ARGN}) 1150 | endif() 1151 | endfunction() 1152 | 1153 | function(target_compile_definitions_ifdef feature_toggle target scope item) 1154 | if(${${feature_toggle}}) 1155 | target_compile_definitions(${target} ${scope} ${item} ${ARGN}) 1156 | endif() 1157 | endfunction() 1158 | 1159 | function(target_include_directories_ifdef feature_toggle target scope item) 1160 | if(${${feature_toggle}}) 1161 | target_include_directories(${target} ${scope} ${item} ${ARGN}) 1162 | endif() 1163 | endfunction() 1164 | 1165 | function(target_link_libraries_ifdef feature_toggle target item) 1166 | if(${${feature_toggle}}) 1167 | target_link_libraries(${target} ${item} ${ARGN}) 1168 | endif() 1169 | endfunction() 1170 | 1171 | function(add_compile_option_ifdef feature_toggle option) 1172 | if(${${feature_toggle}}) 1173 | add_compile_options(${option}) 1174 | endif() 1175 | endfunction() 1176 | 1177 | function(target_compile_option_ifdef feature_toggle target scope option) 1178 | if(${feature_toggle}) 1179 | target_compile_options(${target} ${scope} ${option}) 1180 | endif() 1181 | endfunction() 1182 | 1183 | function(target_cc_option_ifdef feature_toggle target scope option) 1184 | if(${feature_toggle}) 1185 | target_cc_option(${target} ${scope} ${option}) 1186 | endif() 1187 | endfunction() 1188 | 1189 | function(zephyr_library_sources_ifdef feature_toggle source) 1190 | if(${${feature_toggle}}) 1191 | zephyr_library_sources(${source} ${ARGN}) 1192 | endif() 1193 | endfunction() 1194 | 1195 | function(zephyr_library_sources_ifndef feature_toggle source) 1196 | if(NOT ${feature_toggle}) 1197 | zephyr_library_sources(${source} ${ARGN}) 1198 | endif() 1199 | endfunction() 1200 | 1201 | function(zephyr_sources_ifdef feature_toggle) 1202 | if(${${feature_toggle}}) 1203 | zephyr_sources(${ARGN}) 1204 | endif() 1205 | endfunction() 1206 | 1207 | function(zephyr_sources_ifndef feature_toggle) 1208 | if(NOT ${feature_toggle}) 1209 | zephyr_sources(${ARGN}) 1210 | endif() 1211 | endfunction() 1212 | 1213 | function(zephyr_cc_option_ifdef feature_toggle) 1214 | if(${${feature_toggle}}) 1215 | zephyr_cc_option(${ARGN}) 1216 | endif() 1217 | endfunction() 1218 | 1219 | function(zephyr_ld_option_ifdef feature_toggle) 1220 | if(${${feature_toggle}}) 1221 | zephyr_ld_options(${ARGN}) 1222 | endif() 1223 | endfunction() 1224 | 1225 | function(zephyr_link_libraries_ifdef feature_toggle) 1226 | if(${${feature_toggle}}) 1227 | zephyr_link_libraries(${ARGN}) 1228 | endif() 1229 | endfunction() 1230 | 1231 | function(zephyr_compile_options_ifdef feature_toggle) 1232 | if(${${feature_toggle}}) 1233 | zephyr_compile_options(${ARGN}) 1234 | endif() 1235 | endfunction() 1236 | 1237 | function(zephyr_compile_definitions_ifdef feature_toggle) 1238 | if(${${feature_toggle}}) 1239 | zephyr_compile_definitions(${ARGN}) 1240 | endif() 1241 | endfunction() 1242 | 1243 | function(zephyr_include_directories_ifdef feature_toggle) 1244 | if(${${feature_toggle}}) 1245 | zephyr_include_directories(${ARGN}) 1246 | endif() 1247 | endfunction() 1248 | 1249 | function(zephyr_library_compile_definitions_ifdef feature_toggle item) 1250 | if(${${feature_toggle}}) 1251 | zephyr_library_compile_definitions(${item} ${ARGN}) 1252 | endif() 1253 | endfunction() 1254 | 1255 | function(zephyr_library_compile_options_ifdef feature_toggle item) 1256 | if(${${feature_toggle}}) 1257 | zephyr_library_compile_options(${item} ${ARGN}) 1258 | endif() 1259 | endfunction() 1260 | 1261 | function(zephyr_link_interface_ifdef feature_toggle interface) 1262 | if(${${feature_toggle}}) 1263 | target_link_libraries(${interface} INTERFACE zephyr_interface) 1264 | endif() 1265 | endfunction() 1266 | 1267 | function(zephyr_library_link_libraries_ifdef feature_toggle item) 1268 | if(${${feature_toggle}}) 1269 | zephyr_library_link_libraries(${item}) 1270 | endif() 1271 | endfunction() 1272 | 1273 | function(zephyr_linker_sources_ifdef feature_toggle) 1274 | if(${${feature_toggle}}) 1275 | zephyr_linker_sources(${ARGN}) 1276 | endif() 1277 | endfunction() 1278 | 1279 | macro(list_append_ifdef feature_toggle list) 1280 | if(${${feature_toggle}}) 1281 | list(APPEND ${list} ${ARGN}) 1282 | endif() 1283 | endmacro() 1284 | 1285 | # 3.2. *_ifndef 1286 | # See 3.1 *_ifdef 1287 | function(set_ifndef variable value) 1288 | if(NOT ${variable}) 1289 | set(${variable} ${value} ${ARGN} PARENT_SCOPE) 1290 | endif() 1291 | endfunction() 1292 | 1293 | function(target_cc_option_ifndef feature_toggle target scope option) 1294 | if(NOT ${feature_toggle}) 1295 | target_cc_option(${target} ${scope} ${option}) 1296 | endif() 1297 | endfunction() 1298 | 1299 | function(zephyr_cc_option_ifndef feature_toggle) 1300 | if(NOT ${feature_toggle}) 1301 | zephyr_cc_option(${ARGN}) 1302 | endif() 1303 | endfunction() 1304 | 1305 | function(zephyr_compile_options_ifndef feature_toggle) 1306 | if(NOT ${feature_toggle}) 1307 | zephyr_compile_options(${ARGN}) 1308 | endif() 1309 | endfunction() 1310 | 1311 | # 3.3. *_option Compiler-compatibility checks 1312 | # 1313 | # Utility functions for silently omitting compiler flags when the 1314 | # compiler lacks support. *_cc_option was ported from KBuild, see 1315 | # cc-option in 1316 | # https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt 1317 | 1318 | # Writes 1 to the output variable 'ok' for the language 'lang' if 1319 | # the flag is supported, otherwise writes 0. 1320 | # 1321 | # lang must be C or CXX 1322 | # 1323 | # TODO: Support ASM 1324 | # 1325 | # Usage: 1326 | # 1327 | # check_compiler_flag(C "-Wall" my_check) 1328 | # print(my_check) # my_check is now 1 1329 | function(check_compiler_flag lang option ok) 1330 | if(NOT DEFINED CMAKE_REQUIRED_QUIET) 1331 | set(CMAKE_REQUIRED_QUIET 1) 1332 | endif() 1333 | 1334 | string(MAKE_C_IDENTIFIER 1335 | check${option}_${lang}_${CMAKE_REQUIRED_FLAGS} 1336 | ${ok} 1337 | ) 1338 | 1339 | if(${lang} STREQUAL C) 1340 | check_c_compiler_flag("${option}" ${${ok}}) 1341 | else() 1342 | check_cxx_compiler_flag("${option}" ${${ok}}) 1343 | endif() 1344 | 1345 | if(${${${ok}}}) 1346 | set(ret 1) 1347 | else() 1348 | set(ret 0) 1349 | endif() 1350 | 1351 | set(${ok} ${ret} PARENT_SCOPE) 1352 | endfunction() 1353 | 1354 | function(target_cc_option target scope option) 1355 | target_cc_option_fallback(${target} ${scope} ${option} "") 1356 | endfunction() 1357 | 1358 | # Support an optional second option for when the first option is not 1359 | # supported. 1360 | function(target_cc_option_fallback target scope option1 option2) 1361 | if(CONFIG_CPLUSPLUS) 1362 | foreach(lang C CXX) 1363 | # For now, we assume that all flags that apply to C/CXX also 1364 | # apply to ASM. 1365 | zephyr_check_compiler_flag(${lang} ${option1} check) 1366 | if(${check}) 1367 | target_compile_options(${target} ${scope} 1368 | $<$:${option1}> 1369 | $<$:${option1}> 1370 | ) 1371 | elseif(option2) 1372 | target_compile_options(${target} ${scope} 1373 | $<$:${option2}> 1374 | $<$:${option2}> 1375 | ) 1376 | endif() 1377 | endforeach() 1378 | else() 1379 | zephyr_check_compiler_flag(C ${option1} check) 1380 | if(${check}) 1381 | target_compile_options(${target} ${scope} ${option1}) 1382 | elseif(option2) 1383 | target_compile_options(${target} ${scope} ${option2}) 1384 | endif() 1385 | endif() 1386 | endfunction() 1387 | 1388 | function(target_ld_options target scope) 1389 | foreach(option ${ARGN}) 1390 | string(MAKE_C_IDENTIFIER check${option} check) 1391 | 1392 | set(SAVED_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) 1393 | set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${option}") 1394 | zephyr_check_compiler_flag(C "" ${check}) 1395 | set(CMAKE_REQUIRED_FLAGS ${SAVED_CMAKE_REQUIRED_FLAGS}) 1396 | 1397 | target_link_libraries_ifdef(${check} ${target} ${scope} ${option}) 1398 | endforeach() 1399 | endfunction() 1400 | 1401 | # 3.3.1 Toolchain integration 1402 | # 1403 | # 'toolchain_parse_make_rule' is a function that parses the output of 1404 | # 'gcc -M'. 1405 | # 1406 | # The argument 'input_file' is in input parameter with the path to the 1407 | # file with the dependency information. 1408 | # 1409 | # The argument 'include_files' is an output parameter with the result 1410 | # of parsing the include files. 1411 | function(toolchain_parse_make_rule input_file include_files) 1412 | file(READ ${input_file} input) 1413 | 1414 | # The file is formatted like this: 1415 | # empty_file.o: misc/empty_file.c \ 1416 | # nrf52840_pca10056/nrf52840_pca10056.dts \ 1417 | # nrf52840_qiaa.dtsi 1418 | 1419 | # Get rid of the backslashes 1420 | string(REPLACE "\\" ";" input_as_list ${input}) 1421 | 1422 | # Pop the first line and treat it specially 1423 | list(GET input_as_list 0 first_input_line) 1424 | string(FIND ${first_input_line} ": " index) 1425 | math(EXPR j "${index} + 2") 1426 | string(SUBSTRING ${first_input_line} ${j} -1 first_include_file) 1427 | list(REMOVE_AT input_as_list 0) 1428 | 1429 | list(APPEND result ${first_include_file}) 1430 | 1431 | # Add the other lines 1432 | list(APPEND result ${input_as_list}) 1433 | 1434 | # Strip away the newlines and whitespaces 1435 | list(TRANSFORM result STRIP) 1436 | 1437 | set(${include_files} ${result} PARENT_SCOPE) 1438 | endfunction() 1439 | 1440 | # 3.4. Debugging CMake 1441 | 1442 | # Usage: 1443 | # print(BOARD) 1444 | # 1445 | # will print: "BOARD: nrf52_pca10040" 1446 | function(print arg) 1447 | message(STATUS "${arg}: ${${arg}}") 1448 | endfunction() 1449 | 1450 | # Usage: 1451 | # assert(ZEPHYR_TOOLCHAIN_VARIANT "ZEPHYR_TOOLCHAIN_VARIANT not set.") 1452 | # 1453 | # will cause a FATAL_ERROR and print an error message if the first 1454 | # expression is false 1455 | macro(assert test comment) 1456 | if(NOT ${test}) 1457 | message(FATAL_ERROR "Assertion failed: ${comment}") 1458 | endif() 1459 | endmacro() 1460 | 1461 | # Usage: 1462 | # assert_not(OBSOLETE_VAR "OBSOLETE_VAR has been removed; use NEW_VAR instead") 1463 | # 1464 | # will cause a FATAL_ERROR and print an error message if the first 1465 | # expression is true 1466 | macro(assert_not test comment) 1467 | if(${test}) 1468 | message(FATAL_ERROR "Assertion failed: ${comment}") 1469 | endif() 1470 | endmacro() 1471 | 1472 | # Usage: 1473 | # assert_exists(CMAKE_READELF) 1474 | # 1475 | # will cause a FATAL_ERROR if there is no file or directory behind the 1476 | # variable 1477 | macro(assert_exists var) 1478 | if(NOT EXISTS ${${var}}) 1479 | message(FATAL_ERROR "No such file or directory: ${var}: '${${var}}'") 1480 | endif() 1481 | endmacro() 1482 | 1483 | function(print_usage) 1484 | message("see usage:") 1485 | string(REPLACE ";" " " BOARD_ROOT_SPACE_SEPARATED "${BOARD_ROOT}") 1486 | string(REPLACE ";" " " SHIELD_LIST_SPACE_SEPARATED "${SHIELD_LIST}") 1487 | execute_process( 1488 | COMMAND 1489 | ${CMAKE_COMMAND} 1490 | -DBOARD_ROOT_SPACE_SEPARATED=${BOARD_ROOT_SPACE_SEPARATED} 1491 | -DSHIELD_LIST_SPACE_SEPARATED=${SHIELD_LIST_SPACE_SEPARATED} 1492 | -P ${ZEPHYR_BASE}/cmake/usage/usage.cmake 1493 | ) 1494 | endfunction() 1495 | 1496 | # 3.5. File system management 1497 | function(check_if_directory_is_writeable dir ok) 1498 | execute_process( 1499 | COMMAND 1500 | ${PYTHON_EXECUTABLE} 1501 | ${ZEPHYR_BASE}/scripts/dir_is_writeable.py 1502 | ${dir} 1503 | RESULT_VARIABLE ret 1504 | ) 1505 | 1506 | if("${ret}" STREQUAL "0") 1507 | # The directory is write-able 1508 | set(${ok} 1 PARENT_SCOPE) 1509 | else() 1510 | set(${ok} 0 PARENT_SCOPE) 1511 | endif() 1512 | endfunction() 1513 | 1514 | function(find_appropriate_cache_directory dir) 1515 | set(env_suffix_LOCALAPPDATA .cache) 1516 | 1517 | if(CMAKE_HOST_APPLE) 1518 | # On macOS, ~/Library/Caches is the preferred cache directory. 1519 | set(env_suffix_HOME Library/Caches) 1520 | else() 1521 | set(env_suffix_HOME .cache) 1522 | endif() 1523 | 1524 | # Determine which env vars should be checked 1525 | if(CMAKE_HOST_APPLE) 1526 | set(dirs HOME) 1527 | elseif(CMAKE_HOST_WIN32) 1528 | set(dirs LOCALAPPDATA) 1529 | else() 1530 | # Assume Linux when we did not detect 'mac' or 'win' 1531 | # 1532 | # On Linux, freedesktop.org recommends using $XDG_CACHE_HOME if 1533 | # that is defined and defaulting to $HOME/.cache otherwise. 1534 | set(dirs 1535 | XDG_CACHE_HOME 1536 | HOME 1537 | ) 1538 | endif() 1539 | 1540 | foreach(env_var ${dirs}) 1541 | if(DEFINED ENV{${env_var}}) 1542 | set(env_dir $ENV{${env_var}}) 1543 | 1544 | set(test_user_dir ${env_dir}/${env_suffix_${env_var}}) 1545 | 1546 | check_if_directory_is_writeable(${test_user_dir} ok) 1547 | if(${ok}) 1548 | # The directory is write-able 1549 | set(user_dir ${test_user_dir}) 1550 | break() 1551 | else() 1552 | # The directory was not writeable, keep looking for a suitable 1553 | # directory 1554 | endif() 1555 | endif() 1556 | endforeach() 1557 | 1558 | # Populate local_dir with a suitable directory for caching 1559 | # files. Prefer a directory outside of the git repository because it 1560 | # is good practice to have clean git repositories. 1561 | if(DEFINED user_dir) 1562 | # Zephyr's cache files go in the "zephyr" subdirectory of the 1563 | # user's cache directory. 1564 | set(local_dir ${user_dir}/zephyr) 1565 | else() 1566 | set(local_dir ${ZEPHYR_BASE}/.cache) 1567 | endif() 1568 | 1569 | set(${dir} ${local_dir} PARENT_SCOPE) 1570 | endfunction() 1571 | 1572 | function(generate_unique_target_name_from_filename filename target_name) 1573 | get_filename_component(basename ${filename} NAME) 1574 | string(REPLACE "." "_" x ${basename}) 1575 | string(REPLACE "@" "_" x ${x}) 1576 | 1577 | string(MD5 unique_chars ${filename}) 1578 | 1579 | set(${target_name} gen_${x}_${unique_chars} PARENT_SCOPE) 1580 | endfunction() 1581 | -------------------------------------------------------------------------------- /cmake/kconfig.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # Folders needed for conf/mconf files (kconfig has no method of redirecting all output files). 4 | # conf/mconf needs to be run from a different directory because of: GH-3408 5 | file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/kconfig/include/generated) 6 | file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/kconfig/include/config) 7 | 8 | if(KCONFIG_ROOT) 9 | # KCONFIG_ROOT has either been specified as a CMake variable or is 10 | # already in the CMakeCache.txt. This has precedence. 11 | elseif(EXISTS ${APPLICATION_SOURCE_DIR}/Kconfig) 12 | set(KCONFIG_ROOT ${APPLICATION_SOURCE_DIR}/Kconfig) 13 | else() 14 | set(KCONFIG_ROOT ${ZEPHYR_BASE}/Kconfig) 15 | endif() 16 | 17 | set(BOARD_DEFCONFIG ${BOARD_DIR}/${BOARD}_defconfig) 18 | set(DOTCONFIG ${PROJECT_BINARY_DIR}/.config) 19 | set(PARSED_KCONFIG_SOURCES_TXT ${PROJECT_BINARY_DIR}/kconfig/sources.txt) 20 | 21 | if(CONF_FILE) 22 | string(REPLACE " " ";" CONF_FILE_AS_LIST "${CONF_FILE}") 23 | endif() 24 | 25 | if(OVERLAY_CONFIG) 26 | string(REPLACE " " ";" OVERLAY_CONFIG_AS_LIST "${OVERLAY_CONFIG}") 27 | endif() 28 | 29 | # DTS_ROOT_BINDINGS is a semicolon separated list, this causes 30 | # problems when invoking kconfig_target since semicolon is a special 31 | # character in the C shell, so we make it into a question-mark 32 | # separated list instead. 33 | string(REPLACE ";" "?" DTS_ROOT_BINDINGS "${DTS_ROOT_BINDINGS}") 34 | 35 | set(ENV{srctree} ${PROJECT_ROOT}) 36 | set(ENV{KCONFIG_BASE} ${PROJECT_ROOT}) 37 | set(ENV{KERNELVERSION} ${KERNELVERSION}) 38 | set(ENV{KCONFIG_CONFIG} ${DOTCONFIG}) 39 | set(ENV{PYTHON_EXECUTABLE} ${PYTHON_EXECUTABLE}) 40 | 41 | # Set environment variables so that Kconfig can prune Kconfig source 42 | # files for other architectures 43 | set(ENV{ARCH} ${ARCH}) 44 | set(ENV{BOARD_DIR} ${BOARD_DIR}) 45 | set(ENV{SOC_DIR} ${SOC_DIR}) 46 | set(ENV{SHIELD_AS_LIST} "${SHIELD_AS_LIST}") 47 | set(ENV{CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}) 48 | set(ENV{ARCH_DIR} ${ARCH_DIR}) 49 | set(ENV{DEVICETREE_CONF} ${DEVICETREE_CONF}) 50 | set(ENV{DTS_POST_CPP} ${DTS_POST_CPP}) 51 | set(ENV{DTS_ROOT_BINDINGS} "${DTS_ROOT_BINDINGS}") 52 | set(ENV{TOOLCHAIN_KCONFIG_DIR} "${TOOLCHAIN_KCONFIG_DIR}") 53 | 54 | # Allow out-of-tree users to add their own Kconfig python frontend 55 | # targets by appending targets to the CMake list 56 | # 'EXTRA_KCONFIG_TARGETS' and setting variables named 57 | # 'EXTRA_KCONFIG_TARGET_COMMAND_FOR_' 58 | # 59 | # e.g. 60 | # cmake -DEXTRA_KCONFIG_TARGETS=cli 61 | # -DEXTRA_KCONFIG_TARGET_COMMAND_FOR_cli=cli_kconfig_frontend.py 62 | 63 | set(EXTRA_KCONFIG_TARGET_COMMAND_FOR_menuconfig 64 | ${PROJECT_ROOT}/scripts/kconfig/menuconfig.py 65 | ) 66 | 67 | set(EXTRA_KCONFIG_TARGET_COMMAND_FOR_guiconfig 68 | ${PROJECT_ROOT}/scripts/kconfig/guiconfig.py 69 | ) 70 | 71 | set(EXTRA_KCONFIG_TARGET_COMMAND_FOR_hardenconfig 72 | ${PROJECT_ROOT}/scripts/kconfig/hardenconfig.py 73 | ) 74 | 75 | foreach(kconfig_target 76 | menuconfig 77 | guiconfig 78 | hardenconfig 79 | ${EXTRA_KCONFIG_TARGETS} 80 | ) 81 | add_custom_target( 82 | ${kconfig_target} 83 | ${CMAKE_COMMAND} -E env 84 | PYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} 85 | srctree=${ZEPHYR_BASE} 86 | KERNELVERSION=${KERNELVERSION} 87 | KCONFIG_BASE=${PROJECT_BASE} 88 | KCONFIG_CONFIG=${DOTCONFIG} 89 | ARCH=$ENV{ARCH} 90 | BOARD_DIR=$ENV{BOARD_DIR} 91 | SOC_DIR=$ENV{SOC_DIR} 92 | SHIELD_AS_LIST=$ENV{SHIELD_AS_LIST} 93 | CMAKE_BINARY_DIR=$ENV{CMAKE_BINARY_DIR} 94 | ZEPHYR_TOOLCHAIN_VARIANT=${ZEPHYR_TOOLCHAIN_VARIANT} 95 | TOOLCHAIN_KCONFIG_DIR=${TOOLCHAIN_KCONFIG_DIR} 96 | ARCH_DIR=$ENV{ARCH_DIR} 97 | DEVICETREE_CONF=${DEVICETREE_CONF} 98 | DTS_POST_CPP=${DTS_POST_CPP} 99 | DTS_ROOT_BINDINGS=${DTS_ROOT_BINDINGS} 100 | ${PYTHON_EXECUTABLE} 101 | ${EXTRA_KCONFIG_TARGET_COMMAND_FOR_${kconfig_target}} 102 | ${KCONFIG_ROOT} 103 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/kconfig 104 | USES_TERMINAL 105 | ) 106 | endforeach() 107 | 108 | # Support assigning Kconfig symbols on the command-line with CMake 109 | # cache variables prefixed with 'CONFIG_'. This feature is 110 | # experimental and undocumented until it has undergone more 111 | # user-testing. 112 | unset(EXTRA_KCONFIG_OPTIONS) 113 | get_cmake_property(cache_variable_names CACHE_VARIABLES) 114 | foreach (name ${cache_variable_names}) 115 | if("${name}" MATCHES "^CONFIG_") 116 | # When a cache variable starts with 'CONFIG_', it is assumed to be 117 | # a Kconfig symbol assignment from the CMake command line. 118 | set(EXTRA_KCONFIG_OPTIONS 119 | "${EXTRA_KCONFIG_OPTIONS}\n${name}=${${name}}" 120 | ) 121 | endif() 122 | endforeach() 123 | 124 | if(EXTRA_KCONFIG_OPTIONS) 125 | set(EXTRA_KCONFIG_OPTIONS_FILE ${PROJECT_BINARY_DIR}/misc/generated/extra_kconfig_options.conf) 126 | file(WRITE 127 | ${EXTRA_KCONFIG_OPTIONS_FILE} 128 | ${EXTRA_KCONFIG_OPTIONS} 129 | ) 130 | endif() 131 | 132 | # Bring in extra configuration files dropped in by the user or anyone else; 133 | # make sure they are set at the end so we can override any other setting 134 | file(GLOB config_files ${APPLICATION_BINARY_DIR}/*.conf) 135 | list(SORT config_files) 136 | set( 137 | merge_config_files 138 | ${BOARD_DEFCONFIG} 139 | ${CONF_FILE_AS_LIST} 140 | ${shield_conf_files} 141 | ${OVERLAY_CONFIG_AS_LIST} 142 | ${EXTRA_KCONFIG_OPTIONS_FILE} 143 | ${config_files} 144 | ) 145 | 146 | # Create a list of absolute paths to the .config sources from 147 | # merge_config_files, which is a mix of absolute and relative paths. 148 | set(merge_config_files_with_absolute_paths "") 149 | foreach(f ${merge_config_files}) 150 | if(IS_ABSOLUTE ${f}) 151 | set(path ${f}) 152 | else() 153 | set(path ${APPLICATION_SOURCE_DIR}/${f}) 154 | endif() 155 | 156 | list(APPEND merge_config_files_with_absolute_paths ${path}) 157 | endforeach() 158 | 159 | foreach(f ${merge_config_files_with_absolute_paths}) 160 | if(NOT EXISTS ${f} OR IS_DIRECTORY ${f}) 161 | message(FATAL_ERROR "File not found: ${f}") 162 | endif() 163 | endforeach() 164 | 165 | # Calculate a checksum of merge_config_files to determine if we need 166 | # to re-generate .config 167 | set(merge_config_files_checksum "") 168 | foreach(f ${merge_config_files_with_absolute_paths}) 169 | file(MD5 ${f} checksum) 170 | set(merge_config_files_checksum "${merge_config_files_checksum}${checksum}") 171 | endforeach() 172 | 173 | # Create a new .config if it does not exists, or if the checksum of 174 | # the dependencies has changed 175 | set(merge_config_files_checksum_file ${PROJECT_BINARY_DIR}/.cmake.dotconfig.checksum) 176 | set(CREATE_NEW_DOTCONFIG 1) 177 | # Check if the checksum file exists too before trying to open it, though it 178 | # should under normal circumstances 179 | if(EXISTS ${DOTCONFIG} AND EXISTS ${merge_config_files_checksum_file}) 180 | # Read out what the checksum was previously 181 | file(READ 182 | ${merge_config_files_checksum_file} 183 | merge_config_files_checksum_prev 184 | ) 185 | if( 186 | ${merge_config_files_checksum} STREQUAL 187 | ${merge_config_files_checksum_prev} 188 | ) 189 | # Checksum is the same as before 190 | set(CREATE_NEW_DOTCONFIG 0) 191 | endif() 192 | endif() 193 | 194 | if(CREATE_NEW_DOTCONFIG) 195 | set(input_configs_are_handwritten --handwritten-input-configs) 196 | set(input_configs ${merge_config_files}) 197 | else() 198 | set(input_configs ${DOTCONFIG}) 199 | endif() 200 | 201 | 202 | message( 203 | ${PYTHON_EXECUTABLE} 204 | ${PROJECT_ROOT}/scripts/kconfig/kconfig.py 205 | ${input_configs_are_handwritten} 206 | ${KCONFIG_ROOT} 207 | ${DOTCONFIG} 208 | ${AUTOCONF_H} 209 | ${PARSED_KCONFIG_SOURCES_TXT} 210 | ${input_configs} 211 | ) 212 | execute_process( 213 | COMMAND 214 | ${PYTHON_EXECUTABLE} 215 | ${PROJECT_ROOT}/scripts/kconfig/kconfig.py 216 | ${input_configs_are_handwritten} 217 | ${KCONFIG_ROOT} 218 | ${DOTCONFIG} 219 | ${AUTOCONF_H} 220 | ${PARSED_KCONFIG_SOURCES_TXT} 221 | ${input_configs} 222 | WORKING_DIRECTORY ${PROJECT_ROOT} 223 | # The working directory is set to the app dir such that the user 224 | # can use relative paths in CONF_FILE, e.g. CONF_FILE=nrf5.conf 225 | RESULT_VARIABLE ret 226 | ) 227 | if(NOT "${ret}" STREQUAL "0") 228 | message(FATAL_ERROR "command failed with return code: ${ret}") 229 | endif() 230 | 231 | if(CREATE_NEW_DOTCONFIG) 232 | # Write the new configuration fragment checksum. Only do this if kconfig.py 233 | # succeeds, to avoid marking zephyr/.config as up-to-date when it hasn't been 234 | # regenerated. 235 | file(WRITE ${merge_config_files_checksum_file} 236 | ${merge_config_files_checksum}) 237 | endif() 238 | 239 | # Read out the list of 'Kconfig' sources that were used by the engine. 240 | file(STRINGS ${PARSED_KCONFIG_SOURCES_TXT} PARSED_KCONFIG_SOURCES_LIST) 241 | 242 | # Force CMAKE configure when the Kconfig sources or configuration files changes. 243 | foreach(kconfig_input 244 | ${merge_config_files} 245 | ${DOTCONFIG} 246 | ${PARSED_KCONFIG_SOURCES_LIST} 247 | ) 248 | set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${kconfig_input}) 249 | endforeach() 250 | 251 | add_custom_target(config-sanitycheck DEPENDS ${DOTCONFIG}) 252 | 253 | # Remove the CLI Kconfig symbols from the namespace and 254 | # CMakeCache.txt. If the symbols end up in DOTCONFIG they will be 255 | # re-introduced to the namespace through 'import_kconfig'. 256 | foreach (name ${cache_variable_names}) 257 | if("${name}" MATCHES "^CONFIG_") 258 | unset(${name}) 259 | unset(${name} CACHE) 260 | endif() 261 | endforeach() 262 | 263 | # Parse the lines prefixed with CONFIG_ in the .config file from Kconfig 264 | import_kconfig(CONFIG_ ${DOTCONFIG}) 265 | 266 | # Re-introduce the CLI Kconfig symbols that survived 267 | foreach (name ${cache_variable_names}) 268 | if("${name}" MATCHES "^CONFIG_") 269 | if(DEFINED ${name}) 270 | set(${name} ${${name}} CACHE STRING "") 271 | endif() 272 | endif() 273 | endforeach() 274 | -------------------------------------------------------------------------------- /cmake/python.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | # On Windows, instruct Python to output UTF-8 even when not 4 | # interacting with a terminal. This is required since Python scripts 5 | # are invoked by CMake code and, on Windows, standard I/O encoding defaults 6 | # to the current code page if not connected to a terminal, which is often 7 | # not what we want. 8 | if (WIN32) 9 | set(ENV{PYTHONIOENCODING} "utf-8") 10 | endif() 11 | 12 | # The 'FindPythonInterp' that is distributed with CMake 3.8 has a bug 13 | # that we need to work around until we upgrade to 3.13. Until then we 14 | # maintain a patched copy in our repo. Bug: 15 | # https://github.com/zephyrproject-rtos/zephyr/issues/11103 16 | set(PythonInterp_FIND_VERSION 3.6) 17 | set(PythonInterp_FIND_VERSION_COUNT 2) 18 | set(PythonInterp_FIND_VERSION_MAJOR 3) 19 | set(PythonInterp_FIND_VERSION_MINOR 6) 20 | set(PythonInterp_FIND_VERSION_EXACT 0) 21 | set(PythonInterp_FIND_REQUIRED 1) 22 | include(cmake/backports/FindPythonInterp.cmake) 23 | -------------------------------------------------------------------------------- /cmake/top.cmake: -------------------------------------------------------------------------------- 1 | set(PROJECT_ROOT ${CMAKE_SOURCE_DIR}) 2 | set(KCONFIG_ROOT ${CMAKE_SOURCE_DIR}/Kconfig) 3 | set(BOARD_DIR ${CMAKE_SOURCE_DIR}/configs) 4 | set(AUTOCONF_H ${CMAKE_CURRENT_BINARY_DIR}/kconfig/include/generated/autoconf.h) 5 | 6 | # Re-configure (Re-execute all CMakeLists.txt code) when autoconf.h changes 7 | set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${AUTOCONF_H}) 8 | 9 | include(cmake/extensions.cmake) 10 | include(cmake/python.cmake) 11 | include(cmake/kconfig.cmake) 12 | 13 | -------------------------------------------------------------------------------- /configs/test_defconfig: -------------------------------------------------------------------------------- 1 | CONFIG_TEST_OPTION=y 2 | -------------------------------------------------------------------------------- /scripts/kconfig/diffconfig: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | # diffconfig - a tool to compare .config files. 6 | # 7 | # originally written in 2006 by Matt Mackall 8 | # (at least, this was in his bloatwatch source code) 9 | # last worked on 2008 by Tim Bird 10 | # 11 | 12 | import sys, os 13 | 14 | def usage(): 15 | print("""Usage: diffconfig [-h] [-m] [ ] 16 | 17 | Diffconfig is a simple utility for comparing two .config files. 18 | Using standard diff to compare .config files often includes extraneous and 19 | distracting information. This utility produces sorted output with only the 20 | changes in configuration values between the two files. 21 | 22 | Added and removed items are shown with a leading plus or minus, respectively. 23 | Changed items show the old and new values on a single line. 24 | 25 | If -m is specified, then output will be in "merge" style, which has the 26 | changed and new values in kernel config option format. 27 | 28 | If no config files are specified, .config and .config.old are used. 29 | 30 | Example usage: 31 | $ diffconfig .config config-with-some-changes 32 | -EXT2_FS_XATTR n 33 | -EXT2_FS_XIP n 34 | CRAMFS n -> y 35 | EXT2_FS y -> n 36 | LOG_BUF_SHIFT 14 -> 16 37 | PRINTK_TIME n -> y 38 | """) 39 | sys.exit(0) 40 | 41 | # returns a dictionary of name/value pairs for config items in the file 42 | def readconfig(config_file): 43 | d = {} 44 | for line in config_file: 45 | line = line[:-1] 46 | if line[:7] == "CONFIG_": 47 | name, val = line[7:].split("=", 1) 48 | d[name] = val 49 | if line[-11:] == " is not set": 50 | d[line[9:-11]] = "n" 51 | return d 52 | 53 | def print_config(op, config, value, new_value): 54 | global merge_style 55 | 56 | if merge_style: 57 | if new_value: 58 | if new_value=="n": 59 | print("# CONFIG_%s is not set" % config) 60 | else: 61 | print("CONFIG_%s=%s" % (config, new_value)) 62 | else: 63 | if op=="-": 64 | print("-%s %s" % (config, value)) 65 | elif op=="+": 66 | print("+%s %s" % (config, new_value)) 67 | else: 68 | print(" %s %s -> %s" % (config, value, new_value)) 69 | 70 | def main(): 71 | global merge_style 72 | 73 | # parse command line args 74 | if ("-h" in sys.argv or "--help" in sys.argv): 75 | usage() 76 | 77 | merge_style = 0 78 | if "-m" in sys.argv: 79 | merge_style = 1 80 | sys.argv.remove("-m") 81 | 82 | argc = len(sys.argv) 83 | if not (argc==1 or argc == 3): 84 | print("Error: incorrect number of arguments or unrecognized option") 85 | usage() 86 | 87 | if argc == 1: 88 | # if no filenames given, assume .config and .config.old 89 | build_dir="" 90 | if "KBUILD_OUTPUT" in os.environ: 91 | build_dir = os.environ["KBUILD_OUTPUT"]+"/" 92 | configa_filename = build_dir + ".config.old" 93 | configb_filename = build_dir + ".config" 94 | else: 95 | configa_filename = sys.argv[1] 96 | configb_filename = sys.argv[2] 97 | 98 | try: 99 | a = readconfig(open(configa_filename)) 100 | b = readconfig(open(configb_filename)) 101 | except (IOError): 102 | e = sys.exc_info()[1] 103 | print("I/O error[%s]: %s\n" % (e.args[0],e.args[1])) 104 | usage() 105 | 106 | # print items in a but not b (accumulate, sort and print) 107 | old = [] 108 | for config in a: 109 | if config not in b: 110 | old.append(config) 111 | old.sort() 112 | for config in old: 113 | print_config("-", config, a[config], None) 114 | del a[config] 115 | 116 | # print items that changed (accumulate, sort, and print) 117 | changed = [] 118 | for config in a: 119 | if a[config] != b[config]: 120 | changed.append(config) 121 | else: 122 | del b[config] 123 | changed.sort() 124 | for config in changed: 125 | print_config("->", config, a[config], b[config]) 126 | del b[config] 127 | 128 | # now print items in b but not in a 129 | # (items from b that were in a were removed above) 130 | new = sorted(b.keys()) 131 | for config in new: 132 | print_config("+", config, None, b[config]) 133 | 134 | main() 135 | -------------------------------------------------------------------------------- /scripts/kconfig/guiconfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2019, Nordic Semiconductor ASA and Ulf Magnusson 4 | # SPDX-License-Identifier: ISC 5 | 6 | # _load_images() builds names dynamically to avoid having to give them twice 7 | # (once for the variable and once for the filename). This forces consistency 8 | # too. 9 | # 10 | # pylint: disable=undefined-variable 11 | 12 | """ 13 | Overview 14 | ======== 15 | 16 | A Tkinter-based menuconfig implementation, based around a treeview control and 17 | a help display. The interface should feel familiar to people used to qconf 18 | ('make xconfig'). Compatible with both Python 2 and Python 3. 19 | 20 | The display can be toggled between showing the full tree and showing just a 21 | single menu (like menuconfig.py). Only single-menu mode distinguishes between 22 | symbols defined with 'config' and symbols defined with 'menuconfig'. 23 | 24 | A show-all mode is available that shows invisible items in red. 25 | 26 | Supports both mouse and keyboard controls. The following keyboard shortcuts are 27 | available: 28 | 29 | Ctrl-S : Save configuration 30 | Ctrl-O : Open configuration 31 | Ctrl-A : Toggle show-all mode 32 | Ctrl-N : Toggle show-name mode 33 | Ctrl-M : Toggle single-menu mode 34 | Ctrl-F, /: Open jump-to dialog 35 | ESC : Close 36 | 37 | Running 38 | ======= 39 | 40 | guiconfig.py can be run either as a standalone executable or by calling the 41 | menuconfig() function with an existing Kconfig instance. The second option is a 42 | bit inflexible in that it will still load and save .config, etc. 43 | 44 | When run in standalone mode, the top-level Kconfig file to load can be passed 45 | as a command-line argument. With no argument, it defaults to "Kconfig". 46 | 47 | The KCONFIG_CONFIG environment variable specifies the .config file to load (if 48 | it exists) and save. If KCONFIG_CONFIG is unset, ".config" is used. 49 | 50 | When overwriting a configuration file, the old version is saved to 51 | .old (e.g. .config.old). 52 | 53 | $srctree is supported through Kconfiglib. 54 | """ 55 | 56 | # Note: There's some code duplication with menuconfig.py below, especially for 57 | # the help text. Maybe some of it could be moved into kconfiglib.py or a shared 58 | # helper script, but OTOH it's pretty nice to have things standalone and 59 | # customizable. 60 | 61 | import errno 62 | import os 63 | import sys 64 | 65 | _PY2 = sys.version_info[0] < 3 66 | 67 | if _PY2: 68 | # Python 2 69 | from Tkinter import * 70 | import ttk 71 | import tkFont as font 72 | import tkFileDialog as filedialog 73 | import tkMessageBox as messagebox 74 | else: 75 | # Python 3 76 | from tkinter import * 77 | import tkinter.ttk as ttk 78 | import tkinter.font as font 79 | from tkinter import filedialog, messagebox 80 | 81 | from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \ 82 | BOOL, TRISTATE, STRING, INT, HEX, \ 83 | AND, OR, \ 84 | expr_str, expr_value, split_expr, \ 85 | standard_sc_expr_str, \ 86 | TRI_TO_STR, TYPE_TO_STR, \ 87 | standard_kconfig, standard_config_filename 88 | 89 | 90 | # If True, use GIF image data embedded in this file instead of separate GIF 91 | # files. See _load_images(). 92 | _USE_EMBEDDED_IMAGES = True 93 | 94 | 95 | # Help text for the jump-to dialog 96 | _JUMP_TO_HELP = """\ 97 | Type one or more strings/regexes and press Enter to list items that match all 98 | of them. Python's regex flavor is used (see the 're' module). Double-clicking 99 | an item will jump to it. Item values can be toggled directly within the dialog.\ 100 | """ 101 | 102 | 103 | def _main(): 104 | menuconfig(standard_kconfig(__doc__)) 105 | 106 | 107 | # Global variables used below: 108 | # 109 | # _root: 110 | # The Toplevel instance for the main window 111 | # 112 | # _tree: 113 | # The Treeview in the main window 114 | # 115 | # _jump_to_tree: 116 | # The Treeview in the jump-to dialog. None if the jump-to dialog isn't 117 | # open. Doubles as a flag. 118 | # 119 | # _jump_to_matches: 120 | # List of Nodes shown in the jump-to dialog 121 | # 122 | # _menupath: 123 | # The Label that shows the menu path of the selected item 124 | # 125 | # _backbutton: 126 | # The button shown in single-menu mode for jumping to the parent menu 127 | # 128 | # _status_label: 129 | # Label with status text shown at the bottom of the main window 130 | # ("Modified", "Saved to ...", etc.) 131 | # 132 | # _id_to_node: 133 | # We can't use Node objects directly as Treeview item IDs, so we use their 134 | # id()s instead. This dictionary maps Node id()s back to Nodes. (The keys 135 | # are actually str(id(node)), just to simplify lookups.) 136 | # 137 | # _cur_menu: 138 | # The current menu. Ignored outside single-menu mode. 139 | # 140 | # _show_all_var/_show_name_var/_single_menu_var: 141 | # Tkinter Variable instances bound to the corresponding checkboxes 142 | # 143 | # _show_all/_single_menu: 144 | # Plain Python bools that track _show_all_var and _single_menu_var, to 145 | # speed up and simplify things a bit 146 | # 147 | # _conf_filename: 148 | # File to save the configuration to 149 | # 150 | # _minconf_filename: 151 | # File to save minimal configurations to 152 | # 153 | # _conf_changed: 154 | # True if the configuration has been changed. If False, we don't bother 155 | # showing the save-and-quit dialog. 156 | # 157 | # We reset this to False whenever the configuration is saved. 158 | # 159 | # _*_img: 160 | # PhotoImage instances for images 161 | 162 | 163 | def menuconfig(kconf): 164 | """ 165 | Launches the configuration interface, returning after the user exits. 166 | 167 | kconf: 168 | Kconfig instance to be configured 169 | """ 170 | global _kconf 171 | global _conf_filename 172 | global _minconf_filename 173 | global _jump_to_tree 174 | global _cur_menu 175 | 176 | _kconf = kconf 177 | 178 | _jump_to_tree = None 179 | 180 | _create_id_to_node() 181 | 182 | _create_ui() 183 | 184 | # Filename to save configuration to 185 | _conf_filename = standard_config_filename() 186 | 187 | # Load existing configuration and check if it's outdated 188 | _set_conf_changed(_load_config()) 189 | 190 | # Filename to save minimal configuration to 191 | _minconf_filename = "defconfig" 192 | 193 | # Current menu in single-menu mode 194 | _cur_menu = _kconf.top_node 195 | 196 | # Any visible items in the top menu? 197 | if not _shown_menu_nodes(kconf.top_node): 198 | # Nothing visible. Start in show-all mode and try again. 199 | _show_all_var.set(True) 200 | if not _shown_menu_nodes(kconf.top_node): 201 | # Give up and show an error. It's nice to be able to assume that 202 | # the tree is non-empty in the rest of the code. 203 | _root.wait_visibility() 204 | messagebox.showerror( 205 | "Error", 206 | "Empty configuration -- nothing to configure.\n\n" 207 | "Check that environment variables are set properly.") 208 | _root.destroy() 209 | return 210 | 211 | # Build the initial tree 212 | _update_tree() 213 | 214 | # Select the first item and focus the Treeview, so that keyboard controls 215 | # work immediately 216 | _select(_tree, _tree.get_children()[0]) 217 | _tree.focus_set() 218 | 219 | # Make geometry information available for centering the window. This 220 | # indirectly creates the window, so hide it so that it's never shown at the 221 | # old location. 222 | _root.withdraw() 223 | _root.update_idletasks() 224 | 225 | # Center the window 226 | _root.geometry("+{}+{}".format( 227 | (_root.winfo_screenwidth() - _root.winfo_reqwidth())//2, 228 | (_root.winfo_screenheight() - _root.winfo_reqheight())//2)) 229 | 230 | # Show it 231 | _root.deiconify() 232 | 233 | # Prevent the window from being automatically resized. Otherwise, it 234 | # changes size when scrollbars appear/disappear before the user has 235 | # manually resized it. 236 | _root.geometry(_root.geometry()) 237 | 238 | _root.mainloop() 239 | 240 | 241 | def _load_config(): 242 | # Loads any existing .config file. See the Kconfig.load_config() docstring. 243 | # 244 | # Returns True if .config is missing or outdated. We always prompt for 245 | # saving the configuration in that case. 246 | 247 | print(_kconf.load_config()) 248 | if not os.path.exists(_conf_filename): 249 | # No .config 250 | return True 251 | 252 | return _needs_save() 253 | 254 | 255 | def _needs_save(): 256 | # Returns True if a just-loaded .config file is outdated (would get 257 | # modified when saving) 258 | 259 | if _kconf.missing_syms: 260 | # Assignments to undefined symbols in the .config 261 | return True 262 | 263 | for sym in _kconf.unique_defined_syms: 264 | if sym.user_value is None: 265 | if sym.config_string: 266 | # Unwritten symbol 267 | return True 268 | elif sym.orig_type in (BOOL, TRISTATE): 269 | if sym.tri_value != sym.user_value: 270 | # Written bool/tristate symbol, new value 271 | return True 272 | elif sym.str_value != sym.user_value: 273 | # Written string/int/hex symbol, new value 274 | return True 275 | 276 | # No need to prompt for save 277 | return False 278 | 279 | 280 | def _create_id_to_node(): 281 | global _id_to_node 282 | 283 | _id_to_node = {str(id(node)): node for node in _kconf.node_iter()} 284 | 285 | 286 | def _create_ui(): 287 | # Creates the main window UI 288 | 289 | global _root 290 | global _tree 291 | 292 | # Create the root window. This initializes Tkinter and makes e.g. 293 | # PhotoImage available, so do it early. 294 | _root = Tk() 295 | 296 | _load_images() 297 | _init_misc_ui() 298 | _fix_treeview_issues() 299 | 300 | _create_top_widgets() 301 | # Create the pane with the Kconfig tree and description text 302 | panedwindow, _tree = _create_kconfig_tree_and_desc(_root) 303 | panedwindow.grid(column=0, row=1, sticky="nsew") 304 | _create_status_bar() 305 | 306 | _root.columnconfigure(0, weight=1) 307 | # Only the pane with the Kconfig tree and description grows vertically 308 | _root.rowconfigure(1, weight=1) 309 | 310 | # Start with show-name disabled 311 | _do_showname() 312 | 313 | _tree.bind("", _tree_left_key) 314 | _tree.bind("", _tree_right_key) 315 | # Note: Binding this for the jump-to tree as well would cause issues due to 316 | # the Tk bug mentioned in _tree_open() 317 | _tree.bind("<>", _tree_open) 318 | # add=True to avoid overriding the description text update 319 | _tree.bind("<>", _update_menu_path, add=True) 320 | 321 | _root.bind("", _save) 322 | _root.bind("", _open) 323 | _root.bind("", _toggle_showall) 324 | _root.bind("", _toggle_showname) 325 | _root.bind("", _toggle_tree_mode) 326 | _root.bind("", _jump_to_dialog) 327 | _root.bind("/", _jump_to_dialog) 328 | _root.bind("", _on_quit) 329 | 330 | 331 | def _load_images(): 332 | # Loads GIF images, creating the global _*_img PhotoImage variables. 333 | # Base64-encoded images embedded in this script are used if 334 | # _USE_EMBEDDED_IMAGES is True, and separate image files in the same 335 | # directory as the script otherwise. 336 | # 337 | # Using a global variable indirectly prevents the image from being 338 | # garbage-collected. Passing an image to a Tkinter function isn't enough to 339 | # keep it alive. 340 | 341 | def load_image(name, data): 342 | var_name = "_{}_img".format(name) 343 | 344 | if _USE_EMBEDDED_IMAGES: 345 | globals()[var_name] = PhotoImage(data=data, format="gif") 346 | else: 347 | globals()[var_name] = PhotoImage( 348 | file=os.path.join(os.path.dirname(__file__), name + ".gif"), 349 | format="gif") 350 | 351 | # Note: Base64 data can be put on the clipboard with 352 | # $ base64 -w0 foo.gif | xclip 353 | 354 | load_image("icon", "R0lGODlhMAAwAPEDAAAAAADQAO7u7v///yH5BAUKAAMALAAAAAAwADAAAAL/nI+gy+2Pokyv2jazuZxryQjiSJZmyXxHeLbumH6sEATvW8OLNtf5bfLZRLFITzgEipDJ4mYxYv6A0ubuqYhWk66tVTE4enHer7jcKvt0LLUw6P45lvEprT6c0+v7OBuqhYdHohcoqIbSAHc4ljhDwrh1UlgSydRCWWlp5wiYZvmSuSh4IzrqV6p4cwhkCsmY+nhK6uJ6t1mrOhuJqfu6+WYiCiwl7HtLjNSZZZis/MeM7NY3TaRKS40ooDeoiVqIultsrav92bi9c3a5KkkOsOJZpSS99m4k/0zPng4Gks9JSbB+8DIcoQfnjwpZCHv5W+ip4aQrKrB0uOikYhiMCBw1/uPoQUMBADs=") 355 | load_image("n_bool", "R0lGODdhEAAQAPAAAAgICP///ywAAAAAEAAQAAACIISPacHtvp5kcb5qG85hZ2+BkyiRF8BBaEqtrKkqslEAADs=") 356 | load_image("y_bool", "R0lGODdhEAAQAPEAAAgICADQAP///wAAACwAAAAAEAAQAAACMoSPacLtvlh4YrIYsst2cV19AvaVF9CUXBNJJoum7ymrsKuCnhiupIWjSSjAFuWhSCIKADs=") 357 | load_image("n_tri", "R0lGODlhEAAQAPD/AAEBAf///yH5BAUKAAIALAAAAAAQABAAAAInlI+pBrAKQnCPSUlXvFhznlkfeGwjKZhnJ65h6nrfi6h0st2QXikFADs=") 358 | load_image("m_tri", "R0lGODlhEAAQAPEDAAEBAeQMuv///wAAACH5BAUKAAMALAAAAAAQABAAAAI5nI+pBrAWAhPCjYhiAJQCnWmdoElHGVBoiK5M21ofXFpXRIrgiecqxkuNciZIhNOZFRNI24PhfEoLADs=") 359 | load_image("y_tri", "R0lGODlhEAAQAPEDAAICAgDQAP///wAAACH5BAUKAAMALAAAAAAQABAAAAI0nI+pBrAYBhDCRRUypfmergmgZ4xjMpmaw2zmxk7cCB+pWiVqp4MzDwn9FhGZ5WFjIZeGAgA7") 360 | load_image("m_my", "R0lGODlhEAAQAPEDAAAAAOQMuv///wAAACH5BAUKAAMALAAAAAAQABAAAAI5nIGpxiAPI2ghxFinq/ZygQhc94zgZopmOLYf67anGr+oZdp02emfV5n9MEHN5QhqICETxkABbQ4KADs=") 361 | load_image("y_my", "R0lGODlhEAAQAPH/AAAAAADQAAPRA////yH5BAUKAAQALAAAAAAQABAAAAM+SArcrhCMSSuIM9Q8rxxBWIXawIBkmWonupLd565Um9G1PIs59fKmzw8WnAlusBYR2SEIN6DmAmqBLBxYSAIAOw==") 362 | load_image("n_locked", "R0lGODlhEAAQAPABAAAAAP///yH5BAUKAAEALAAAAAAQABAAAAIgjB8AyKwN04pu0vMutpqqz4Hih4ydlnUpyl2r23pxUAAAOw==") 363 | load_image("m_locked", "R0lGODlhEAAQAPD/AAAAAOQMuiH5BAUKAAIALAAAAAAQABAAAAIylC8AyKwN04ohnGcqqlZmfXDWI26iInZoyiore05walolV39ftxsYHgL9QBBMBGFEFAAAOw==") 364 | load_image("y_locked", "R0lGODlhEAAQAPD/AAAAAADQACH5BAUKAAIALAAAAAAQABAAAAIylC8AyKzNgnlCtoDTwvZwrHydIYpQmR3KWq4uK74IOnp0HQPmnD3cOVlUIAgKsShkFAAAOw==") 365 | load_image("not_selected", "R0lGODlhEAAQAPD/AAAAAP///yH5BAUKAAIALAAAAAAQABAAAAIrlA2px6IBw2IpWglOvTYhzmUbGD3kNZ5QqrKn2YrqigCxZoMelU6No9gdCgA7") 366 | load_image("selected", "R0lGODlhEAAQAPD/AAAAAP///yH5BAUKAAIALAAAAAAQABAAAAIzlA2px6IBw2IpWglOvTah/kTZhimASJomiqonlLov1qptHTsgKSEzh9H8QI0QzNPwmRoFADs=") 367 | load_image("edit", "R0lGODlhEAAQAPIFAAAAAKOLAMuuEPvXCvrxvgAAAAAAAAAAACH5BAUKAAUALAAAAAAQABAAAANCWLqw/gqMBp8cszJxcwVC2FEOEIAi5kVBi3IqWZhuCGMyfdpj2e4pnK+WAshmvxeAcETWlsxPkkBtsqBMa8TIBSQAADs=") 368 | 369 | 370 | def _fix_treeview_issues(): 371 | # Fixes some Treeview issues 372 | 373 | global _treeview_rowheight 374 | 375 | style = ttk.Style() 376 | 377 | # The treeview rowheight isn't adjusted automatically on high-DPI displays, 378 | # so do it ourselves. The font will probably always be TkDefaultFont, but 379 | # play it safe and look it up. 380 | 381 | _treeview_rowheight = font.Font(font=style.lookup("Treeview", "font")) \ 382 | .metrics("linespace") + 2 383 | 384 | style.configure("Treeview", rowheight=_treeview_rowheight) 385 | 386 | # Work around regression in https://core.tcl.tk/tk/tktview?name=509cafafae, 387 | # which breaks tag background colors 388 | 389 | for option in "foreground", "background": 390 | # Filter out any styles starting with ("!disabled", "!selected", ...). 391 | # style.map() returns an empty list for missing options, so this should 392 | # be future-safe. 393 | style.map( 394 | "Treeview", 395 | **{option: [elm for elm in style.map("Treeview", query_opt=option) 396 | if elm[:2] != ("!disabled", "!selected")]}) 397 | 398 | 399 | def _init_misc_ui(): 400 | # Does misc. UI initialization, like setting the title, icon, and theme 401 | 402 | _root.title(_kconf.mainmenu_text) 403 | # iconphoto() isn't available in Python 2's Tkinter 404 | _root.tk.call("wm", "iconphoto", _root._w, "-default", _icon_img) 405 | # Reducing the width of the window to 1 pixel makes it move around, at 406 | # least on GNOME. Prevent weird stuff like that. 407 | _root.minsize(128, 128) 408 | _root.protocol("WM_DELETE_WINDOW", _on_quit) 409 | 410 | # Use the 'clam' theme on *nix if it's available. It looks nicer than the 411 | # 'default' theme. 412 | if _root.tk.call("tk", "windowingsystem") == "x11": 413 | style = ttk.Style() 414 | if "clam" in style.theme_names(): 415 | style.theme_use("clam") 416 | 417 | 418 | def _create_top_widgets(): 419 | # Creates the controls above the Kconfig tree in the main window 420 | 421 | global _show_all_var 422 | global _show_name_var 423 | global _single_menu_var 424 | global _menupath 425 | global _backbutton 426 | 427 | topframe = ttk.Frame(_root) 428 | topframe.grid(column=0, row=0, sticky="ew") 429 | 430 | ttk.Button(topframe, text="Save", command=_save) \ 431 | .grid(column=0, row=0, sticky="ew", padx=".05c", pady=".05c") 432 | 433 | ttk.Button(topframe, text="Save as...", command=_save_as) \ 434 | .grid(column=1, row=0, sticky="ew") 435 | 436 | ttk.Button(topframe, text="Save minimal (advanced)...", 437 | command=_save_minimal) \ 438 | .grid(column=2, row=0, sticky="ew", padx=".05c") 439 | 440 | ttk.Button(topframe, text="Open...", command=_open) \ 441 | .grid(column=3, row=0) 442 | 443 | ttk.Button(topframe, text="Jump to...", command=_jump_to_dialog) \ 444 | .grid(column=4, row=0, padx=".05c") 445 | 446 | _show_name_var = BooleanVar() 447 | ttk.Checkbutton(topframe, text="Show name", command=_do_showname, 448 | variable=_show_name_var) \ 449 | .grid(column=0, row=1, sticky="nsew", padx=".05c", pady="0 .05c", 450 | ipady=".2c") 451 | 452 | _show_all_var = BooleanVar() 453 | ttk.Checkbutton(topframe, text="Show all", command=_do_showall, 454 | variable=_show_all_var) \ 455 | .grid(column=1, row=1, sticky="nsew", pady="0 .05c") 456 | 457 | # Allow the show-all and single-menu status to be queried via plain global 458 | # Python variables, which is faster and simpler 459 | 460 | def show_all_updated(*_): 461 | global _show_all 462 | _show_all = _show_all_var.get() 463 | 464 | _trace_write(_show_all_var, show_all_updated) 465 | _show_all_var.set(False) 466 | 467 | _single_menu_var = BooleanVar() 468 | ttk.Checkbutton(topframe, text="Single-menu mode", command=_do_tree_mode, 469 | variable=_single_menu_var) \ 470 | .grid(column=2, row=1, sticky="nsew", padx=".05c", pady="0 .05c") 471 | 472 | _backbutton = ttk.Button(topframe, text="<--", command=_leave_menu, 473 | state="disabled") 474 | _backbutton.grid(column=0, row=4, sticky="nsew", padx=".05c", pady="0 .05c") 475 | 476 | def tree_mode_updated(*_): 477 | global _single_menu 478 | _single_menu = _single_menu_var.get() 479 | 480 | if _single_menu: 481 | _backbutton.grid() 482 | else: 483 | _backbutton.grid_remove() 484 | 485 | _trace_write(_single_menu_var, tree_mode_updated) 486 | _single_menu_var.set(False) 487 | 488 | # Column to the right of the buttons that the menu path extends into, so 489 | # that it can grow wider than the buttons 490 | topframe.columnconfigure(5, weight=1) 491 | 492 | _menupath = ttk.Label(topframe) 493 | _menupath.grid(column=0, row=3, columnspan=6, sticky="w", padx="0.05c", 494 | pady="0 .05c") 495 | 496 | 497 | def _create_kconfig_tree_and_desc(parent): 498 | # Creates a Panedwindow with a Treeview that shows Kconfig nodes and a Text 499 | # that shows a description of the selected node. Returns a tuple with the 500 | # Panedwindow and the Treeview. This code is shared between the main window 501 | # and the jump-to dialog. 502 | 503 | panedwindow = ttk.Panedwindow(parent, orient=VERTICAL) 504 | 505 | tree_frame, tree = _create_kconfig_tree(panedwindow) 506 | desc_frame, desc = _create_kconfig_desc(panedwindow) 507 | 508 | panedwindow.add(tree_frame, weight=1) 509 | panedwindow.add(desc_frame) 510 | 511 | def tree_select(_): 512 | # The Text widget does not allow editing the text in its disabled 513 | # state. We need to temporarily enable it. 514 | desc["state"] = "normal" 515 | 516 | sel = tree.selection() 517 | if not sel: 518 | desc.delete("1.0", "end") 519 | desc["state"] = "disabled" 520 | return 521 | 522 | # Text.replace() is not available in Python 2's Tkinter 523 | desc.delete("1.0", "end") 524 | desc.insert("end", _info_str(_id_to_node[sel[0]])) 525 | 526 | desc["state"] = "disabled" 527 | 528 | tree.bind("<>", tree_select) 529 | tree.bind("<1>", _tree_click) 530 | tree.bind("", _tree_double_click) 531 | tree.bind("", _tree_enter) 532 | tree.bind("", _tree_enter) 533 | tree.bind("", _tree_toggle) 534 | tree.bind("n", _tree_set_val(0)) 535 | tree.bind("m", _tree_set_val(1)) 536 | tree.bind("y", _tree_set_val(2)) 537 | 538 | return panedwindow, tree 539 | 540 | 541 | def _create_kconfig_tree(parent): 542 | # Creates a Treeview for showing Kconfig nodes 543 | 544 | frame = ttk.Frame(parent) 545 | 546 | tree = ttk.Treeview(frame, selectmode="browse", height=20, 547 | columns=("name",)) 548 | tree.heading("#0", text="Option", anchor="w") 549 | tree.heading("name", text="Name", anchor="w") 550 | 551 | tree.tag_configure("n-bool", image=_n_bool_img) 552 | tree.tag_configure("y-bool", image=_y_bool_img) 553 | tree.tag_configure("m-tri", image=_m_tri_img) 554 | tree.tag_configure("n-tri", image=_n_tri_img) 555 | tree.tag_configure("m-tri", image=_m_tri_img) 556 | tree.tag_configure("y-tri", image=_y_tri_img) 557 | tree.tag_configure("m-my", image=_m_my_img) 558 | tree.tag_configure("y-my", image=_y_my_img) 559 | tree.tag_configure("n-locked", image=_n_locked_img) 560 | tree.tag_configure("m-locked", image=_m_locked_img) 561 | tree.tag_configure("y-locked", image=_y_locked_img) 562 | tree.tag_configure("not-selected", image=_not_selected_img) 563 | tree.tag_configure("selected", image=_selected_img) 564 | tree.tag_configure("edit", image=_edit_img) 565 | tree.tag_configure("invisible", foreground="red") 566 | 567 | tree.grid(column=0, row=0, sticky="nsew") 568 | 569 | _add_vscrollbar(frame, tree) 570 | 571 | frame.columnconfigure(0, weight=1) 572 | frame.rowconfigure(0, weight=1) 573 | 574 | # Create items for all menu nodes. These can be detached/moved later. 575 | # Micro-optimize this a bit. 576 | insert = tree.insert 577 | id_ = id 578 | Symbol_ = Symbol 579 | for node in _kconf.node_iter(): 580 | item = node.item 581 | insert("", "end", iid=id_(node), 582 | values=item.name if item.__class__ is Symbol_ else "") 583 | 584 | return frame, tree 585 | 586 | 587 | def _create_kconfig_desc(parent): 588 | # Creates a Text for showing the description of the selected Kconfig node 589 | 590 | frame = ttk.Frame(parent) 591 | 592 | desc = Text(frame, height=12, wrap="none", borderwidth=0, 593 | state="disabled") 594 | desc.grid(column=0, row=0, sticky="nsew") 595 | 596 | # Work around not being to Ctrl-C/V text from a disabled Text widget, with a 597 | # tip found in https://stackoverflow.com/questions/3842155/is-there-a-way-to-make-the-tkinter-text-widget-read-only 598 | desc.bind("<1>", lambda _: desc.focus_set()) 599 | 600 | _add_vscrollbar(frame, desc) 601 | 602 | frame.columnconfigure(0, weight=1) 603 | frame.rowconfigure(0, weight=1) 604 | 605 | return frame, desc 606 | 607 | 608 | def _add_vscrollbar(parent, widget): 609 | # Adds a vertical scrollbar to 'widget' that's only shown as needed 610 | 611 | vscrollbar = ttk.Scrollbar(parent, orient="vertical", 612 | command=widget.yview) 613 | vscrollbar.grid(column=1, row=0, sticky="ns") 614 | 615 | def yscrollcommand(first, last): 616 | # Only show the scrollbar when needed. 'first' and 'last' are 617 | # strings. 618 | if float(first) <= 0.0 and float(last) >= 1.0: 619 | vscrollbar.grid_remove() 620 | else: 621 | vscrollbar.grid() 622 | 623 | vscrollbar.set(first, last) 624 | 625 | widget["yscrollcommand"] = yscrollcommand 626 | 627 | 628 | def _create_status_bar(): 629 | # Creates the status bar at the bottom of the main window 630 | 631 | global _status_label 632 | 633 | _status_label = ttk.Label(_root, anchor="e", padding="0 0 0.4c 0") 634 | _status_label.grid(column=0, row=3, sticky="ew") 635 | 636 | 637 | def _set_status(s): 638 | # Sets the text in the status bar to 's' 639 | 640 | _status_label["text"] = s 641 | 642 | 643 | def _set_conf_changed(changed): 644 | # Updates the status re. whether there are unsaved changes 645 | 646 | global _conf_changed 647 | 648 | _conf_changed = changed 649 | if changed: 650 | _set_status("Modified") 651 | 652 | 653 | def _update_tree(): 654 | # Updates the Kconfig tree in the main window by first detaching all nodes 655 | # and then updating and reattaching them. The tree structure might have 656 | # changed. 657 | 658 | # If a selected/focused item is detached and later reattached, it stays 659 | # selected/focused. That can give multiple selections even though 660 | # selectmode=browse. Save and later restore the selection and focus as a 661 | # workaround. 662 | old_selection = _tree.selection() 663 | old_focus = _tree.focus() 664 | 665 | # Detach all tree items before re-stringing them. This is relatively fast, 666 | # luckily. 667 | _tree.detach(*_id_to_node.keys()) 668 | 669 | if _single_menu: 670 | _build_menu_tree() 671 | else: 672 | _build_full_tree(_kconf.top_node) 673 | 674 | _tree.selection_set(old_selection) 675 | _tree.focus(old_focus) 676 | 677 | 678 | def _build_full_tree(menu): 679 | # Updates the tree starting from menu.list, in full-tree mode. To speed 680 | # things up, only open menus are updated. The menu-at-a-time logic here is 681 | # to deal with invisible items that can show up outside show-all mode (see 682 | # _shown_full_nodes()). 683 | 684 | for node in _shown_full_nodes(menu): 685 | _add_to_tree(node, _kconf.top_node) 686 | 687 | # _shown_full_nodes() includes nodes from menus rooted at symbols, so 688 | # we only need to check "real" menus/choices here 689 | if node.list and not isinstance(node.item, Symbol): 690 | if _tree.item(id(node), "open"): 691 | _build_full_tree(node) 692 | else: 693 | # We're just probing here, so _shown_menu_nodes() will work 694 | # fine, and might be a bit faster 695 | shown = _shown_menu_nodes(node) 696 | if shown: 697 | # Dummy element to make the open/closed toggle appear 698 | _tree.move(id(shown[0]), id(shown[0].parent), "end") 699 | 700 | 701 | def _shown_full_nodes(menu): 702 | # Returns the list of menu nodes shown in 'menu' (a menu node for a menu) 703 | # for full-tree mode. A tricky detail is that invisible items need to be 704 | # shown if they have visible children. 705 | 706 | def rec(node): 707 | res = [] 708 | 709 | while node: 710 | if _visible(node) or _show_all: 711 | res.append(node) 712 | if node.list and isinstance(node.item, Symbol): 713 | # Nodes from menu created from dependencies 714 | res += rec(node.list) 715 | 716 | elif node.list and isinstance(node.item, Symbol): 717 | # Show invisible symbols (defined with either 'config' and 718 | # 'menuconfig') if they have visible children. This can happen 719 | # for an m/y-valued symbol with an optional prompt 720 | # ('prompt "foo" is COND') that is currently disabled. 721 | shown_children = rec(node.list) 722 | if shown_children: 723 | res.append(node) 724 | res += shown_children 725 | 726 | node = node.next 727 | 728 | return res 729 | 730 | return rec(menu.list) 731 | 732 | 733 | def _build_menu_tree(): 734 | # Updates the tree in single-menu mode. See _build_full_tree() as well. 735 | 736 | for node in _shown_menu_nodes(_cur_menu): 737 | _add_to_tree(node, _cur_menu) 738 | 739 | 740 | def _shown_menu_nodes(menu): 741 | # Used for single-menu mode. Similar to _shown_full_nodes(), but doesn't 742 | # include children of symbols defined with 'menuconfig'. 743 | 744 | def rec(node): 745 | res = [] 746 | 747 | while node: 748 | if _visible(node) or _show_all: 749 | res.append(node) 750 | if node.list and not node.is_menuconfig: 751 | res += rec(node.list) 752 | 753 | elif node.list and isinstance(node.item, Symbol): 754 | shown_children = rec(node.list) 755 | if shown_children: 756 | # Invisible item with visible children 757 | res.append(node) 758 | if not node.is_menuconfig: 759 | res += shown_children 760 | 761 | node = node.next 762 | 763 | return res 764 | 765 | return rec(menu.list) 766 | 767 | 768 | def _visible(node): 769 | # Returns True if the node should appear in the menu (outside show-all 770 | # mode) 771 | 772 | return node.prompt and expr_value(node.prompt[1]) and not \ 773 | (node.item == MENU and not expr_value(node.visibility)) 774 | 775 | 776 | def _add_to_tree(node, top): 777 | # Adds 'node' to the tree, at the end of its menu. We rely on going through 778 | # the nodes linearly to get the correct order. 'top' holds the menu that 779 | # corresponds to the top-level menu, and can vary in single-menu mode. 780 | 781 | parent = node.parent 782 | _tree.move(id(node), "" if parent is top else id(parent), "end") 783 | _tree.item( 784 | id(node), 785 | text=_node_str(node), 786 | # The _show_all test avoids showing invisible items in red outside 787 | # show-all mode, which could look confusing/broken. Invisible symbols 788 | # are shown outside show-all mode if an invisible symbol has visible 789 | # children in an implicit menu. 790 | tags=_img_tag(node) if _visible(node) or not _show_all else 791 | _img_tag(node) + " invisible") 792 | 793 | 794 | def _node_str(node): 795 | # Returns the string shown to the right of the image (if any) for the node 796 | 797 | if node.prompt: 798 | if node.item == COMMENT: 799 | s = "*** {} ***".format(node.prompt[0]) 800 | else: 801 | s = node.prompt[0] 802 | 803 | if isinstance(node.item, Symbol): 804 | sym = node.item 805 | 806 | # Print "(NEW)" next to symbols without a user value (from e.g. a 807 | # .config), but skip it for choice symbols in choices in y mode, 808 | # and for symbols of UNKNOWN type (which generate a warning though) 809 | if sym.user_value is None and sym.type and not \ 810 | (sym.choice and sym.choice.tri_value == 2): 811 | 812 | s += " (NEW)" 813 | 814 | elif isinstance(node.item, Symbol): 815 | # Symbol without prompt (can show up in show-all) 816 | s = "<{}>".format(node.item.name) 817 | 818 | else: 819 | # Choice without prompt. Use standard_sc_expr_str() so that it shows up 820 | # as ''. 821 | s = standard_sc_expr_str(node.item) 822 | 823 | 824 | if isinstance(node.item, Symbol): 825 | sym = node.item 826 | if sym.orig_type == STRING: 827 | s += ": " + sym.str_value 828 | elif sym.orig_type in (INT, HEX): 829 | s = "({}) {}".format(sym.str_value, s) 830 | 831 | elif isinstance(node.item, Choice) and node.item.tri_value == 2: 832 | # Print the prompt of the selected symbol after the choice for 833 | # choices in y mode 834 | sym = node.item.selection 835 | if sym: 836 | for sym_node in sym.nodes: 837 | # Use the prompt used at this choice location, in case the 838 | # choice symbol is defined in multiple locations 839 | if sym_node.parent is node and sym_node.prompt: 840 | s += " ({})".format(sym_node.prompt[0]) 841 | break 842 | else: 843 | # If the symbol isn't defined at this choice location, then 844 | # just use whatever prompt we can find for it 845 | for sym_node in sym.nodes: 846 | if sym_node.prompt: 847 | s += " ({})".format(sym_node.prompt[0]) 848 | break 849 | 850 | # In single-menu mode, print "--->" next to nodes that have menus that can 851 | # potentially be entered. Print "----" if the menu is empty. We don't allow 852 | # those to be entered. 853 | if _single_menu and node.is_menuconfig: 854 | s += " --->" if _shown_menu_nodes(node) else " ----" 855 | 856 | return s 857 | 858 | 859 | def _img_tag(node): 860 | # Returns the tag for the image that should be shown next to 'node', or the 861 | # empty string if it shouldn't have an image 862 | 863 | item = node.item 864 | 865 | if item in (MENU, COMMENT) or not item.orig_type: 866 | return "" 867 | 868 | if item.orig_type in (STRING, INT, HEX): 869 | return "edit" 870 | 871 | # BOOL or TRISTATE 872 | 873 | if _is_y_mode_choice_sym(item): 874 | # Choice symbol in y-mode choice 875 | return "selected" if item.choice.selection is item else "not-selected" 876 | 877 | if len(item.assignable) <= 1: 878 | # Pinned to a single value 879 | return "" if isinstance(item, Choice) else item.str_value + "-locked" 880 | 881 | if item.type == BOOL: 882 | return item.str_value + "-bool" 883 | 884 | # item.type == TRISTATE 885 | if item.assignable == (1, 2): 886 | return item.str_value + "-my" 887 | return item.str_value + "-tri" 888 | 889 | 890 | def _is_y_mode_choice_sym(item): 891 | # The choice mode is an upper bound on the visibility of choice symbols, so 892 | # we can check the choice symbols' own visibility to see if the choice is 893 | # in y mode 894 | return isinstance(item, Symbol) and item.choice and item.visibility == 2 895 | 896 | 897 | def _tree_click(event): 898 | # Click on the Kconfig Treeview 899 | 900 | tree = event.widget 901 | if tree.identify_element(event.x, event.y) == "image": 902 | item = tree.identify_row(event.y) 903 | # Select the item before possibly popping up a dialog for 904 | # string/int/hex items, so that its help is visible 905 | _select(tree, item) 906 | _change_node(_id_to_node[item], tree.winfo_toplevel()) 907 | return "break" 908 | 909 | 910 | def _tree_double_click(event): 911 | # Double-click on the Kconfig treeview 912 | 913 | # Do an extra check to avoid weirdness when double-clicking in the tree 914 | # heading area 915 | if not _in_heading(event): 916 | return _tree_enter(event) 917 | 918 | 919 | def _in_heading(event): 920 | # Returns True if 'event' took place in the tree heading 921 | 922 | tree = event.widget 923 | return hasattr(tree, "identify_region") and \ 924 | tree.identify_region(event.x, event.y) in ("heading", "separator") 925 | 926 | 927 | def _tree_enter(event): 928 | # Enter press or double-click within the Kconfig treeview. Prefer to 929 | # open/close/enter menus, but toggle the value if that's not possible. 930 | 931 | tree = event.widget 932 | sel = tree.focus() 933 | if sel: 934 | node = _id_to_node[sel] 935 | 936 | if tree.get_children(sel): 937 | _tree_toggle_open(sel) 938 | elif _single_menu_mode_menu(node, tree): 939 | _enter_menu_and_select_first(node) 940 | else: 941 | _change_node(node, tree.winfo_toplevel()) 942 | 943 | return "break" 944 | 945 | 946 | def _tree_toggle(event): 947 | # Space press within the Kconfig treeview. Prefer to toggle the value, but 948 | # open/close/enter the menu if that's not possible. 949 | 950 | tree = event.widget 951 | sel = tree.focus() 952 | if sel: 953 | node = _id_to_node[sel] 954 | 955 | if _changeable(node): 956 | _change_node(node, tree.winfo_toplevel()) 957 | elif _single_menu_mode_menu(node, tree): 958 | _enter_menu_and_select_first(node) 959 | elif tree.get_children(sel): 960 | _tree_toggle_open(sel) 961 | 962 | return "break" 963 | 964 | 965 | def _tree_left_key(_): 966 | # Left arrow key press within the Kconfig treeview 967 | 968 | if _single_menu: 969 | # Leave the current menu in single-menu mode 970 | _leave_menu() 971 | return "break" 972 | 973 | # Otherwise, default action 974 | 975 | 976 | def _tree_right_key(_): 977 | # Right arrow key press within the Kconfig treeview 978 | 979 | sel = _tree.focus() 980 | if sel: 981 | node = _id_to_node[sel] 982 | # If the node can be entered in single-menu mode, do it 983 | if _single_menu_mode_menu(node, _tree): 984 | _enter_menu_and_select_first(node) 985 | return "break" 986 | 987 | # Otherwise, default action 988 | 989 | 990 | def _single_menu_mode_menu(node, tree): 991 | # Returns True if single-menu mode is on and 'node' is an (interface) 992 | # menu that can be entered 993 | 994 | return _single_menu and tree is _tree and node.is_menuconfig and \ 995 | _shown_menu_nodes(node) 996 | 997 | 998 | def _changeable(node): 999 | # Returns True if 'node' is a Symbol/Choice whose value can be changed 1000 | 1001 | sc = node.item 1002 | 1003 | if not isinstance(sc, (Symbol, Choice)): 1004 | return False 1005 | 1006 | # This will hit for invisible symbols, which appear in show-all mode and 1007 | # when an invisible symbol has visible children (which can happen e.g. for 1008 | # symbols with optional prompts) 1009 | if not (node.prompt and expr_value(node.prompt[1])): 1010 | return False 1011 | 1012 | return sc.orig_type in (STRING, INT, HEX) or len(sc.assignable) > 1 \ 1013 | or _is_y_mode_choice_sym(sc) 1014 | 1015 | 1016 | def _tree_toggle_open(item): 1017 | # Opens/closes the Treeview item 'item' 1018 | 1019 | if _tree.item(item, "open"): 1020 | _tree.item(item, open=False) 1021 | else: 1022 | node = _id_to_node[item] 1023 | if not isinstance(node.item, Symbol): 1024 | # Can only get here in full-tree mode 1025 | _build_full_tree(node) 1026 | _tree.item(item, open=True) 1027 | 1028 | 1029 | def _tree_set_val(tri_val): 1030 | def tree_set_val(event): 1031 | # n/m/y press within the Kconfig treeview 1032 | 1033 | # Sets the value of the currently selected item to 'tri_val', if that 1034 | # value can be assigned 1035 | 1036 | sel = event.widget.focus() 1037 | if sel: 1038 | sc = _id_to_node[sel].item 1039 | if isinstance(sc, (Symbol, Choice)) and tri_val in sc.assignable: 1040 | _set_val(sc, tri_val) 1041 | 1042 | return tree_set_val 1043 | 1044 | 1045 | def _tree_open(_): 1046 | # Lazily populates the Kconfig tree when menus are opened in full-tree mode 1047 | 1048 | if _single_menu: 1049 | # Work around https://core.tcl.tk/tk/tktview?name=368fa4561e 1050 | # ("ttk::treeview open/closed indicators can be toggled while hidden"). 1051 | # Clicking on the hidden indicator will call _build_full_tree() in 1052 | # single-menu mode otherwise. 1053 | return 1054 | 1055 | node = _id_to_node[_tree.focus()] 1056 | # _shown_full_nodes() includes nodes from menus rooted at symbols, so we 1057 | # only need to check "real" menus and choices here 1058 | if not isinstance(node.item, Symbol): 1059 | _build_full_tree(node) 1060 | 1061 | 1062 | def _update_menu_path(_): 1063 | # Updates the displayed menu path when nodes are selected in the Kconfig 1064 | # treeview 1065 | 1066 | sel = _tree.selection() 1067 | _menupath["text"] = _menu_path_info(_id_to_node[sel[0]]) if sel else "" 1068 | 1069 | 1070 | def _item_row(item): 1071 | # Returns the row number 'item' appears on within the Kconfig treeview, 1072 | # starting from the top of the tree. Used to preserve scrolling. 1073 | # 1074 | # ttkTreeview.c in the Tk sources defines a RowNumber() function that does 1075 | # the same thing, but it's not exposed. 1076 | 1077 | row = 0 1078 | 1079 | while True: 1080 | prev = _tree.prev(item) 1081 | if prev: 1082 | item = prev 1083 | row += _n_rows(item) 1084 | else: 1085 | item = _tree.parent(item) 1086 | if not item: 1087 | return row 1088 | row += 1 1089 | 1090 | 1091 | def _n_rows(item): 1092 | # _item_row() helper. Returns the number of rows occupied by 'item' and # 1093 | # its children. 1094 | 1095 | rows = 1 1096 | 1097 | if _tree.item(item, "open"): 1098 | for child in _tree.get_children(item): 1099 | rows += _n_rows(child) 1100 | 1101 | return rows 1102 | 1103 | 1104 | def _attached(item): 1105 | # Heuristic for checking if a Treeview item is attached. Doesn't seem to be 1106 | # good APIs for this. Might fail for super-obscure cases with tiny trees, 1107 | # but you'd just get a small scroll mess-up. 1108 | 1109 | return bool(_tree.next(item) or _tree.prev(item) or _tree.parent(item)) 1110 | 1111 | 1112 | def _change_node(node, parent): 1113 | # Toggles/changes the value of 'node'. 'parent' is the parent window 1114 | # (either the main window or the jump-to dialog), in case we need to pop up 1115 | # a dialog. 1116 | 1117 | if not _changeable(node): 1118 | return 1119 | 1120 | # sc = symbol/choice 1121 | sc = node.item 1122 | 1123 | if sc.type in (INT, HEX, STRING): 1124 | s = _set_val_dialog(node, parent) 1125 | 1126 | # Tkinter can return 'unicode' strings on Python 2, which Kconfiglib 1127 | # can't deal with. UTF-8-encode the string to work around it. 1128 | if _PY2 and isinstance(s, unicode): 1129 | s = s.encode("utf-8", "ignore") 1130 | 1131 | if s is not None: 1132 | _set_val(sc, s) 1133 | 1134 | elif len(sc.assignable) == 1: 1135 | # Handles choice symbols for choices in y mode, which are a special 1136 | # case: .assignable can be (2,) while .tri_value is 0. 1137 | _set_val(sc, sc.assignable[0]) 1138 | 1139 | else: 1140 | # Set the symbol to the value after the current value in 1141 | # sc.assignable, with wrapping 1142 | val_index = sc.assignable.index(sc.tri_value) 1143 | _set_val(sc, sc.assignable[(val_index + 1) % len(sc.assignable)]) 1144 | 1145 | 1146 | def _set_val(sc, val): 1147 | # Wrapper around Symbol/Choice.set_value() for updating the menu state and 1148 | # _conf_changed 1149 | 1150 | # Use the string representation of tristate values. This makes the format 1151 | # consistent for all symbol types. 1152 | if val in TRI_TO_STR: 1153 | val = TRI_TO_STR[val] 1154 | 1155 | if val != sc.str_value: 1156 | sc.set_value(val) 1157 | _set_conf_changed(True) 1158 | 1159 | # Update the tree and try to preserve the scroll. Do a cheaper variant 1160 | # than in the show-all case, that might mess up the scroll slightly in 1161 | # rare cases, but is fast and flicker-free. 1162 | 1163 | stayput = _loc_ref_item() # Item to preserve scroll for 1164 | old_row = _item_row(stayput) 1165 | 1166 | _update_tree() 1167 | 1168 | # If the reference item disappeared (can happen if the change was done 1169 | # from the jump-to dialog), then avoid messing with the scroll and hope 1170 | # for the best 1171 | if _attached(stayput): 1172 | _tree.yview_scroll(_item_row(stayput) - old_row, "units") 1173 | 1174 | if _jump_to_tree: 1175 | _update_jump_to_display() 1176 | 1177 | 1178 | def _set_val_dialog(node, parent): 1179 | # Pops up a dialog for setting the value of the string/int/hex 1180 | # symbol at node 'node'. 'parent' is the parent window. 1181 | 1182 | def ok(_=None): 1183 | # No 'nonlocal' in Python 2 1184 | global _entry_res 1185 | 1186 | s = entry.get() 1187 | if sym.type == HEX and not s.startswith(("0x", "0X")): 1188 | s = "0x" + s 1189 | 1190 | if _check_valid(dialog, entry, sym, s): 1191 | _entry_res = s 1192 | dialog.destroy() 1193 | 1194 | def cancel(_=None): 1195 | global _entry_res 1196 | _entry_res = None 1197 | dialog.destroy() 1198 | 1199 | sym = node.item 1200 | 1201 | dialog = Toplevel(parent) 1202 | dialog.title("Enter {} value".format(TYPE_TO_STR[sym.type])) 1203 | dialog.resizable(False, False) 1204 | dialog.transient(parent) 1205 | dialog.protocol("WM_DELETE_WINDOW", cancel) 1206 | 1207 | ttk.Label(dialog, text=node.prompt[0] + ":") \ 1208 | .grid(column=0, row=0, columnspan=2, sticky="w", padx=".3c", 1209 | pady=".2c .05c") 1210 | 1211 | entry = ttk.Entry(dialog, width=30) 1212 | # Start with the previous value in the editbox, selected 1213 | entry.insert(0, sym.str_value) 1214 | entry.selection_range(0, "end") 1215 | entry.grid(column=0, row=1, columnspan=2, sticky="ew", padx=".3c") 1216 | entry.focus_set() 1217 | 1218 | range_info = _range_info(sym) 1219 | if range_info: 1220 | ttk.Label(dialog, text=range_info) \ 1221 | .grid(column=0, row=2, columnspan=2, sticky="w", padx=".3c", 1222 | pady=".2c 0") 1223 | 1224 | ttk.Button(dialog, text="OK", command=ok) \ 1225 | .grid(column=0, row=4 if range_info else 3, sticky="e", padx=".3c", 1226 | pady=".4c") 1227 | 1228 | ttk.Button(dialog, text="Cancel", command=cancel) \ 1229 | .grid(column=1, row=4 if range_info else 3, padx="0 .3c") 1230 | 1231 | # Give all horizontal space to the grid cell with the OK button, so that 1232 | # Cancel moves to the right 1233 | dialog.columnconfigure(0, weight=1) 1234 | 1235 | _center_on_root(dialog) 1236 | 1237 | # Hack to scroll the entry so that the end of the text is shown, from 1238 | # https://stackoverflow.com/questions/29334544/why-does-tkinters-entry-xview-moveto-fail. 1239 | # Related Tk ticket: https://core.tcl.tk/tk/info/2513186fff 1240 | def scroll_entry(_): 1241 | _root.update_idletasks() 1242 | entry.unbind("") 1243 | entry.xview_moveto(1) 1244 | entry.bind("", scroll_entry) 1245 | 1246 | # The dialog must be visible before we can grab the input 1247 | dialog.wait_visibility() 1248 | dialog.grab_set() 1249 | 1250 | dialog.bind("", ok) 1251 | dialog.bind("", ok) 1252 | dialog.bind("", cancel) 1253 | 1254 | # Wait for the user to be done with the dialog 1255 | parent.wait_window(dialog) 1256 | 1257 | # Regrab the input in the parent 1258 | parent.grab_set() 1259 | 1260 | return _entry_res 1261 | 1262 | 1263 | def _center_on_root(dialog): 1264 | # Centers 'dialog' on the root window. It often ends up at some bad place 1265 | # like the top-left corner of the screen otherwise. See the menuconfig() 1266 | # function, which has similar logic. 1267 | 1268 | dialog.withdraw() 1269 | _root.update_idletasks() 1270 | 1271 | dialog_width = dialog.winfo_reqwidth() 1272 | dialog_height = dialog.winfo_reqheight() 1273 | 1274 | screen_width = _root.winfo_screenwidth() 1275 | screen_height = _root.winfo_screenheight() 1276 | 1277 | x = _root.winfo_rootx() + (_root.winfo_width() - dialog_width)//2 1278 | y = _root.winfo_rooty() + (_root.winfo_height() - dialog_height)//2 1279 | 1280 | # Clamp so that no part of the dialog is outside the screen 1281 | if x + dialog_width > screen_width: 1282 | x = screen_width - dialog_width 1283 | elif x < 0: 1284 | x = 0 1285 | if y + dialog_height > screen_height: 1286 | y = screen_height - dialog_height 1287 | elif y < 0: 1288 | y = 0 1289 | 1290 | dialog.geometry("+{}+{}".format(x, y)) 1291 | 1292 | dialog.deiconify() 1293 | 1294 | 1295 | def _check_valid(dialog, entry, sym, s): 1296 | # Returns True if the string 's' is a well-formed value for 'sym'. 1297 | # Otherwise, pops up an error and returns False. 1298 | 1299 | if sym.type not in (INT, HEX): 1300 | # Anything goes for non-int/hex symbols 1301 | return True 1302 | 1303 | base = 10 if sym.type == INT else 16 1304 | try: 1305 | int(s, base) 1306 | except ValueError: 1307 | messagebox.showerror( 1308 | "Bad value", 1309 | "'{}' is a malformed {} value".format( 1310 | s, TYPE_TO_STR[sym.type]), 1311 | parent=dialog) 1312 | entry.focus_set() 1313 | return False 1314 | 1315 | for low_sym, high_sym, cond in sym.ranges: 1316 | if expr_value(cond): 1317 | low_s = low_sym.str_value 1318 | high_s = high_sym.str_value 1319 | 1320 | if not int(low_s, base) <= int(s, base) <= int(high_s, base): 1321 | messagebox.showerror( 1322 | "Value out of range", 1323 | "{} is outside the range {}-{}".format(s, low_s, high_s), 1324 | parent=dialog) 1325 | entry.focus_set() 1326 | return False 1327 | 1328 | break 1329 | 1330 | return True 1331 | 1332 | 1333 | def _range_info(sym): 1334 | # Returns a string with information about the valid range for the symbol 1335 | # 'sym', or None if 'sym' doesn't have a range 1336 | 1337 | if sym.type in (INT, HEX): 1338 | for low, high, cond in sym.ranges: 1339 | if expr_value(cond): 1340 | return "Range: {}-{}".format(low.str_value, high.str_value) 1341 | 1342 | return None 1343 | 1344 | 1345 | def _save(_=None): 1346 | # Tries to save the configuration 1347 | 1348 | if _try_save(_kconf.write_config, _conf_filename, "configuration"): 1349 | _set_conf_changed(False) 1350 | 1351 | _tree.focus_set() 1352 | 1353 | 1354 | def _save_as(): 1355 | # Pops up a dialog for saving the configuration to a specific location 1356 | 1357 | global _conf_filename 1358 | 1359 | filename = _conf_filename 1360 | while True: 1361 | filename = filedialog.asksaveasfilename( 1362 | title="Save configuration as", 1363 | initialdir=os.path.dirname(filename), 1364 | initialfile=os.path.basename(filename), 1365 | parent=_root) 1366 | 1367 | if not filename: 1368 | break 1369 | 1370 | if _try_save(_kconf.write_config, filename, "configuration"): 1371 | _conf_filename = filename 1372 | break 1373 | 1374 | _tree.focus_set() 1375 | 1376 | 1377 | def _save_minimal(): 1378 | # Pops up a dialog for saving a minimal configuration (defconfig) to a 1379 | # specific location 1380 | 1381 | global _minconf_filename 1382 | 1383 | filename = _minconf_filename 1384 | while True: 1385 | filename = filedialog.asksaveasfilename( 1386 | title="Save minimal configuration as", 1387 | initialdir=os.path.dirname(filename), 1388 | initialfile=os.path.basename(filename), 1389 | parent=_root) 1390 | 1391 | if not filename: 1392 | break 1393 | 1394 | if _try_save(_kconf.write_min_config, filename, 1395 | "minimal configuration"): 1396 | 1397 | _minconf_filename = filename 1398 | break 1399 | 1400 | _tree.focus_set() 1401 | 1402 | 1403 | def _open(_=None): 1404 | # Pops up a dialog for loading a configuration 1405 | 1406 | global _conf_filename 1407 | 1408 | if _conf_changed and \ 1409 | not messagebox.askokcancel( 1410 | "Unsaved changes", 1411 | "You have unsaved changes. Load new configuration anyway?"): 1412 | 1413 | return 1414 | 1415 | filename = _conf_filename 1416 | while True: 1417 | filename = filedialog.askopenfilename( 1418 | title="Open configuration", 1419 | initialdir=os.path.dirname(filename), 1420 | initialfile=os.path.basename(filename), 1421 | parent=_root) 1422 | 1423 | if not filename: 1424 | break 1425 | 1426 | if _try_load(filename): 1427 | # Maybe something fancier could be done here later to try to 1428 | # preserve the scroll 1429 | 1430 | _conf_filename = filename 1431 | _set_conf_changed(_needs_save()) 1432 | 1433 | if _single_menu and not _shown_menu_nodes(_cur_menu): 1434 | # Turn on show-all if we're in single-menu mode and would end 1435 | # up with an empty menu 1436 | _show_all_var.set(True) 1437 | 1438 | _update_tree() 1439 | 1440 | break 1441 | 1442 | _tree.focus_set() 1443 | 1444 | 1445 | def _toggle_showname(_): 1446 | # Toggles show-name mode on/off 1447 | 1448 | _show_name_var.set(not _show_name_var.get()) 1449 | _do_showname() 1450 | 1451 | 1452 | def _do_showname(): 1453 | # Updates the UI for the current show-name setting 1454 | 1455 | # Columns do not automatically shrink/expand, so we have to update 1456 | # column widths ourselves 1457 | 1458 | tree_width = _tree.winfo_width() 1459 | 1460 | if _show_name_var.get(): 1461 | _tree["displaycolumns"] = ("name",) 1462 | _tree["show"] = "tree headings" 1463 | name_width = tree_width//3 1464 | _tree.column("#0", width=max(tree_width - name_width, 1)) 1465 | _tree.column("name", width=name_width) 1466 | else: 1467 | _tree["displaycolumns"] = () 1468 | _tree["show"] = "tree" 1469 | _tree.column("#0", width=tree_width) 1470 | 1471 | _tree.focus_set() 1472 | 1473 | 1474 | def _toggle_showall(_): 1475 | # Toggles show-all mode on/off 1476 | 1477 | _show_all_var.set(not _show_all) 1478 | _do_showall() 1479 | 1480 | 1481 | def _do_showall(): 1482 | # Updates the UI for the current show-all setting 1483 | 1484 | # Don't allow turning off show-all if we'd end up with no visible nodes 1485 | if _nothing_shown(): 1486 | _show_all_var.set(True) 1487 | return 1488 | 1489 | # Save scroll information. old_scroll can end up negative here, if the 1490 | # reference item isn't shown (only invisible items on the screen, and 1491 | # show-all being turned off). 1492 | 1493 | stayput = _vis_loc_ref_item() 1494 | # Probe the middle of the first row, to play it safe. identify_row(0) seems 1495 | # to return the row before the top row. 1496 | old_scroll = _item_row(stayput) - \ 1497 | _item_row(_tree.identify_row(_treeview_rowheight//2)) 1498 | 1499 | _update_tree() 1500 | 1501 | if _show_all: 1502 | # Deep magic: Unless we call update_idletasks(), the scroll adjustment 1503 | # below is restricted to the height of the old tree, instead of the 1504 | # height of the new tree. Since the tree with show-all on is guaranteed 1505 | # to be taller, and we want the maximum range, we only call it when 1506 | # turning show-all on. 1507 | # 1508 | # Strictly speaking, something similar ought to be done when changing 1509 | # symbol values, but it causes annoying flicker, and in 99% of cases 1510 | # things work anyway there (with usually minor scroll mess-ups in the 1511 | # 1% case). 1512 | _root.update_idletasks() 1513 | 1514 | # Restore scroll 1515 | _tree.yview(_item_row(stayput) - old_scroll) 1516 | 1517 | _tree.focus_set() 1518 | 1519 | 1520 | def _nothing_shown(): 1521 | # _do_showall() helper. Returns True if no nodes would get 1522 | # shown with the current show-all setting. Also handles the 1523 | # (obscure) case when there are no visible nodes in the entire 1524 | # tree, meaning guiconfig was automatically started in 1525 | # show-all mode, which mustn't be turned off. 1526 | 1527 | return not _shown_menu_nodes( 1528 | _cur_menu if _single_menu else _kconf.top_node) 1529 | 1530 | 1531 | def _toggle_tree_mode(_): 1532 | # Toggles single-menu mode on/off 1533 | 1534 | _single_menu_var.set(not _single_menu) 1535 | _do_tree_mode() 1536 | 1537 | 1538 | def _do_tree_mode(): 1539 | # Updates the UI for the current tree mode (full-tree or single-menu) 1540 | 1541 | loc_ref_node = _id_to_node[_loc_ref_item()] 1542 | 1543 | if not _single_menu: 1544 | # _jump_to() -> _enter_menu() already updates the tree, but 1545 | # _jump_to() -> load_parents() doesn't, because it isn't always needed. 1546 | # We always need to update the tree here, e.g. to add/remove "--->". 1547 | _update_tree() 1548 | 1549 | _jump_to(loc_ref_node) 1550 | _tree.focus_set() 1551 | 1552 | 1553 | def _enter_menu_and_select_first(menu): 1554 | # Enters the menu 'menu' and selects the first item. Used in single-menu 1555 | # mode. 1556 | 1557 | _enter_menu(menu) 1558 | _select(_tree, _tree.get_children()[0]) 1559 | 1560 | 1561 | def _enter_menu(menu): 1562 | # Enters the menu 'menu'. Used in single-menu mode. 1563 | 1564 | global _cur_menu 1565 | 1566 | _cur_menu = menu 1567 | _update_tree() 1568 | 1569 | _backbutton["state"] = "disabled" if menu is _kconf.top_node else "normal" 1570 | 1571 | 1572 | def _leave_menu(): 1573 | # Leaves the current menu. Used in single-menu mode. 1574 | 1575 | global _cur_menu 1576 | 1577 | if _cur_menu is not _kconf.top_node: 1578 | old_menu = _cur_menu 1579 | 1580 | _cur_menu = _parent_menu(_cur_menu) 1581 | _update_tree() 1582 | 1583 | _select(_tree, id(old_menu)) 1584 | 1585 | if _cur_menu is _kconf.top_node: 1586 | _backbutton["state"] = "disabled" 1587 | 1588 | _tree.focus_set() 1589 | 1590 | 1591 | def _select(tree, item): 1592 | # Selects, focuses, and see()s 'item' in 'tree' 1593 | 1594 | tree.selection_set(item) 1595 | tree.focus(item) 1596 | tree.see(item) 1597 | 1598 | 1599 | def _loc_ref_item(): 1600 | # Returns a Treeview item that can serve as a reference for the current 1601 | # scroll location. We try to make this item stay on the same row on the 1602 | # screen when updating the tree. 1603 | 1604 | # If the selected item is visible, use that 1605 | sel = _tree.selection() 1606 | if sel and _tree.bbox(sel[0]): 1607 | return sel[0] 1608 | 1609 | # Otherwise, use the middle item on the screen. If it doesn't exist, the 1610 | # tree is probably really small, so use the first item in the entire tree. 1611 | return _tree.identify_row(_tree.winfo_height()//2) or \ 1612 | _tree.get_children()[0] 1613 | 1614 | 1615 | def _vis_loc_ref_item(): 1616 | # Like _loc_ref_item(), but finds a visible item around the reference item. 1617 | # Used when changing show-all mode, where non-visible (red) items will 1618 | # disappear. 1619 | 1620 | item = _loc_ref_item() 1621 | 1622 | vis_before = _vis_before(item) 1623 | if vis_before and _tree.bbox(vis_before): 1624 | return vis_before 1625 | 1626 | vis_after = _vis_after(item) 1627 | if vis_after and _tree.bbox(vis_after): 1628 | return vis_after 1629 | 1630 | return vis_before or vis_after 1631 | 1632 | 1633 | def _vis_before(item): 1634 | # _vis_loc_ref_item() helper. Returns the first visible (not red) item, 1635 | # searching backwards from 'item'. 1636 | 1637 | while item: 1638 | if not _tree.tag_has("invisible", item): 1639 | return item 1640 | 1641 | prev = _tree.prev(item) 1642 | item = prev if prev else _tree.parent(item) 1643 | 1644 | return None 1645 | 1646 | 1647 | def _vis_after(item): 1648 | # _vis_loc_ref_item() helper. Returns the first visible (not red) item, 1649 | # searching forwards from 'item'. 1650 | 1651 | while item: 1652 | if not _tree.tag_has("invisible", item): 1653 | return item 1654 | 1655 | next = _tree.next(item) 1656 | if next: 1657 | item = next 1658 | else: 1659 | item = _tree.parent(item) 1660 | if not item: 1661 | break 1662 | item = _tree.next(item) 1663 | 1664 | return None 1665 | 1666 | 1667 | def _on_quit(_=None): 1668 | # Called when the user wants to exit 1669 | 1670 | if not _conf_changed: 1671 | _quit("No changes to save (for '{}')".format(_conf_filename)) 1672 | return 1673 | 1674 | while True: 1675 | ync = messagebox.askyesnocancel("Quit", "Save changes?") 1676 | if ync is None: 1677 | return 1678 | 1679 | if not ync: 1680 | _quit("Configuration ({}) was not saved".format(_conf_filename)) 1681 | return 1682 | 1683 | if _try_save(_kconf.write_config, _conf_filename, "configuration"): 1684 | # _try_save() already prints the "Configuration saved to ..." 1685 | # message 1686 | _quit() 1687 | return 1688 | 1689 | 1690 | def _quit(msg=None): 1691 | # Quits the application 1692 | 1693 | # Do not call sys.exit() here, in case we're being run from a script 1694 | _root.destroy() 1695 | if msg: 1696 | print(msg) 1697 | 1698 | 1699 | def _try_save(save_fn, filename, description): 1700 | # Tries to save a configuration file. Pops up an error and returns False on 1701 | # failure. 1702 | # 1703 | # save_fn: 1704 | # Function to call with 'filename' to save the file 1705 | # 1706 | # description: 1707 | # String describing the thing being saved 1708 | 1709 | try: 1710 | # save_fn() returns a message to print 1711 | msg = save_fn(filename) 1712 | _set_status(msg) 1713 | print(msg) 1714 | return True 1715 | except EnvironmentError as e: 1716 | messagebox.showerror( 1717 | "Error saving " + description, 1718 | "Error saving {} to '{}': {} (errno: {})" 1719 | .format(description, e.filename, e.strerror, 1720 | errno.errorcode[e.errno])) 1721 | return False 1722 | 1723 | 1724 | def _try_load(filename): 1725 | # Tries to load a configuration file. Pops up an error and returns False on 1726 | # failure. 1727 | # 1728 | # filename: 1729 | # Configuration file to load 1730 | 1731 | try: 1732 | msg = _kconf.load_config(filename) 1733 | _set_status(msg) 1734 | print(msg) 1735 | return True 1736 | except EnvironmentError as e: 1737 | messagebox.showerror( 1738 | "Error loading configuration", 1739 | "Error loading '{}': {} (errno: {})" 1740 | .format(filename, e.strerror, errno.errorcode[e.errno])) 1741 | return False 1742 | 1743 | 1744 | def _jump_to_dialog(_=None): 1745 | # Pops up a dialog for jumping directly to a particular node. Symbol values 1746 | # can also be changed within the dialog. 1747 | # 1748 | # Note: There's nothing preventing this from doing an incremental search 1749 | # like menuconfig.py does, but currently it's a bit jerky for large Kconfig 1750 | # trees, at least when inputting the beginning of the search string. We'd 1751 | # need to somehow only update the tree items that are shown in the Treeview 1752 | # to fix it. 1753 | 1754 | global _jump_to_tree 1755 | 1756 | def search(_=None): 1757 | _update_jump_to_matches(msglabel, entry.get()) 1758 | 1759 | def jump_to_selected(event=None): 1760 | # Jumps to the selected node and closes the dialog 1761 | 1762 | # Ignore double clicks on the image and in the heading area 1763 | if event and (tree.identify_element(event.x, event.y) == "image" or 1764 | _in_heading(event)): 1765 | return 1766 | 1767 | sel = tree.selection() 1768 | if not sel: 1769 | return 1770 | 1771 | node = _id_to_node[sel[0]] 1772 | 1773 | if node not in _shown_menu_nodes(_parent_menu(node)): 1774 | _show_all_var.set(True) 1775 | if not _single_menu: 1776 | # See comment in _do_tree_mode() 1777 | _update_tree() 1778 | 1779 | _jump_to(node) 1780 | 1781 | dialog.destroy() 1782 | 1783 | def tree_select(_): 1784 | jumpto_button["state"] = "normal" if tree.selection() else "disabled" 1785 | 1786 | 1787 | dialog = Toplevel(_root) 1788 | dialog.geometry("+{}+{}".format( 1789 | _root.winfo_rootx() + 50, _root.winfo_rooty() + 50)) 1790 | dialog.title("Jump to symbol/choice/menu/comment") 1791 | dialog.minsize(128, 128) # See _create_ui() 1792 | dialog.transient(_root) 1793 | 1794 | ttk.Label(dialog, text=_JUMP_TO_HELP) \ 1795 | .grid(column=0, row=0, columnspan=2, sticky="w", padx=".1c", 1796 | pady=".1c") 1797 | 1798 | entry = ttk.Entry(dialog) 1799 | entry.grid(column=0, row=1, sticky="ew", padx=".1c", pady=".1c") 1800 | entry.focus_set() 1801 | 1802 | entry.bind("", search) 1803 | entry.bind("", search) 1804 | 1805 | ttk.Button(dialog, text="Search", command=search) \ 1806 | .grid(column=1, row=1, padx="0 .1c", pady="0 .1c") 1807 | 1808 | msglabel = ttk.Label(dialog) 1809 | msglabel.grid(column=0, row=2, sticky="w", pady="0 .1c") 1810 | 1811 | panedwindow, tree = _create_kconfig_tree_and_desc(dialog) 1812 | panedwindow.grid(column=0, row=3, columnspan=2, sticky="nsew") 1813 | 1814 | # Clear tree 1815 | tree.set_children("") 1816 | 1817 | _jump_to_tree = tree 1818 | 1819 | jumpto_button = ttk.Button(dialog, text="Jump to selected item", 1820 | state="disabled", command=jump_to_selected) 1821 | jumpto_button.grid(column=0, row=4, columnspan=2, sticky="ns", pady=".1c") 1822 | 1823 | dialog.columnconfigure(0, weight=1) 1824 | # Only the pane with the Kconfig tree and description grows vertically 1825 | dialog.rowconfigure(3, weight=1) 1826 | 1827 | # See the menuconfig() function 1828 | _root.update_idletasks() 1829 | dialog.geometry(dialog.geometry()) 1830 | 1831 | # The dialog must be visible before we can grab the input 1832 | dialog.wait_visibility() 1833 | dialog.grab_set() 1834 | 1835 | tree.bind("", jump_to_selected) 1836 | tree.bind("", jump_to_selected) 1837 | tree.bind("", jump_to_selected) 1838 | # add=True to avoid overriding the description text update 1839 | tree.bind("<>", tree_select, add=True) 1840 | 1841 | dialog.bind("", lambda _: dialog.destroy()) 1842 | 1843 | # Wait for the user to be done with the dialog 1844 | _root.wait_window(dialog) 1845 | 1846 | _jump_to_tree = None 1847 | 1848 | _tree.focus_set() 1849 | 1850 | 1851 | def _update_jump_to_matches(msglabel, search_string): 1852 | # Searches for nodes matching the search string and updates 1853 | # _jump_to_matches. Puts a message in 'msglabel' if there are no matches, 1854 | # or regex errors. 1855 | 1856 | global _jump_to_matches 1857 | 1858 | _jump_to_tree.selection_set(()) 1859 | 1860 | try: 1861 | # We could use re.IGNORECASE here instead of lower(), but this is 1862 | # faster for regexes like '.*debug$' (though the '.*' is redundant 1863 | # there). Those probably have bad interactions with re.search(), which 1864 | # matches anywhere in the string. 1865 | regex_searches = [re.compile(regex).search 1866 | for regex in search_string.lower().split()] 1867 | except re.error as e: 1868 | msg = "Bad regular expression" 1869 | # re.error.msg was added in Python 3.5 1870 | if hasattr(e, "msg"): 1871 | msg += ": " + e.msg 1872 | msglabel["text"] = msg 1873 | # Clear tree 1874 | _jump_to_tree.set_children("") 1875 | return 1876 | 1877 | _jump_to_matches = [] 1878 | add_match = _jump_to_matches.append 1879 | 1880 | for node in _sorted_sc_nodes(): 1881 | # Symbol/choice 1882 | sc = node.item 1883 | 1884 | for search in regex_searches: 1885 | # Both the name and the prompt might be missing, since 1886 | # we're searching both symbols and choices 1887 | 1888 | # Does the regex match either the symbol name or the 1889 | # prompt (if any)? 1890 | if not (sc.name and search(sc.name.lower()) or 1891 | node.prompt and search(node.prompt[0].lower())): 1892 | 1893 | # Give up on the first regex that doesn't match, to 1894 | # speed things up a bit when multiple regexes are 1895 | # entered 1896 | break 1897 | 1898 | else: 1899 | add_match(node) 1900 | 1901 | # Search menus and comments 1902 | 1903 | for node in _sorted_menu_comment_nodes(): 1904 | for search in regex_searches: 1905 | if not search(node.prompt[0].lower()): 1906 | break 1907 | else: 1908 | add_match(node) 1909 | 1910 | msglabel["text"] = "" if _jump_to_matches else "No matches" 1911 | 1912 | _update_jump_to_display() 1913 | 1914 | if _jump_to_matches: 1915 | item = id(_jump_to_matches[0]) 1916 | _jump_to_tree.selection_set(item) 1917 | _jump_to_tree.focus(item) 1918 | 1919 | 1920 | def _update_jump_to_display(): 1921 | # Updates the images and text for the items in _jump_to_matches, and sets 1922 | # them as the items of _jump_to_tree 1923 | 1924 | # Micro-optimize a bit 1925 | item = _jump_to_tree.item 1926 | id_ = id 1927 | node_str = _node_str 1928 | img_tag = _img_tag 1929 | visible = _visible 1930 | for node in _jump_to_matches: 1931 | item(id_(node), 1932 | text=node_str(node), 1933 | tags=img_tag(node) if visible(node) else 1934 | img_tag(node) + " invisible") 1935 | 1936 | _jump_to_tree.set_children("", *map(id, _jump_to_matches)) 1937 | 1938 | 1939 | def _jump_to(node): 1940 | # Jumps directly to 'node' and selects it 1941 | 1942 | if _single_menu: 1943 | _enter_menu(_parent_menu(node)) 1944 | else: 1945 | _load_parents(node) 1946 | 1947 | _select(_tree, id(node)) 1948 | 1949 | 1950 | # Obscure Python: We never pass a value for cached_nodes, and it keeps pointing 1951 | # to the same list. This avoids a global. 1952 | def _sorted_sc_nodes(cached_nodes=[]): 1953 | # Returns a sorted list of symbol and choice nodes to search. The symbol 1954 | # nodes appear first, sorted by name, and then the choice nodes, sorted by 1955 | # prompt and (secondarily) name. 1956 | 1957 | if not cached_nodes: 1958 | # Add symbol nodes 1959 | for sym in sorted(_kconf.unique_defined_syms, 1960 | key=lambda sym: sym.name): 1961 | # += is in-place for lists 1962 | cached_nodes += sym.nodes 1963 | 1964 | # Add choice nodes 1965 | 1966 | choices = sorted(_kconf.unique_choices, 1967 | key=lambda choice: choice.name or "") 1968 | 1969 | cached_nodes += sorted( 1970 | [node for choice in choices for node in choice.nodes], 1971 | key=lambda node: node.prompt[0] if node.prompt else "") 1972 | 1973 | return cached_nodes 1974 | 1975 | 1976 | def _sorted_menu_comment_nodes(cached_nodes=[]): 1977 | # Returns a list of menu and comment nodes to search, sorted by prompt, 1978 | # with the menus first 1979 | 1980 | if not cached_nodes: 1981 | def prompt_text(mc): 1982 | return mc.prompt[0] 1983 | 1984 | cached_nodes += sorted(_kconf.menus, key=prompt_text) 1985 | cached_nodes += sorted(_kconf.comments, key=prompt_text) 1986 | 1987 | return cached_nodes 1988 | 1989 | 1990 | def _load_parents(node): 1991 | # Menus are lazily populated as they're opened in full-tree mode, but 1992 | # jumping to an item needs its parent menus to be populated. This function 1993 | # populates 'node's parents. 1994 | 1995 | # Get all parents leading up to 'node', sorted with the root first 1996 | parents = [] 1997 | cur = node.parent 1998 | while cur is not _kconf.top_node: 1999 | parents.append(cur) 2000 | cur = cur.parent 2001 | parents.reverse() 2002 | 2003 | for i, parent in enumerate(parents): 2004 | if not _tree.item(id(parent), "open"): 2005 | # Found a closed menu. Populate it and all the remaining menus 2006 | # leading up to 'node'. 2007 | for parent in parents[i:]: 2008 | # We only need to populate "real" menus/choices. Implicit menus 2009 | # are populated when their parents menus are entered. 2010 | if not isinstance(parent.item, Symbol): 2011 | _build_full_tree(parent) 2012 | return 2013 | 2014 | 2015 | def _parent_menu(node): 2016 | # Returns the menu node of the menu that contains 'node'. In addition to 2017 | # proper 'menu's, this might also be a 'menuconfig' symbol or a 'choice'. 2018 | # "Menu" here means a menu in the interface. 2019 | 2020 | menu = node.parent 2021 | while not menu.is_menuconfig: 2022 | menu = menu.parent 2023 | return menu 2024 | 2025 | 2026 | def _trace_write(var, fn): 2027 | # Makes fn() be called whenever the Tkinter Variable 'var' changes value 2028 | 2029 | # trace_variable() is deprecated according to the docstring, 2030 | # which recommends trace_add() 2031 | if hasattr(var, "trace_add"): 2032 | var.trace_add("write", fn) 2033 | else: 2034 | var.trace_variable("w", fn) 2035 | 2036 | 2037 | def _info_str(node): 2038 | # Returns information about the menu node 'node' as a string. 2039 | # 2040 | # The helper functions are responsible for adding newlines. This allows 2041 | # them to return "" if they don't want to add any output. 2042 | 2043 | if isinstance(node.item, Symbol): 2044 | sym = node.item 2045 | 2046 | return ( 2047 | _name_info(sym) + 2048 | _help_info(sym) + 2049 | _direct_dep_info(sym) + 2050 | _defaults_info(sym) + 2051 | _select_imply_info(sym) + 2052 | _kconfig_def_info(sym) 2053 | ) 2054 | 2055 | if isinstance(node.item, Choice): 2056 | choice = node.item 2057 | 2058 | return ( 2059 | _name_info(choice) + 2060 | _help_info(choice) + 2061 | 'Mode: {}\n\n'.format(choice.str_value) + 2062 | _choice_syms_info(choice) + 2063 | _direct_dep_info(choice) + 2064 | _defaults_info(choice) + 2065 | _kconfig_def_info(choice) 2066 | ) 2067 | 2068 | # node.item in (MENU, COMMENT) 2069 | return _kconfig_def_info(node) 2070 | 2071 | 2072 | def _name_info(sc): 2073 | # Returns a string with the name of the symbol/choice. Choices are shown as 2074 | # . 2075 | 2076 | return (sc.name if sc.name else standard_sc_expr_str(sc)) + "\n\n" 2077 | 2078 | 2079 | def _value_info(sym): 2080 | # Returns a string showing 'sym's value 2081 | 2082 | # Only put quotes around the value for string symbols 2083 | return "Value: {}\n".format( 2084 | '"{}"'.format(sym.str_value) 2085 | if sym.orig_type == STRING 2086 | else sym.str_value) 2087 | 2088 | 2089 | def _choice_syms_info(choice): 2090 | # Returns a string listing the choice symbols in 'choice'. Adds 2091 | # "(selected)" next to the selected one. 2092 | 2093 | s = "Choice symbols:\n" 2094 | 2095 | for sym in choice.syms: 2096 | s += " - " + sym.name 2097 | if sym is choice.selection: 2098 | s += " (selected)" 2099 | s += "\n" 2100 | 2101 | return s + "\n" 2102 | 2103 | 2104 | def _help_info(sc): 2105 | # Returns a string with the help text(s) of 'sc' (Symbol or Choice). 2106 | # Symbols and choices defined in multiple locations can have multiple help 2107 | # texts. 2108 | 2109 | s = "" 2110 | 2111 | for node in sc.nodes: 2112 | if node.help is not None: 2113 | s += node.help + "\n\n" 2114 | 2115 | return s 2116 | 2117 | 2118 | def _direct_dep_info(sc): 2119 | # Returns a string describing the direct dependencies of 'sc' (Symbol or 2120 | # Choice). The direct dependencies are the OR of the dependencies from each 2121 | # definition location. The dependencies at each definition location come 2122 | # from 'depends on' and dependencies inherited from parent items. 2123 | 2124 | return "" if sc.direct_dep is _kconf.y else \ 2125 | 'Direct dependencies (={}):\n{}\n' \ 2126 | .format(TRI_TO_STR[expr_value(sc.direct_dep)], 2127 | _split_expr_info(sc.direct_dep, 2)) 2128 | 2129 | 2130 | def _defaults_info(sc): 2131 | # Returns a string describing the defaults of 'sc' (Symbol or Choice) 2132 | 2133 | if not sc.defaults: 2134 | return "" 2135 | 2136 | s = "Default" 2137 | if len(sc.defaults) > 1: 2138 | s += "s" 2139 | s += ":\n" 2140 | 2141 | for val, cond in sc.orig_defaults: 2142 | s += " - " 2143 | if isinstance(sc, Symbol): 2144 | s += _expr_str(val) 2145 | 2146 | # Skip the tristate value hint if the expression is just a single 2147 | # symbol. _expr_str() already shows its value as a string. 2148 | # 2149 | # This also avoids showing the tristate value for string/int/hex 2150 | # defaults, which wouldn't make any sense. 2151 | if isinstance(val, tuple): 2152 | s += ' (={})'.format(TRI_TO_STR[expr_value(val)]) 2153 | else: 2154 | # Don't print the value next to the symbol name for choice 2155 | # defaults, as it looks a bit confusing 2156 | s += val.name 2157 | s += "\n" 2158 | 2159 | if cond is not _kconf.y: 2160 | s += " Condition (={}):\n{}" \ 2161 | .format(TRI_TO_STR[expr_value(cond)], 2162 | _split_expr_info(cond, 4)) 2163 | 2164 | return s + "\n" 2165 | 2166 | 2167 | def _split_expr_info(expr, indent): 2168 | # Returns a string with 'expr' split into its top-level && or || operands, 2169 | # with one operand per line, together with the operand's value. This is 2170 | # usually enough to get something readable for long expressions. A fancier 2171 | # recursive thingy would be possible too. 2172 | # 2173 | # indent: 2174 | # Number of leading spaces to add before the split expression. 2175 | 2176 | if len(split_expr(expr, AND)) > 1: 2177 | split_op = AND 2178 | op_str = "&&" 2179 | else: 2180 | split_op = OR 2181 | op_str = "||" 2182 | 2183 | s = "" 2184 | for i, term in enumerate(split_expr(expr, split_op)): 2185 | s += "{}{} {}".format(indent*" ", 2186 | " " if i == 0 else op_str, 2187 | _expr_str(term)) 2188 | 2189 | # Don't bother showing the value hint if the expression is just a 2190 | # single symbol. _expr_str() already shows its value. 2191 | if isinstance(term, tuple): 2192 | s += " (={})".format(TRI_TO_STR[expr_value(term)]) 2193 | 2194 | s += "\n" 2195 | 2196 | return s 2197 | 2198 | 2199 | def _select_imply_info(sym): 2200 | # Returns a string with information about which symbols 'select' or 'imply' 2201 | # 'sym'. The selecting/implying symbols are grouped according to which 2202 | # value they select/imply 'sym' to (n/m/y). 2203 | 2204 | def sis(expr, val, title): 2205 | # sis = selects/implies 2206 | sis = [si for si in split_expr(expr, OR) if expr_value(si) == val] 2207 | if not sis: 2208 | return "" 2209 | 2210 | res = title 2211 | for si in sis: 2212 | res += " - {}\n".format(split_expr(si, AND)[0].name) 2213 | return res + "\n" 2214 | 2215 | s = "" 2216 | 2217 | if sym.rev_dep is not _kconf.n: 2218 | s += sis(sym.rev_dep, 2, 2219 | "Symbols currently y-selecting this symbol:\n") 2220 | s += sis(sym.rev_dep, 1, 2221 | "Symbols currently m-selecting this symbol:\n") 2222 | s += sis(sym.rev_dep, 0, 2223 | "Symbols currently n-selecting this symbol (no effect):\n") 2224 | 2225 | if sym.weak_rev_dep is not _kconf.n: 2226 | s += sis(sym.weak_rev_dep, 2, 2227 | "Symbols currently y-implying this symbol:\n") 2228 | s += sis(sym.weak_rev_dep, 1, 2229 | "Symbols currently m-implying this symbol:\n") 2230 | s += sis(sym.weak_rev_dep, 0, 2231 | "Symbols currently n-implying this symbol (no effect):\n") 2232 | 2233 | return s 2234 | 2235 | 2236 | def _kconfig_def_info(item): 2237 | # Returns a string with the definition of 'item' in Kconfig syntax, 2238 | # together with the definition location(s) and their include and menu paths 2239 | 2240 | nodes = [item] if isinstance(item, MenuNode) else item.nodes 2241 | 2242 | s = "Kconfig definition{}, with parent deps. propagated to 'depends on'\n" \ 2243 | .format("s" if len(nodes) > 1 else "") 2244 | s += (len(s) - 1)*"=" 2245 | 2246 | for node in nodes: 2247 | s += "\n\n" \ 2248 | "At {}:{}\n" \ 2249 | "{}" \ 2250 | "Menu path: {}\n\n" \ 2251 | "{}" \ 2252 | .format(node.filename, node.linenr, 2253 | _include_path_info(node), 2254 | _menu_path_info(node), 2255 | node.custom_str(_name_and_val_str)) 2256 | 2257 | return s 2258 | 2259 | 2260 | def _include_path_info(node): 2261 | if not node.include_path: 2262 | # In the top-level Kconfig file 2263 | return "" 2264 | 2265 | return "Included via {}\n".format( 2266 | " -> ".join("{}:{}".format(filename, linenr) 2267 | for filename, linenr in node.include_path)) 2268 | 2269 | 2270 | def _menu_path_info(node): 2271 | # Returns a string describing the menu path leading up to 'node' 2272 | 2273 | path = "" 2274 | 2275 | while node.parent is not _kconf.top_node: 2276 | node = node.parent 2277 | 2278 | # Promptless choices might appear among the parents. Use 2279 | # standard_sc_expr_str() for them, so that they show up as 2280 | # ''. 2281 | path = " -> " + (node.prompt[0] if node.prompt else 2282 | standard_sc_expr_str(node.item)) + path 2283 | 2284 | return "(Top)" + path 2285 | 2286 | 2287 | def _name_and_val_str(sc): 2288 | # Custom symbol/choice printer that shows symbol values after symbols 2289 | 2290 | # Show the values of non-constant (non-quoted) symbols that don't look like 2291 | # numbers. Things like 123 are actually symbol references, and only work as 2292 | # expected due to undefined symbols getting their name as their value. 2293 | # Showing the symbol value for those isn't helpful though. 2294 | if isinstance(sc, Symbol) and not sc.is_constant and not _is_num(sc.name): 2295 | if not sc.nodes: 2296 | # Undefined symbol reference 2297 | return "{}(undefined/n)".format(sc.name) 2298 | 2299 | return '{}(={})'.format(sc.name, sc.str_value) 2300 | 2301 | # For other items, use the standard format 2302 | return standard_sc_expr_str(sc) 2303 | 2304 | 2305 | def _expr_str(expr): 2306 | # Custom expression printer that shows symbol values 2307 | return expr_str(expr, _name_and_val_str) 2308 | 2309 | 2310 | def _is_num(name): 2311 | # Heuristic to see if a symbol name looks like a number, for nicer output 2312 | # when printing expressions. Things like 16 are actually symbol names, only 2313 | # they get their name as their value when the symbol is undefined. 2314 | 2315 | try: 2316 | int(name) 2317 | except ValueError: 2318 | if not name.startswith(("0x", "0X")): 2319 | return False 2320 | 2321 | try: 2322 | int(name, 16) 2323 | except ValueError: 2324 | return False 2325 | 2326 | return True 2327 | 2328 | 2329 | if __name__ == "__main__": 2330 | _main() 2331 | -------------------------------------------------------------------------------- /scripts/kconfig/hardenconfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2019 Intel Corporation 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | import csv 7 | import os 8 | 9 | from kconfiglib import standard_kconfig 10 | 11 | 12 | def hardenconfig(kconf): 13 | kconf.load_config() 14 | 15 | hardened_kconf_filename = os.path.join(os.environ['KCONFIG_BASE'], 16 | 'scripts', 'kconfig', 'hardened.csv') 17 | 18 | options = compare_with_hardened_conf(kconf, hardened_kconf_filename) 19 | 20 | display_results(options) 21 | 22 | 23 | class Option: 24 | 25 | def __init__(self, name, recommended, current=None, symbol=None): 26 | self.name = name 27 | self.recommended = recommended 28 | self.current = current 29 | self.symbol = symbol 30 | 31 | if current is None: 32 | self.result = 'NA' 33 | elif recommended == current: 34 | self.result = 'PASS' 35 | else: 36 | self.result = 'FAIL' 37 | 38 | 39 | def compare_with_hardened_conf(kconf, hardened_kconf_filename): 40 | options = [] 41 | 42 | with open(hardened_kconf_filename) as csvfile: 43 | csvreader = csv.reader(csvfile) 44 | for row in csvreader: 45 | name = row[0] 46 | recommended = row[1] 47 | try: 48 | symbol = kconf.syms[name] 49 | current = symbol.str_value 50 | except KeyError: 51 | symbol = None 52 | current = None 53 | options.append(Option(name=name, current=current, 54 | recommended=recommended, symbol=symbol)) 55 | return options 56 | 57 | 58 | def display_results(options): 59 | # header 60 | print('{:^50}|{:^13}|{:^20}'.format('name', 'current', 'recommended'), end='') 61 | print('||{:^28}\n'.format('check result'), end='') 62 | print('=' * 116) 63 | 64 | # results, only printing options that have failed for now. It simplify the readability. 65 | # TODO: add command line option to show all results 66 | for opt in options: 67 | if opt.result == 'FAIL' and opt.symbol.visibility != 0: 68 | print('CONFIG_{:<43}|{:^13}|{:^20}'.format( 69 | opt.name, opt.current, opt.recommended), end='') 70 | print('||{:^28}\n'.format(opt.result), end='') 71 | print() 72 | 73 | 74 | def main(): 75 | hardenconfig(standard_kconfig()) 76 | 77 | 78 | if __name__ == '__main__': 79 | main() 80 | -------------------------------------------------------------------------------- /scripts/kconfig/hardened.csv: -------------------------------------------------------------------------------- 1 | HW_STACK_PROTECTION,y 2 | INIT_STACKS,n 3 | KERNEL_DEBUG,n 4 | BOOT_BANNER,n 5 | BOOT_DELAY,0 6 | EXECUTION_BENCHMARKING,n 7 | THREAD_MONITOR,n 8 | THREAD_NAME,n 9 | STACK_CANARIES,y 10 | EXECUTE_XOR_WRITE,y 11 | STACK_POINTER_RANDOM,100 12 | BOUNDS_CHECK_BYPASS_MITIGATION,y 13 | PERFORMANCE_METRICS,n 14 | STATS,n 15 | DEBUG,n 16 | TRACING,n 17 | STACK_USAGE,n 18 | PRINTK,n 19 | EARLY_CONSOLE,n 20 | ASSERT,n 21 | OBJECT_TRACING,n 22 | OVERRIDE_FRAME_POINTER_DEFAULT,y 23 | DEBUG_INFO,n 24 | OPENOCD_SUPPORT,n 25 | TRACING_CPU_STATS,n 26 | TRACING_CTF,n 27 | USE_SEGGER_RTT,n 28 | LOG,n 29 | SHELL,n 30 | TEST_RANDOM_GENERATOR,n 31 | ZTEST,n 32 | TEST,n 33 | TEST_SHELL,n 34 | TEST_EXTRA_STACKSIZE,0 35 | TEST_USERSPACE,n 36 | CUSTOM_RODATA_LD,n 37 | CUSTOM_RWDATA_LD,n 38 | CUSTOM_SECTIONS_LD,n 39 | BUILD_OUTPUT_STRIPPED,y 40 | SOC_ATMEL_SAME70_DISABLE_ERASE_PIN,y 41 | SOC_ATMEL_SAME70_WAIT_MODE,n 42 | FAULT_DUMP,0 43 | EXCEPTION_DEBUG,n 44 | X86_MMU,y 45 | BUILTIN_STACK_GUARD,y 46 | MPU_STACK_GUARD,y 47 | STACK_SENTINEL,y 48 | BOOT_TIME_MEASUREMENT,n,experimental 49 | BT_A2DP,n,experimental 50 | BT_AVDTP,n,experimental 51 | BT_BREDR,n,experimental 52 | BT_H5,n,experimental, 53 | BT_HFP_HF,n,experimental 54 | BT_RFCOMM,n,experimental 55 | CAN_NET,n,experimental 56 | CONSOLE_SUBSYS,n,experimental 57 | CRYPTO,n,experimental 58 | CRYPTO_MBEDTLS_SHIM,n,experimental 59 | CRYPTO_TINYCRYPT_SHIM,n,experimental 60 | MODEM_CONTEXT,n,experimental 61 | NET_BUF_VARIABLE_DATA_SIZE,n,experimental 62 | NET_CONNECTION_MANAGER,n,experimental 63 | NET_GPTP,n,experimental 64 | NET_IPV4_AUTO,n,experimental 65 | NET_L2_CANBUS,n,experimental 66 | NET_L2_IEEE802154_SECURITY,n,experimental 67 | NET_L2_PPP,n,experimental 68 | NET_OFFLOAD,n,experimental 69 | NET_PROMISCUOUS_MODE,n,experimental 70 | NET_SOCKETS_CAN,n,experimental 71 | NET_SOCKETS_ENABLE_DTLS,n,experimental 72 | NET_SOCKETS_NET_MGMT,n,experimental 73 | NET_SOCKETS_OFFLOAD,n,experimental 74 | NET_SOCKETS_SOCKOPT_TLS,n,experimental 75 | OPENOCD_SUPPORT,n,experimental 76 | PERFORMANCE_METRICS,n,experimental 77 | SHELL_TELNET_SUPPORT_COMMAND,n,experimental 78 | SPI_SLAVE,n,experimental 79 | THREAD_MONITOR,n,experimental 80 | THREAD_NAME,n,experimental 81 | UART_ASYNC_API,n,experimental 82 | -------------------------------------------------------------------------------- /scripts/kconfig/kconfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Writes/updates the zephyr/.config configuration file by merging configuration 4 | # files passed as arguments, e.g. board *_defconfig and application prj.conf 5 | # files. 6 | # 7 | # When fragments haven't changed, zephyr/.config is both the input and the 8 | # output, which just updates it. This is handled in the CMake files. 9 | # 10 | # Also does various checks (most via Kconfiglib warnings). 11 | 12 | import argparse 13 | import os 14 | import sys 15 | import textwrap 16 | 17 | # Zephyr doesn't use tristate symbols. They're supported here just to make the 18 | # script a bit more generic. 19 | from kconfiglib import Kconfig, split_expr, expr_value, expr_str, BOOL, \ 20 | TRISTATE, TRI_TO_STR, AND 21 | 22 | 23 | def main(): 24 | args = parse_args() 25 | 26 | print("Parsing " + args.kconfig_file) 27 | kconf = Kconfig(args.kconfig_file, warn_to_stderr=False, 28 | suppress_traceback=True) 29 | 30 | if args.handwritten_input_configs: 31 | # Warn for assignments to undefined symbols, but only for handwritten 32 | # fragments, to avoid warnings-turned-errors when using an old 33 | # configuration file together with updated Kconfig files 34 | kconf.warn_assign_undef = True 35 | 36 | # prj.conf may override settings from the board configuration, so 37 | # disable warnings about symbols being assigned more than once 38 | kconf.warn_assign_override = False 39 | kconf.warn_assign_redun = False 40 | 41 | # Load configuration files 42 | print(kconf.load_config(args.configs_in[0])) 43 | for config in args.configs_in[1:]: 44 | # replace=False creates a merged configuration 45 | print(kconf.load_config(config, replace=False)) 46 | 47 | if args.handwritten_input_configs: 48 | # Check that there are no assignments to promptless symbols, which 49 | # have no effect. 50 | # 51 | # This only makes sense when loading handwritten fragments and not when 52 | # loading zephyr/.config, because zephyr/.config is configuration 53 | # output and also assigns promptless symbols. 54 | check_no_promptless_assign(kconf) 55 | 56 | # Print warnings for symbols that didn't get the assigned value. Only 57 | # do this for handwritten input too, to avoid likely unhelpful warnings 58 | # when using an old configuration and updating Kconfig files. 59 | check_assigned_sym_values(kconf) 60 | check_assigned_choice_values(kconf) 61 | 62 | # Hack: Force all symbols to be evaluated, to catch warnings generated 63 | # during evaluation. Wait till the end to write the actual output files, so 64 | # that we don't generate any output if there are warnings-turned-errors. 65 | # 66 | # Kconfiglib caches calculated symbol values internally, so this is still 67 | # fast. 68 | kconf.write_config(os.devnull) 69 | 70 | if kconf.warnings: 71 | # Put a blank line between warnings to make them easier to read 72 | for warning in kconf.warnings: 73 | print("\n" + warning, file=sys.stderr) 74 | 75 | # Turn all warnings into errors, so that e.g. assignments to undefined 76 | # Kconfig symbols become errors. 77 | # 78 | # A warning is generated by this script whenever a symbol gets a 79 | # different value than the one it was assigned. Keep that one as just a 80 | # warning for now. 81 | err("Aborting due to Kconfig warnings") 82 | 83 | # Write the merged configuration and the C header 84 | print(kconf.write_config(args.config_out)) 85 | print(kconf.write_autoconf(args.header_out)) 86 | 87 | # Write the list of parsed Kconfig files to a file 88 | write_kconfig_filenames(kconf, args.kconfig_list_out) 89 | 90 | 91 | def check_no_promptless_assign(kconf): 92 | # Checks that no promptless symbols are assigned 93 | 94 | for sym in kconf.unique_defined_syms: 95 | if sym.user_value is not None and promptless(sym): 96 | err(f"""\ 97 | {sym.name_and_loc} is assigned in a configuration file, but is not directly 98 | user-configurable (has no prompt). It gets its value indirectly from other 99 | symbols. """ + SYM_INFO_HINT.format(sym)) 100 | 101 | 102 | def check_assigned_sym_values(kconf): 103 | # Verifies that the values assigned to symbols "took" (matches the value 104 | # the symbols actually got), printing warnings otherwise. Choice symbols 105 | # are checked separately, in check_assigned_choice_values(). 106 | 107 | for sym in kconf.unique_defined_syms: 108 | if sym.choice: 109 | continue 110 | 111 | user_value = sym.user_value 112 | if user_value is None: 113 | continue 114 | 115 | # Tristate values are represented as 0, 1, 2. Having them as "n", "m", 116 | # "y" is more convenient here, so convert. 117 | if sym.type in (BOOL, TRISTATE): 118 | user_value = TRI_TO_STR[user_value] 119 | 120 | if user_value != sym.str_value: 121 | msg = f"{sym.name_and_loc} was assigned the value '{user_value}' " \ 122 | f"but got the value '{sym.str_value}'. " 123 | 124 | # List any unsatisfied 'depends on' dependencies in the warning 125 | mdeps = missing_deps(sym) 126 | if mdeps: 127 | expr_strs = [] 128 | for expr in mdeps: 129 | estr = expr_str(expr) 130 | if isinstance(expr, tuple): 131 | # Add () around dependencies that aren't plain symbols. 132 | # Gives '(FOO || BAR) (=n)' instead of 133 | # 'FOO || BAR (=n)', which might be clearer. 134 | estr = f"({estr})" 135 | expr_strs.append(f"{estr} (={TRI_TO_STR[expr_value(expr)]})") 136 | 137 | msg += "Check these unsatisfied dependencies: " + \ 138 | ", ".join(expr_strs) + ". " 139 | 140 | warn(msg + SYM_INFO_HINT.format(sym)) 141 | 142 | 143 | def missing_deps(sym): 144 | # check_assigned_sym_values() helper for finding unsatisfied dependencies. 145 | # 146 | # Given direct dependencies 147 | # 148 | # depends on && && ... && 149 | # 150 | # on 'sym' (which can also come from e.g. a surrounding 'if'), returns a 151 | # list of all s with a value less than the value 'sym' was assigned 152 | # ("less" instead of "not equal" just to be general and handle tristates, 153 | # even though Zephyr doesn't use them). 154 | # 155 | # For string/int/hex symbols, just looks for = n. 156 | # 157 | # Note that s can be something more complicated than just a symbol, 158 | # like 'FOO || BAR' or 'FOO = "string"'. 159 | 160 | deps = split_expr(sym.direct_dep, AND) 161 | 162 | if sym.type in (BOOL, TRISTATE): 163 | return [dep for dep in deps if expr_value(dep) < sym.user_value] 164 | # string/int/hex 165 | return [dep for dep in deps if expr_value(dep) == 0] 166 | 167 | 168 | def check_assigned_choice_values(kconf): 169 | # Verifies that any choice symbols that were selected (by setting them to 170 | # y) ended up as the selection, printing warnings otherwise. 171 | # 172 | # We check choice symbols separately to avoid warnings when two different 173 | # choice symbols within the same choice are set to y. This might happen if 174 | # a choice selection from a board defconfig is overridden in a prj.conf, for 175 | # example. The last choice symbol set to y becomes the selection (and all 176 | # other choice symbols get the value n). 177 | # 178 | # Without special-casing choices, we'd detect that the first symbol set to 179 | # y ended up as n, and print a spurious warning. 180 | 181 | for choice in kconf.unique_choices: 182 | if choice.user_selection and \ 183 | choice.user_selection is not choice.selection: 184 | 185 | warn(f"""\ 186 | The choice symbol {choice.user_selection.name_and_loc} was selected (set =y), 187 | but {choice.selection.name_and_loc if choice.selection else "no symbol"} ended 188 | up as the choice selection. """ + SYM_INFO_HINT.format(choice.user_selection)) 189 | 190 | 191 | # Hint on where to find symbol information. Used like 192 | # SYM_INFO_HINT.format(sym). 193 | SYM_INFO_HINT = """\ 194 | See http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_{0.name}.html 195 | and/or look up {0.name} in the menuconfig/guiconfig interface. The Application 196 | Development Primer, Setting Configuration Values, and Kconfig - Tips and Best 197 | Practices sections of the manual might be helpful too.\ 198 | """ 199 | 200 | 201 | def promptless(sym): 202 | # Returns True if 'sym' has no prompt. Since the symbol might be defined in 203 | # multiple locations, we need to check all locations. 204 | 205 | return not any(node.prompt for node in sym.nodes) 206 | 207 | 208 | def write_kconfig_filenames(kconf, kconfig_list_path): 209 | # Writes a sorted list with the absolute paths of all parsed Kconfig files 210 | # to 'kconfig_list_path'. The paths are realpath()'d, and duplicates are 211 | # removed. This file is used by CMake to look for changed Kconfig files. It 212 | # needs to be deterministic. 213 | 214 | with open(kconfig_list_path, 'w') as out: 215 | for path in sorted({os.path.realpath(os.path.join(kconf.srctree, path)) 216 | for path in kconf.kconfig_filenames}): 217 | print(path, file=out) 218 | 219 | 220 | def parse_args(): 221 | parser = argparse.ArgumentParser() 222 | 223 | parser.add_argument("--handwritten-input-configs", 224 | action="store_true", 225 | help="Assume the input configuration fragments are " 226 | "handwritten fragments and do additional checks " 227 | "on them, like no promptless symbols being " 228 | "assigned") 229 | parser.add_argument("kconfig_file", 230 | help="Top-level Kconfig file") 231 | parser.add_argument("config_out", 232 | help="Output configuration file") 233 | parser.add_argument("header_out", 234 | help="Output header file") 235 | parser.add_argument("kconfig_list_out", 236 | help="Output file for list of parsed Kconfig files") 237 | parser.add_argument("configs_in", 238 | nargs="+", 239 | help="Input configuration fragments. Will be merged " 240 | "together.") 241 | 242 | return parser.parse_args() 243 | 244 | 245 | def warn(msg): 246 | # Use a large fill() width to try to avoid linebreaks in the symbol 247 | # reference link, and add some extra newlines to set the message off from 248 | # surrounding text (this usually gets printed as part of spammy CMake 249 | # output) 250 | print("\n" + textwrap.fill("warning: " + msg, 100) + "\n", file=sys.stderr) 251 | 252 | 253 | def err(msg): 254 | sys.exit("\n" + textwrap.fill("error: " + msg, 100) + "\n") 255 | 256 | 257 | if __name__ == "__main__": 258 | main() 259 | -------------------------------------------------------------------------------- /scripts/kconfig/kconfigfunctions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018-2019 Linaro 2 | # Copyright (c) 2019 Nordic Semiconductor ASA 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | import os 7 | import sys 8 | 9 | KCONFIG_BASE = os.environ.get("KCONFIG_BASE") 10 | sys.path.insert(0, os.path.join(KCONFIG_BASE, "scripts/dts")) 11 | 12 | import edtlib 13 | 14 | # Types we support 15 | # 'string', 'int', 'hex', 'bool' 16 | 17 | doc_mode = os.environ.get('KCONFIG_DOC_MODE') == "1" 18 | 19 | dt_defines = {} 20 | if not doc_mode: 21 | DTS_POST_CPP = os.environ["DTS_POST_CPP"] 22 | BINDINGS_DIRS = os.environ.get("DTS_ROOT_BINDINGS") 23 | 24 | # if a board port doesn't use DTS than these might not be set 25 | if os.path.isfile(DTS_POST_CPP) and BINDINGS_DIRS is not None: 26 | edt = edtlib.EDT(DTS_POST_CPP, BINDINGS_DIRS.split("?")) 27 | else: 28 | edt = None 29 | 30 | # The env. var DEVICETREE_CONF must be set unless we are in doc mode 31 | DEVICETREE_CONF = os.environ['DEVICETREE_CONF'] 32 | if os.path.isfile(DEVICETREE_CONF): 33 | with open(DEVICETREE_CONF, 'r', encoding='utf-8') as fd: 34 | for line in fd: 35 | if '=' in line: 36 | define, val = line.split('=', 1) 37 | dt_defines[define] = val.strip() 38 | 39 | 40 | def _warn(kconf, msg): 41 | print("{}:{}: WARNING: {}".format(kconf.filename, kconf.linenr, msg)) 42 | 43 | 44 | def _dt_units_to_scale(unit): 45 | if not unit: 46 | return 0 47 | if unit in {'k', 'K'}: 48 | return 10 49 | if unit in {'m', 'M'}: 50 | return 20 51 | if unit in {'g', 'G'}: 52 | return 30 53 | 54 | def dt_int_val(kconf, _, name, unit=None): 55 | """ 56 | This function looks up 'name' in the DTS generated "conf" style database 57 | (devicetree.conf in /zephyr/include/generated/) and if it's 58 | found it will return the value as an decimal integer. The function will 59 | divide the value based on 'unit': 60 | None No division 61 | 'k' or 'K' divide by 1024 (1 << 10) 62 | 'm' or 'M' divide by 1,048,576 (1 << 20) 63 | 'g' or 'G' divide by 1,073,741,824 (1 << 30) 64 | """ 65 | if doc_mode or name not in dt_defines: 66 | return "0" 67 | 68 | _warn(kconf, "dt_int_val is deprecated.") 69 | 70 | d = dt_defines[name] 71 | if d.startswith(('0x', '0X')): 72 | d = int(d, 16) 73 | else: 74 | d = int(d) 75 | d >>= _dt_units_to_scale(unit) 76 | 77 | return str(d) 78 | 79 | def dt_hex_val(kconf, _, name, unit=None): 80 | """ 81 | This function looks up 'name' in the DTS generated "conf" style database 82 | (devicetree.conf in /zephyr/include/generated/) and if it's 83 | found it will return the value as an hex integer. The function will divide 84 | the value based on 'unit': 85 | None No division 86 | 'k' or 'K' divide by 1024 (1 << 10) 87 | 'm' or 'M' divide by 1,048,576 (1 << 20) 88 | 'g' or 'G' divide by 1,073,741,824 (1 << 30) 89 | """ 90 | if doc_mode or name not in dt_defines: 91 | return "0x0" 92 | 93 | _warn(kconf, "dt_hex_val is deprecated.") 94 | 95 | d = dt_defines[name] 96 | if d.startswith(('0x', '0X')): 97 | d = int(d, 16) 98 | else: 99 | d = int(d) 100 | d >>= _dt_units_to_scale(unit) 101 | 102 | return hex(d) 103 | 104 | def dt_str_val(kconf, _, name): 105 | """ 106 | This function looks up 'name' in the DTS generated "conf" style database 107 | (devicetree.conf in /zephyr/include/generated/) and if it's 108 | found it will return the value as string. If it's not found we return an 109 | empty string. 110 | """ 111 | if doc_mode or name not in dt_defines: 112 | return "" 113 | 114 | _warn(kconf, "dt_str_val is deprecated.") 115 | 116 | return dt_defines[name].strip('"') 117 | 118 | 119 | def dt_chosen_label(kconf, _, chosen): 120 | """ 121 | This function takes a 'chosen' property and treats that property as a path 122 | to an EDT node. If it finds an EDT node, it will look to see if that node 123 | has a "label" property and return the value of that "label", if not we 124 | return an empty string. 125 | """ 126 | if doc_mode or edt is None: 127 | return "" 128 | 129 | node = edt.chosen_node(chosen) 130 | if not node: 131 | return "" 132 | 133 | if "label" not in node.props: 134 | return "" 135 | 136 | return node.props["label"].val 137 | 138 | 139 | def dt_chosen_enabled(kconf, _, chosen): 140 | """ 141 | This function returns "y" if /chosen contains a property named 'chosen' 142 | that points to an enabled node, and "n" otherwise 143 | """ 144 | if doc_mode or edt is None: 145 | return "n" 146 | 147 | node = edt.chosen_node(chosen) 148 | return "y" if node and node.enabled else "n" 149 | 150 | def _node_reg_addr(node, index, unit): 151 | if not node: 152 | return 0 153 | 154 | if not node.regs: 155 | return 0 156 | 157 | if int(index) >= len(node.regs): 158 | return 0 159 | 160 | return node.regs[int(index)].addr >> _dt_units_to_scale(unit) 161 | 162 | 163 | def _node_reg_size(node, index, unit): 164 | if not node: 165 | return 0 166 | 167 | if not node.regs: 168 | return 0 169 | 170 | if int(index) >= len(node.regs): 171 | return 0 172 | 173 | return node.regs[int(index)].size >> _dt_units_to_scale(unit) 174 | 175 | 176 | def _dt_chosen_reg_addr(kconf, chosen, index=0, unit=None): 177 | """ 178 | This function takes a 'chosen' property and treats that property as a path 179 | to an EDT node. If it finds an EDT node, it will look to see if that 180 | nodnode has a register at the given 'index' and return the address value of 181 | that reg, if not we return 0. 182 | 183 | The function will divide the value based on 'unit': 184 | None No division 185 | 'k' or 'K' divide by 1024 (1 << 10) 186 | 'm' or 'M' divide by 1,048,576 (1 << 20) 187 | 'g' or 'G' divide by 1,073,741,824 (1 << 30) 188 | """ 189 | if doc_mode or edt is None: 190 | return 0 191 | 192 | node = edt.chosen_node(chosen) 193 | 194 | return _node_reg_addr(node, index, unit) 195 | 196 | 197 | def _dt_chosen_reg_size(kconf, chosen, index=0, unit=None): 198 | """ 199 | This function takes a 'chosen' property and treats that property as a path 200 | to an EDT node. If it finds an EDT node, it will look to see if that node 201 | has a register at the given 'index' and return the size value of that reg, 202 | if not we return 0. 203 | 204 | The function will divide the value based on 'unit': 205 | None No division 206 | 'k' or 'K' divide by 1024 (1 << 10) 207 | 'm' or 'M' divide by 1,048,576 (1 << 20) 208 | 'g' or 'G' divide by 1,073,741,824 (1 << 30) 209 | """ 210 | if doc_mode or edt is None: 211 | return 0 212 | 213 | node = edt.chosen_node(chosen) 214 | 215 | return _node_reg_size(node, index, unit) 216 | 217 | 218 | def dt_chosen_reg(kconf, name, chosen, index=0, unit=None): 219 | """ 220 | This function just routes to the proper function and converts 221 | the result to either a string int or string hex value. 222 | """ 223 | if name == "dt_chosen_reg_size_int": 224 | return str(_dt_chosen_reg_size(kconf, chosen, index, unit)) 225 | if name == "dt_chosen_reg_size_hex": 226 | return hex(_dt_chosen_reg_size(kconf, chosen, index, unit)) 227 | if name == "dt_chosen_reg_addr_int": 228 | return str(_dt_chosen_reg_addr(kconf, chosen, index, unit)) 229 | if name == "dt_chosen_reg_addr_hex": 230 | return hex(_dt_chosen_reg_addr(kconf, chosen, index, unit)) 231 | 232 | 233 | def _dt_node_reg_addr(kconf, path, index=0, unit=None): 234 | """ 235 | This function takes a 'path' and looks for an EDT node at that path. If it 236 | finds an EDT node, it will look to see if that node has a register at the 237 | given 'index' and return the address value of that reg, if not we return 0. 238 | 239 | The function will divide the value based on 'unit': 240 | None No division 241 | 'k' or 'K' divide by 1024 (1 << 10) 242 | 'm' or 'M' divide by 1,048,576 (1 << 20) 243 | 'g' or 'G' divide by 1,073,741,824 (1 << 30) 244 | """ 245 | if doc_mode or edt is None: 246 | return 0 247 | 248 | try: 249 | node = edt.get_node(path) 250 | except edtlib.EDTError: 251 | return 0 252 | 253 | return _node_reg_addr(node, index, unit) 254 | 255 | 256 | def _dt_node_reg_size(kconf, path, index=0, unit=None): 257 | """ 258 | This function takes a 'path' and looks for an EDT node at that path. If it 259 | finds an EDT node, it will look to see if that node has a register at the 260 | given 'index' and return the size value of that reg, if not we return 0. 261 | 262 | The function will divide the value based on 'unit': 263 | None No division 264 | 'k' or 'K' divide by 1024 (1 << 10) 265 | 'm' or 'M' divide by 1,048,576 (1 << 20) 266 | 'g' or 'G' divide by 1,073,741,824 (1 << 30) 267 | """ 268 | if doc_mode or edt is None: 269 | return 0 270 | 271 | try: 272 | node = edt.get_node(path) 273 | except edtlib.EDTError: 274 | return 0 275 | 276 | return _node_reg_size(node, index, unit) 277 | 278 | 279 | def dt_node_reg(kconf, name, path, index=0, unit=None): 280 | """ 281 | This function just routes to the proper function and converts 282 | the result to either a string int or string hex value. 283 | """ 284 | if name == "dt_node_reg_size_int": 285 | return str(_dt_node_reg_size(kconf, path, index, unit)) 286 | if name == "dt_node_reg_size_hex": 287 | return hex(_dt_node_reg_size(kconf, path, index, unit)) 288 | if name == "dt_node_reg_addr_int": 289 | return str(_dt_node_reg_addr(kconf, path, index, unit)) 290 | if name == "dt_node_reg_addr_hex": 291 | return hex(_dt_node_reg_addr(kconf, path, index, unit)) 292 | 293 | 294 | def dt_node_has_bool_prop(kconf, _, path, prop): 295 | """ 296 | This function takes a 'path' and looks for an EDT node at that path. If it 297 | finds an EDT node, it will look to see if that node has a boolean property 298 | by the name of 'prop'. If the 'prop' exists it will return "y" otherwise 299 | we return "n". 300 | """ 301 | if doc_mode or edt is None: 302 | return "n" 303 | 304 | try: 305 | node = edt.get_node(path) 306 | except edtlib.EDTError: 307 | return "n" 308 | 309 | if prop not in node.props: 310 | return "n" 311 | 312 | if node.props[prop].type != "boolean": 313 | return "n" 314 | 315 | if node.props[prop].val: 316 | return "y" 317 | 318 | return "n" 319 | 320 | 321 | def dt_compat_enabled(kconf, _, compat): 322 | """ 323 | This function takes a 'compat' and returns "y" if we find an "enabled" 324 | compatible node in the EDT otherwise we return "n" 325 | """ 326 | if doc_mode or edt is None: 327 | return "n" 328 | 329 | for node in edt.nodes: 330 | if compat in node.compats and node.enabled: 331 | return "y" 332 | 333 | return "n" 334 | 335 | 336 | def shields_list_contains(kconf, _, shield): 337 | """ 338 | Return "n" if cmake environment variable 'SHIELD_AS_LIST' doesn't exist. 339 | Return "y" if 'shield' is present list obtained after 'SHIELD_AS_LIST' 340 | has been split using ";" as a separator and "n" otherwise. 341 | """ 342 | try: 343 | list = os.environ['SHIELD_AS_LIST'] 344 | except KeyError: 345 | return "n" 346 | 347 | return "y" if shield in list.split(";") else "n" 348 | 349 | 350 | functions = { 351 | "dt_int_val": (dt_int_val, 1, 2), 352 | "dt_hex_val": (dt_hex_val, 1, 2), 353 | "dt_str_val": (dt_str_val, 1, 1), 354 | "dt_compat_enabled": (dt_compat_enabled, 1, 1), 355 | "dt_chosen_label": (dt_chosen_label, 1, 1), 356 | "dt_chosen_enabled": (dt_chosen_enabled, 1, 1), 357 | "dt_chosen_reg_addr_int": (dt_chosen_reg, 1, 3), 358 | "dt_chosen_reg_addr_hex": (dt_chosen_reg, 1, 3), 359 | "dt_chosen_reg_size_int": (dt_chosen_reg, 1, 3), 360 | "dt_chosen_reg_size_hex": (dt_chosen_reg, 1, 3), 361 | "dt_node_reg_addr_int": (dt_node_reg, 1, 3), 362 | "dt_node_reg_addr_hex": (dt_node_reg, 1, 3), 363 | "dt_node_reg_size_int": (dt_node_reg, 1, 3), 364 | "dt_node_reg_size_hex": (dt_node_reg, 1, 3), 365 | "dt_node_has_bool_prop": (dt_node_has_bool_prop, 2, 2), 366 | "shields_list_contains": (shields_list_contains, 1, 1), 367 | } 368 | -------------------------------------------------------------------------------- /scripts/kconfig/lint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2019 Nordic Semiconductor ASA 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | """ 7 | Linter for the Zephyr Kconfig files. Pass --help to see 8 | available checks. By default, all checks are enabled. 9 | 10 | Some of the checks rely on heuristics and can get tripped up 11 | by things like preprocessor magic, so manual checking is 12 | still needed. 'git grep' is handy. 13 | 14 | Requires west, because the checks need to see Kconfig files 15 | and source code from modules. 16 | """ 17 | 18 | import argparse 19 | import os 20 | import re 21 | import shlex 22 | import subprocess 23 | import sys 24 | import tempfile 25 | 26 | TOP_DIR = os.path.join(os.path.dirname(__file__), "..", "..") 27 | 28 | sys.path.insert(0, os.path.join(TOP_DIR, "scripts", "kconfig")) 29 | import kconfiglib 30 | 31 | 32 | def main(): 33 | init_kconfig() 34 | 35 | args = parse_args() 36 | if args.checks: 37 | checks = args.checks 38 | else: 39 | # Run all checks if no checks were specified 40 | checks = (check_always_n, 41 | check_unused, 42 | check_pointless_menuconfigs, 43 | check_defconfig_only_definition, 44 | check_missing_config_prefix) 45 | 46 | first = True 47 | for check in checks: 48 | if not first: 49 | print() 50 | first = False 51 | check() 52 | 53 | 54 | def parse_args(): 55 | # args.checks is set to a list of check functions to run 56 | 57 | parser = argparse.ArgumentParser( 58 | formatter_class=argparse.RawTextHelpFormatter, 59 | description=__doc__) 60 | 61 | parser.add_argument( 62 | "-n", "--check-always-n", 63 | action="append_const", dest="checks", const=check_always_n, 64 | help="""\ 65 | List symbols that can never be anything but n/empty. These 66 | are detected as symbols with no prompt or defaults that 67 | aren't selected or implied. 68 | """) 69 | 70 | parser.add_argument( 71 | "-u", "--check-unused", 72 | action="append_const", dest="checks", const=check_unused, 73 | help="""\ 74 | List symbols that might be unused. 75 | 76 | Heuristic: 77 | 78 | - Isn't referenced in Kconfig 79 | - Isn't referenced as CONFIG_ outside Kconfig 80 | (besides possibly as CONFIG_=) 81 | - Isn't selecting/implying other symbols 82 | - Isn't a choice symbol 83 | 84 | C preprocessor magic can trip up this check.""") 85 | 86 | parser.add_argument( 87 | "-m", "--check-pointless-menuconfigs", 88 | action="append_const", dest="checks", const=check_pointless_menuconfigs, 89 | help="""\ 90 | List symbols defined with 'menuconfig' where the menu is 91 | empty due to the symbol not being followed by stuff that 92 | depends on it""") 93 | 94 | parser.add_argument( 95 | "-d", "--check-defconfig-only-definition", 96 | action="append_const", dest="checks", const=check_defconfig_only_definition, 97 | help="""\ 98 | List symbols that are only defined in Kconfig.defconfig 99 | files. A common base definition should probably be added 100 | somewhere for such symbols, and the type declaration ('int', 101 | 'hex', etc.) removed from Kconfig.defconfig.""") 102 | 103 | parser.add_argument( 104 | "-p", "--check-missing-config-prefix", 105 | action="append_const", dest="checks", const=check_missing_config_prefix, 106 | help="""\ 107 | Look for references like 108 | 109 | #if MACRO 110 | #if(n)def MACRO 111 | defined(MACRO) 112 | IS_ENABLED(MACRO) 113 | 114 | where MACRO is the name of a defined Kconfig symbol but 115 | doesn't have a CONFIG_ prefix. Could be a typo. 116 | 117 | Macros that are #define'd somewhere are not flagged.""") 118 | 119 | return parser.parse_args() 120 | 121 | 122 | def check_always_n(): 123 | print_header("Symbols that can't be anything but n/empty") 124 | for sym in kconf.unique_defined_syms: 125 | if not has_prompt(sym) and not is_selected_or_implied(sym) and \ 126 | not has_defaults(sym): 127 | print(name_and_locs(sym)) 128 | 129 | 130 | def check_unused(): 131 | print_header("Symbols that look unused") 132 | referenced = referenced_sym_names() 133 | for sym in kconf.unique_defined_syms: 134 | if not is_selecting_or_implying(sym) and not sym.choice and \ 135 | sym.name not in referenced: 136 | print(name_and_locs(sym)) 137 | 138 | 139 | def check_pointless_menuconfigs(): 140 | print_header("menuconfig symbols with empty menus") 141 | for node in kconf.node_iter(): 142 | if node.is_menuconfig and not node.list and \ 143 | isinstance(node.item, kconfiglib.Symbol): 144 | print("{0.item.name:40} {0.filename}:{0.linenr}".format(node)) 145 | 146 | 147 | def check_defconfig_only_definition(): 148 | print_header("Symbols only defined in Kconfig.defconfig files") 149 | for sym in kconf.unique_defined_syms: 150 | if all("defconfig" in node.filename for node in sym.nodes): 151 | print(name_and_locs(sym)) 152 | 153 | 154 | def check_missing_config_prefix(): 155 | print_header("Symbol references that might be missing a CONFIG_ prefix") 156 | 157 | # Paths to modules 158 | modpaths = run(("west", "list", "-f{abspath}")).splitlines() 159 | 160 | # Gather #define'd macros that might overlap with symbol names, so that 161 | # they don't trigger false positives 162 | defined = set() 163 | for modpath in modpaths: 164 | regex = r"#\s*define\s+([A-Z0-9_]+)\b" 165 | defines = run(("git", "grep", "--extended-regexp", regex), 166 | cwd=modpath, check=False) 167 | # Could pass --only-matching to git grep as well, but it was added 168 | # pretty recently (2018) 169 | defined.update(re.findall(regex, defines)) 170 | 171 | # Filter out symbols whose names are #define'd too. Preserve definition 172 | # order to make the output consistent. 173 | syms = [sym for sym in kconf.unique_defined_syms 174 | if sym.name not in defined] 175 | 176 | # grep for symbol references in #ifdef/defined() that are missing a CONFIG_ 177 | # prefix. Work around an "argument list too long" error from 'git grep' by 178 | # checking symbols in batches. 179 | for batch in split_list(syms, 200): 180 | # grep for '#if((n)def) ', 'defined(', and 181 | # 'IS_ENABLED(', with a missing CONFIG_ prefix 182 | regex = r"(?:#\s*if(?:n?def)\s+|\bdefined\s*\(\s*|IS_ENABLED\(\s*)(?:" + \ 183 | "|".join(sym.name for sym in batch) + r")\b" 184 | cmd = ("git", "grep", "--line-number", "-I", "--perl-regexp", regex) 185 | 186 | for modpath in modpaths: 187 | print(run(cmd, cwd=modpath, check=False), end="") 188 | 189 | 190 | def split_list(lst, batch_size): 191 | # check_missing_config_prefix() helper generator that splits a list into 192 | # equal-sized batches (possibly with a shorter batch at the end) 193 | 194 | for i in range(0, len(lst), batch_size): 195 | yield lst[i:i + batch_size] 196 | 197 | 198 | def print_header(s): 199 | print(s + "\n" + len(s)*"=") 200 | 201 | 202 | def init_kconfig(): 203 | global kconf 204 | 205 | os.environ.update( 206 | srctree=TOP_DIR, 207 | CMAKE_BINARY_DIR=modules_file_dir(), 208 | KCONFIG_DOC_MODE="1", 209 | KCONFIG_BASE=TOP_DIR, 210 | SOC_DIR="soc", 211 | ARCH_DIR="arch", 212 | BOARD_DIR="boards/*/*", 213 | ARCH="*") 214 | 215 | kconf = kconfiglib.Kconfig(suppress_traceback=True) 216 | 217 | 218 | def modules_file_dir(): 219 | # Creates Kconfig.modules in a temporary directory and returns the path to 220 | # the directory. Kconfig.modules brings in Kconfig files from modules. 221 | 222 | tmpdir = tempfile.mkdtemp() 223 | run((os.path.join("scripts", "zephyr_module.py"), 224 | "--kconfig-out", os.path.join(tmpdir, "Kconfig.modules"))) 225 | return tmpdir 226 | 227 | 228 | def referenced_sym_names(): 229 | # Returns the names of all symbols referenced inside and outside the 230 | # Kconfig files (that we can detect), without any "CONFIG_" prefix 231 | 232 | return referenced_in_kconfig() | referenced_outside_kconfig() 233 | 234 | 235 | def referenced_in_kconfig(): 236 | # Returns the names of all symbols referenced inside the Kconfig files 237 | 238 | return {ref.name 239 | for node in kconf.node_iter() 240 | for ref in node.referenced 241 | if isinstance(ref, kconfiglib.Symbol)} 242 | 243 | 244 | def referenced_outside_kconfig(): 245 | # Returns the names of all symbols referenced outside the Kconfig files 246 | 247 | regex = r"\bCONFIG_[A-Z0-9_]+\b" 248 | 249 | res = set() 250 | 251 | # 'git grep' all modules 252 | for modpath in run(("west", "list", "-f{abspath}")).splitlines(): 253 | for line in run(("git", "grep", "-h", "-I", "--extended-regexp", regex), 254 | cwd=modpath).splitlines(): 255 | # Don't record lines starting with "CONFIG_FOO=" or "# CONFIG_FOO=" 256 | # as references, so that symbols that are only assigned in .config 257 | # files are not included 258 | if re.match(r"[\s#]*CONFIG_[A-Z0-9_]+=.*", line): 259 | continue 260 | 261 | # Could pass --only-matching to git grep as well, but it was added 262 | # pretty recently (2018) 263 | for match in re.findall(regex, line): 264 | res.add(match[7:]) # Strip "CONFIG_" 265 | 266 | return res 267 | 268 | 269 | def has_prompt(sym): 270 | return any(node.prompt for node in sym.nodes) 271 | 272 | 273 | def is_selected_or_implied(sym): 274 | return sym.rev_dep is not kconf.n or sym.weak_rev_dep is not kconf.n 275 | 276 | 277 | def has_defaults(sym): 278 | return bool(sym.defaults) 279 | 280 | 281 | def is_selecting_or_implying(sym): 282 | return sym.selects or sym.implies 283 | 284 | 285 | def name_and_locs(sym): 286 | # Returns a string with the name and definition location(s) for 'sym' 287 | 288 | return "{:40} {}".format( 289 | sym.name, 290 | ", ".join("{0.filename}:{0.linenr}".format(node) for node in sym.nodes)) 291 | 292 | 293 | def run(cmd, cwd=TOP_DIR, check=True): 294 | # Runs 'cmd' with subprocess, returning the decoded stdout output. 'cwd' is 295 | # the working directory. It defaults to the top-level Zephyr directory. 296 | # Exits with an error if the command exits with a non-zero return code if 297 | # 'check' is True. 298 | 299 | cmd_s = " ".join(shlex.quote(word) for word in cmd) 300 | 301 | try: 302 | process = subprocess.Popen( 303 | cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) 304 | except OSError as e: 305 | err("Failed to run '{}': {}".format(cmd_s, e)) 306 | 307 | stdout, stderr = process.communicate() 308 | # errors="ignore" temporarily works around 309 | # https://github.com/zephyrproject-rtos/esp-idf/pull/2 310 | stdout = stdout.decode("utf-8", errors="ignore") 311 | stderr = stderr.decode("utf-8") 312 | if check and process.returncode: 313 | err("""\ 314 | '{}' exited with status {}. 315 | 316 | ===stdout=== 317 | {} 318 | ===stderr=== 319 | {}""".format(cmd_s, process.returncode, stdout, stderr)) 320 | 321 | if stderr: 322 | warn("'{}' wrote to stderr:\n{}".format(cmd_s, stderr)) 323 | 324 | return stdout 325 | 326 | 327 | def err(msg): 328 | sys.exit(executable() + "error: " + msg) 329 | 330 | 331 | def warn(msg): 332 | print(executable() + "warning: " + msg, file=sys.stderr) 333 | 334 | 335 | def executable(): 336 | cmd = sys.argv[0] # Empty string if missing 337 | return cmd + ": " if cmd else "" 338 | 339 | 340 | if __name__ == "__main__": 341 | main() 342 | --------------------------------------------------------------------------------