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