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