1#!/usr/bin/env python3 2 3# Copyright (c) 2018-2023 Nordic Semiconductor ASA and Ulf Magnusson 4# Originally modified from: 5# https://github.com/ulfalizer/Kconfiglib/blob/master/examples/merge_config.py 6 7# SPDX-License-Identifier: ISC 8 9# Writes/updates the zephyr/.config configuration file by merging configuration 10# files passed as arguments, e.g. board *_defconfig and application prj.conf 11# files. 12# 13# When fragments haven't changed, zephyr/.config is both the input and the 14# output, which just updates it. This is handled in the CMake files. 15# 16# Also does various checks (most via Kconfiglib warnings). 17 18import argparse 19import os 20import re 21import sys 22import textwrap 23 24# Zephyr doesn't use tristate symbols. They're supported here just to make the 25# script a bit more generic. 26from kconfiglib import ( 27 AND, 28 BOOL, 29 OR, 30 TRI_TO_STR, 31 TRISTATE, 32 Kconfig, 33 expr_str, 34 expr_value, 35 split_expr, 36) 37 38 39def main(): 40 args = parse_args() 41 42 if args.zephyr_base: 43 os.environ['ZEPHYR_BASE'] = args.zephyr_base 44 45 print("Parsing " + args.kconfig_file) 46 kconf = Kconfig(args.kconfig_file, warn_to_stderr=False, 47 suppress_traceback=True) 48 49 if args.handwritten_input_configs: 50 # Warn for assignments to undefined symbols, but only for handwritten 51 # fragments, to avoid warnings-turned-errors when using an old 52 # configuration file together with updated Kconfig files 53 kconf.warn_assign_undef = True 54 55 # prj.conf may override settings from the board configuration, so 56 # disable warnings about symbols being assigned more than once 57 kconf.warn_assign_override = False 58 kconf.warn_assign_redun = False 59 60 if args.forced_input_configs: 61 # Do not warn on a redundant config. 62 # The reason is that a regular .config will be followed by the forced 63 # config which under normal circumstances should be identical to the 64 # configured setting. 65 # Only if user has modified to a value that gets overruled by the forced 66 # a warning shall be issued. 67 kconf.warn_assign_redun = False 68 69 # Load files 70 print(kconf.load_config(args.configs_in[0])) 71 for config in args.configs_in[1:]: 72 # replace=False creates a merged configuration 73 print(kconf.load_config(config, replace=False)) 74 75 if args.handwritten_input_configs: 76 # Check that there are no assignments to promptless symbols, which 77 # have no effect. 78 # 79 # This only makes sense when loading handwritten fragments and not when 80 # loading zephyr/.config, because zephyr/.config is configuration 81 # output and also assigns promptless symbols. 82 check_no_promptless_assign(kconf) 83 84 # Print warnings for symbols that didn't get the assigned value. Only 85 # do this for handwritten input too, to avoid likely unhelpful warnings 86 # when using an old configuration and updating Kconfig files. 87 check_assigned_sym_values(kconf) 88 check_assigned_choice_values(kconf) 89 90 if kconf.syms.get('WARN_DEPRECATED', kconf.y).tri_value == 2: 91 check_deprecated(kconf) 92 93 if kconf.syms.get('WARN_EXPERIMENTAL', kconf.y).tri_value == 2: 94 check_experimental(kconf) 95 96 check_not_secure(kconf) 97 98 # Hack: Force all symbols to be evaluated, to catch warnings generated 99 # during evaluation. Wait till the end to write the actual output files, so 100 # that we don't generate any output if there are warnings-turned-errors. 101 # 102 # Kconfiglib caches calculated symbol values internally, so this is still 103 # fast. 104 kconf.write_config(os.devnull) 105 106 warn_only = r"warning:.*set more than once." 107 108 if kconf.warnings: 109 if args.forced_input_configs: 110 error_out = False 111 else: 112 error_out = True 113 114 # Put a blank line between warnings to make them easier to read 115 for warning in kconf.warnings: 116 print("\n" + warning, file=sys.stderr) 117 118 if not error_out and not re.search(warn_only, warning): 119 # The warning is not a warn_only, fail the Kconfig. 120 error_out = True 121 122 # Turn all warnings into errors, so that e.g. assignments to undefined 123 # Kconfig symbols become errors. 124 # 125 # A warning is generated by this script whenever a symbol gets a 126 # different value than the one it was assigned. Keep that one as just a 127 # warning for now. 128 if error_out: 129 err("Aborting due to Kconfig warnings") 130 131 # Write the merged configuration and the C header 132 print(kconf.write_config(args.config_out)) 133 print(kconf.write_autoconf(args.header_out)) 134 135 # Write the list of parsed Kconfig files to a file 136 write_kconfig_filenames(kconf, args.kconfig_list_out) 137 138 139def check_no_promptless_assign(kconf): 140 # Checks that no promptless symbols are assigned 141 142 for sym in kconf.unique_defined_syms: 143 if sym.user_value is not None and promptless(sym): 144 err(f"""\ 145{sym.name_and_loc} is assigned in a configuration file, but is not directly 146user-configurable (has no prompt). It gets its value indirectly from other 147symbols. """ + SYM_INFO_HINT.format(sym)) 148 149 150def check_assigned_sym_values(kconf): 151 # Verifies that the values assigned to symbols "took" (matches the value 152 # the symbols actually got), printing warnings otherwise. Choice symbols 153 # are checked separately, in check_assigned_choice_values(). 154 155 for sym in kconf.unique_defined_syms: 156 if sym.choice: 157 continue 158 159 user_value = sym.user_value 160 if user_value is None: 161 continue 162 163 # Tristate values are represented as 0, 1, 2. Having them as "n", "m", 164 # "y" is more convenient here, so convert. 165 if sym.type in (BOOL, TRISTATE): 166 user_value = TRI_TO_STR[user_value] 167 168 if user_value != sym.str_value: 169 msg = f"{sym.name_and_loc} was assigned the value '{user_value}'" \ 170 f" but got the value '{sym.str_value}'. " 171 172 # List any unsatisfied 'depends on' dependencies in the warning 173 mdeps = missing_deps(sym) 174 if mdeps: 175 expr_strs = [] 176 for expr in mdeps: 177 estr = expr_str(expr) 178 if isinstance(expr, tuple): 179 # Add () around dependencies that aren't plain symbols. 180 # Gives '(FOO || BAR) (=n)' instead of 181 # 'FOO || BAR (=n)', which might be clearer. 182 estr = f"({estr})" 183 expr_strs.append(f"{estr} " 184 f"(={TRI_TO_STR[expr_value(expr)]})") 185 186 msg += "Check these unsatisfied dependencies: " + \ 187 ", ".join(expr_strs) + ". " 188 189 warn(msg + SYM_INFO_HINT.format(sym)) 190 191 192def missing_deps(sym): 193 # check_assigned_sym_values() helper for finding unsatisfied dependencies. 194 # 195 # Given direct dependencies 196 # 197 # depends on <expr> && <expr> && ... && <expr> 198 # 199 # on 'sym' (which can also come from e.g. a surrounding 'if'), returns a 200 # list of all <expr>s with a value less than the value 'sym' was assigned 201 # ("less" instead of "not equal" just to be general and handle tristates, 202 # even though Zephyr doesn't use them). 203 # 204 # For string/int/hex symbols, just looks for <expr> = n. 205 # 206 # Note that <expr>s can be something more complicated than just a symbol, 207 # like 'FOO || BAR' or 'FOO = "string"'. 208 209 deps = split_expr(sym.direct_dep, AND) 210 211 if sym.type in (BOOL, TRISTATE): 212 return [dep for dep in deps if expr_value(dep) < sym.user_value] 213 # string/int/hex 214 return [dep for dep in deps if expr_value(dep) == 0] 215 216 217def check_assigned_choice_values(kconf): 218 # Verifies that any choice symbols that were selected (by setting them to 219 # y) ended up as the selection, printing warnings otherwise. 220 # 221 # We check choice symbols separately to avoid warnings when two different 222 # choice symbols within the same choice are set to y. This might happen if 223 # a choice selection from a board defconfig is overridden in a prj.conf, 224 # for example. The last choice symbol set to y becomes the selection (and 225 # all other choice symbols get the value n). 226 # 227 # Without special-casing choices, we'd detect that the first symbol set to 228 # y ended up as n, and print a spurious warning. 229 230 for choice in kconf.unique_choices: 231 if choice.user_selection and \ 232 choice.user_selection is not choice.selection: 233 234 warn(f"""\ 235The choice symbol {choice.user_selection.name_and_loc} was selected (set =y), 236but {choice.selection.name_and_loc if choice.selection else "no symbol"} ended 237up as the choice selection. """ + SYM_INFO_HINT.format(choice.user_selection)) 238 239 240# Hint on where to find symbol information. Used like 241# SYM_INFO_HINT.format(sym). 242SYM_INFO_HINT = """\ 243See http://docs.zephyrproject.org/latest/kconfig.html#CONFIG_{0.name} and/or 244look up {0.name} in the menuconfig/guiconfig interface. The Application 245Development Primer, Setting Configuration Values, and Kconfig - Tips and Best 246Practices sections of the manual might be helpful too.\ 247""" 248 249 250def check_deprecated(kconf): 251 deprecated = kconf.syms.get('DEPRECATED') 252 dep_expr = kconf.n if deprecated is None else deprecated.rev_dep 253 254 if dep_expr is not kconf.n: 255 selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2] 256 for selector in selectors: 257 selector_name = split_expr(selector, AND)[0].name 258 warn(f'Deprecated symbol {selector_name} is enabled.') 259 260 261def check_experimental(kconf): 262 experimental = kconf.syms.get('EXPERIMENTAL') 263 dep_expr = kconf.n if experimental is None else experimental.rev_dep 264 265 if dep_expr is not kconf.n: 266 selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2] 267 for selector in selectors: 268 selector_name = split_expr(selector, AND)[0].name 269 warn(f'Experimental symbol {selector_name} is enabled.') 270 271def check_not_secure(kconf): 272 not_secure = kconf.syms.get('NOT_SECURE') 273 dep_expr = kconf.n if not_secure is None else not_secure.rev_dep 274 275 if dep_expr is not kconf.n: 276 selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2] 277 for selector in selectors: 278 selector_name = split_expr(selector, AND)[0].name 279 warn(f'Not secure symbol {selector_name} is enabled.') 280 281 282def promptless(sym): 283 # Returns True if 'sym' has no prompt. Since the symbol might be defined in 284 # multiple locations, we need to check all locations. 285 286 return not any(node.prompt for node in sym.nodes) 287 288 289def write_kconfig_filenames(kconf, kconfig_list_path): 290 # Writes a sorted list with the absolute paths of all parsed Kconfig files 291 # to 'kconfig_list_path'. The paths are realpath()'d, and duplicates are 292 # removed. This file is used by CMake to look for changed Kconfig files. It 293 # needs to be deterministic. 294 295 with open(kconfig_list_path, 'w') as out: 296 for path in sorted({os.path.realpath(os.path.join(kconf.srctree, path)) 297 for path in set(kconf.kconfig_filenames)}): 298 print(path, file=out) 299 300 301def parse_args(): 302 parser = argparse.ArgumentParser(allow_abbrev=False) 303 304 parser.add_argument("--handwritten-input-configs", 305 action="store_true", 306 help="Assume the input configuration fragments are " 307 "handwritten fragments and do additional checks " 308 "on them, like no promptless symbols being " 309 "assigned") 310 parser.add_argument("--forced-input-configs", 311 action="store_true", 312 help="Indicate the input configuration files are " 313 "followed by an forced configuration file." 314 "The forced configuration is used to forcefully " 315 "set specific configuration settings to a " 316 "pre-defined value and thereby remove any user " 317 " adjustments.") 318 parser.add_argument("--zephyr-base", 319 help="Path to current Zephyr installation") 320 parser.add_argument("kconfig_file", 321 help="Top-level Kconfig file") 322 parser.add_argument("config_out", 323 help="Output configuration file") 324 parser.add_argument("header_out", 325 help="Output header file") 326 parser.add_argument("kconfig_list_out", 327 help="Output file for list of parsed Kconfig files") 328 parser.add_argument("configs_in", 329 nargs="+", 330 help="Input configuration fragments. Will be merged " 331 "together.") 332 333 return parser.parse_args() 334 335 336def warn(msg): 337 # Use a large fill() width to try to avoid linebreaks in the symbol 338 # reference link, and add some extra newlines to set the message off from 339 # surrounding text (this usually gets printed as part of spammy CMake 340 # output) 341 print("\n" + textwrap.fill("warning: " + msg, 100) + "\n", file=sys.stderr) 342 343 344def err(msg): 345 sys.exit("\n" + textwrap.fill("error: " + msg, 100) + "\n") 346 347 348if __name__ == "__main__": 349 main() 350