diff options
| author | Ulf Magnusson <ulfalizer@gmail.com> | 2018-05-27 19:18:40 +0200 |
|---|---|---|
| committer | Ulf Magnusson <ulfalizer@gmail.com> | 2018-05-27 19:18:40 +0200 |
| commit | 81a0b2ede3f59784d271a60701291e2a0dfc2a7d (patch) | |
| tree | 060455da0f5ccec185658bd91b59816c9e60cbcf /oldconfig.py | |
| parent | 58234d60b45ff5a8c04caf2cb5a18f77252cdbca (diff) | |
oldconfig: Move from examples/ to root
All the packaged code will appear in the root.
Diffstat (limited to 'oldconfig.py')
| -rwxr-xr-x | oldconfig.py | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/oldconfig.py b/oldconfig.py new file mode 100755 index 0000000..1b58831 --- /dev/null +++ b/oldconfig.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python + +# Implements oldconfig-like functionality: +# +# 1. Load existing .config +# 2. Prompt the user for the value of all changeable symbols/choices +# that aren't already set in the .config +# 3. Write new .config +# +# Unlike 'make oldconfig', this script doesn't print menu titles and +# comments, but instead gives the Kconfig locations of all symbols and +# choices. Printing menu titles and comments as well 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). +# +# Inputting '?' on the prompt will display the help text of the item, if any. +# Hopefully no one will want to use that as a value. +# +# Sample session: +# +# OldconfigExample contents: +# +# config MODULES +# def_bool y +# option modules +# +# config BOOL_SYM +# bool "BOOL_SYM prompt" +# default y +# +# config TRISTATE_SYM +# tristate "TRISTATE_SYM prompt" +# default m +# +# config STRING_SYM +# string "STRING_SYM prompt" +# default "foo" +# +# config INT_SYM +# int "INT_SYM prompt" +# +# config HEX_SYM +# hex "HEX_SYM prompt" +# +# choice +# bool "A choice that defaults to CHOICE_B" +# default CHOICE_B +# +# config CHOICE_A +# bool "CHOICE_A's prompt" +# +# config CHOICE_B +# bool "CHOICE_B's prompt" +# +# config CHOICE_C +# bool "CHOICE_C's prompt" +# +# endchoice +# +# +# Running: +# +# $ touch .config # Run with empty .config +# +# $ python(3) oldconfig.py Kconfig +# BOOL_SYM prompt (BOOL_SYM, defined at Kconfig:5) [n/Y] foo +# Invalid tristate value +# BOOL_SYM prompt (BOOL_SYM, defined at Kconfig:5) [n/Y] n +# TRISTATE_SYM prompt (TRISTATE_SYM, defined at Kconfig:9) [n/M/y] +# STRING_SYM prompt (STRING_SYM, defined at Kconfig:13) [foo] bar +# INT_SYM prompt (INT_SYM, defined at Kconfig:17) [] 0x123 +# warning: the value '0x123' is invalid for INT_SYM (defined at Kconfig:17), which has type int. Assignment ignored. +# INT_SYM prompt (INT_SYM, defined at Kconfig:17) [] 123 +# HEX_SYM prompt (HEX_SYM, defined at Kconfig:20) [] 0x123 +# A choice that default to B (defined at Kconfig:23) +# 1. CHOICE_A's prompt (CHOICE_A) +# > 2. CHOICE_B's prompt (CHOICE_B) +# 3. CHOICE_C's prompt (CHOICE_C) +# choice[1-3]: 5 +# Bad index +# A choice that default to B (defined at Kconfig:23) +# 1. CHOICE_A's prompt (CHOICE_A) +# > 2. CHOICE_B's prompt (CHOICE_B) +# 3. CHOICE_C's prompt (CHOICE_C) +# choice[1-3]: 3 +# Configuration written to .config +# +# $ cat .config +# # Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) +# CONFIG_MODULES=y +# # CONFIG_BOOL_SYM is not set +# CONFIG_TRISTATE_SYM=m +# CONFIG_STRING_SYM="bar" +# CONFIG_INT_SYM=123 +# CONFIG_HEX_SYM=0x123 +# # CONFIG_CHOICE_A is not set +# # CONFIG_CHOICE_B is not set +# CONFIG_CHOICE_C=y +# +# $ python oldconfig.py Kconfig # Everything's already up to date +# Configuration written to .config +from __future__ import print_function +from kconfiglib import Kconfig, Symbol, Choice, BOOL, TRISTATE, HEX +import os +import sys + +# Python 2/3 compatibility hack +if sys.version_info[0] < 3: + input = raw_input + +def eprint(*args): + print(*args, file=sys.stderr) + +def print_help(node): + if node.help is not None: + print("\n" + node.help) + else: + print("\nNo help text\n") + +def name_and_loc_str(sym): + """ + Helper for printing the symbol name along with the location(s) in the + Kconfig files where the symbol is defined + """ + return "{}, defined at {}".format( + sym.name, + ", ".join("{}:{}".format(node.filename, node.linenr) + for node in sym.nodes)) + +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 + +def do_oldconfig_for_node(node): + """ + Prompts the user for a value for the menu node item, where applicable in + oldconfig mode + """ + # See do_oldconfig() + 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("{} (defined at {}:{})".format( + node.prompt[0], node.filename, node.linenr)) + + 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: + eprint("Bad index") + continue + + if not 1 <= sel_index <= len(options): + eprint("Bad index") + 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) + +# Entry point when run as an executable, split out so that setuptools' +# 'entry_points' can be used. It produces a handy oldconfig.exe launcher on +# Windows. +def main(): + if len(sys.argv) > 2: + sys.exit("usage: {} [Kconfig]".format(sys.argv[0])) + + kconf = Kconfig("Kconfig" if len(sys.argv) < 2 else sys.argv[1]) + + config_filename = os.environ.get("KCONFIG_CONFIG") + if config_filename is None: + config_filename = ".config" + + if not os.path.exists(config_filename): + sys.exit("{}: '{}' does not exist" + .format(sys.argv[0], config_filename)) + + kconf.load_config(config_filename) + do_oldconfig(kconf) + kconf.write_config(config_filename) + + print("Configuration saved to '{}'".format(config_filename)) + +def do_oldconfig(kconf): + # An earlier symbol in the Kconfig files might depend on a later symbol and + # become visible if its value changes. 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 + + while True: + conf_changed = False + do_oldconfig_rec(kconf.top_node) + if not conf_changed: + break + +def do_oldconfig_rec(node): + while node: + do_oldconfig_for_node(node) + + if node.list: + do_oldconfig_rec(node.list) + + node = node.next + +if __name__ == "__main__": + main() |
