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