aboutsummaryrefslogtreecommitdiffstats
path: root/Kconfiglib/oldconfig.py
blob: 53434b252ff60969f948effd1218bbd4dbc200fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#!/usr/bin/env python3

# Copyright (c) 2018-2019, Ulf Magnusson
# SPDX-License-Identifier: ISC

"""
Implements oldconfig functionality.

  1. Loads existing .config
  2. Prompts for the value of all modifiable symbols/choices that
     aren't already set in the .config
  3. Writes an updated .config

The default input/output filename is '.config'. A different filename can be
passed in the KCONFIG_CONFIG environment variable.

When overwriting a configuration file, the old version is saved to
<filename>.old (e.g. .config.old).

Entering '?' displays the help text of the symbol/choice, if any.

Unlike 'make oldconfig', this script doesn't print menu titles and comments,
but gives Kconfig definition locations. Printing menus and comments would be
pretty easy to add: Look at the parents of each item, and print all menu
prompts and comments unless they have already been printed (assuming you want
to skip "irrelevant" menus).
"""
from __future__ import print_function

import sys

from kconfiglib import Symbol, Choice, BOOL, TRISTATE, HEX, standard_kconfig


# Python 2/3 compatibility hack
if sys.version_info[0] < 3:
    input = raw_input


def _main():
    # Earlier symbols in Kconfig files might depend on later symbols and become
    # visible if their values change. This flag is set to True if the value of
    # any symbol changes, in which case we rerun the oldconfig to check for new
    # visible symbols.
    global conf_changed

    kconf = standard_kconfig(__doc__)
    print(kconf.load_config())

    while True:
        conf_changed = False

        for node in kconf.node_iter():
            oldconfig(node)

        if not conf_changed:
            break

    print(kconf.write_config())


def oldconfig(node):
    """
    Prompts the user for a value if node.item is a visible symbol/choice with
    no user value.
    """
    # See main()
    global conf_changed

    # Only symbols and choices can be configured
    if not isinstance(node.item, (Symbol, Choice)):
        return

    # Skip symbols and choices that aren't visible
    if not node.item.visibility:
        return

    # Skip symbols and choices that don't have a prompt (at this location)
    if not node.prompt:
        return

    if isinstance(node.item, Symbol):
        sym = node.item

        # Skip symbols that already have a user value
        if sym.user_value is not None:
            return

        # Skip symbols that can only have a single value, due to selects
        if len(sym.assignable) == 1:
            return

        # Skip symbols in choices in y mode. We ask once for the entire choice
        # instead.
        if sym.choice and sym.choice.tri_value == 2:
            return

        # Loop until the user enters a valid value or enters a blank string
        # (for the default value)
        while True:
            val = input("{} ({}) [{}] ".format(
                node.prompt[0], _name_and_loc_str(sym),
                _default_value_str(sym)))

            if val == "?":
                _print_help(node)
                continue

            # Substitute a blank string with the default value the symbol
            # would get
            if not val:
                val = sym.str_value

            # Automatically add a "0x" prefix for hex symbols, like the
            # menuconfig interface does. This isn't done when loading .config
            # files, hence why set_value() doesn't do it automatically.
            if sym.type == HEX and not val.startswith(("0x", "0X")):
                val = "0x" + val

            old_str_val = sym.str_value

            # Kconfiglib itself will print a warning here if the value
            # is invalid, so we don't need to bother
            if sym.set_value(val):
                # Valid value input. We're done with this node.

                if sym.str_value != old_str_val:
                    conf_changed = True

                return

    else:
        choice = node.item

        # Skip choices that already have a visible user selection...
        if choice.user_selection and choice.user_selection.visibility == 2:
            # ...unless there are new visible symbols in the choice. (We know
            # they have y (2) visibility in that case, because m-visible
            # symbols get demoted to n-visibility in y-mode choices, and the
            # user-selected symbol had visibility y.)
            for sym in choice.syms:
                if sym is not choice.user_selection and sym.visibility and \
                   sym.user_value is None:
                    # New visible symbols in the choice
                    break
            else:
                # No new visible symbols in the choice
                return

        # Get a list of available selections. The mode of the choice limits
        # the visibility of the choice value symbols, so this will indirectly
        # skip choices in n and m mode.
        options = [sym for sym in choice.syms if sym.visibility == 2]

        if not options:
            # No y-visible choice value symbols
            return

        # Loop until the user enters a valid selection or a blank string (for
        # the default selection)
        while True:
            print("{} ({})".format(node.prompt[0], _name_and_loc_str(choice)))

            for i, sym in enumerate(options, 1):
                print("{} {}. {} ({})".format(
                    ">" if sym is choice.selection else " ",
                    i,
                    # Assume people don't define choice symbols with multiple
                    # prompts. That generates a warning anyway.
                    sym.nodes[0].prompt[0],
                    sym.name))

            sel_index = input("choice[1-{}]: ".format(len(options)))

            if sel_index == "?":
                _print_help(node)
                continue

            # Pick the default selection if the string is blank
            if not sel_index:
                choice.selection.set_value(2)
                break

            try:
                sel_index = int(sel_index)
            except ValueError:
                print("Bad index", file=sys.stderr)
                continue

            if not 1 <= sel_index <= len(options):
                print("Bad index", file=sys.stderr)
                continue

            # Valid selection

            if options[sel_index - 1].tri_value != 2:
                conf_changed = True

            options[sel_index - 1].set_value(2)
            break

        # Give all of the non-selected visible choice symbols the user value n.
        # This makes it so that the choice is no longer considered new once we
        # do additional passes, if the reason that it was considered new was
        # that it had new visible choice symbols.
        #
        # Only giving visible choice symbols the user value n means we will
        # prompt for the choice again if later user selections make more new
        # choice symbols visible, which is correct.
        for sym in choice.syms:
            if sym is not choice.user_selection and sym.visibility:
                sym.set_value(0)


def _name_and_loc_str(sc):
    # Helper for printing the name of the symbol/choice 'sc' along with the
    # location(s) in the Kconfig files where it is defined. Unnamed choices
    # return "choice" instead of the name.

    return "{}, defined at {}".format(
        sc.name or "choice",
        ", ".join("{}:{}".format(node.filename, node.linenr)
                  for node in sc.nodes))


def _print_help(node):
    print("\n" + (node.help or "No help text\n"))


def _default_value_str(sym):
    # Returns the "m/M/y" string in e.g.
    #
    #   TRISTATE_SYM prompt (TRISTATE_SYM, defined at Kconfig:9) [n/M/y]:
    #
    # For string/int/hex, returns the default value as-is.

    if sym.type in (BOOL, TRISTATE):
        return "/".join(("NMY" if sym.tri_value == tri else "nmy")[tri]
                        for tri in sym.assignable)

    # string/int/hex
    return sym.str_value


if __name__ == "__main__":
    _main()