xref: /optee_os/scripts/kconfig/kconfiglib/oldconfig.py (revision c689edbb2550c76ae81dcecab717d82a564b2d7b)
1#!/usr/bin/env python3
2
3# Copyright (c) 2018-2019, Ulf Magnusson
4# SPDX-License-Identifier: ISC
5
6"""
7Implements oldconfig functionality.
8
9  1. Loads existing .config
10  2. Prompts for the value of all modifiable symbols/choices that
11     aren't already set in the .config
12  3. Writes an updated .config
13
14The default input/output filename is '.config'. A different filename can be
15passed in the KCONFIG_CONFIG environment variable.
16
17When overwriting a configuration file, the old version is saved to
18<filename>.old (e.g. .config.old).
19
20Entering '?' displays the help text of the symbol/choice, if any.
21
22Unlike 'make oldconfig', this script doesn't print menu titles and comments,
23but gives Kconfig definition locations. Printing menus and comments would be
24pretty easy to add: Look at the parents of each item, and print all menu
25prompts and comments unless they have already been printed (assuming you want
26to skip "irrelevant" menus).
27"""
28from __future__ import print_function
29
30import sys
31
32from kconfiglib import Symbol, Choice, BOOL, TRISTATE, HEX, standard_kconfig
33
34
35# Python 2/3 compatibility hack
36if sys.version_info[0] < 3:
37    input = raw_input
38
39
40def _main():
41    # Earlier symbols in Kconfig files might depend on later symbols and become
42    # visible if their values change. This flag is set to True if the value of
43    # any symbol changes, in which case we rerun the oldconfig to check for new
44    # visible symbols.
45    global conf_changed
46
47    kconf = standard_kconfig(__doc__)
48    print(kconf.load_config())
49
50    while True:
51        conf_changed = False
52
53        for node in kconf.node_iter():
54            oldconfig(node)
55
56        if not conf_changed:
57            break
58
59    print(kconf.write_config())
60
61
62def oldconfig(node):
63    """
64    Prompts the user for a value if node.item is a visible symbol/choice with
65    no user value.
66    """
67    # See main()
68    global conf_changed
69
70    # Only symbols and choices can be configured
71    if not isinstance(node.item, (Symbol, Choice)):
72        return
73
74    # Skip symbols and choices that aren't visible
75    if not node.item.visibility:
76        return
77
78    # Skip symbols and choices that don't have a prompt (at this location)
79    if not node.prompt:
80        return
81
82    if isinstance(node.item, Symbol):
83        sym = node.item
84
85        # Skip symbols that already have a user value
86        if sym.user_value is not None:
87            return
88
89        # Skip symbols that can only have a single value, due to selects
90        if len(sym.assignable) == 1:
91            return
92
93        # Skip symbols in choices in y mode. We ask once for the entire choice
94        # instead.
95        if sym.choice and sym.choice.tri_value == 2:
96            return
97
98        # Loop until the user enters a valid value or enters a blank string
99        # (for the default value)
100        while True:
101            val = input("{} ({}) [{}] ".format(
102                node.prompt[0], _name_and_loc_str(sym),
103                _default_value_str(sym)))
104
105            if val == "?":
106                _print_help(node)
107                continue
108
109            # Substitute a blank string with the default value the symbol
110            # would get
111            if not val:
112                val = sym.str_value
113
114            # Automatically add a "0x" prefix for hex symbols, like the
115            # menuconfig interface does. This isn't done when loading .config
116            # files, hence why set_value() doesn't do it automatically.
117            if sym.type == HEX and not val.startswith(("0x", "0X")):
118                val = "0x" + val
119
120            old_str_val = sym.str_value
121
122            # Kconfiglib itself will print a warning here if the value
123            # is invalid, so we don't need to bother
124            if sym.set_value(val):
125                # Valid value input. We're done with this node.
126
127                if sym.str_value != old_str_val:
128                    conf_changed = True
129
130                return
131
132    else:
133        choice = node.item
134
135        # Skip choices that already have a visible user selection...
136        if choice.user_selection and choice.user_selection.visibility == 2:
137            # ...unless there are new visible symbols in the choice. (We know
138            # they have y (2) visibility in that case, because m-visible
139            # symbols get demoted to n-visibility in y-mode choices, and the
140            # user-selected symbol had visibility y.)
141            for sym in choice.syms:
142                if sym is not choice.user_selection and sym.visibility and \
143                   sym.user_value is None:
144                    # New visible symbols in the choice
145                    break
146            else:
147                # No new visible symbols in the choice
148                return
149
150        # Get a list of available selections. The mode of the choice limits
151        # the visibility of the choice value symbols, so this will indirectly
152        # skip choices in n and m mode.
153        options = [sym for sym in choice.syms if sym.visibility == 2]
154
155        if not options:
156            # No y-visible choice value symbols
157            return
158
159        # Loop until the user enters a valid selection or a blank string (for
160        # the default selection)
161        while True:
162            print("{} ({})".format(node.prompt[0], _name_and_loc_str(choice)))
163
164            for i, sym in enumerate(options, 1):
165                print("{} {}. {} ({})".format(
166                    ">" if sym is choice.selection else " ",
167                    i,
168                    # Assume people don't define choice symbols with multiple
169                    # prompts. That generates a warning anyway.
170                    sym.nodes[0].prompt[0],
171                    sym.name))
172
173            sel_index = input("choice[1-{}]: ".format(len(options)))
174
175            if sel_index == "?":
176                _print_help(node)
177                continue
178
179            # Pick the default selection if the string is blank
180            if not sel_index:
181                choice.selection.set_value(2)
182                break
183
184            try:
185                sel_index = int(sel_index)
186            except ValueError:
187                print("Bad index", file=sys.stderr)
188                continue
189
190            if not 1 <= sel_index <= len(options):
191                print("Bad index", file=sys.stderr)
192                continue
193
194            # Valid selection
195
196            if options[sel_index - 1].tri_value != 2:
197                conf_changed = True
198
199            options[sel_index - 1].set_value(2)
200            break
201
202        # Give all of the non-selected visible choice symbols the user value n.
203        # This makes it so that the choice is no longer considered new once we
204        # do additional passes, if the reason that it was considered new was
205        # that it had new visible choice symbols.
206        #
207        # Only giving visible choice symbols the user value n means we will
208        # prompt for the choice again if later user selections make more new
209        # choice symbols visible, which is correct.
210        for sym in choice.syms:
211            if sym is not choice.user_selection and sym.visibility:
212                sym.set_value(0)
213
214
215def _name_and_loc_str(sc):
216    # Helper for printing the name of the symbol/choice 'sc' along with the
217    # location(s) in the Kconfig files where it is defined. Unnamed choices
218    # return "choice" instead of the name.
219
220    return "{}, defined at {}".format(
221        sc.name or "choice",
222        ", ".join("{}:{}".format(node.filename, node.linenr)
223                  for node in sc.nodes))
224
225
226def _print_help(node):
227    print("\n" + (node.help or "No help text\n"))
228
229
230def _default_value_str(sym):
231    # Returns the "m/M/y" string in e.g.
232    #
233    #   TRISTATE_SYM prompt (TRISTATE_SYM, defined at Kconfig:9) [n/M/y]:
234    #
235    # For string/int/hex, returns the default value as-is.
236
237    if sym.type in (BOOL, TRISTATE):
238        return "/".join(("NMY" if sym.tri_value == tri else "nmy")[tri]
239                        for tri in sym.assignable)
240
241    # string/int/hex
242    return sym.str_value
243
244
245if __name__ == "__main__":
246    _main()
247