123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- #!/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()
|