diff options
| author | Ulf Magnusson <ulfalizer@gmail.com> | 2018-02-06 02:44:14 +0100 |
|---|---|---|
| committer | Ulf Magnusson <ulfalizer@gmail.com> | 2018-02-06 02:47:43 +0100 |
| commit | c1d2c10ad95827e3a04c9f432b32fbcf175f5a5e (patch) | |
| tree | ab715f50d61b65381019580368fe00b58627b0a5 /examples | |
| parent | 2fb1d811855162fe9d723806a7b5fb995b14ff7b (diff) | |
Add oldconfig.py example script
Implements the standard 'make oldconfig' functionality, prompting the
user for the values of new symbols to update an old .config file.
This came up in
https://github.com/zephyrproject-rtos/zephyr/issues/5426.
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/oldconfig.py | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/examples/oldconfig.py b/examples/oldconfig.py new file mode 100644 index 0000000..3cb60f0 --- /dev/null +++ b/examples/oldconfig.py @@ -0,0 +1,283 @@ +# 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). +# +# 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 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, STR_TO_TRI +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 name_and_loc_str(sym): + """ + Helper for printing the symbol name along with the location(s) in the + Kconfig files where it 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): + res = [] + + if 0 in sym.assignable: + res.append("N" if sym.tri_value == 0 else "n") + + if 1 in sym.assignable: + res.append("M" if sym.tri_value == 1 else "m") + + if 2 in sym.assignable: + res.append("Y" if sym.tri_value == 2 else "y") + + return "/".join(res) + + # 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 + """ + # 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))) + + # Substitute a blank string with the default value the symbol + # would get + if not val: + val = sym.str_value + + if sym.type in (BOOL, TRISTATE): + if val not in STR_TO_TRI: + eprint("Invalid tristate value") + continue + val = STR_TO_TRI[val] + + # 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 + + # 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. + return + + else: + choice = node.item + + # Skip choices that already have a visible user selection + if choice.user_selection and choice.user_selection.visibility == 2: + 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] + + # No y-visible choice value symbols + if not options: + 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 choice.selection is sym 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))) + + # Pick the default selection if the string is blank + if not sel_index: + choice.selection.set_value(2) + return + + 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 + options[sel_index - 1].set_value(2) + return + +def do_oldconfig(node): + while node: + do_oldconfig_for_node(node) + + if node.list: + do_oldconfig(node.list) + + node = node.next + +if __name__ == "__main__": + if len(sys.argv) != 2: + eprint("error: pass name of base Kconfig file as argument") + sys.exit(1) + + if not os.path.exists(".config"): + eprint("error: no existing .config") + sys.exit(1) + + kconf = Kconfig(sys.argv[1]) + + kconf.load_config(".config") + do_oldconfig(kconf.top_node) + kconf.write_config(".config") + + print("Configuration written to .config") |
