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