summaryrefslogtreecommitdiff
path: root/oldconfig.py
diff options
context:
space:
mode:
authorUlf Magnusson <ulfalizer@gmail.com>2018-05-27 19:18:40 +0200
committerUlf Magnusson <ulfalizer@gmail.com>2018-05-27 19:18:40 +0200
commit81a0b2ede3f59784d271a60701291e2a0dfc2a7d (patch)
tree060455da0f5ccec185658bd91b59816c9e60cbcf /oldconfig.py
parent58234d60b45ff5a8c04caf2cb5a18f77252cdbca (diff)
oldconfig: Move from examples/ to root
All the packaged code will appear in the root.
Diffstat (limited to 'oldconfig.py')
-rwxr-xr-xoldconfig.py344
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()