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