diff options
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/Kmenuconfig | 102 | ||||
| -rw-r--r-- | examples/allnoconfig.py | 96 | ||||
| -rw-r--r-- | examples/allnoconfig_simpler.py | 34 | ||||
| -rw-r--r-- | examples/allyesconfig.py | 117 | ||||
| -rw-r--r-- | examples/defconfig.py | 15 | ||||
| -rw-r--r-- | examples/defconfig_oldconfig.py | 28 | ||||
| -rw-r--r-- | examples/eval_expr.py | 24 | ||||
| -rw-r--r-- | examples/find_symbol.py | 205 | ||||
| -rw-r--r-- | examples/help_grep.py | 109 | ||||
| l--------- | examples/kconfiglib.py | 1 | ||||
| -rw-r--r-- | examples/menuconfig.py | 348 | ||||
| -rw-r--r-- | examples/print_refs.py | 13 | ||||
| -rw-r--r-- | examples/print_sym_info.py | 61 | ||||
| -rw-r--r-- | examples/print_tree.py | 80 | ||||
| -rw-r--r-- | examples/print_undefined.py | 15 |
15 files changed, 1022 insertions, 226 deletions
diff --git a/examples/Kmenuconfig b/examples/Kmenuconfig new file mode 100644 index 0000000..f1cb67b --- /dev/null +++ b/examples/Kmenuconfig @@ -0,0 +1,102 @@ +mainmenu "Example Kconfig configuration" + +config MODULES + bool "Enable loadable module support" + option modules + default y + +menu "Bool and tristate symbols" + +config BOOL + bool "Bool symbol" + default y + +config BOOL_DEP + bool "Dependent bool symbol" + depends on BOOL + +# Mix it up a bit with an 'if' instead of a 'depends on' +if BOOL + +config TRI_DEP + tristate "Dependent tristate symbol" + select SELECTED_BY_TRI_DEP + imply IMPLIED_BY_TRI_DEP + +endif + +config TWO_MENU_NODES + bool "First prompt" + depends on BOOL + +config TRI + tristate "Tristate symbol" + +config TWO_MENU_NODES + bool "Second prompt" + +comment "These are selected by TRI_DEP" + +config SELECTED_BY_TRI_DEP + tristate "Tristate selected by TRI_DEP" + +config IMPLIED_BY_TRI_DEP + tristate "Tristate implied by TRI_DEP" + +endmenu + + +menu "String, int, and hex symbols" + +config STRING + string "String symbol" + default "foo" + +config INT + int "Int symbol" + default 747 + +config HEX + hex "Hex symbol" + default 0xABC + +endmenu + + +menu "Various choices" + +choice BOOL_CHOICE + bool "Bool choice" + +config BOOL_CHOICE_SYM_1 + bool "Bool choice sym 1" + +config BOOL_CHOICE_SYM_2 + bool "Bool choice sym 2" + +endchoice + +choice TRI_CHOICE + tristate "Tristate choice" + +config TRI_CHOICE_SYM_1 + tristate "Tristate choice sym 1" + +config TRI_CHOICE_SYM_2 + tristate "Tristate choice sym 2" + +endchoice + +choice OPT_BOOL_CHOICE + bool "Optional bool choice" + optional + +config OPT_BOOL_CHOICE_SYM_1 + bool "Optional bool choice sym 1" + +config OPT_BOOL_CHOICE_SYM_2 + bool "Optional bool choice sym 2" + +endchoice + +endmenu diff --git a/examples/allnoconfig.py b/examples/allnoconfig.py index 9423352..127c60d 100644 --- a/examples/allnoconfig.py +++ b/examples/allnoconfig.py @@ -1,40 +1,62 @@ -# Works like allnoconfig. Automatically verified by the testsuite to generate -# identical output to 'make allnoconfig' for all ARCHes. The looping is done in -# case setting one symbol to "n" allows other symbols to be set to "n" (due to -# dependencies). +# Works like 'make allnoconfig'. Verified by the test suite to generate +# identical output to 'make allnoconfig' for all ARCHes. +# +# See allnoconfig_simpler.py for a much simpler version. This more roundabout +# version demonstrates some tree walking and value processing. +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py -import kconfiglib +from kconfiglib import Kconfig, Symbol, STR_TO_TRI import sys -conf = kconfiglib.Config(sys.argv[1]) - -# Do an initial pass to give allnoconfig_y symbols the user value 'y'. It might -# be possible to handle this through "successive raising" similarly to the -# "successive lowering" below too, but keep it simple. -for sym in conf: - if sym.get_type() in (kconfiglib.BOOL, kconfiglib.TRISTATE) and \ - sym.is_allnoconfig_y(): - sym.set_user_value('y') - -done = False -while not done: - done = True - - for sym in conf: - # Choices take care of themselves for allnoconfig, so we only need to - # worry about non-choice symbols - if not sym.is_choice_symbol() and not sym.is_allnoconfig_y(): - # If we can assign a value to the symbol (where "n", "m" and "y" - # are ordered from lowest to highest), then assign the lowest - # value. lower_bound() returns None for symbols whose values cannot - # (currently) be changed, as well as for non-bool/tristate symbols. - lower_bound = sym.get_lower_bound() - if lower_bound is not None and \ - kconfiglib.tri_less(lower_bound, sym.get_value()): - - sym.set_user_value(lower_bound) - # We just changed the value of some symbol. As this may affect - # other symbols, keep going. - done = False - -conf.write_config(".config") +def do_allnoconfig(node): + global changed + + # Walk the tree of menu nodes. You can imagine this as going down/into menu + # entries in the menuconfig interface, setting each to n (or the lowest + # assignable value). + + while node: + if isinstance(node.item, Symbol): + sym = node.item + + # Is the symbol a non-allnoconfig_y symbol that can be set to a + # lower value than its current value? + if (not sym.is_allnoconfig_y and + sym.assignable and + sym.assignable[0] < sym.tri_value): + + # Yup, lower it + sym.set_value(sym.assignable[0]) + changed = True + + # Recursively lower children + if node.list: + do_allnoconfig(node.list) + + node = node.next + +# Parse the Kconfig files +kconf = Kconfig(sys.argv[1]) + +# Do an initial pass to set 'option allnoconfig_y' symbols to y +for sym in kconf.defined_syms: + if sym.is_allnoconfig_y: + sym.set_value(2) + +while 1: + # Changing later symbols in the configuration can sometimes allow earlier + # symbols to be lowered, e.g. if a later symbol 'select's an earlier + # symbol. To handle such situations, we do additional passes over the tree + # until we're no longer able to change the value of any symbol in a pass. + changed = False + + do_allnoconfig(kconf.top_node) + + # Did the pass change any symbols? + if not changed: + break + +kconf.write_config(".config") diff --git a/examples/allnoconfig_simpler.py b/examples/allnoconfig_simpler.py index f3cfe9a..81e701c 100644 --- a/examples/allnoconfig_simpler.py +++ b/examples/allnoconfig_simpler.py @@ -1,28 +1,22 @@ # This is a simpler version of allnoconfig.py, corresponding to how the C -# implementation does it. Setting a user value that's not in the assignable -# range of the symbol (between get_lower_bound() and get_upper_bound(), or, -# equivalently, not in get_assignable_values()) is OK; the value will simply -# get truncated downwards or upwards as determined by the visibility and -# selects. +# implementation does it. Verified by the test suite to produce identical +# output to 'make allnoconfig' for all ARCHes. +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig_simpler.py -# This version is a bit slower compared allnoconfig.py since Kconfiglib -# invalidates all dependent symbols for each set_user_value() call. This does not -# happen for load_config(), which instead invalidates all symbols once after -# the configuration has been loaded. This is OK for load_config() since nearly -# all symbols will tend to be affected anyway. - -import kconfiglib +from kconfiglib import Kconfig, BOOL, TRISTATE import sys -conf = kconfiglib.Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) -# Avoid warnings printed by Kconfiglib when assigning a user value with -# set_user_value() to a symbol that has no prompt (such assignments never have -# an effect) -conf.set_print_warnings(False) +# Avoid warnings printed by Kconfiglib when assigning a value to a symbol that +# has no prompt. Such assignments never have an effect. +conf.disable_warnings() -for sym in conf: - if sym.get_type() in (kconfiglib.BOOL, kconfiglib.TRISTATE): - sym.set_user_value("y" if sym.is_allnoconfig_y() else "n") +for sym in conf.defined_syms: + if sym.type in (BOOL, TRISTATE): + sym.set_value(2 if sym.is_allnoconfig_y else 0) conf.write_config(".config") diff --git a/examples/allyesconfig.py b/examples/allyesconfig.py index 906343c..1bac713 100644 --- a/examples/allyesconfig.py +++ b/examples/allyesconfig.py @@ -1,65 +1,90 @@ -# Works like allyesconfig. This is a bit more involved than allnoconfig as we -# need to handle choices in two different modes: +# Works like 'make allyesconfig'. Verified by the test suite to generate output +# identical to 'make allyesconfig', for all ARCHES. # -# "y": One symbol is "y", the rest are "n". -# "m": Any number of symbols are "m", the rest are "n". +# This example is implemented a bit differently from allnoconfig.py to +# demonstrate some other possibilities. A variant similar to +# allnoconfig_simpler.py could be constructed too. # -# Only tristate choices can be in "m" mode. It is safe since the code for two -# conflicting options will appear as separate modules instead of simultaneously -# in the kernel. +# In theory, we need to handle choices in two different modes: # -# If a choice can be in "y" mode, it will be. If it can only be in "m" mode -# (due to dependencies), then all the options will be set to "m". +# y: One symbol is y, the rest are n +# m: Any number of symbols are m, the rest are n # -# The looping is in case setting one symbol to "y" (or "m") allows the value of -# other symbols to be raised. +# Only tristate choices can be in m mode. +# +# In practice, no m mode choices appear for allyesconfig as of 4.14, as +# expected, but we still handle them here for completeness. Here's a convoluted +# example of how you might get an m-mode choice even during allyesconfig: +# +# choice +# tristate "weird choice" +# depends on m +# +# ... +# +# endchoice +# +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py -import kconfiglib +from kconfiglib import Kconfig, Choice, STR_TO_TRI import sys -conf = kconfiglib.Config(sys.argv[1]) +def all_choices(node): + """ + Returns all choices in the menu tree rooted at 'node'. See the + Kconfig.write_config() implementation in kconfiglib.py for an example of + how the tree can be walked iteratively instead. -# Get a list of all symbols that are not in choices -non_choice_syms = [sym for sym in conf.get_symbols() if - not sym.is_choice_symbol()] + (I was thinking of making a list of choices available directly in the API, + but I'm not sure it will always be needed internally, and I'm trying to + spam the API with less seldomly-used stuff compared to Kconfiglib 1.) + """ + res = [] -done = False -while not done: - done = True + while node: + if isinstance(node.item, Choice): + res.append(node.item) - # Handle symbols outside of choices + if node.list: + res.extend(all_choices(node.list)) - for sym in non_choice_syms: - upper_bound = sym.get_upper_bound() + node = node.next - # See corresponding comment for allnoconfig implementation - if upper_bound is not None and \ - kconfiglib.tri_less(sym.get_value(), upper_bound): - sym.set_user_value(upper_bound) - done = False + return res - # Handle symbols within choices +kconf = Kconfig(sys.argv[1]) - for choice in conf.get_choices(): +non_choice_syms = [sym for sym in kconf.defined_syms if not sym.choice] +choices = all_choices(kconf.top_node) # All choices in the configuration - # Handle choices whose visibility allow them to be in "y" mode +while True: + changed = False + + for sym in non_choice_syms: + # Set the symbol to the highest assignable value, unless it already has + # that value. sym.assignable[-1] gives the last element in assignable. + if sym.assignable and sym.tri_value < sym.assignable[-1]: + sym.set_value(sym.assignable[-1]) + changed = True - if choice.get_visibility() == "y": - selection = choice.get_selection_from_defaults() - if selection is not None and \ - selection is not choice.get_user_selection(): - selection.set_user_value("y") - done = False + for choice in choices: + # Same logic as above for choices + if choice.assignable and choice.tri_value < choice.assignable[-1]: + choice.set_value(choice.assignable[-1]) + changed = True - # Handle choices whose visibility only allow them to be in "m" mode. - # This might happen if a choice depends on a symbol that can only be - # "m" for example. + # For y-mode choices, we just let the choice get its default + # selection. For m-mode choices, we set all choice symbols to m. + if choice.tri_value == 1: + for sym in choice.syms: + sym.set_value(1) - elif choice.get_visibility() == "m": - for sym in choice.get_symbols(): - if sym.get_value() != "m" and \ - sym.get_upper_bound() != "n": - sym.set_user_value("m") - done = False + # Do multiple passes until we longer manage to raise any symbols or + # choices, like in allnoconfig.py + if not changed: + break -conf.write_config(".config") +kconf.write_config(".config") diff --git a/examples/defconfig.py b/examples/defconfig.py index 3e958e2..236db8d 100644 --- a/examples/defconfig.py +++ b/examples/defconfig.py @@ -1,17 +1,22 @@ # Works like entering "make menuconfig" and immediately saving and exiting +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py import kconfiglib import os import sys -conf = kconfiglib.Config(sys.argv[1]) +conf = kconfiglib.Kconfig(sys.argv[1]) if os.path.exists(".config"): + print("using existing .config") conf.load_config(".config") else: - defconfig = conf.get_defconfig_filename() - if defconfig is not None: - print("Using" + defconfig) - conf.load_config(defconfig) + if conf.defconfig_filename is not None: + print("using " + conf.defconfig_filename) + conf.load_config(conf.defconfig_filename) conf.write_config(".config") +print("configuration written to .config") diff --git a/examples/defconfig_oldconfig.py b/examples/defconfig_oldconfig.py index 9a85440..84aa134 100644 --- a/examples/defconfig_oldconfig.py +++ b/examples/defconfig_oldconfig.py @@ -7,27 +7,31 @@ # yes n | make oldconfig # # This came up in https://github.com/ulfalizer/Kconfiglib/issues/15. +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/defconfig_oldconfig.py import kconfiglib import sys -conf = kconfiglib.Config(sys.argv[1]) +kconf = kconfiglib.Kconfig(sys.argv[1]) # Mirrors defconfig -conf.load_config("arch/x86/configs/x86_64_defconfig") -conf.write_config(".config") +kconf.load_config("arch/x86/configs/x86_64_defconfig") +kconf.write_config(".config") # Mirrors the first oldconfig -conf.load_config(".config") -conf["ETHERNET"].set_user_value('n') -conf.write_config(".config") +kconf.load_config(".config") +kconf.syms["ETHERNET"].set_value(0) +kconf.write_config(".config") # Mirrors the second oldconfig -conf.load_config(".config") -conf["ETHERNET"].set_user_value('y') -for s in conf: - if s.get_user_value() is None and 'n' in s.get_assignable_values(): - s.set_user_value('n') +kconf.load_config(".config") +kconf.syms["ETHERNET"].set_value(2) +for s in kconf.defined_syms: + if s.user_value is None and 0 in s.assignable: + s.set_value(0) # Write the final configuration -conf.write_config(".config") +kconf.write_config(".config") diff --git a/examples/eval_expr.py b/examples/eval_expr.py index edb33e6..36a7e6a 100644 --- a/examples/eval_expr.py +++ b/examples/eval_expr.py @@ -1,8 +1,24 @@ -# Evaluates an expression in the context of a configuration. (Here we could -# load a .config as well.) +# Evaluates an expression (e.g. "X86_64 || (X86_32 && X86_LOCAL_APIC)") in the +# context of a configuration. Note that this always yields a tristate value (n, +# m, or y). +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/eval_expr.py SCRIPT_ARG=<expr> import kconfiglib import sys -conf = kconfiglib.Config(sys.argv[1]) -print(conf.eval("(TRACE_IRQFLAGS_SUPPORT || PPC32) && STACKTRACE_SUPPORT")) +if len(sys.argv) < 3: + print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=NAME') + sys.exit(1) + +expr = sys.argv[2] + +kconf = kconfiglib.Kconfig(sys.argv[1]) + +# Enable modules so that m doesn't get demoted to n +kconf.syms["MODULES"].set_value(2) + +print("the expression '{}' evaluates to {}" + .format(expr, kconf.eval_string(expr))) diff --git a/examples/find_symbol.py b/examples/find_symbol.py new file mode 100644 index 0000000..42677ec --- /dev/null +++ b/examples/find_symbol.py @@ -0,0 +1,205 @@ +# Prints all symbols, choices, menus, and comments that reference a symbol with +# a particular name in any of their properties or property conditions. +# Demonstrates expression fetching and walking. +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/find_symbol.py SCRIPT_ARG=<name> +# +# Example output for SCRIPT_ARG=X86: +# +# +# Found 452 locations that reference 'X86': +# +# ========== Location 1 (init/Kconfig:1122) ========== +# +# config SGETMASK_SYSCALL +# bool +# prompt "sgetmask/ssetmask syscalls support" if EXPERT +# default PARISC || MN10300 || BLACKFIN || M68K || PPC || MIPS || X86 || SPARC || CRIS || MICROBLAZE || SUPERH +# help +# sys_sgetmask and sys_ssetmask are obsolete system calls +# no longer supported in libc but still enabled by default in some +# architectures. +# +# If unsure, leave the default option here. +# +# ---------- Parent 1 (init/Kconfig:1091) ---------- +# +# menuconfig EXPERT +# bool +# prompt "Configure standard kernel features (expert users)" +# select DEBUG_KERNEL +# help +# This option allows certain base kernel options and settings +# to be disabled or tweaked. This is for specialized +# environments which can tolerate a "non-standard" kernel. +# Only use this if you really know what you are doing. +# +# ---------- Parent 2 (init/Kconfig:39) ---------- +# +# menu "General setup" +# +# ========== Location 2 (arch/Kconfig:28) ========== +# +# config OPROFILE_EVENT_MULTIPLEX +# bool +# prompt "OProfile multiplexing support (EXPERIMENTAL)" if OPROFILE && X86 +# default "n" if OPROFILE && X86 +# help +# The number of hardware counters is limited. The multiplexing +# feature enables OProfile to gather more events than counters +# are provided by the hardware. This is realized by switching +# between events at a user specified time interval. +# +# If unsure, say N. +# +# ---------- Parent 1 (arch/Kconfig:15) ---------- +# +# config OPROFILE +# ... (tons more lines) + +from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT, NOT +import sys + +def expr_contains_sym(expr, sym_name): + """ + Returns True if a symbol (or choice, though that's unlikely) with name + 'sym_name' appears in the expression 'expr', and False otherwise. + + Note that "foo" is represented as a constant symbol, like in the C + implementation. + """ + # Choice symbols have a Choice instance propagated to the conditions of + # their properties, so we need this test rather than + # isinstance(expr, Symbol) + if not isinstance(expr, tuple): + return expr.name == sym_name + + if expr[0] == NOT: + return expr_contains_sym(expr[1], sym_name) + + # AND, OR, or relation + return expr_contains_sym(expr[1], sym_name) or \ + expr_contains_sym(expr[2], sym_name) + +def sc_references_sym(sc, sym_name): + """ + Returns True if a symbol with name 'sym_name' appears in any of the + properties or property conditions of the Symbol or Choice 'sc', and False + otherwise. + """ + # Search defaults + for default, cond in sc.defaults: + if expr_contains_sym(default, sym_name) or \ + expr_contains_sym(cond, sym_name): + return True + + if isinstance(sc, Symbol): + # Search selects + for select, cond in sc.selects: + if select.name == sym_name or \ + expr_contains_sym(cond, sym_name): + return True + + # Search implies + for imply, cond in sc.implies: + if imply.name == sym_name or \ + expr_contains_sym(cond, sym_name): + return True + + # Search ranges + for low, high, cond in sc.ranges: + if low.name == sym_name or \ + high.name == sym_name or \ + expr_contains_sym(cond, sym_name): + return True + + return False + +def node_references_sym(node, sym_name): + """ + Returns True if a symbol with name 'sym_name' appears in the prompt + condition of the MenuNode 'node' or in any of the properties of a + symbol/choice stored in the menu node, and False otherwise. + + For MENU menu nodes, also searches the 'visible if' condition. + + Note that prompts are always stored in menu nodes. This is why a symbol can + be defined in multiple locations and have a different prompt in each + location. For MENU and COMMENT menu nodes, the prompt holds the menu title + or comment text. This organization matches the C implementation. + """ + if node.prompt: + # Search the prompt condition + if expr_contains_sym(node.prompt[1], sym_name): + return True + + if isinstance(node.item, (Symbol, Choice)): + # Search symbol or choice + return sc_references_sym(node.item, sym_name) + + if node.item == MENU: + # Search the 'visible if' condition + return expr_contains_sym(node.visibility, sym_name) + + # Comments are already handled by searching the prompt condition, because + # 'depends on' gets propagated to its condition. This is why we don't need + # to look at the direct dependencies for MENU either. + +def nodes_referencing_sym(node, sym_name): + """ + Returns a list of all menu nodes in the menu tree rooted at 'node' that + reference a symbol with name 'sym_name' in any of their properties. Also + checks the properties of any symbols or choices contained in the menu + nodes. + """ + res = [] + + while node: + if node_references_sym(node, sym_name): + res.append(node) + + if node.list: + res.extend(nodes_referencing_sym(node.list, sym_name)) + + node = node.next + + return res + +if len(sys.argv) < 3: + print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=<name>') + sys.exit(1) + +sym_name = sys.argv[2] + +kconf = Kconfig(sys.argv[1]) +nodes = nodes_referencing_sym(kconf.top_node, sym_name) + +if not nodes: + print("No reference to '{}' found".format(sym_name)) + sys.exit() + +print("Found {} locations that reference '{}':\n".format(len(nodes), sym_name)) + +for i, node in enumerate(nodes, 1): + print("========== Location {} ({}:{}) ==========\n".format(i, node.filename, node.linenr)) + print(node) + + parent_i = 0 + + # Print the parents of the menu node too + while True: + node = node.parent + if node is kconf.top_node: + # Don't print the top node. Would say something like the following, + # which isn't that interesting: + # + # menu "Linux/$ARCH $KERNELVERSION Kernel Configuration" + break + + parent_i += 1 + + print("---------- Parent {} ({}:{}) ----------\n" + .format(parent_i, node.filename, node.linenr)) + print(node) diff --git a/examples/help_grep.py b/examples/help_grep.py index 61ac936..01ea5e7 100644 --- a/examples/help_grep.py +++ b/examples/help_grep.py @@ -1,49 +1,72 @@ -# Does a case-insensitive search for a string in the help texts for symbols and -# choices and the titles of menus and comments. Prints the matching items -# together with their locations and the matching text. Used like +# Does a case-insensitive search for a regular expression in the help texts of +# symbols and choices and the prompts of menus and comments. Prints the +# matching items together with their locations and the matching text. # -# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/help_grep.py SCRIPT_ARG=<search text> +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/help_grep.py SCRIPT_ARG=<regex> +# +# Shortened example output for SCRIPT_ARG=general: +# +# menu "General setup" +# location: init/Kconfig:39 +# +# config SYSVIPC +# bool +# prompt "System V IPC" +# help +# ... +# exchange information. It is generally considered to be a good thing, +# ... +# +# location: init/Kconfig:233 +# +# config BSD_PROCESS_ACCT +# bool +# prompt "BSD Process Accounting" if MULTIUSER +# help +# ... +# information. This is generally a good idea, so say Y. +# +# location: init/Kconfig:403 +# +# ... + -import kconfiglib +from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT +import re import sys if len(sys.argv) < 3: - print('Pass search string with SCRIPT_ARG="search string"') + print('Pass the regex with SCRIPT_ARG=regex') sys.exit(1) -search_string = sys.argv[2].lower() - -conf = kconfiglib.Config(sys.argv[1]) - -for item in conf.get_symbols() + \ - conf.get_choices() + conf.get_menus() + conf.get_comments(): - if item.is_symbol() or item.is_choice(): - text = item.get_help() - elif item.is_menu(): - text = item.get_title() - else: - # Comment - text = item.get_text() - - # Case-insensitive search - if text is not None and search_string in text.lower(): - if item.is_symbol() or item.is_choice(): - # Indent lines in help text. (There might be a nicer way. :) - text = "\n".join([" " + s for s in text.splitlines()]) - - # Don't worry about symbols/choices defined in multiple locations to - # keep things simple - fname, linenr = item.get_def_locations()[0] - if item.is_symbol(): - print("config {0} at {1}:{2}:\n{3}" - .format(item.get_name(), fname, linenr, text)) - elif item.is_choice(): - print("choice at {0}:{1}:\n{2}".format(fname, linenr, text)) - - else: - # Menu or comment - fname, linenr = item.get_location() - if item.is_menu(): - print('menu "{0}" at {1}:{2}'.format(text, fname, linenr)) - else: - # Comment - print('comment "{0}" at {1}:{2}'.format(text, fname, linenr)) + +search = re.compile(sys.argv[2], re.IGNORECASE).search + +def search_tree(node): + while node: + match = False + + if isinstance(node.item, (Symbol, Choice)) and \ + node.help is not None and search(node.help): + print(node.item) + match = True + + elif node.item == MENU and search(node.prompt[0]): + print('menu "{}"'.format(node.prompt[0])) + match = True + + elif node.item == COMMENT and search(node.prompt[0]): + print('comment "{}"'.format(node.prompt[0])) + match = True + + if match: + print("location: {}:{}\n".format(node.filename, node.linenr)) + + if node.list: + search_tree(node.list) + + node = node.next + +kconf = Kconfig(sys.argv[1]) +search_tree(kconf.top_node) diff --git a/examples/kconfiglib.py b/examples/kconfiglib.py new file mode 120000 index 0000000..b9dfb64 --- /dev/null +++ b/examples/kconfiglib.py @@ -0,0 +1 @@ +../kconfiglib.py
\ No newline at end of file diff --git a/examples/menuconfig.py b/examples/menuconfig.py new file mode 100644 index 0000000..375b8c8 --- /dev/null +++ b/examples/menuconfig.py @@ -0,0 +1,348 @@ +# Implements a simple configuration interface on top of Kconfiglib to +# demonstrate concepts for building a menuconfig-like. Emulates how the +# standard menuconfig prints menu entries. +# +# Always displays the entire Kconfig tree to keep things as simple as possible +# (all symbols, choices, menus, and comments). +# +# Usage: +# +# $ python(3) Kconfiglib/examples/menuconfig.py <Kconfig file> +# +# A sample Kconfig is available in Kconfiglib/examples/Kmenuconfig. +# +# Here's a notation guide. The notation matches the one used by menuconfig +# (scripts/kconfig/mconf): +# +# [ ] prompt - Bool +# < > prompt - Tristate +# {M} prompt - Tristate selected to m. Can only be set to m or y. +# -*- prompt - Bool/tristate selected to y, pinning it +# -M- prompt - Tristate selected to m that also has m visibility, +# pinning it to m +# (foo) prompt - String/int/hex symbol with value "foo" +# --> prompt - The selected symbol in a choice in y mode. This +# syntax is unique to this example. +# +# When modules are disabled, the .type attribute of TRISTATE symbols and +# choices automatically changes to BOOL. This trick is used by the C +# implementation as well, and gives the expected behavior without having to do +# anything extra here. The original type is available in .orig_type if needed. +# +# The Kconfiglib/examples/Kmenuconfig example uses named choices to be able to +# refer to choices by name. Named choices are supported in the C tools too, but +# I don't think I've ever seen them used in the wild. +# +# Sample session: +# +# $ python Kconfiglib/examples/menuconfig.py Kconfiglib/examples/Kmenuconfig +# +# ======== Example Kconfig configuration ======== +# +# [*] Enable loadable module support (MODULES) +# Bool and tristate symbols +# [*] Bool symbol (BOOL) +# [ ] Dependent bool symbol (BOOL_DEP) +# < > Dependent tristate symbol (TRI_DEP) +# [ ] First prompt (TWO_MENU_NODES) +# < > Tristate symbol (TRI) +# [ ] Second prompt (TWO_MENU_NODES) +# *** These are selected by TRI_DEP *** +# < > Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) +# < > Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) +# String, int, and hex symbols +# (foo) String symbol (STRING) +# (747) Int symbol (INT) +# (0xABC) Hex symbol (HEX) +# Various choices +# -*- Bool choice (BOOL_CHOICE) +# --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) +# Bool choice sym 2 (BOOL_CHOICE_SYM_2) +# {M} Tristate choice (TRI_CHOICE) +# < > Tristate choice sym 1 (TRI_CHOICE_SYM_1) +# < > Tristate choice sym 2 (TRI_CHOICE_SYM_2) +# [ ] Optional bool choice (OPT_BOOL_CHOICE) +# +# Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): BOOL +# Value for BOOL (available: n, y): n +# +# ======== Example Kconfig configuration ======== +# +# [*] Enable loadable module support (MODULES) +# Bool and tristate symbols +# [ ] Bool symbol (BOOL) +# < > Tristate symbol (TRI) +# [ ] Second prompt (TWO_MENU_NODES) +# *** These are selected by TRI_DEP *** +# < > Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) +# < > Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) +# String, int, and hex symbols +# (foo) String symbol (STRING) +# (747) Int symbol (INT) +# (0xABC) Hex symbol (HEX) +# Various choices +# -*- Bool choice (BOOL_CHOICE) +# --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) +# Bool choice sym 2 (BOOL_CHOICE_SYM_2) +# {M} Tristate choice (TRI_CHOICE) +# < > Tristate choice sym 1 (TRI_CHOICE_SYM_1) +# < > Tristate choice sym 2 (TRI_CHOICE_SYM_2) +# [ ] Optional bool choice (OPT_BOOL_CHOICE) +# +# Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): MODULES +# Value for MODULES (available: n, y): n +# +# ======== Example Kconfig configuration ======== +# +# [ ] Enable loadable module support (MODULES) +# Bool and tristate symbols +# [ ] Bool symbol (BOOL) +# [ ] Tristate symbol (TRI) +# [ ] Second prompt (TWO_MENU_NODES) +# *** These are selected by TRI_DEP *** +# [ ] Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) +# [ ] Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) +# String, int, and hex symbols +# (foo) String symbol (STRING) +# (747) Int symbol (INT) +# (0xABC) Hex symbol (HEX) +# Various choices +# -*- Bool choice (BOOL_CHOICE) +# --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) +# Bool choice sym 2 (BOOL_CHOICE_SYM_2) +# -*- Tristate choice (TRI_CHOICE) +# --> Tristate choice sym 1 (TRI_CHOICE_SYM_1) +# Tristate choice sym 2 (TRI_CHOICE_SYM_2) +# [ ] Optional bool choice (OPT_BOOL_CHOICE) +# +# Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): ^D + +from kconfiglib import Kconfig, \ + Symbol, Choice, MENU, COMMENT, \ + BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN, \ + expr_value, \ + TRI_TO_STR, STR_TO_TRI +import readline +import sys + +# Python 2/3 compatibility hack +if sys.version_info[0] < 3: + input = raw_input + +def indent_print(s, indent): + print((" " * indent) + s) + +def value_str(sc): + """ + Returns the value part ("[*]", "<M>", "(foo)" etc.) of a menu entry. + + sc: Symbol or Choice. + """ + if sc.type in (STRING, INT, HEX): + return "({})".format(sc.str_value) + + # BOOL or TRISTATE + + # The choice mode acts as an upper bound on the visibility of choice + # symbols, so we can check the choice symbols' own visibility to see if the + # choice is in y mode + if isinstance(sc, Symbol) and sc.choice and sc.visibility == 2: + # For choices in y mode, print '-->' next to the selected symbol + if sc.choice.selection is sc: + return "-->" + return " " + + tri_val_str = {0: " ", 1: "M", 2: "*"}[sc.tri_value] + + if len(sc.assignable) == 1: + # Pinned to a single value + return "-{}-".format(tri_val_str) + + if sc.type == BOOL: + return "[{}]".format(tri_val_str) + + if sc.type == TRISTATE: + if sc.assignable == (1, 2): + # m and y available + return "{" + tri_val_str + "}" # Gets a bit confusing with .format() + return "<{}>".format(tri_val_str) + +def node_str(node): + """ + Returns the complete menu entry text for a menu node, or "" for invisible + menu nodes. Invisible menu nodes are those that lack a prompt or don't have + a satisfied prompt condition. + + Example return value: "[*] Bool symbol (BOOL)" + + The symbol name is printed in parentheses to the right of the prompt. This + is so that symbols can easily be referred to in the configuration + interface. + """ + if not node.prompt: + return "" + + # Even for menu nodes for symbols and choices, it's wrong to check + # Symbol.visibility / Choice.visibility here. The reason is that a symbol + # (and a choice, in theory) can be defined in multiple locations, giving it + # multiple menu nodes, which do not necessarily all have the same prompt + # visibility. Symbol.visibility / Choice.visibility is calculated as the OR + # of the visibility of all the prompts. + prompt, prompt_cond = node.prompt + if not expr_value(prompt_cond): + return "" + + if node.item == MENU: + return " " + prompt + + if node.item == COMMENT: + return " *** {} ***".format(prompt) + + # Symbol or Choice + + sc = node.item + + if sc.type == UNKNOWN: + # Skip symbols defined without a type + return "" + + # {:3} sets the field width to three. Gives nice alignment for empty string + # values. + res = "{:3} {}".format(value_str(sc), prompt) + + # Don't print the name for unnamed choices (the normal kind) + if sc.name is not None: + res += " ({})".format(sc.name) + + return res + +def print_menuconfig_nodes(node, indent): + """ + Prints a tree with all the menu entries rooted at node. Child menu entries + are indented. + """ + while node: + string = node_str(node) + if string: + indent_print(string, indent) + + if node.list: + print_menuconfig_nodes(node.list, indent + 8) + + node = node.next + +def print_menuconfig(kconf): + """ + Prints all menu entries for the configuration. + """ + # Print the expanded mainmenu text at the top. This is the same as + # kconf.top_node.prompt[0], but with variable references expanded. + print("\n======== {} ========\n".format(kconf.mainmenu_text)) + + print_menuconfig_nodes(kconf.top_node.list, 0) + print("") + +def get_value_from_user(sc): + """ + Prompts the user for a value for the symbol or choice 'sc'. For + bool/tristate symbols and choices, provides a list of all the assignable + values. + """ + if not sc.visibility: + print(sc.name + " is not currently visible") + return False + + prompt = "Value for {}".format(sc.name) + if sc.type in (BOOL, TRISTATE): + prompt += " (available: {})" \ + .format(", ".join([TRI_TO_STR[val] for val in sc.assignable])) + prompt += ": " + + val_str = input(prompt).strip() + if sc.type in (BOOL, TRISTATE): + if val_str not in STR_TO_TRI: + print("'{}' is not a valid tristate value".format(val_str)) + return False + + # I was thinking of having set_value() accept "n", "m", "y" as well as + # a convenience for BOOL / TRISTATE symbols. Consistently using 0, 1, 2 + # makes the format clearer though. That's the best format in all ways + # except for readability (where it isn't horrible either). + val = STR_TO_TRI[val_str] + else: + val = val_str + + # 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 sc.type == HEX and not val.startswith(("0x", "0X")): + val = "0x" + val + + # Let Kconfiglib itself print a warning here if the value is invalid. We + # could also disable warnings temporarily with + # kconf.disable_warnings() / kconf.enable_warnings() and print our own + # warning. + return sc.set_value(val) + +if __name__ == "__main__": + if len(sys.argv) != 2: + sys.exit("usage: menuconfig.py <Kconfig file>") + + # Load Kconfig configuration files + kconf = Kconfig(sys.argv[1]) + + # Print the initial configuration tree + print_menuconfig(kconf) + + while True: + try: + cmd = input('Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): ') \ + .strip() + except EOFError: + print("") + break + + if cmd == "load_config": + config_filename = input(".config file to load: ") + + try: + kconf.load_config(config_filename) + except IOError as e: + # Print the (spammy) error from Kconfiglib itself + print(e.message + "\n") + else: + print("Configuration loaded from " + config_filename) + + print_menuconfig(kconf) + continue + + if cmd == "write_config": + config_filename = input("To this file: ") + + try: + kconf.write_config(config_filename) + except IOError as e: + print(e.message) + else: + print("Configuration written to " + config_filename) + + continue + + # Assume 'cmd' is the name of a symbol or choice if it isn't one of the + # commands above, prompt the user for a value for it, and print the new + # configuration tree + + if cmd in kconf.syms: + if get_value_from_user(kconf.syms[cmd]): + print_menuconfig(kconf) + + continue + + if cmd in kconf.named_choices: + if get_value_from_user(kconf.named_choices[cmd]): + print_menuconfig(kconf) + + continue + + print("No symbol/choice named '{}' in the configuration" + .format(cmd)) diff --git a/examples/print_refs.py b/examples/print_refs.py deleted file mode 100644 index ea62223..0000000 --- a/examples/print_refs.py +++ /dev/null @@ -1,13 +0,0 @@ -# Prints the names of all symbols that reference a particular symbol. (There's -# also a method get_selected_symbols() for determining just selection -# relations.) - -import kconfiglib -import sys - -conf = kconfiglib.Config(sys.argv[1]) - -x86 = conf["X86"] -for sym in conf: - if x86 in sym.get_referenced_symbols(): - print(sym.get_name()) diff --git a/examples/print_sym_info.py b/examples/print_sym_info.py index c913358..3ee3c97 100644 --- a/examples/print_sym_info.py +++ b/examples/print_sym_info.py @@ -1,18 +1,53 @@ -# Loads a Kconfig and a .config and prints information about a symbol. +# Loads a Kconfig and a .config and prints a symbol. +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/print_sym_info.py SCRIPT_ARG=<name> +# +# Example output for SCRIPT_ARG=MODULES: +# +# menuconfig MODULES +# bool +# prompt "Enable loadable module support" +# option modules +# help +# Kernel modules are small pieces of compiled code which can +# be inserted in the running kernel, rather than being +# permanently built into the kernel. You use the "modprobe" +# tool to add (and sometimes remove) them. If you say Y here, +# many parts of the kernel can be built as modules (by +# answering M instead of Y where indicated): this is most +# useful for infrequently used options which are not required +# for booting. For more information, see the man pages for +# modprobe, lsmod, modinfo, insmod and rmmod. +# +# If you say Y here, you will need to run "make +# modules_install" to put the modules under /lib/modules/ +# where modprobe can find them (you may need to be root to do +# this). +# +# If unsure, say Y. +# +# value = n +# visibility = y +# currently assignable values: n, y +# defined at init/Kconfig:1674 -import kconfiglib +from kconfiglib import Kconfig, TRI_TO_STR import sys -# Create a Config object representing a Kconfig configuration. (Any number of -# these can be created -- the library has no global state.) -conf = kconfiglib.Config(sys.argv[1]) +if len(sys.argv) < 3: + print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=<name>') + sys.exit(1) -# Load values from a .config file. 'srctree' is an environment variable set by -# the Linux makefiles to the top-level directory of the kernel tree. It needs -# to be used here for the script to work with alternative build directories -# (specified e.g. with O=). -conf.load_config("$srctree/arch/x86/configs/i386_defconfig") +kconf = Kconfig(sys.argv[1]) +sym = kconf.syms[sys.argv[2]] -# Print some information about a symbol. (The Config class implements -# __getitem__() to provide a handy syntax for getting symbols.) -print(conf["SERIAL_UARTLITE_CONSOLE"]) +print(sym) +print("value = " + sym.str_value) +print("visibility = " + TRI_TO_STR[sym.visibility]) +print("currently assignable values: " + + ", ".join([TRI_TO_STR[v] for v in sym.assignable])) + +for node in sym.nodes: + print("defined at {}:{}".format(node.filename, node.linenr)) diff --git a/examples/print_tree.py b/examples/print_tree.py index 1405ed5..1a53a3a 100644 --- a/examples/print_tree.py +++ b/examples/print_tree.py @@ -1,23 +1,67 @@ -# Prints a tree of all items in the configuration +# Prints the menu tree of the configuration. Dependencies between symbols can +# sometimes implicitly alter the menu structure (see kconfig-language.txt), and +# that's implemented too. +# +# Usage: +# +# $ make [ARCH=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/print_tree.py +# +# Example output: +# +# ... +# config HAVE_KERNEL_LZO +# config HAVE_KERNEL_LZ4 +# choice +# config KERNEL_GZIP +# config KERNEL_BZIP2 +# config KERNEL_LZMA +# config KERNEL_XZ +# config KERNEL_LZO +# config KERNEL_LZ4 +# config DEFAULT_HOSTNAME +# config SWAP +# config SYSVIPC +# config SYSVIPC_SYSCTL +# config POSIX_MQUEUE +# config POSIX_MQUEUE_SYSCTL +# config CROSS_MEMORY_ATTACH +# config FHANDLE +# config USELIB +# config AUDIT +# config HAVE_ARCH_AUDITSYSCALL +# config AUDITSYSCALL +# config AUDIT_WATCH +# config AUDIT_TREE +# menu "IRQ subsystem" +# config MAY_HAVE_SPARSE_IRQ +# config GENERIC_IRQ_LEGACY +# config GENERIC_IRQ_PROBE +# ... -import kconfiglib +from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT import sys -def print_with_indent(s, indent): +def indent_print(s, indent): print((" " * indent) + s) -def print_items(items, indent): - for item in items: - if item.is_symbol(): - print_with_indent("config {0}".format(item.get_name()), indent) - elif item.is_menu(): - print_with_indent('menu "{0}"'.format(item.get_title()), indent) - print_items(item.get_items(), indent + 2) - elif item.is_choice(): - print_with_indent('choice', indent) - print_items(item.get_items(), indent + 2) - elif item.is_comment(): - print_with_indent('comment "{0}"'.format(item.get_text()), indent) - -conf = kconfiglib.Config(sys.argv[1]) -print_items(conf.get_top_level_items(), 0) +def print_items(node, indent): + while node: + if isinstance(node.item, Symbol): + indent_print("config " + node.item.name, indent) + + elif isinstance(node.item, Choice): + indent_print("choice", indent) + + elif node.item == MENU: + indent_print('menu "{0}"'.format(node.prompt[0]), indent) + + elif node.item == COMMENT: + indent_print('comment "{0}"'.format(node.prompt[0]), indent) + + if node.list: + print_items(node.list, indent + 2) + + node = node.next + +kconf = Kconfig(sys.argv[1]) +print_items(kconf.top_node, 0) diff --git a/examples/print_undefined.py b/examples/print_undefined.py deleted file mode 100644 index fb8120b..0000000 --- a/examples/print_undefined.py +++ /dev/null @@ -1,15 +0,0 @@ -# Prints the names of all symbols that are referenced but never defined in the -# current configuration together with the locations where they are referenced. -# Integers being included in the list is not a bug, as these need to be treated -# as symbols per the design of Kconfig. - -import kconfiglib -import sys - -conf = kconfiglib.Config(sys.argv[1]) - -for sym in conf.get_symbols(): - if not sym.is_defined(): - print(sym.get_name()) - for (filename, linenr) in sym.get_ref_locations(): - print(" {0}:{1}".format(filename, linenr)) |
