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