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