summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorUlf Magnusson <ulfalizer@gmail.com>2018-02-06 02:44:14 +0100
committerUlf Magnusson <ulfalizer@gmail.com>2018-02-06 02:47:43 +0100
commitc1d2c10ad95827e3a04c9f432b32fbcf175f5a5e (patch)
treeab715f50d61b65381019580368fe00b58627b0a5 /examples
parent2fb1d811855162fe9d723806a7b5fb995b14ff7b (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.py283
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")