From dd0e227216e247d2040cdd40bf7397702880cdc4 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Mon, 9 Oct 2017 23:05:00 +0200 Subject: Kconfiglib 2 backup WIP --- examples/allnoconfig.py | 87 +++++++++++++++----------- examples/allnoconfig_simpler.py | 47 ++++++++------ examples/allyesconfig.py | 133 +++++++++++++++++++++++++++------------- examples/defconfig.py | 13 ++-- examples/defconfig_oldconfig.py | 12 ++-- examples/eval_expr.py | 20 +++++- examples/help_grep.py | 109 +++++++++++++++++++------------- examples/print_refs.py | 13 ---- examples/print_sym_info.py | 56 +++++++++++++---- examples/print_tree.py | 80 ++++++++++++++++++------ examples/print_undefined.py | 15 ----- 11 files changed, 376 insertions(+), 209 deletions(-) delete mode 100644 examples/print_refs.py delete mode 100644 examples/print_undefined.py (limited to 'examples') diff --git a/examples/allnoconfig.py b/examples/allnoconfig.py index 9423352..edc473d 100644 --- a/examples/allnoconfig.py +++ b/examples/allnoconfig.py @@ -1,40 +1,57 @@ -# 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 version +# demonstrates some tree walking and value processing. +# +# Usage: +# +# $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py -import kconfiglib +from kconfiglib import Config, Symbol, tri_less 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 +def do_allnoconfig(node): + global no_changes + + # 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 is not None: + if isinstance(node.item, Symbol): + sym = node.item + + # Is the symbol a non-choice symbol that can be set to a lower + # value than its current value? + if sym.choice is None and sym.assignable and \ + tri_less(sym.assignable[0], sym.value): + + # Yup, lower it + sym.set_value(sym.assignable[0]) + no_changes = False + + # Recursively lower children + if node.list is not None: + do_allnoconfig(node.list) + + node = node.next + +conf = Config(sys.argv[1]) + +while 1: + # For tricky dependencies involving '!', setting later symbols to 'n' might + # actually raise the value of earlier symbols. To be super safe, we do + # additional passes until a pass no longer changes the value of any symbol. + # + # This isn't actually needed for any ARCH in the kernel as of 4.14. A + # single pass gives the correct result. + no_changes = True + + do_allnoconfig(conf.top_menu) + + # Did the pass change any symbols? + if no_changes: + break conf.write_config(".config") diff --git a/examples/allnoconfig_simpler.py b/examples/allnoconfig_simpler.py index f3cfe9a..e732669 100644 --- a/examples/allnoconfig_simpler.py +++ b/examples/allnoconfig_simpler.py @@ -1,28 +1,35 @@ # 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=] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig_simpler.py +# +# Implementation/performance note +# =============================== +# +# Kconfiglib immediately invalidates (flags for recalculation) all (possibly) +# dependent symbols when a value is assigned to a symbol, which slows this down +# a bit (due to tons of redundant invalidation), but makes any assignment +# pattern safe ("just works"). Config.load_config() instead invalidates all +# symbols up front, making it much faster. If you really need to eke out +# performance, look at how load_config() does things (which involves internal +# APIs that don't invalidate symbols). This has been fast enough for all cases +# I've seen so far though (around 3 seconds for this particular script on my +# Core i7 2600K, including the initial Kconfig parsing). -# 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 Config, BOOL, TRISTATE import sys -conf = kconfiglib.Config(sys.argv[1]) +conf = Config(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("y" if sym.is_allnoconfig_y else "n") conf.write_config(".config") diff --git a/examples/allyesconfig.py b/examples/allyesconfig.py index 906343c..4847c05 100644 --- a/examples/allyesconfig.py +++ b/examples/allyesconfig.py @@ -1,65 +1,112 @@ -# 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 +# identical output 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 could be implemented as a straightforward tree walk just like +# allnoconfig.py (or even simpler like allnoconfig_simpler.py), but do it a bit +# differently (roundabout) just to demonstrate some other possibilities. # -# 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. +# allyesconfig is a bit more involved than allnoconfig as 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. No "m" mode choices seem to appear +# for allyesconfig on the kernel Kconfigs as of 4.14, but we still handle it. +# +# Usage: +# +# $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py -import kconfiglib +from kconfiglib import Config, Choice, tri_less import sys -conf = kconfiglib.Config(sys.argv[1]) +conf = Config(sys.argv[1]) + +# Collect all the choices in the configuration. Demonstrates how the menu node +# tree can be walked iteratively by using the parent pointers. + +choices = [] +node = conf.top_menu + +while 1: + if isinstance(node.item, Choice): + choices.append(node.item) + + # Iterative tree walking by using parent pointers. + # + # Recursing on next pointers can blow the Python stack. Recursing on child + # pointers is safe (as is done in the other examples). This gives a + # template for how you can avoid recursing on both. The same logic is found + # in the C implementation. + + if node.list is not None: + # Jump to child node if available + node = node.list + + elif node.next is not None: + # Otherwise, jump to next node if available + node = node.next -# 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()] + else: + # Otherwise, look for parents with next nodes to jump to + while node.parent is not None: + node = node.parent + if node.next is not None: + node = node.next + break + else: + # No parents with next nodes, all nodes visited + break -done = False -while not done: - done = True +# Collect all symbols that are not in choices +non_choice_syms = [sym for sym in conf.defined_syms if sym.choice is None] + +while 1: + no_changes = True # Handle symbols outside of choices for sym in non_choice_syms: - upper_bound = sym.get_upper_bound() - - # 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 + # See allnoconfig example. [-1] gives the last (highest) assignable + # value. + if sym.assignable and tri_less(sym.value, sym.assignable[-1]): + sym.set_value(sym.assignable[-1]) + no_changes = False - # Handle symbols within choices + # Handle choices - for choice in conf.get_choices(): + for choice in choices: + # Handle a choice whose visibility allows it to be in "y" mode - # Handle choices whose visibility allow them to be in "y" mode + if choice.visibility == "y": + selection = choice.default_selection - if choice.get_visibility() == "y": - selection = choice.get_selection_from_defaults() + # Does the choice have a default selection that we haven't already + # selected? if selection is not None and \ - selection is not choice.get_user_selection(): - selection.set_user_value("y") - done = False + selection is not choice.user_value: - # Handle choices whose visibility only allow them to be in "m" mode. + # Yup, select it + selection.set_value("y") + no_changes = False + + # Handle a choice whose visibility only allows it to be in "m" mode. # This might happen if a choice depends on a symbol that can only be - # "m" for example. - - 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 + # "m", for example. + + elif choice.visibility == "m": + for sym in choice.symbols: + + # Does the choice have a symbol that can be "m" that we haven't + # already set to "m"? + if sym.user_value != "m" and "m" in sym.assignable: + + # Yup, set it + sym.set_value("m") + no_changes = False + + if no_changes: + break conf.write_config(".config") diff --git a/examples/defconfig.py b/examples/defconfig.py index 3e958e2..ce2bf6e 100644 --- a/examples/defconfig.py +++ b/examples/defconfig.py @@ -1,4 +1,8 @@ # Works like entering "make menuconfig" and immediately saving and exiting +# +# Usage: +# +# $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py import kconfiglib import os @@ -7,11 +11,12 @@ import sys conf = kconfiglib.Config(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..98173e7 100644 --- a/examples/defconfig_oldconfig.py +++ b/examples/defconfig_oldconfig.py @@ -7,6 +7,10 @@ # yes n | make oldconfig # # This came up in https://github.com/ulfalizer/Kconfiglib/issues/15. +# +# Usage: +# +# $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/defconfig_oldconfig.py import kconfiglib import sys @@ -19,15 +23,15 @@ conf.write_config(".config") # Mirrors the first oldconfig conf.load_config(".config") -conf["ETHERNET"].set_user_value('n') +conf.syms["ETHERNET"].set_value('n') conf.write_config(".config") # Mirrors the second oldconfig conf.load_config(".config") -conf["ETHERNET"].set_user_value('y') +conf.syms["ETHERNET"].set_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') + if s.user_value is None and 'n' in s.assignable: + s.set_value('n') # Write the final configuration conf.write_config(".config") diff --git a/examples/eval_expr.py b/examples/eval_expr.py index edb33e6..a907f35 100644 --- a/examples/eval_expr.py +++ b/examples/eval_expr.py @@ -1,8 +1,22 @@ -# 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=] scriptconfig SCRIPT=Kconfiglib/examples/eval_expr.py SCRIPT_ARG= import kconfiglib import sys +if len(sys.argv) < 3: + print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=NAME') + sys.exit(1) + conf = kconfiglib.Config(sys.argv[1]) -print(conf.eval("(TRACE_IRQFLAGS_SUPPORT || PPC32) && STACKTRACE_SUPPORT")) + +# Enable modules so that 'm' doesn't get demoted to 'n' +conf.syms["MODULES"].set_value("y") + +print("the expression '{}' evaluates to {}" + .format(sys.argv[2], conf.eval_string(sys.argv[2]))) diff --git a/examples/help_grep.py b/examples/help_grep.py index 61ac936..fed1731 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=] scriptconfig SCRIPT=Kconfiglib/examples/help_grep.py SCRIPT_ARG= +# Usage: +# +# $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/help_grep.py SCRIPT_ARG= +# +# 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 Config, 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 is not None: + 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 is not None: + search_tree(node.list) + + node = node.next + +conf = Config(sys.argv[1]) +search_tree(conf.top_menu) 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..2c1b0f0 100644 --- a/examples/print_sym_info.py +++ b/examples/print_sym_info.py @@ -1,18 +1,52 @@ -# 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=] scriptconfig SCRIPT=Kconfiglib/examples/print_sym_info.py SCRIPT_ARG= +# +# Example output for SCRIPT_ARG=modules: +# +# config 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:1678 import kconfiglib import sys -# Create a Config object representing a Kconfig configuration. (Any number of -# these can be created -- the library has no global state.) +if len(sys.argv) < 3: + print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=') + sys.exit(1) + conf = kconfiglib.Config(sys.argv[1]) +sym = conf.syms[sys.argv[2]] -# 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") +print(sym) +print("value = " + sym.value) +print("visibility = " + sym.visibility) +print("currently assignable values: " + ", ".join(sym.assignable)) -# Print some information about a symbol. (The Config class implements -# __getitem__() to provide a handy syntax for getting symbols.) -print(conf["SERIAL_UARTLITE_CONSOLE"]) +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..da795c9 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=] 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 Config, 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 is not None: + 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 is not None: + print_items(node.list, indent + 2) + + node = node.next + +conf = Config(sys.argv[1]) +print_items(conf.top_menu, 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)) -- cgit v1.2.3 From 463cebd7f8bf26673732bf5afcd113ee5e2268b7 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Wed, 25 Oct 2017 13:35:31 +0200 Subject: Backup --- examples/allnoconfig.py | 35 +-- examples/allyesconfig.py | 4 +- examples/help_grep.py | 2 +- examples/print_tree.py | 2 +- kconfiglib.py | 637 ++++++++++++++++++++++++++--------------------- testsuite.py | 8 +- 6 files changed, 388 insertions(+), 300 deletions(-) (limited to 'examples') diff --git a/examples/allnoconfig.py b/examples/allnoconfig.py index edc473d..8d41912 100644 --- a/examples/allnoconfig.py +++ b/examples/allnoconfig.py @@ -12,7 +12,7 @@ from kconfiglib import Config, Symbol, tri_less import sys def do_allnoconfig(node): - global no_changes + 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 @@ -22,14 +22,16 @@ def do_allnoconfig(node): if isinstance(node.item, Symbol): sym = node.item - # Is the symbol a non-choice symbol that can be set to a lower - # value than its current value? - if sym.choice is None and sym.assignable and \ - tri_less(sym.assignable[0], sym.value): + # Is the symbol a non-choice, non-allnoconfig_y symbol that can be + # set to a lower value than its current value? + if (sym.choice is None and + not sym.is_allnoconfig_y and + sym.assignable and + tri_less(sym.assignable[0], sym.value)): # Yup, lower it sym.set_value(sym.assignable[0]) - no_changes = False + changed = True # Recursively lower children if node.list is not None: @@ -39,19 +41,22 @@ def do_allnoconfig(node): conf = Config(sys.argv[1]) +# Do an initial pass to set 'option allnoconfig_y' symbols to 'y' +for sym in conf.defined_syms: + if sym.is_allnoconfig_y: + sym.set_value("y") + while 1: - # For tricky dependencies involving '!', setting later symbols to 'n' might - # actually raise the value of earlier symbols. To be super safe, we do - # additional passes until a pass no longer changes the value of any symbol. - # - # This isn't actually needed for any ARCH in the kernel as of 4.14. A - # single pass gives the correct result. - no_changes = True + # 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(conf.top_menu) + do_allnoconfig(conf.top_node) # Did the pass change any symbols? - if no_changes: + if not changed: break conf.write_config(".config") diff --git a/examples/allyesconfig.py b/examples/allyesconfig.py index 4847c05..32b302c 100644 --- a/examples/allyesconfig.py +++ b/examples/allyesconfig.py @@ -27,7 +27,7 @@ conf = Config(sys.argv[1]) # tree can be walked iteratively by using the parent pointers. choices = [] -node = conf.top_menu +node = conf.top_node while 1: if isinstance(node.item, Choice): @@ -85,7 +85,7 @@ while 1: # Does the choice have a default selection that we haven't already # selected? if selection is not None and \ - selection is not choice.user_value: + selection is not choice.user_selection: # Yup, select it selection.set_value("y") diff --git a/examples/help_grep.py b/examples/help_grep.py index fed1731..6fcb08f 100644 --- a/examples/help_grep.py +++ b/examples/help_grep.py @@ -69,4 +69,4 @@ def search_tree(node): node = node.next conf = Config(sys.argv[1]) -search_tree(conf.top_menu) +search_tree(conf.top_node) diff --git a/examples/print_tree.py b/examples/print_tree.py index da795c9..8d18ce9 100644 --- a/examples/print_tree.py +++ b/examples/print_tree.py @@ -64,4 +64,4 @@ def print_items(node, indent): node = node.next conf = Config(sys.argv[1]) -print_items(conf.top_menu, 0) +print_items(conf.top_node, 0) diff --git a/kconfiglib.py b/kconfiglib.py index e4d0b58..04edb67 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -1,4 +1,7 @@ """ +Overview +======== + Kconfiglib is a Python 2/3 library for scripting and extracting information from Kconfig-based configuration systems. Features include the following: @@ -6,40 +9,79 @@ from Kconfig-based configuration systems. Features include the following: - Reading/writing of .config files - - Expression inspection and evaluation. All expressions are exposed and use a - simple format that can be processed manually if needed. + - Inspection of symbol properties: print()ing a symbol (which calls __str__()) + produces output which could be fed back into a Kconfig parser to redefine + the symbol, and __str__() is implemented with only public APIs. + + A helpful __repr__() is implemented on all objects as well. + + - Expression inspection and evaluation: All expressions are exposed and use a + simple tuple-based format that can be processed manually if needed. + + - Menu tree inspection: The underlying menu tree is exposed, including + submenus created implicitly from symbols depending on preceding symbols. + This can be used e.g. to implement menuconfig-like functionality. - - Menu tree inspection. The underlying menu tree is exposed, including - submenus created implicitly by symbols depending on preceding symbols. This - can be used e.g. to implement menuconfig-like functionality. + - Runs under both Python 2 and 3. The code mostly uses basic Python features + (the most advanced things used are probably @property and __slots__). - - Highly compatible with the standard Kconfig C tools: The test suite compares - outputs between Kconfiglib and the C tools on real-world kernel Kconfig and - defconfig files for a large number of cases (by diffing generated .configs). + - Robust and highly compatible with the standard Kconfig C tools: The test + suite automatically compares the output from Kconfiglib with the output from + the C tools on the real kernel Kconfig and defconfig files for all ARCHes. + The comparison is done by diffing the generated .config files to make sure + they're identical. All tests are expected to pass. + + A suite of self tests is also included. + + - Internals that (mostly) mirror the C implementation. A lot can indirectly be + learned about how it works by reading the Kconfiglib documentation and code. - Pretty speedy by pure Python standards: Parses the x86 Kconfigs in about a second on a Core i7 2600K (with a warm file cache). For long-running jobs, - PyPy gives a nice speedup. + PyPy gives a large performance boost. + + +Using Kconfiglib on the Linux kernel +==================================== For the Linux kernel, a handy interface is provided by the -scripts/kconfig/Makefile patch. For experimentation, you can use the -iscriptconfig target, which gives an interactive Python prompt where the -configuration for ARCH has been loaded: +scripts/kconfig/Makefile patch. - $ make [ARCH=] iscriptconfig +Use the 'iscriptconfig' target for experimentation. It gives an interactive +Python prompt where the configuration for ARCH has been preloaded. -To run a script, use the scriptconfig target: + $ make [ARCH=] [PYTHONCMD=] iscriptconfig - $ make [ARCH=] scriptconfig SCRIPT= [SCRIPT_ARG=] +To run a script, use the 'scriptconfig' target. + + $ make [ARCH=] [PYTHONCMD=] scriptconfig SCRIPT= [SCRIPT_ARG=] + +PYTHONCMD is the Python interpreter to use. It defaults to "python". + +Tip: IronPython (PYTHONCMD=ipython) autocompletion is handy when figuring out +the API. + + +Writing scriptconfig scripts +---------------------------- See the examples/ subdirectory for example scripts. +Scripts receive the name of the Kconfig file to load in sys.argv[1]. As far as +I can tell, this is always "Kconfig" from the kernel top-level directory as of +Linux 4.14. If an argument is provided with SCRIPT_ARG, it appears as +sys.argv[2]. + + +Running Kconfiglib without the Makefile patch +--------------------------------------------- The Makefile patch is used to pick up the ARCH, SRCARCH, and KERNELVERSION environment variables (and any future environment variables that might get used). If you want to run Kconfiglib without the Makefile patch, the following will probably work in practice (it's what the test suite does in 'speedy' mode, -except it tests all ARCHes): +except it tests all ARCHes and doesn't bother setting KERNELVERSION to a sane +value to save some time on startup). $ ARCH=x86 SRCARCH=x86 KERNELVERSION=`make kernelversion` python script.py @@ -49,18 +91,40 @@ possible variations. The value of KERNELVERSION doesn't seem to matter as of Linux 4.14. Kconfiglib will warn if you forget to set some environment variable that's -referenced in the configuration (via 'option env="ENV_VAR"'). +referenced in a Kconfig file (via 'option env="ENV_VAR"'). -When using scriptconfig, scripts receive the name of the Kconfig file to load -in sys.argv[1]. As far as I can tell, this is always "Kconfig" from the kernel -top-level directory as of Linux 4.14. If an argument is provided with -SCRIPT_ARG, it appears as sys.argv[2]. +Expression format +================= +The following table should help you figure out how expressions are represented. +A, B, C, ... are symbols (Symbol instances) or strings (which represent +constant symbols). NOT is the kconfiglib.NOT constant, etc. -Kconfiglib supports both Python 2 and Python 3 (and PyPy). For (i)scriptconfig, -the Python interpreter to use can be passed in PYTHONCMD, which defaults to -"python". +Expression Representation +---------- -------------- +A A +!A (NOT, A) +A && B (AND, A, B) +A || B (OR, A, B) +A = B (EQUAL, A, B) +A != "foo" (UNEQUAL, A, "foo") +A || (B && C && D) (OR, A, (AND, B, (AND, C, D))) +y "y" +"y" "y" + +As seen in the final two examples, n/m/y are always represented as the strings +(constant symbols) "n"/"m"/"y" in Kconfiglib, regardless of whether they're +written with or without quotes. This simplifies some internals. + +A missing expression (e.g. if the 'if ' part is removed from +'default A if ') is represented as "y". The standard __str__() functions +avoid printing 'if y' conditions to give cleaner output. + +Implementation note +------------------- + +TODO: blah blah constant symbols Send bug reports, suggestions, and questions to ulfalizer a.t Google's email @@ -97,58 +161,70 @@ class Config(object): The following attributes are available on Config instances. They should be viewed as read-only, and some are implemented through @property magic. - Modifying symbols is fine, but not the 'syms' dictionary itself. syms: A dictionary with all symbols in the configuration. The key is the name - of the symbol, so that e.g. conf.syms["MODULES"] returns the MODULES - symbol. Symbols that are referenced in expressions but never defined are - included as well. + of the symbol, so that e.g. conf.syms["FOO"] returns the Symbol instance + for the symbol FOO. Symbols that are referenced in expressions but never + defined also appear in 'syms'. + + Constant symbols, e.g. "foo" in 'A = "foo"', are not included in + Config.syms. defined_syms: A list of all defined symbols, in the same order as they appear in the - Kconfig files. Provided as a convenience (and also used internally). The - defined symbols are those whose 'nodes' attribute is non-empty. + Kconfig files. Provided as a convenience. The defined symbols are those + whose 'nodes' attribute is non-empty. named_choices: - A dictionary like 'syms' for named choices (choice FOO). This is mostly - for completeness. I've never seen named choices being used. + A dictionary like 'syms' for named choices (choice FOO). This is for + completeness. I've never seen named choices being used. - top_menu: - The menu node (see the MenuNode class) of the top-level menu. Acts as the - root of the menu tree. + modules: + The Symbol instance for the modules symbol. This is currently hardcoded + to MODULES, which is backwards compatible, and Kconfiglib will warn if + 'option modules' is specified on some other symbol. Tell me if you need + proper 'option modules' support. - mainmenu_text: - The prompt (title) of the top_menu menu, with Kconfig variable references - ("$FOO") expanded. Defaults to "Linux Kernel Configuration" (like in the - C tools). Can be changed with the 'mainmenu' statement (see - kconfig-language.txt). + Never None. + + defconfig_list: + The Symbol instance for the 'option defconfig_list' symbol, or None if no + defconfig_list symbol exists. The defconfig filename derived from this + symbol can be found in Config.defconfig_filename. + + Setting 'option defconfig_list' on multiple symbols ignores the setting + on all symbols after the first. defconfig_filename: - The filename given by the 'option defconfig_list' symbol. This is the - first existing file with a satisfied condition among the 'default' - properties of the symbol. If a file is not found at the given path, it is - also looked up relative to $srctree if set ($srctree/foo/defconfig is - looked up if foo/defconfig is not found). + The filename given by the 'option defconfig_list' symbol. This is taken + from the first 'default' with a satisfied condition where the file + specified by the 'default' exists. If a defconfig file foo/defconfig is + not found and $srctree was set when the Config was created, + $srctree/foo/defconfig is looked up as well. - Has the value None if either no defconfig_list symbol exists, or if it - has no 'default' with a satisfied dependency that points to an existing - file. + None if either no defconfig_list symbol exists, or if the defconfig_list + symbol has no 'default' with a satisfied condition that points to an + existing file. References to Kconfig symbols ("$FOO") are expanded in 'default' properties. - Setting 'option defconfig_list' on multiple symbols ignores symbols past - the first one. + Something to look out for is that scripts/kconfig/Makefile might pass + --defconfig= to scripts/kconfig/conf when running e.g. + 'make defconfig'. This option overrides the defconfig_list symbol, + meaning defconfig_filename might not match what 'make defconfig' would + use in those cases. - Do print(c.syms["DEFCONFIG_LIST"]) on a kernel configuration to see an - example of a defconfig_list symbol. + top_node: + The menu node (see the MenuNode class) of the top-level menu. Acts as the + root of the menu tree. - Something to look out for is that scripts/kconfig/Makefile might use the - --defconfig= option when calling the C tools of e.g. 'make - defconfig'. This option overrides the 'option defconfig_list' symbol, - meaning defconfig_filename might not match what 'make defconfig' would - use. + mainmenu_text: + The prompt (title) of the top_node menu, with Kconfig variable references + ("$FOO") expanded. Defaults to "Linux Kernel Configuration" (like in the + C tools). Can be changed with the 'mainmenu' statement (see + kconfig-language.txt). srctree: The value of the $srctree environment variable when the configuration was @@ -157,9 +233,9 @@ class Config(object): (unless absolute paths are specified). This is to support out-of-tree builds. The C tools use this variable in the same way. - Changing $srctree after loading the configuration has no effect. Only the - value when the configuration is loaded matters. This avoids surprises if - multiple configurations are loaded with different values for $srctree. + Changing $srctree after creating the Config instance has no effect. Only + the value when the configuration is loaded matters. This avoids surprises + if multiple configurations are loaded with different values for $srctree. config_prefix: The value of the $CONFIG_ environment variable when the configuration was @@ -183,7 +259,7 @@ class Config(object): "named_choices", "srctree", "syms", - "top_menu", + "top_node", ) # @@ -199,13 +275,15 @@ class Config(object): filename (default: "Kconfig"): The base Kconfig file. For the Linux kernel, you'll want "Kconfig" from the top-level directory, as environment variables will make sure - the right Kconfig is included from there - (arch//Kconfig). If you are using Kconfiglib via 'make - scriptconfig', the filename of the base base Kconfig file will be in - sys.argv[1] (always "Kconfig" in practice). + the right Kconfig is included from there (arch/$SRCARCH/Kconfig as of + writing). + + If you are using Kconfiglib via 'make scriptconfig', the filename of + the base base Kconfig file will be in sys.argv[1]. It's currently + always "Kconfig" in practice. - The $srctree environment variable is used if set (see the class - documentation). + The $srctree environment variable is used to look up Kconfig files if + set (see the class documentation). warn (default: True): True if warnings related to this configuration should be printed to @@ -214,67 +292,66 @@ class Config(object): argument since warnings might be generated during parsing. """ + self.srctree = os.environ.get("srctree") + + self.config_prefix = os.environ.get("CONFIG_") + if self.config_prefix is None: + self.config_prefix = "CONFIG_" + + # Regular expressions for parsing .config files + self._set_re = re.compile(r"{}(\w+)=(.*)" + .format(self.config_prefix)) + self._unset_re = re.compile(r"# {}(\w+) is not set" + .format(self.config_prefix)) + + self._print_warnings = warn + self._print_undef_assign = False + self.syms = {} self.defined_syms = [] self.named_choices = {} - # Used for quickly invalidating all choices self._choices = [] - # Predefined symbol. DEFCONFIG_LIST has been seen using this. + self.modules = self._lookup_sym("MODULES") + self.defconfig_list = None + + # Predefined symbol. DEFCONFIG_LIST uses this. uname_sym = Symbol() uname_sym._type = STRING uname_sym.name = "UNAME_RELEASE" uname_sym.config = self - uname_sym.defaults.append((platform.uname()[2], None)) + uname_sym.defaults.append((platform.uname()[2], "y")) # env_var doubles as the SYMBOL_AUTO flag from the C implementation, so # just set it to something. The naming breaks a bit here, but it's # pretty obscure. uname_sym.env_var = "" self.syms["UNAME_RELEASE"] = uname_sym - # The symbol with "option defconfig_list" set, containing a list of - # default .config files - self.defconfig_list = None - - self.config_prefix = os.environ.get("CONFIG_") - if self.config_prefix is None: - self.config_prefix = "CONFIG_" - - # Regular expressions for parsing .config files - self._set_re = re.compile(r"{}(\w+)=(.*)" - .format(self.config_prefix)) - self._unset_re = re.compile(r"# {}(\w+) is not set" - .format(self.config_prefix)) - - self.srctree = os.environ.get("srctree") - - self._print_warnings = warn - self._print_undef_assign = False - - self.top_menu = MenuNode() - self.top_menu.config = self - self.top_menu.item = MENU - self.top_menu.visibility = None - self.top_menu.prompt = ("Linux Kernel Configuration", None) - self.top_menu.parent = None - self.top_menu.dep = None - self.top_menu.filename = filename - self.top_menu.linenr = 1 - - # We hardcode MODULES for backwards compatibility. Proper support via - # 'option modules' wouldn't be that tricky to add with backwards - # compatibility either though. - self.modules = self._lookup_sym("MODULES") + self.top_node = MenuNode() + self.top_node.config = self + self.top_node.item = MENU + self.top_node.visibility = None + self.top_node.prompt = ("Linux Kernel Configuration", "y") + self.top_node.parent = None + self.top_node.dep = "y" + self.top_node.filename = filename + self.top_node.linenr = 1 # Parse the Kconfig files self._parse_block(_FileFeed(self._open(filename), filename), - None, self.top_menu, None, None, self.top_menu) + None, # end_token + self.top_node, # parent + "y", # visible_if_deps + None, # prev_line + self.top_node) # prev_node - self.top_menu.list = self.top_menu.next - self.top_menu.next = None + self.top_node.list = self.top_node.next + self.top_node.next = None - _finalize_tree(self.top_menu) + # Do various post-processing of the menu tree, e.g. to finalize + # choices, flatten ifs, and implicitly create menus + _finalize_tree(self.top_node) # Build Symbol._direct_dependents for all symbols self._build_dep() @@ -284,7 +361,7 @@ class Config(object): """ See the class documentation. """ - return self._expand_sym_refs(self.top_menu.prompt[0]) + return self._expand_sym_refs(self.top_node.prompt[0]) @property def defconfig_filename(self): @@ -727,7 +804,7 @@ class Config(object): return _Feed(tokens) - def _parse_block(self, line_feeder, end_marker, parent, visible_if_deps, + def _parse_block(self, line_feeder, end_token, parent, visible_if_deps, prev_line, prev_node): """ Parses a block, which is the contents of either a file or an if, menu, @@ -737,7 +814,7 @@ class Config(object): A _FileFeed instance feeding lines from a file. The Kconfig language is line-based in practice. - end_marker: + end_token: The token that ends the block, e.g. _T_ENDIF ("endif") for ifs. None for files. @@ -774,7 +851,7 @@ class Config(object): else: line = line_feeder.next() if line is None: - if end_marker is not None: + if end_token is not None: raise KconfigSyntaxError("Unexpected end of file " + line_feeder.filename) @@ -837,11 +914,14 @@ class Config(object): e.message)) prev_node = self._parse_block(_FileFeed(f, exp_kconfig_file), - None, parent, visible_if_deps, - None, prev_node) + None, # end_token + parent, + visible_if_deps, + None, # prev_line + prev_node) prev_line = None - elif t0 == end_marker: + elif t0 == end_token: # We have reached the end of the block. Terminate the final # node and return it. prev_node.next = None @@ -860,8 +940,12 @@ class Config(object): line_feeder.filename, line_feeder.linenr, True)) - self._parse_block(line_feeder, _T_ENDIF, node, visible_if_deps, - None, node) + self._parse_block(line_feeder, + _T_ENDIF, + node, # parent + visible_if_deps, + None, # prev_line + node) # prev_node node.list = node.next prev_line = None @@ -872,7 +956,7 @@ class Config(object): node = MenuNode() node.config = self node.item = MENU - node.visibility = None + node.visibility = "y" node.parent = parent node.filename = line_feeder.filename node.linenr = line_feeder.linenr @@ -881,9 +965,12 @@ class Config(object): visible_if_deps) node.prompt = (tokens.next(), node.dep) - self._parse_block(line_feeder, _T_ENDMENU, node, + self._parse_block(line_feeder, + _T_ENDMENU, + node, # parent _make_and(visible_if_deps, node.visibility), - prev_line, node) + prev_line, + node) # prev_node node.list = node.next prev_line = None @@ -931,8 +1018,12 @@ class Config(object): prev_line = self._parse_properties(line_feeder, node, visible_if_deps) - self._parse_block(line_feeder, _T_ENDCHOICE, node, - visible_if_deps, prev_line, node) + self._parse_block(line_feeder, + _T_ENDCHOICE, + node, # parent + visible_if_deps, + prev_line, + node) # prev_node node.list = node.next prev_line = None @@ -942,9 +1033,9 @@ class Config(object): prev_node.next = prev_node = node elif t0 == _T_MAINMENU: - self.top_menu.prompt = (tokens.next(), None) - self.top_menu.filename = line_feeder.filename - self.top_menu.linenr = line_feeder.linenr + self.top_node.prompt = (tokens.next(), "y") + self.top_node.filename = line_feeder.filename + self.top_node.linenr = line_feeder.linenr else: _parse_error(line, "unrecognized construct", @@ -953,15 +1044,15 @@ class Config(object): def _parse_cond(self, tokens, line, filename, linenr): """ Parses an optional 'if ' construct and returns the parsed , - or None if the next token is not _T_IF + or "y" if the next token is not _T_IF """ return self._parse_expr(tokens, line, filename, linenr, True) \ - if tokens.check(_T_IF) else None + if tokens.check(_T_IF) else "y" def _parse_val_and_cond(self, tokens, line, filename, linenr): """ Parses ' if ' constructs, where the 'if' part is - optional. Returns a tuple containing the parsed expressions, with None + optional. Returns a tuple containing the parsed expressions, with "y" as the second element if the 'if' part is missing. """ return (self._parse_expr(tokens, line, filename, linenr, False), @@ -1001,7 +1092,7 @@ class Config(object): # Menu node dependency from 'depends on'. Will get propagated to the # properties above. - node.dep = None + node.dep = "y" # The cached (line, tokens) tuple that we return last_line = None @@ -1145,7 +1236,7 @@ class Config(object): "service.".format(node.item.name, env_var), filename, linenr) else: - defaults.append((os.environ[env_var], None)) + defaults.append((os.environ[env_var], "y")) elif tokens.check(_T_DEFCONFIG_LIST): if self.defconfig_list is None: @@ -1272,12 +1363,12 @@ class Config(object): Parses an expression from the tokens in 'feed' using a simple top-down approach. The result has the form '( )' where is e.g. - kconfiglib._AND. If there is only one operand (i.e., no && or ||), then + kconfiglib.AND. If there is only one operand (i.e., no && or ||), then the operand is returned directly. This also goes for subexpressions. As an example, A && B && (!C || D == 3) is represented as the tuple - structure (_AND, A, (_AND, B, (_OR, (_NOT, C), (_EQUAL, D, 3)))), with - the Symbol objects stored directly in the expression. + structure (AND, A, (AND, B, (OR, (NOT, C), (EQUAL, D, 3)))), with the + Symbol objects stored directly in the expression. feed: _Feed instance containing the tokens for the expression. @@ -1326,24 +1417,23 @@ class Config(object): transform_m) # Return 'and_expr' directly if we have a "single-operand" OR. - # Otherwise, parse the expression on the right and make an _OR node. - # This turns A || B || C || D into - # (_OR, A, (_OR, B, (_OR, C, D))). + # Otherwise, parse the expression on the right and make an OR node. + # This turns A || B || C || D into (OR, A, (OR, B, (OR, C, D))). return and_expr \ if not feed.check(_T_OR) else \ - (_OR, and_expr, self._parse_expr(feed, line, filename, linenr, - transform_m)) + (OR, and_expr, self._parse_expr(feed, line, filename, linenr, + transform_m)) def _parse_and_expr(self, feed, line, filename, linenr, transform_m): factor = self._parse_factor(feed, line, filename, linenr, transform_m) # Return 'factor' directly if we have a "single-operand" AND. - # Otherwise, parse the right operand and make an _AND node. This turns - # A && B && C && D into (_AND, A, (_AND, B, (_AND, C, D))). + # Otherwise, parse the right operand and make an AND node. This turns + # A && B && C && D into (AND, A, (AND, B, (AND, C, D))). return factor \ if not feed.check(_T_AND) else \ - (_AND, factor, self._parse_and_expr(feed, line, filename, - linenr, transform_m)) + (AND, factor, self._parse_and_expr(feed, line, filename, + linenr, transform_m)) def _parse_factor(self, feed, line, filename, linenr, transform_m): token = feed.next() @@ -1359,7 +1449,7 @@ class Config(object): # '... if ', etc.), "m" and m are rewritten to # "m" && MODULES. if transform_m and token == "m": - return (_AND, "m", self.modules) + return (AND, "m", self.modules) return token @@ -1367,8 +1457,8 @@ class Config(object): return (_TOKEN_TO_REL[feed.next()], token, feed.next()) if token == _T_NOT: - return (_NOT, self._parse_factor(feed, line, filename, linenr, - transform_m)) + return (NOT, self._parse_factor(feed, line, filename, linenr, + transform_m)) if token == _T_OPEN_PAREN: expr_parse = self._parse_expr(feed, line, filename, @@ -1414,7 +1504,7 @@ class Config(object): config_strings = [] add_fn = config_strings.append - node = self.top_menu.list + node = self.top_node.list if node is None: # Empty configuration return config_strings @@ -1424,7 +1514,7 @@ class Config(object): # .config entry. We reset it prior to writing out a new .config. It # only needs to be reset for defined symbols, because undefined symbols # will never be written out (because they do not appear structure - # rooted at Config.top_menu). + # rooted at Config.top_node). # # The C tools reuse _write_to_conf for this, but we cache # _write_to_conf together with the value and don't invalidate cached @@ -1731,12 +1821,18 @@ class Symbol(object): locations, the dependencies at each location are ORed together. env_var: - If the Symbol is set from the environment via 'option env="FOO"', this - contains the name ("FOO") of the environment variable. None for symbols - that aren't set from the environment. + If the Symbol has an 'option env="FOO"' option, this contains the name + ("FOO") of the environment variable. None for symbols that aren't set + from the environment. + + 'option env="FOO"' acts as a 'default' property whose value is the value + of $FOO. - Internally, this is only used to print the symbol. The value of the - environment variable is looked up once when the configuration is parsed. + env_var is also set (to "") on the predefined symbol + UNAME_RELEASE, which holds the 'release' field from uname. + + Symbols with an 'option env' option are never written out to .config + files. is_allnoconfig_y: True if the symbol has 'option allnoconfig_y' set on it. This has no @@ -1815,7 +1911,7 @@ class Symbol(object): if vis != "n" and self.user_value is not None: # If the symbol is visible and has a user value, we use # that - val = _eval_min(self.user_value, vis) + val = _tri_min(self.user_value, vis) else: # Otherwise, we look at defaults and weak reverse @@ -1825,7 +1921,7 @@ class Symbol(object): cond_val = eval_expr(cond) if cond_val != "n": self._write_to_conf = True - val = _eval_min(default, cond_val) + val = _tri_min(eval_expr(default), cond_val) break # Weak reverse dependencies are only considered if our @@ -1835,13 +1931,13 @@ class Symbol(object): eval_expr(self.weak_rev_dep) if weak_rev_dep_val != "n": self._write_to_conf = True - val = _eval_max(val, weak_rev_dep_val) + val = _tri_max(val, weak_rev_dep_val) # Reverse (select-related) dependencies take precedence rev_dep_val = eval_expr(self.rev_dep) if rev_dep_val != "n": self._write_to_conf = True - val = _eval_max(val, rev_dep_val) + val = _tri_max(val, rev_dep_val) else: # (bool/tristate) symbol in choice. See _get_visibility() for @@ -2082,7 +2178,7 @@ class Symbol(object): if self.is_allnoconfig_y: fields.append("allnoconfig_y") - if self is self.config.defconfig_list: + if self is self.config.defconfig_list: fields.append("is the defconfig_list symbol") if self.env_var is not None: @@ -2239,8 +2335,8 @@ class Symbol(object): if self.choice is not None and self._type in (BOOL, TRISTATE): if value == "y": - self.choice.user_selection = self self.choice.user_value = "y" + self.choice.user_selection = self elif value == "m": self.choice.user_value = "m" @@ -2441,7 +2537,7 @@ class Choice(object): See the class documentation. """ if self.user_value is not None: - val = _eval_min(self.user_value, self.visibility) + val = _tri_min(self.user_value, self.visibility) else: val = "n" @@ -2592,8 +2688,8 @@ class Choice(object): self.nodes = [] - self.user_selection = None self.user_value = None + self.user_selection = None # The prompts and default values without any dependencies from # enclosing menus and ifs propagated @@ -2638,7 +2734,7 @@ class MenuNode(object): menu node for each location. The top-level menu node, corresponding to the implicit top-level menu, is - available in Config.top_menu. + available in Config.top_node. For symbols and choices, the menu nodes are available in the 'nodes' attribute. Menus and comments are represented as plain menu nodes, with @@ -2819,12 +2915,9 @@ def tri_greater_eq(v1, v2): def eval_expr(expr): """ - Evaluates an expression to "n", "m", or "y". Returns "y" for None, which - makes sense as None usually indicates a missing condition. + Evaluates an expression to "n", "m", or "y". """ - return "y" if expr is None else _eval_expr_rec(expr) -def _eval_expr_rec(expr): if isinstance(expr, Symbol): # Non-bool/tristate symbols are always "n" in a tristate sense, # regardless of their value @@ -2833,28 +2926,22 @@ def _eval_expr_rec(expr): if isinstance(expr, str): return expr if expr in ("m", "y") else "n" - if expr[0] == _AND: - ev1 = _eval_expr_rec(expr[1]) - if ev1 == "n": - # No need to look at expr[2] - return "n" - ev2 = _eval_expr_rec(expr[2]) - return ev2 if ev1 == "y" else \ - "m" if ev2 != "n" else \ - "n" - - if expr[0] == _OR: - ev1 = _eval_expr_rec(expr[1]) - if ev1 == "y": - # No need to look at expr[2] - return "y" - ev2 = _eval_expr_rec(expr[2]) - return ev2 if ev1 == "n" else \ - "y" if ev2 == "y" else \ - "m" + if expr[0] == AND: + ev1 = eval_expr(expr[1]) + + # Short-circuit the ev1 == "n" case + return "n" if ev1 == "n" else \ + _tri_min(ev1, eval_expr(expr[2])) - if expr[0] == _NOT: - ev = _eval_expr_rec(expr[1]) + if expr[0] == OR: + ev1 = eval_expr(expr[1]) + + # Short-circuit the ev1 == "y" case + return "y" if ev1 == "y" else \ + _tri_max(ev1, eval_expr(expr[2])) + + if expr[0] == NOT: + ev = eval_expr(expr[1]) return "n" if ev == "y" else \ "y" if ev == "n" else \ "m" @@ -2884,16 +2971,16 @@ def _eval_expr_rec(expr): # They're not both valid numbers. If the comparison is # anything but = or !=, return 'n'. Otherwise, reuse # _strcmp() to check for (in)equality. - if oper not in (_EQUAL, _UNEQUAL): + if oper not in (EQUAL, UNEQUAL): return "n" comp = _strcmp(op1_str, op2_str) - if oper == _EQUAL: res = comp == 0 - elif oper == _UNEQUAL: res = comp != 0 - elif oper == _LESS: res = comp < 0 - elif oper == _LESS_EQUAL: res = comp <= 0 - elif oper == _GREATER: res = comp > 0 - elif oper == _GREATER_EQUAL: res = comp >= 0 + if oper == EQUAL: res = comp == 0 + elif oper == UNEQUAL: res = comp != 0 + elif oper == LESS: res = comp < 0 + elif oper == LESS_EQUAL: res = comp <= 0 + elif oper == GREATER: res = comp > 0 + elif oper == GREATER_EQUAL: res = comp >= 0 return "y" if res else "n" @@ -2997,7 +3084,7 @@ def _get_visibility(sc): for node in sc.nodes: if node.prompt: - vis = _eval_max(vis, node.prompt[1]) + vis = _tri_max(vis, eval_expr(node.prompt[1])) if isinstance(sc, Symbol) and sc.choice is not None: if sc.choice._type == TRISTATE and sc._type != TRISTATE and \ @@ -3011,7 +3098,7 @@ def _get_visibility(sc): # choice has mode "y" return "n" - vis = _eval_min(vis, sc.choice.visibility) + vis = _tri_min(vis, sc.choice.visibility) # Promote "m" to "y" if we're dealing with a non-tristate. This might lead # to infinite recursion if something really weird is done with MODULES, but @@ -3024,48 +3111,45 @@ def _get_visibility(sc): def _make_and(e1, e2): """ - Constructs an _AND (&&) expression. Performs trivial simplification. Nones - equate to 'y'. - - Returns None if e1 == e2 == None, so that ANDing two nonexistent - expressions gives a nonexistent expression. + Constructs an AND (&&) expression. Performs trivial simplification. """ - if e1 is None or e1 == "y": + if e1 == "y": return e2 - if e2 is None or e2 == "y": + + if e2 == "y": return e1 - return (_AND, e1, e2) + + if e1 == "n" or e2 == "n": + return "n" + + return (AND, e1, e2) def _make_or(e1, e2): """ - Constructs an _OR (||) expression. Performs trivial simplification and - avoids Nones. Nones equate to 'y', which is usually what we want, but needs - to be kept in mind. + Constructs an OR (||) expression. Performs trivial simplification. """ - - # Perform trivial simplification and avoid None's (which - # correspond to y's) - if e1 is None or e2 is None or e1 == "y" or e2 == "y": - return "y" if e1 == "n": return e2 - return (_OR, e1, e2) -def _eval_min(e1, e2): + if e2 == "n": + return e1 + + if e1 == "y" or e2 == "y": + return "y" + + return (OR, e1, e2) + +def _tri_min(v1, v2): """ - Returns the minimum value of the two expressions. Equates None with 'y'. + Returns the smallest tristate value among v1 and v2. """ - e1_eval = eval_expr(e1) - e2_eval = eval_expr(e2) - return e1_eval if tri_less(e1_eval, e2_eval) else e2_eval + return v1 if _TRI_TO_INT[v1] <= _TRI_TO_INT[v2] else v2 -def _eval_max(e1, e2): +def _tri_max(v1, v2): """ - Returns the maximum value of the two expressions. Equates None with 'y'. + Returns the largest tristate value among v1 and v2. """ - e1_eval = eval_expr(e1) - e2_eval = eval_expr(e2) - return e1_eval if tri_greater(e1_eval, e2_eval) else e2_eval + return v1 if _TRI_TO_INT[v1] >= _TRI_TO_INT[v2] else v2 def _expr_syms_rec(expr, res): """ @@ -3075,10 +3159,10 @@ def _expr_syms_rec(expr, res): res.append(expr) elif isinstance(expr, str): return - elif expr[0] in (_AND, _OR): + elif expr[0] in (AND, OR): _expr_syms_rec(expr[1], res) _expr_syms_rec(expr[2], res) - elif expr[0] == _NOT: + elif expr[0] == NOT: _expr_syms_rec(expr[1], res) elif expr[0] in _RELATIONS: if isinstance(expr[1], Symbol): @@ -3106,9 +3190,9 @@ def _str_val(obj): def _format_and_op(expr): """ _expr_to_str() helper. Returns the string representation of 'expr', which - is assumed to be an operand to _AND, with parentheses added if needed. + is assumed to be an operand to AND, with parentheses added if needed. """ - if isinstance(expr, tuple) and expr[0] == _OR: + if isinstance(expr, tuple) and expr[0] == OR: return "({})".format(_expr_to_str(expr)) return _expr_to_str(expr) @@ -3122,16 +3206,16 @@ def _expr_to_str(expr): if isinstance(expr, Symbol): return expr.name - if expr[0] == _NOT: + if expr[0] == NOT: if isinstance(expr[1], (str, Symbol)): return "!" + _expr_to_str(expr[1]) return "!({})".format(_expr_to_str(expr[1])) - if expr[0] == _AND: + if expr[0] == AND: return "{} && {}".format(_format_and_op(expr[1]), _format_and_op(expr[2])) - if expr[0] == _OR: + if expr[0] == OR: return "{} || {}".format(_expr_to_str(expr[1]), _expr_to_str(expr[2])) @@ -3251,7 +3335,7 @@ def _sym_choice_str(sc): if node.prompt is not None: prompt_str = 'prompt "{}"'.format(node.prompt[0]) - if node.prompt[1] is not None: + if node.prompt[1] != "y": prompt_str += " if " + _expr_to_str(node.prompt[1]) indent_add(prompt_str) @@ -3259,11 +3343,11 @@ def _sym_choice_str(sc): if isinstance(sc, Symbol): if sc.is_allnoconfig_y: indent_add("option allnoconfig_y") - if sc is sc.config.defconfig_list: + if sc is sc.config.defconfig_list: indent_add("option defconfig_list") if sc.env_var is not None: indent_add('option env="{}"'.format(sc.env_var)) - if sc is sc.config.modules: + if sc is sc.config.modules: indent_add("option modules") if isinstance(sc, Symbol): @@ -3271,13 +3355,13 @@ def _sym_choice_str(sc): range_string = "range {} {}" \ .format(_expr_to_str(range_[0]), _expr_to_str(range_[1])) - if range_[2] is not None: + if range_[2] != "y": range_string += " if " + _expr_to_str(range_[2]) indent_add(range_string) for default in sc.defaults: default_string = "default " + _expr_to_str(default[0]) - if default[1] is not None: + if default[1] != "y": default_string += " if " + _expr_to_str(default[1]) indent_add(default_string) @@ -3287,13 +3371,13 @@ def _sym_choice_str(sc): if isinstance(sc, Symbol): for select in sc.selects: select_string = "select " + select[0].name - if select[1] is not None: + if select[1] != "y": select_string += " if " + _expr_to_str(select[1]) indent_add(select_string) for imply in sc.implies: imply_string = "imply " + imply[0].name - if imply[1] is not None: + if imply[1] != "y": imply_string += " if " + _expr_to_str(imply[1]) indent_add(imply_string) @@ -3322,8 +3406,8 @@ def _eq_to_sym(eq): left, right = right, left if not isinstance(left, Symbol): return None - if (relation == _EQUAL and right in ("m", "y")) or \ - (relation == _UNEQUAL and right == "n"): + if (relation == EQUAL and right in ("m", "y")) or \ + (relation == UNEQUAL and right == "n"): return left return None @@ -3342,9 +3426,9 @@ def _expr_depends_on(expr, sym): if isinstance(expr, Symbol): return expr is sym - if expr[0] in (_EQUAL, _UNEQUAL): + if expr[0] in (EQUAL, UNEQUAL): return _eq_to_sym(expr) is sym - if expr[0] == _AND: + if expr[0] == AND: return rec(expr[1]) or rec(expr[2]) return False @@ -3494,13 +3578,6 @@ def _finalize_tree(node): # Public global constants # -# Integers representing menu and comment nodes - -( - MENU, - COMMENT, -) = range(2) - # Integers representing symbol types ( BOOL, @@ -3511,6 +3588,25 @@ def _finalize_tree(node): UNKNOWN ) = range(6) +# Integers representing expression types +( + AND, + OR, + NOT, + EQUAL, + UNEQUAL, + LESS, + LESS_EQUAL, + GREATER, + GREATER_EQUAL, +) = range(9) + +# Integers representing menu and comment menu nodes +( + MENU, + COMMENT, +) = range(2) + # # Internal global constants # @@ -3679,19 +3775,6 @@ _DEFAULT_VALUE = { # will do) for it so we can test with 'is'. _NO_CACHED_SELECTION = object() -# Integers representing expression types -( - _AND, - _OR, - _NOT, - _EQUAL, - _UNEQUAL, - _LESS, - _LESS_EQUAL, - _GREATER, - _GREATER_EQUAL, -) = range(9) - # Used in comparisons. 0 means the base is inferred from the format of the # string. The entries for BOOL and TRISTATE are a convenience - they should # never convert to valid numbers. @@ -3712,29 +3795,29 @@ _TRI_TO_INT = { } _RELATIONS = frozenset(( - _EQUAL, - _UNEQUAL, - _LESS, - _LESS_EQUAL, - _GREATER, - _GREATER_EQUAL, + EQUAL, + UNEQUAL, + LESS, + LESS_EQUAL, + GREATER, + GREATER_EQUAL, )) # Token to relation (=, !=, <, ...) mapping _TOKEN_TO_REL = { - _T_EQUAL: _EQUAL, - _T_GREATER: _GREATER, - _T_GREATER_EQUAL: _GREATER_EQUAL, - _T_LESS: _LESS, - _T_LESS_EQUAL: _LESS_EQUAL, - _T_UNEQUAL: _UNEQUAL, + _T_EQUAL: EQUAL, + _T_GREATER: GREATER, + _T_GREATER_EQUAL: GREATER_EQUAL, + _T_LESS: LESS, + _T_LESS_EQUAL: LESS_EQUAL, + _T_UNEQUAL: UNEQUAL, } _RELATION_TO_STR = { - _EQUAL: "=", - _GREATER: ">", - _GREATER_EQUAL: ">=", - _LESS: "<", - _LESS_EQUAL: "<=", - _UNEQUAL: "!=", + EQUAL: "=", + GREATER: ">", + GREATER_EQUAL: ">=", + LESS: "<", + LESS_EQUAL: "<=", + UNEQUAL: "!=", } diff --git a/testsuite.py b/testsuite.py index 69eae6e..4eba62a 100644 --- a/testsuite.py +++ b/testsuite.py @@ -110,7 +110,7 @@ def get_items(config, type_): items.append(node.item) rec(node.list) rec(node.next) - rec(config.top_menu) + rec(config.top_node) return items def get_comments(config): @@ -121,7 +121,7 @@ def get_comments(config): items.append(node) rec(node.list) rec(node.next) - rec(config.top_menu) + rec(config.top_node) return items def get_menus(config): @@ -132,7 +132,7 @@ def get_menus(config): items.append(node) rec(node.list) rec(node.next) - rec(config.top_menu) + rec(config.top_node) return items def get_choices(config): @@ -1803,7 +1803,7 @@ def run_compatibility_tests(): if all_passed: print("All selftests and compatibility tests passed") - print(nconfigs, "arch/defconfig pairs tested") + print("{} arch/defconfig pairs tested".format(nconfigs)) else: print("Some tests failed") -- cgit v1.2.3 From 481bfd60d0b283f30b906d2edf8228aeb82e8492 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Sat, 28 Oct 2017 00:46:00 +0200 Subject: Nearly finalize API Probably just some usability tweaks left. Having to do STR_TO_TRI[] for comparisons against 'assignable' values is kinda ugly and confusing. --- examples/allnoconfig.py | 4 +- examples/allyesconfig.py | 10 +- kconfiglib.py | 617 +++++++++++++++++++++++------------------------ testsuite.py | 561 +++++++++++++++++++++--------------------- 4 files changed, 582 insertions(+), 610 deletions(-) (limited to 'examples') diff --git a/examples/allnoconfig.py b/examples/allnoconfig.py index 8d41912..2348da4 100644 --- a/examples/allnoconfig.py +++ b/examples/allnoconfig.py @@ -8,7 +8,7 @@ # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py -from kconfiglib import Config, Symbol, tri_less +from kconfiglib import Config, Symbol, STR_TO_TRI import sys def do_allnoconfig(node): @@ -27,7 +27,7 @@ def do_allnoconfig(node): if (sym.choice is None and not sym.is_allnoconfig_y and sym.assignable and - tri_less(sym.assignable[0], sym.value)): + STR_TO_TRI[sym.assignable[0]] < sym.tri_value): # Yup, lower it sym.set_value(sym.assignable[0]) diff --git a/examples/allyesconfig.py b/examples/allyesconfig.py index 32b302c..f91b6d7 100644 --- a/examples/allyesconfig.py +++ b/examples/allyesconfig.py @@ -18,7 +18,7 @@ # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py -from kconfiglib import Config, Choice, tri_less +from kconfiglib import Config, Choice, STR_TO_TRI import sys conf = Config(sys.argv[1]) @@ -70,7 +70,7 @@ while 1: for sym in non_choice_syms: # See allnoconfig example. [-1] gives the last (highest) assignable # value. - if sym.assignable and tri_less(sym.value, sym.assignable[-1]): + if sym.assignable and sym.tri_value < STR_TO_TRI[sym.assignable[-1]]: sym.set_value(sym.assignable[-1]) no_changes = False @@ -79,7 +79,7 @@ while 1: for choice in choices: # Handle a choice whose visibility allows it to be in "y" mode - if choice.visibility == "y": + if choice.visibility == 2: selection = choice.default_selection # Does the choice have a default selection that we haven't already @@ -95,12 +95,12 @@ while 1: # This might happen if a choice depends on a symbol that can only be # "m", for example. - elif choice.visibility == "m": + elif choice.visibility == 1: for sym in choice.symbols: # Does the choice have a symbol that can be "m" that we haven't # already set to "m"? - if sym.user_value != "m" and "m" in sym.assignable: + if sym.user_tri_value != 1 and "m" in sym.assignable: # Yup, set it sym.set_value("m") diff --git a/kconfiglib.py b/kconfiglib.py index d0c46c6..25bb380 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -338,7 +338,8 @@ class Config(object): sym.name = nmy sym.is_constant = True sym._type = TRISTATE - sym._cached_val = nmy + sym._cached_tri_val = STR_TO_TRI[nmy] + sym._cached_str_val = nmy self.const_syms[nmy] = sym @@ -416,9 +417,10 @@ class Config(object): """ if self.defconfig_list is None: return None + for filename, cond_expr in self.defconfig_list.defaults: - if eval_expr(cond_expr) != "n": - filename = self._expand_sym_refs(filename.value) + if eval_expr(cond_expr): + filename = self._expand_sym_refs(filename.str_value) try: with self._open(filename) as f: return f.name @@ -474,6 +476,7 @@ class Config(object): self._warn("malformed string literal", filename, linenr) continue + # Strip quotes and remove escapings. The unescaping # procedure should be safe since " can only appear as # \" inside the string. @@ -481,7 +484,7 @@ class Config(object): .replace("\\\\", "\\") if sym.choice is not None: - mode = sym.choice.user_value + mode = sym.choice.user_str_value if mode is not None and mode != val: self._warn("assignment to {} changes mode of " 'containing choice from "{}" to "{}".' @@ -504,10 +507,10 @@ class Config(object): # Done parsing the assignment. Set the value. - if sym.user_value is not None: + if sym.user_str_value is not None: self._warn('{} set more than once. Old value: "{}", new ' 'value: "{}".' - .format(name, sym.user_value, val), + .format(name, sym.user_str_value, val), filename, linenr) sym._set_value_no_invalidate(val, True) @@ -542,7 +545,7 @@ class Config(object): returns "y". This function always yields a tristate value. To get the value of - non-bool, non-tristate symbols, use Symbol.value. + non-bool, non-tristate symbols, use Symbol.str_value. The result of this function is consistent with how evaluation works for conditional ('if ...') expressions in the configuration (as well as in @@ -571,11 +574,12 @@ class Config(object): for sym in self.defined_syms: # We're iterating over all symbols, so no need for symbols to # invalidate their dependent symbols - sym.user_value = None + sym.user_str_value = sym.user_tri_value = None sym._invalidate() for choice in self._choices: - choice.user_value = choice.user_selection = None + choice.user_str_value = choice.user_tri_value = \ + choice.user_selection = None choice._invalidate() def enable_warnings(self): @@ -616,7 +620,7 @@ class Config(object): 'config symbol prefix "{}"'.format(self.config_prefix), "warnings " + ("enabled" if self._print_warnings else "disabled"), "undef. symbol assignment warnings " + - ("enabled" if self._print_undef_assign else "disabled") + ("enabled" if self._print_undef_assign else "disabled"), ) return "<{}>".format(", ".join(fields)) @@ -1007,10 +1011,7 @@ class Config(object): prev_node.next = prev_node = node elif t0 == _T_SOURCE: - kconfig_file = self._next_token() - exp_kconfig_file = self._expand_sym_refs(kconfig_file) - - self._enter_file(exp_kconfig_file) + self._enter_file(self._expand_sym_refs(self._next_token())) prev_node = self._parse_block(None, # end_token parent, visible_if_deps, @@ -1030,8 +1031,7 @@ class Config(object): node.parent = parent node.filename = self._filename node.linenr = self._linenr - node.dep = \ - self._make_and(parent.dep, self._parse_expr(True)) + node.dep = self._make_and(parent.dep, self._parse_expr(True)) self._parse_block(_T_ENDIF, node, # parent @@ -1259,16 +1259,10 @@ class Config(object): node.item.env_var = env_var if env_var not in os.environ: - self._warn("the symbol {0} references the " - "non-existent environment variable {1} " - "(meaning the 'option env=\"{1}\"' will " - "have no effect). If you're using " - "Kconfiglib via 'make (i)scriptconfig', it " - "should have set up the environment " - "correctly for you. If you still got this " - "message, that might be an error, and you " - "should email ulfalizer a.t Google's email " - "service.".format(node.item.name, env_var), + self._warn("'option env=\"{0}\"' on symbol {1} will " + "have no effect, because the environment " + "variable {0} is not set" + .format(node.item.name, env_var), self._filename, self._linenr) else: defaults.append( @@ -1565,9 +1559,9 @@ class Config(object): add_fn(config_string) sym._already_written = True - elif (node.item == MENU and eval_expr(node.dep) != "n" and - eval_expr(node.visibility) != "n") or \ - (node.item == COMMENT and eval_expr(node.dep) != "n"): + elif eval_expr(node.dep) and \ + ((node.item == MENU and eval_expr(node.visibility)) or + node.item == COMMENT): add_fn("\n#\n# {}\n#\n".format(node.prompt[0])) @@ -1669,13 +1663,13 @@ class Config(object): while 1: sym_ref_match = _sym_ref_re_search(s) - if sym_ref_match is None: + if not sym_ref_match: return s sym = self.syms.get(sym_ref_match.group(1)) s = s[:sym_ref_match.start()] + \ - (sym.value if sym is not None else "") + \ + (sym.str_value if sym is not None else "") + \ s[sym_ref_match.end():] # @@ -1772,10 +1766,14 @@ class Symbol(object): menuconfig-like functionality. (Check the implementation of the property if you need to get the original type.) - value: + str_value: + TODO The current value of the symbol. Automatically recalculated as dependencies change. + tri_value: + TODO + assignable: A string containing the tristate values that can be assigned to the symbol, ordered from lowest (n) to highest (y). This corresponds to the @@ -1819,13 +1817,23 @@ class Symbol(object): dependencies) are propagated to the prompt dependencies. Additional dependencies can be specified with e.g. 'bool "foo" if ". - user_value: - The value assigned with Symbol.set_value(), or None if no value has been - assigned. This won't necessarily match 'value' even if set, as - dependencies and prompt visibility take precedence. + user_str_value: + The string value assigned with Symbol.set_value(), or None if no value + has been assigned. This won't necessarily match 'str_value' even if set, + as dependencies and prompt visibility take precedence. + + Note that you should use Symbol.set_value() to change this value (which + will also change user_tri_value). Changing the value directly will break + things, as Kconfiglib might need to invalidate other symbols. Properties + are always read-only. + + The string value is only used in comparisons (e.g. + 'depends on SYMBOL = "foo"'). See user_tri_value. - Note that you should use Symbol.set_value() to change this value. - Properties are always read-only. + user_tri_value: + The tristate value corresponding to user_str_value. The rule is that "n", + "m", and "y" correspond to 0, 1, and 2 for BOOL and TRISTATE symbols. + Other symbol types always evaluate to 0 (n) in a tristate sense. config_string: The .config assignment string that would get written out for the symbol @@ -1917,7 +1925,8 @@ class Symbol(object): "_already_written", "_cached_assignable", "_cached_deps", - "_cached_val", + "_cached_str_val", + "_cached_tri_val", "_cached_vis", "_direct_dependents", "_type", @@ -1935,7 +1944,8 @@ class Symbol(object): "ranges", "rev_dep", "selects", - "user_value", + "user_str_value", + "user_tri_value", "weak_rev_dep", ) @@ -1950,138 +1960,79 @@ class Symbol(object): """ if self._type == TRISTATE and \ - ((self.choice is not None and self.choice.value == "y") or - self.config.modules.value == "n"): + ((self.choice is not None and self.choice.tri_value == 2) or + not self.config.modules.tri_value): return BOOL return self._type @property - def value(self): + def str_value(self): """ See the class documentation. """ - if self._cached_val is not None: - return self._cached_val + if self._cached_str_val is not None: + return self._cached_str_val + + if self._type in (BOOL, TRISTATE): + self._cached_str_val = TRI_TO_STR[self.tri_value] + return self._cached_str_val # As a quirk of Kconfig, undefined symbols get their name as their - # value. This is why things like "FOO = bar" work for seeing if FOO has - # the value "bar". + # string value. This is why things like "FOO = bar" work for seeing if + # FOO has the value "bar". if self._type == UNKNOWN: - self._cached_val = self.name + self._cached_str_val = self.name return self.name - # This will hold the value at the end of the function - val = _DEFAULT_VALUE[self._type] - + val = "" vis = self.visibility - if self._type in (BOOL, TRISTATE): - if self.choice is None: - self._write_to_conf = (vis != "n") + self._write_to_conf = (vis != 0) - if vis != "n" and self.user_value is not None: - # If the symbol is visible and has a user value, we use - # that - val = _tri_min(self.user_value, vis) - - else: - # Otherwise, we look at defaults and weak reverse - # dependencies (implies) - - for default, cond in self.defaults: - cond_val = eval_expr(cond) - if cond_val != "n": - self._write_to_conf = True - val = _tri_min(eval_expr(default), cond_val) - break - - # Weak reverse dependencies are only considered if our - # direct dependencies are met - if eval_expr(self.direct_dep) != "n": - weak_rev_dep_val = \ - eval_expr(self.weak_rev_dep) - if weak_rev_dep_val != "n": - self._write_to_conf = True - val = _tri_max(val, weak_rev_dep_val) - - # Reverse (select-related) dependencies take precedence - rev_dep_val = eval_expr(self.rev_dep) - if rev_dep_val != "n": - self._write_to_conf = True - val = _tri_max(val, rev_dep_val) - - else: - # (bool/tristate) symbol in choice. See _get_visibility() for - # more choice-related logic. - - # Initially - self._write_to_conf = False - - if vis != "n": - mode = self.choice.value - - if mode != "n": - self._write_to_conf = True - - if mode == "y": - val = "y" if self.choice.selection is self else "n" - elif self.user_value in ("m", "y"): - # mode == "m" and self.user_value is not None or - # "n" - val = "m" - - # "m" is promoted to "y" in two circumstances: - # 1) If our type is boolean - # 2) If our weak_rev_dep (from IMPLY) is "y" - if val == "m" and \ - (self.type == BOOL or eval_expr(self.weak_rev_dep) == "y"): - val = "y" - - elif self._type in (INT, HEX): + if self._type in (INT, HEX): base = _TYPE_TO_BASE[self._type] # Check if a range is in effect for low_expr, high_expr, cond_expr in self.ranges: - if eval_expr(cond_expr) != "n": + if eval_expr(cond_expr): has_active_range = True - low = int(low_expr.value, base) if \ - _is_base_n(low_expr.value, base) else 0 - high = int(high_expr.value, base) if \ - _is_base_n(high_expr.value, base) else 0 + low = int(low_expr.str_value, base) if \ + _is_base_n(low_expr.str_value, base) else 0 + high = int(high_expr.str_value, base) if \ + _is_base_n(high_expr.str_value, base) else 0 break else: has_active_range = False - self._write_to_conf = (vis != "n") - - if vis != "n" and self.user_value is not None and \ - _is_base_n(self.user_value, base) and \ + if vis and self.user_str_value is not None and \ + _is_base_n(self.user_str_value, base) and \ (not has_active_range or - low <= int(self.user_value, base) <= high): + low <= int(self.user_str_value, base) <= high): # If the user value is well-formed and satisfies range # contraints, it is stored in exactly the same form as # specified in the assignment (with or without "0x", etc.) - val = self.user_value + val = self.user_str_value else: # No user value or invalid user value. Look at defaults. for val_expr, cond_expr in self.defaults: - if eval_expr(cond_expr) != "n": + if eval_expr(cond_expr): self._write_to_conf = True # Similarly to above, well-formed defaults are # preserved as is. Defaults that do not satisfy a range # constraints are clamped and take on a standard form. - val = val_expr.value + val = val_expr.str_value if _is_base_n(val, base): val_num = int(val, base) + # TODO: move outside? if has_active_range: clamped_val = None @@ -2105,18 +2056,93 @@ class Symbol(object): val = (hex(low) if self._type == HEX else str(low)) elif self._type == STRING: - self._write_to_conf = (vis != "n") - - if vis != "n" and self.user_value is not None: - val = self.user_value + if vis and self.user_str_value is not None: + val = self.user_str_value else: for val_expr, cond_expr in self.defaults: - if eval_expr(cond_expr) != "n": + if eval_expr(cond_expr): self._write_to_conf = True - val = val_expr.value + val = val_expr.str_value break - self._cached_val = val + self._cached_str_val = val + return val + + @property + def tri_value(self): + """ + See the class documentation. + """ + + if self._cached_tri_val is not None: + return self._cached_tri_val + + if self._type not in (BOOL, TRISTATE): + self._cached_tri_val = 0 + return self._cached_tri_val + + val = 0 + vis = self.visibility + + if self.choice is None: + self._write_to_conf = (vis != 0) + + if vis and self.user_tri_value is not None: + # If the symbol is visible and has a user value, we use that + val = min(self.user_tri_value, vis) + + else: + # Otherwise, we look at defaults and weak reverse dependencies + # (implies) + + for default, cond in self.defaults: + cond_val = eval_expr(cond) + if cond_val: + val = min(cond_val, eval_expr(default)) + self._write_to_conf = True + break + + # Weak reverse dependencies are only considered if our + # direct dependencies are met + if eval_expr(self.direct_dep): + weak_rev_dep_val = eval_expr(self.weak_rev_dep) + if weak_rev_dep_val: + val = max(weak_rev_dep_val, val) + self._write_to_conf = True + + # Reverse (select-related) dependencies take precedence + rev_dep_val = eval_expr(self.rev_dep) + if rev_dep_val: + val = max(rev_dep_val, val) + self._write_to_conf = True + + else: + # (bool/tristate) symbol in choice. See _get_visibility() for + # more choice-related logic. + + # Initially + self._write_to_conf = False + + if vis: + mode = self.choice.tri_value + + if mode: + self._write_to_conf = True + + if mode == 2: + val = 2 if self.choice.selection is self else 0 + elif self.user_tri_value: + # mode == 1, user value available and not 0 + val = 1 + + # m is promoted to y in two circumstances: + # 1) If our type is boolean + # 2) If our weak_rev_dep (from IMPLY) is y + if val == 1 and \ + (self.type == BOOL or eval_expr(self.weak_rev_dep) == 2): + val = 2 + + self._cached_tri_val = val return val @property @@ -2152,8 +2178,9 @@ class Symbol(object): # corresponds to the SYMBOL_AUTO flag in the C implementation. return None - # Note: _write_to_conf is determined when the value is calculated - val = self.value + # Note: _write_to_conf is determined when the value is calculated. This + # is a hidden function call due to property magic. + val = self.str_value if not self._write_to_conf: return None @@ -2184,10 +2211,11 @@ class Symbol(object): Equal in effect to assigning the value to the symbol within a .config file. Use the 'assignable' attribute to check which values can currently be assigned. Setting values outside 'assignable' will cause - Symbol.user_value to differ from Symbol.value (be truncated down or - up). Values that are invalid for the type (such as "foo" or "m" for a - BOOL) are ignored (and won't be stored in Symbol.user_value). A warning - is printed for attempts to assign invalid values. + Symbol.user_str/tri_value to differ from Symbol.str/tri_value (be + truncated down or up). Values that are invalid for the type (such as + "foo" or "m" for a BOOL) are ignored (and won't be stored in + Symbol.user_str/tri_value). A warning is printed for attempts to assign + invalid values. The values of other symbols that depend on this symbol are automatically recalculated to reflect the new value. @@ -2208,7 +2236,7 @@ class Symbol(object): Resets the user value of the symbol, as if the symbol had never gotten a user value via Config.load_config() or Symbol.set_value(). """ - self.user_value = None + self.user_str_value = self.user_tri_value = None self._rec_invalidate() def __str__(self): @@ -2229,17 +2257,17 @@ class Symbol(object): def __repr__(self): """ Prints some information about the symbol (including its name, value, - and visibility) when it is evaluated. + visibility, and location(s)) when it is evaluated. """ fields = [ "symbol " + self.name, _TYPENAME[self.type], - 'value "{}"'.format(self.value), - "visibility {}".format(self.visibility) + 'value "{}"'.format(self.str_value), + "visibility " + TRI_TO_STR[self.visibility], ] - if self.user_value is not None: - fields.append('user value "{}"'.format(self.user_value)) + if self.user_str_value is not None: + fields.append('user value "{}"'.format(self.user_str_value)) if self.choice is not None: fields.append("choice symbol") @@ -2256,11 +2284,13 @@ class Symbol(object): if self is self.config.modules: fields.append("is the modules symbol") - fields.append("direct deps " + eval_expr(self.direct_dep)) + fields.append("direct deps " + TRI_TO_STR[eval_expr(self.direct_dep)]) - fields.append("{} menu node{}" - .format(len(self.nodes), - "" if len(self.nodes) == 1 else "s")) + if self.nodes: + for node in self.nodes: + fields.append("{}:{}".format(node.filename, node.linenr)) + else: + fields.append("undefined") return "<{}>".format(", ".join(fields)) @@ -2292,7 +2322,7 @@ class Symbol(object): self.nodes = [] - self.user_value = None + self.user_str_value = self.user_tri_value = None # Populated in Config._build_dep() after parsing. Links the symbol to # the symbols that immediately depend on it (in a caching/invalidation @@ -2302,15 +2332,8 @@ class Symbol(object): # Cached values - # Caches the calculated value - self._cached_val = None - # Caches the visibility - self._cached_vis = None - # Caches the total list of dependent symbols. Calculated in - # _get_dependent(). - self._cached_deps = None - # Caches the 'assignable' attribute - self._cached_assignable = None + self._cached_str_val = self._cached_tri_val = self._cached_vis = \ + self._cached_deps = self._cached_assignable = None # Flags @@ -2332,35 +2355,35 @@ class Symbol(object): vis = self.visibility - if vis == "n": + if not vis: return "" rev_dep_val = eval_expr(self.rev_dep) - if vis == "y": - if rev_dep_val == "n": - if self.type == BOOL or eval_expr(self.weak_rev_dep) == "y": + if vis == 2: + if not rev_dep_val: + if self.type == BOOL or eval_expr(self.weak_rev_dep) == 2: return "ny" return "nmy" - if rev_dep_val == "y": + if rev_dep_val == 2: return "y" - # rev_dep_val == "m" + # rev_dep_val == 1 - if self.type == BOOL or eval_expr(self.weak_rev_dep) == "y": + if self.type == BOOL or eval_expr(self.weak_rev_dep) == 2: return "y" return "my" - # vis == "m" + # vis == 1 - if rev_dep_val == "n": - return "m" if eval_expr(self.weak_rev_dep) != "y" else "y" + if not rev_dep_val: + return "m" if eval_expr(self.weak_rev_dep) != 2 else "y" - if rev_dep_val == "y": + if rev_dep_val == 2: return "y" - # vis == "m", rev_dep == "m" (rare) + # vis == rev_dep_val == 1 return "m" @@ -2399,20 +2422,29 @@ class Symbol(object): "promptless symbol {} will have no effect" .format(value, self.name)) - self.user_value = value + self.user_str_value = value + self.user_tri_value = \ + STR_TO_TRI[value] \ + if self._type in (BOOL, TRISTATE) else \ + 0 + + # TODO: assigning automatically changes choice yada yada - if self.choice is not None and self._type in (BOOL, TRISTATE): - if value == "y": - self.choice.user_value = "y" + if self.choice is not None: + if self.user_tri_value == 2: + self.choice.user_str_value = "y" + self.choice.user_tri_value = 2 self.choice.user_selection = self - elif value == "m": - self.choice.user_value = "m" + elif self.user_tri_value == 1: + self.choice.user_str_value = "m" + self.choice.user_tri_value = 1 def _invalidate(self): """ Marks the symbol as needing to be recalculated. """ - self._cached_val = self._cached_vis = self._cached_assignable = None + self._cached_str_val = self._cached_tri_val = self._cached_vis = \ + self._cached_assignable = None def _rec_invalidate(self): """ @@ -2531,10 +2563,13 @@ class Choice(object): The symbol that would be selected by default, had the user not selected any symbol. Can be None for the same reasons as 'selected'. - user_value: + user_str_value: TODO The value (mode) selected by the user (by assigning some choice symbol or calling Choice.set_value()). This does not necessarily match Choice.value - for the same reasons that Symbol.user_value might not match Symbol.value. + for the same reasons that Symbol.user_str_value might not match + Symbol.value. + + user_tri_value: TODO user_selection: The symbol selected by the user (by setting it to "y"). Ignored if the @@ -2585,7 +2620,8 @@ class Choice(object): "nodes", "syms", "user_selection", - "user_value", + "user_str_value", + "user_tri_value", ) # @@ -2595,25 +2631,32 @@ class Choice(object): @property def type(self): """Returns the type of the choice. See Symbol.type.""" - if self._type == TRISTATE and self.config.modules.value == "n": + if self._type == TRISTATE and not self.config.modules.tri_value: return BOOL return self._type @property - def value(self): + def str_value(self): """ See the class documentation. """ - if self.user_value is not None: - val = _tri_min(self.user_value, self.visibility) + return TRI_TO_STR[self.tri_value] + + @property + def tri_value(self): + """ + See the class documentation. + """ + if self.user_tri_value is not None: + val = min(self.user_tri_value, self.visibility) else: - val = "n" + val = 0 - if val == "n" and not self.is_optional: - val = "m" + if not val and not self.is_optional: + val = 1 # Promote "m" to "y" for boolean choices - return "y" if val == "m" and self.type == BOOL else val + return 2 if val == 1 and self.type == BOOL else val @property def assignable(self): @@ -2645,13 +2688,13 @@ class Choice(object): if self._cached_selection is not _NO_CACHED_SELECTION: return self._cached_selection - if self.value != "y": + if self.tri_value != 2: self._cached_selection = None return None # User choice available? if self.user_selection is not None and \ - self.user_selection.visibility == "y": + self.user_selection.visibility == 2: self._cached_selection = self.user_selection return self.user_selection @@ -2666,14 +2709,12 @@ class Choice(object): See the class documentation. """ for sym, cond_expr in self.defaults: - if (eval_expr(cond_expr) != "n" and - # Must be visible too - sym.visibility != "n"): + if eval_expr(cond_expr) and sym.visibility: return sym # Otherwise, pick the first visible symbol, if any for sym in self.syms: - if sym.visibility != "n": + if sym.visibility: return sym # Couldn't find a default @@ -2692,7 +2733,8 @@ class Choice(object): "which has type {}. Assignment ignored" .format(value, _TYPENAME[self._type])) - self.user_value = value + self.user_str_value = value + self.user_tri_value = STR_TO_TRI[value] if self.syms: # Hackish way to invalidate the choice and all the choice symbols @@ -2703,7 +2745,7 @@ class Choice(object): Resets the user value (mode) and user selection of the Choice, as if the user had never touched the mode or any of the choice symbols. """ - self.user_value = self.user_selection = None + self.user_str_value = self.user_tri_value = self.user_selection = None if self.syms: # Hackish way to invalidate the choice and all the choice symbols self.syms[0]._rec_invalidate() @@ -2716,11 +2758,14 @@ class Choice(object): return _sym_choice_str(self) def __repr__(self): + """ + TODO + """ fields = [ "choice" if self.name is None else "choice " + self.name, _TYPENAME[self.type], - "mode " + self.value, - "visibility " + self.visibility + "mode " + self.str_value, + "visibility " + TRI_TO_STR[self.visibility], ] if self.is_optional: @@ -2729,13 +2774,11 @@ class Choice(object): if self.selection is not None: fields.append("{} selected".format(self.selection.name)) - fields.append("{} menu node{}" - .format(len(self.nodes), - "" if len(self.nodes) == 1 else "s")) + for node in self.nodes: + fields.append("{}:{}".format(node.filename, node.linenr)) return "<{}>".format(", ".join(fields)) - # # Private methods # @@ -2757,17 +2800,15 @@ class Choice(object): self.nodes = [] - self.user_value = None - self.user_selection = None + self.user_str_value = self.user_tri_value = self.user_selection = None # The prompts and default values without any dependencies from # enclosing menus and ifs propagated self.defaults = [] # Cached values + self._cached_vis = self._cached_assignable = None self._cached_selection = _NO_CACHED_SELECTION - self._cached_vis = None - self._cached_assignable = None self.is_optional = False @@ -2778,21 +2819,21 @@ class Choice(object): vis = self.visibility - if vis == "n": + if not vis: return "" - if vis == "y": + if vis == 2: if not self.is_optional: return "y" if self.type == BOOL else "my" return "y" - # vis == "m" + # vis == 1 return "nm" if self.is_optional else "m" def _invalidate(self): - self._cached_selection = _NO_CACHED_SELECTION self._cached_vis = self._cached_assignable = None + self._cached_selection = _NO_CACHED_SELECTION class MenuNode(object): """ @@ -2920,15 +2961,17 @@ class MenuNode(object): if self.prompt is not None: fields.append('prompt "{}" (visibility {})' - .format(self.prompt[0], eval_expr(self.prompt[1]))) + .format(self.prompt[0], + TRI_TO_STR[eval_expr(self.prompt[1])])) if isinstance(self.item, Symbol) and self.is_menuconfig: fields.append("is menuconfig") - fields.append("deps " + eval_expr(self.dep)) + fields.append("deps " + TRI_TO_STR[eval_expr(self.dep)]) if self.item == MENU: - fields.append("'visible if' deps " + eval_expr(self.visibility)) + fields.append("'visible if' deps " + \ + TRI_TO_STR[eval_expr(self.visibility)]) if isinstance(self.item, (Symbol, Choice)) and self.help is not None: fields.append("has help") @@ -2957,65 +3000,26 @@ class InternalError(Exception): # Public functions # -def tri_less(v1, v2): - """ - Returns True if the tristate v1 is less than the tristate v2, where "n", - "m" and "y" are ordered from lowest to highest. - """ - return _TRI_TO_INT[v1] < _TRI_TO_INT[v2] - -def tri_less_eq(v1, v2): - """ - Returns True if the tristate v1 is less than or equal to the tristate v2, - where "n", "m" and "y" are ordered from lowest to highest. - """ - return _TRI_TO_INT[v1] <= _TRI_TO_INT[v2] - -def tri_greater(v1, v2): - """ - Returns True if the tristate v1 is greater than the tristate v2, where "n", - "m" and "y" are ordered from lowest to highest. - """ - return _TRI_TO_INT[v1] > _TRI_TO_INT[v2] - -def tri_greater_eq(v1, v2): - """ - Returns True if the tristate v1 is greater than or equal to the tristate - v2, where "n", "m" and "y" are ordered from lowest to highest. - """ - return _TRI_TO_INT[v1] >= _TRI_TO_INT[v2] - -# Expression evaluation - def eval_expr(expr): """ - Evaluates an expression to "n", "m", or "y". + TODO """ - if isinstance(expr, Symbol): - # Non-bool/tristate symbols are always "n" in a tristate sense, - # regardless of their value - return expr.value if expr._type in (BOOL, TRISTATE) else "n" + return expr.tri_value if expr[0] == AND: - ev1 = eval_expr(expr[1]) - - # Short-circuit the ev1 == "n" case - return "n" if ev1 == "n" else \ - _tri_min(ev1, eval_expr(expr[2])) + v1 = eval_expr(expr[1]) + # Short-circuit the n case as an optimization (~5% faster + # allnoconfig.py and allyesconfig.py, as of writing) + return 0 if not v1 else min(v1, eval_expr(expr[2])) if expr[0] == OR: - ev1 = eval_expr(expr[1]) - - # Short-circuit the ev1 == "y" case - return "y" if ev1 == "y" else \ - _tri_max(ev1, eval_expr(expr[2])) + v1 = eval_expr(expr[1]) + # Short-circuit the y case as an optimization + return 2 if v1 == 2 else max(v1, eval_expr(expr[2])) if expr[0] == NOT: - ev = eval_expr(expr[1]) - return "n" if ev == "y" else \ - "y" if ev == "n" else \ - "m" + return 2 - eval_expr(expr[1]) if expr[0] in _RELATIONS: # Implements <, <=, >, >= comparisons as well. These were added to @@ -3030,16 +3034,16 @@ def eval_expr(expr): # If both operands are strings... if op1._type == STRING and op2._type == STRING: # ...then compare them lexicographically - comp = _strcmp(op1.value, op2.value) + comp = _strcmp(op1.str_value, op2.str_value) else: # Otherwise, try to compare them as numbers... try: - comp = int(op1.value, _TYPE_TO_BASE[op1._type]) - \ - int(op2.value, _TYPE_TO_BASE[op2._type]) + comp = int(op1.str_value, _TYPE_TO_BASE[op1._type]) - \ + int(op2.str_value, _TYPE_TO_BASE[op2._type]) except ValueError: # Fall back on a lexicographic comparison if the operands don't # parse as numbers - comp = _strcmp(op1.value, op2.value) + comp = _strcmp(op1.str_value, op2.str_value) if oper == EQUAL: res = comp == 0 elif oper == UNEQUAL: res = comp != 0 @@ -3048,12 +3052,15 @@ def eval_expr(expr): elif oper == GREATER: res = comp > 0 elif oper == GREATER_EQUAL: res = comp >= 0 - return "y" if res else "n" + return 2*res _internal_error("Internal error while evaluating expression: " "unknown operation {}.".format(expr[0])) def expr_str(expr): + """ + TODO + """ if isinstance(expr, Symbol): return expr.name if not expr.is_constant else '"{}"'.format(expr.name) @@ -3085,47 +3092,35 @@ def _get_visibility(sc): 'make menuconfig'. This function calculates the visibility for the Symbol or Choice 'sc' -- the logic is nearly identical. """ - vis = "n" + vis = 0 for node in sc.nodes: if node.prompt: - vis = _tri_max(vis, eval_expr(node.prompt[1])) + vis = max(vis, eval_expr(node.prompt[1])) if isinstance(sc, Symbol) and sc.choice is not None: if sc.choice._type == TRISTATE and sc._type != TRISTATE and \ - sc.choice.value != "y": + sc.choice.tri_value != 2: # Non-tristate choice symbols in tristate choices depend on the # choice being in mode "y" - return "n" + return 0 - if sc._type == TRISTATE and vis == "m" and sc.choice.value == "y": + if sc._type == TRISTATE and vis == 1 and sc.choice.tri_value == 2: # Choice symbols with visibility "m" are not visible if the # choice has mode "y" - return "n" + return 0 - vis = _tri_min(vis, sc.choice.visibility) + vis = min(vis, sc.choice.visibility) - # Promote "m" to "y" if we're dealing with a non-tristate. This might lead - # to infinite recursion if something really weird is done with MODULES, but + # Promote m to y if we're dealing with a non-tristate. This might lead to + # infinite recursion if something really weird is done with MODULES, but # it's not a problem in practice. - if vis == "m" and \ - (sc._type != TRISTATE or sc.config.modules.value == "n"): - return "y" + if vis == 1 and \ + (sc._type != TRISTATE or not sc.config.modules.tri_value): + return 2 return vis -def _tri_min(v1, v2): - """ - Returns the smallest tristate value among v1 and v2. - """ - return v1 if _TRI_TO_INT[v1] <= _TRI_TO_INT[v2] else v2 - -def _tri_max(v1, v2): - """ - Returns the largest tristate value among v1 and v2. - """ - return v1 if _TRI_TO_INT[v1] >= _TRI_TO_INT[v2] else v2 - def _make_depend_on(sym, expr): """ Adds 'sym' as a dependency to all symbols in 'expr'. Constant symbols in @@ -3192,10 +3187,8 @@ def _strcmp(s1, s2): return (s1 > s2) - (s1 < s2) def _stderr_msg(msg, filename, linenr): - if filename is None: - s = msg - else: - s = "{}:{}: ".format(filename, linenr) + if filename is not None: + msg = "{}:{}: {}".format(filename, linenr, msg) sys.stderr.write(msg + "\n") @@ -3509,6 +3502,18 @@ def _finalize_tree(node): COMMENT, ) = range(2) +TRI_TO_STR = { + 0: "n", + 1: "m", + 2: "y", +} + +STR_TO_TRI = { + "n": 0, + "m": 1, + "y": 2, +} + # # Internal global constants # @@ -3643,12 +3648,12 @@ _sym_ref_re_search = re.compile(r"\$([A-Za-z0-9_]+)").search # Strings to use for types _TYPENAME = { - UNKNOWN: "unknown", - BOOL: "bool", + UNKNOWN: "unknown", + BOOL: "bool", TRISTATE: "tristate", - STRING: "string", - HEX: "hex", - INT: "int", + STRING: "string", + HEX: "hex", + INT: "int", } # Token to type mapping @@ -3662,16 +3667,6 @@ _TOKEN_TO_TYPE = { _T_TRISTATE: TRISTATE, } -# Default values for symbols of different types (the value the symbol gets if -# it is not assigned a user value and none of its 'default' clauses kick in) -_DEFAULT_VALUE = { - BOOL: "n", - TRISTATE: "n", - HEX: "", - INT: "", - STRING: "", -} - # Constant representing that there's no cached choice selection. This is # distinct from a cached None (no selection). We create a unique object (any # will do) for it so we can test with 'is'. diff --git a/testsuite.py b/testsuite.py index e81ba1d..711a0fb 100644 --- a/testsuite.py +++ b/testsuite.py @@ -168,9 +168,9 @@ def run_selftests(): Verifies that a symbol has a particular value. """ sym = c.syms[sym_name] - verify(sym.value == val, + verify(sym.str_value == val, 'expected {} to have the value "{}", had the value "{}"' - .format(sym_name, val, sym.value)) + .format(sym_name, val, sym.str_value)) def assign_and_verify_value(sym_name, val, new_val): """ @@ -178,13 +178,13 @@ def run_selftests(): 'new_val'. """ sym = c.syms[sym_name] - old_val = sym.value + old_val = sym.str_value sym.set_value(val) - verify(sym.value == new_val, + verify(sym.str_value == new_val, 'expected {} to have the value "{}" after being assigned the ' 'value "{}". Instead, the value is "{}". The old value was ' '"{}".' - .format(sym_name, new_val, val, sym.value, old_val)) + .format(sym_name, new_val, val, sym.str_value, old_val)) def assign_and_verify(sym_name, user_val): """ @@ -197,51 +197,19 @@ def run_selftests(): """Assigns a user value to the symbol and verifies the new user value.""" sym = c.syms[sym_name] - sym_old_user_val = sym.user_value + sym_old_user_val = sym.user_str_value sym.set_value(val) - verify(sym.user_value == user_val, + verify(sym.user_str_value == user_val, "{} should have the user value '{}' after being assigned " "the user value '{}'. Instead, the new user value was '{}'. " "The old user value was '{}'." - .format(sym_name, user_val, user_val, sym.user_value, + .format(sym_name, user_val, user_val, sym.user_str_value, sym_old_user_val)) # # Selftests # - print("Testing tristate comparisons") - - def verify_truth_table(comp_fn, *table): - for (x, y), expected in zip((("n", "n"), ("n", "m"), ("n", "y"), - ("m", "n"), ("m", "m"), ("m", "y"), - ("y", "n"), ("y", "m"), ("y", "y")), - table): - verify(comp_fn(x, y) == expected, - "expected {} on ('{}', '{}') to be '{}'". - format(comp_fn, x, y, expected)) - - verify_truth_table(kconfiglib.tri_less, - False, True, True, - False, False, True, - False, False, False) - - verify_truth_table(kconfiglib.tri_less_eq, - True, True, True, - False, True, True, - False, False, True) - - verify_truth_table(kconfiglib.tri_greater, - False, False, False, - True, False, False, - True, True, False) - - verify_truth_table(kconfiglib.tri_greater_eq, - True, False, False, - True, True, False, - True, True, True) - - print("Testing string literal lexing") # Dummy empty configuration just to get a Config object @@ -250,14 +218,12 @@ def run_selftests(): def verify_string_lex(s, res): """ Verifies that a constant symbol with the name 'res' is produced from - lexing 's'. Strips the first and last characters from 's' so that - readable raw strings can be used as input + lexing 's' """ - c._line = s[1:-1] - c._tokenize(True) + c.eval_string(s) verify(c._tokens[0].name == res, - 'expected {} to produced the constant symbol {}, produced {}' - .format(s, c._tokens[0].name, res)) + "expected <{}> to produced the constant symbol <{}>, " + 'produced <{}>'.format(s[1:-1], c._tokens[0].name, res)) verify_string_lex(r""" "" """, "") verify_string_lex(r""" '' """, "") @@ -293,13 +259,12 @@ def run_selftests(): first and last characters from 's' so we can use readable raw strings as input. """ - c._line = s[1:-1] try: - c._tokenize(True) + c.eval_string(s) except kconfiglib.KconfigSyntaxError: pass else: - fail("expected tokenization of {} to fail, didn't".format(s)) + fail("expected tokenization of {} to fail, didn't".format(s[1:-1])) verify_string_bad(r""" " """) verify_string_bad(r""" ' """) @@ -323,168 +288,168 @@ def run_selftests(): "'{}' evaluated to {}, expected {}".format(expr, res, val)) # No modules - verify_eval("n", "n") - verify_eval("m", "n") - verify_eval("y", "y") - verify_eval("'n'", "n") - verify_eval("'m'", "n") - verify_eval("'y'", "y") - verify_eval("M", "y") + verify_eval("n", 0) + verify_eval("m", 0) + verify_eval("y", 2) + verify_eval("'n'", 0) + verify_eval("'m'", 0) + verify_eval("'y'", 2) + verify_eval("M", 2) # Modules - c.syms["MODULES"].set_value("y") - verify_eval("n", "n") - verify_eval("m", "m") - verify_eval("y", "y") - verify_eval("'n'", "n") - verify_eval("'m'", "m") - verify_eval("'y'", "y") - verify_eval("M", "m") - verify_eval("(Y || N) && (m && y)", "m") + c.modules.set_value("y") + verify_eval("n", 0) + verify_eval("m", 1) + verify_eval("y", 2) + verify_eval("'n'", 0) + verify_eval("'m'", 1) + verify_eval("'y'", 2) + verify_eval("M", 1) + verify_eval("(Y || N) && (m && y)", 1) # Non-bool/non-tristate symbols are always "n" in a tristate sense - verify_eval("Y_STRING", "n") - verify_eval("Y_STRING || m", "m") + verify_eval("Y_STRING", 0) + verify_eval("Y_STRING || m", 1) # As are all constants besides "y" and "m" - verify_eval('"foo"', "n") - verify_eval('"foo" || "bar"', "n") - verify_eval('"foo" || m', "m") + verify_eval('"foo"', 0) + verify_eval('"foo" || "bar"', 0) + verify_eval('"foo" || m', 1) # Test equality for symbols - verify_eval("N = N", "y") - verify_eval("N = n", "y") - verify_eval("N = 'n'", "y") - verify_eval("N != N", "n") - verify_eval("N != n", "n") - verify_eval("N != 'n'", "n") - - verify_eval("M = M", "y") - verify_eval("M = m", "y") - verify_eval("M = 'm'", "y") - verify_eval("M != M", "n") - verify_eval("M != m", "n") - verify_eval("M != 'm'", "n") - - verify_eval("Y = Y", "y") - verify_eval("Y = y", "y") - verify_eval("Y = 'y'", "y") - verify_eval("Y != Y", "n") - verify_eval("Y != y", "n") - verify_eval("Y != 'y'", "n") - - verify_eval("N != M", "y") - verify_eval("N != Y", "y") - verify_eval("M != Y", "y") - - verify_eval("Y_STRING = y", "y") - verify_eval("Y_STRING = 'y'", "y") - verify_eval('FOO_BAR_STRING = "foo bar"', "y") - verify_eval('FOO_BAR_STRING != "foo bar baz"', "y") - verify_eval('INT_37 = 37', "y") - verify_eval("INT_37 = '37'", "y") - verify_eval('HEX_0X37 = 0x37', "y") - verify_eval("HEX_0X37 = '0x37'", "y") + verify_eval("N = N", 2) + verify_eval("N = n", 2) + verify_eval("N = 'n'", 2) + verify_eval("N != N", 0) + verify_eval("N != n", 0) + verify_eval("N != 'n'", 0) + + verify_eval("M = M", 2) + verify_eval("M = m", 2) + verify_eval("M = 'm'", 2) + verify_eval("M != M", 0) + verify_eval("M != m", 0) + verify_eval("M != 'm'", 0) + + verify_eval("Y = Y", 2) + verify_eval("Y = y", 2) + verify_eval("Y = 'y'", 2) + verify_eval("Y != Y", 0) + verify_eval("Y != y", 0) + verify_eval("Y != 'y'", 0) + + verify_eval("N != M", 2) + verify_eval("N != Y", 2) + verify_eval("M != Y", 2) + + verify_eval("Y_STRING = y", 2) + verify_eval("Y_STRING = 'y'", 2) + verify_eval('FOO_BAR_STRING = "foo bar"', 2) + verify_eval('FOO_BAR_STRING != "foo bar baz"', 2) + verify_eval('INT_37 = 37', 2) + verify_eval("INT_37 = '37'", 2) + verify_eval('HEX_0X37 = 0x37', 2) + verify_eval("HEX_0X37 = '0x37'", 2) # These should also hold after 31847b67 (kconfig: allow use of relations # other than (in)equality) - verify_eval("HEX_0X37 = '0x037'", "y") - verify_eval("HEX_0X37 = '0x0037'", "y") + verify_eval("HEX_0X37 = '0x037'", 2) + verify_eval("HEX_0X37 = '0x0037'", 2) # Constant symbol comparisons - verify_eval('"foo" != "bar"', "y") - verify_eval('"foo" = "bar"', "n") - verify_eval('"foo" = "foo"', "y") + verify_eval('"foo" != "bar"', 2) + verify_eval('"foo" = "bar"', 0) + verify_eval('"foo" = "foo"', 2) # Undefined symbols get their name as their value c.disable_warnings() - verify_eval("'not_defined' = not_defined", "y") - verify_eval("not_defined_2 = not_defined_2", "y") - verify_eval("not_defined_1 != not_defined_2", "y") + verify_eval("'not_defined' = not_defined", 2) + verify_eval("not_defined_2 = not_defined_2", 2) + verify_eval("not_defined_1 != not_defined_2", 2) # Test less than/greater than # Basic evaluation - verify_eval("INT_37 < 38", "y") - verify_eval("38 < INT_37", "n") - verify_eval("INT_37 < '38'", "y") - verify_eval("'38' < INT_37", "n") - verify_eval("INT_37 < 138", "y") - verify_eval("138 < INT_37", "n") - verify_eval("INT_37 < '138'", "y") - verify_eval("'138' < INT_37", "n") - verify_eval("INT_37 < -138", "n") - verify_eval("-138 < INT_37", "y") - verify_eval("INT_37 < '-138'", "n") - verify_eval("'-138' < INT_37", "y") - verify_eval("INT_37 < 37", "n") - verify_eval("37 < INT_37", "n") - verify_eval("INT_37 < 36", "n") - verify_eval("36 < INT_37", "y") + verify_eval("INT_37 < 38", 2) + verify_eval("38 < INT_37", 0) + verify_eval("INT_37 < '38'", 2) + verify_eval("'38' < INT_37", 0) + verify_eval("INT_37 < 138", 2) + verify_eval("138 < INT_37", 0) + verify_eval("INT_37 < '138'", 2) + verify_eval("'138' < INT_37", 0) + verify_eval("INT_37 < -138", 0) + verify_eval("-138 < INT_37", 2) + verify_eval("INT_37 < '-138'", 0) + verify_eval("'-138' < INT_37", 2) + verify_eval("INT_37 < 37", 0) + verify_eval("37 < INT_37", 0) + verify_eval("INT_37 < 36", 0) + verify_eval("36 < INT_37", 2) # Different formats in comparison - verify_eval("INT_37 < 0x26", "y") # 38 - verify_eval("INT_37 < 0x25", "n") # 37 - verify_eval("INT_37 < 0x24", "n") # 36 - verify_eval("HEX_0X37 < 56", "y") # 0x38 - verify_eval("HEX_0X37 < 55", "n") # 0x37 - verify_eval("HEX_0X37 < 54", "n") # 0x36 + verify_eval("INT_37 < 0x26", 2) # 38 + verify_eval("INT_37 < 0x25", 0) # 37 + verify_eval("INT_37 < 0x24", 0) # 36 + verify_eval("HEX_0X37 < 56", 2) # 0x38 + verify_eval("HEX_0X37 < 55", 0) # 0x37 + verify_eval("HEX_0X37 < 54", 0) # 0x36 # Other int comparisons - verify_eval("INT_37 <= 38", "y") - verify_eval("INT_37 <= 37", "y") - verify_eval("INT_37 <= 36", "n") - verify_eval("INT_37 > 38", "n") - verify_eval("INT_37 > 37", "n") - verify_eval("INT_37 > 36", "y") - verify_eval("INT_37 >= 38", "n") - verify_eval("INT_37 >= 37", "y") - verify_eval("INT_37 >= 36", "y") + verify_eval("INT_37 <= 38", 2) + verify_eval("INT_37 <= 37", 2) + verify_eval("INT_37 <= 36", 0) + verify_eval("INT_37 > 38", 0) + verify_eval("INT_37 > 37", 0) + verify_eval("INT_37 > 36", 2) + verify_eval("INT_37 >= 38", 0) + verify_eval("INT_37 >= 37", 2) + verify_eval("INT_37 >= 36", 2) # Other hex comparisons - verify_eval("HEX_0X37 <= 0x38", "y") - verify_eval("HEX_0X37 <= 0x37", "y") - verify_eval("HEX_0X37 <= 0x36", "n") - verify_eval("HEX_0X37 > 0x38", "n") - verify_eval("HEX_0X37 > 0x37", "n") - verify_eval("HEX_0X37 > 0x36", "y") - verify_eval("HEX_0X37 >= 0x38", "n") - verify_eval("HEX_0X37 >= 0x37", "y") - verify_eval("HEX_0X37 >= 0x36", "y") + verify_eval("HEX_0X37 <= 0x38", 2) + verify_eval("HEX_0X37 <= 0x37", 2) + verify_eval("HEX_0X37 <= 0x36", 0) + verify_eval("HEX_0X37 > 0x38", 0) + verify_eval("HEX_0X37 > 0x37", 0) + verify_eval("HEX_0X37 > 0x36", 2) + verify_eval("HEX_0X37 >= 0x38", 0) + verify_eval("HEX_0X37 >= 0x37", 2) + verify_eval("HEX_0X37 >= 0x36", 2) # A hex holding a value without a "0x" prefix should still be treated as # hexadecimal - verify_eval("HEX_37 < 0x38", "y") - verify_eval("HEX_37 < 0x37", "n") - verify_eval("HEX_37 < 0x36", "n") + verify_eval("HEX_37 < 0x38", 2) + verify_eval("HEX_37 < 0x37", 0) + verify_eval("HEX_37 < 0x36", 0) # Symbol comparisons - verify_eval("INT_37 < HEX_0X37", "y") - verify_eval("INT_37 > HEX_0X37", "n") - verify_eval("HEX_0X37 < INT_37 ", "n") - verify_eval("HEX_0X37 > INT_37 ", "y") - verify_eval("INT_37 < INT_37 ", "n") - verify_eval("INT_37 <= INT_37 ", "y") - verify_eval("INT_37 > INT_37 ", "n") - verify_eval("INT_37 <= INT_37 ", "y") + verify_eval("INT_37 < HEX_0X37", 2) + verify_eval("INT_37 > HEX_0X37", 0) + verify_eval("HEX_0X37 < INT_37 ", 0) + verify_eval("HEX_0X37 > INT_37 ", 2) + verify_eval("INT_37 < INT_37 ", 0) + verify_eval("INT_37 <= INT_37 ", 2) + verify_eval("INT_37 > INT_37 ", 0) + verify_eval("INT_37 <= INT_37 ", 2) # Strings compare lexicographically - verify_eval("'aa' < 'ab'", "y") - verify_eval("'aa' > 'ab'", "n") - verify_eval("'ab' < 'aa'", "n") - verify_eval("'ab' > 'aa'", "y") + verify_eval("'aa' < 'ab'", 2) + verify_eval("'aa' > 'ab'", 0) + verify_eval("'ab' < 'aa'", 0) + verify_eval("'ab' > 'aa'", 2) # Comparisons where one of the operands doesn't parse as a number also give # a lexicographic comparison - verify_eval("INT_37 < '37a' ", "y") - verify_eval("'37a' > INT_37", "y") - verify_eval("INT_37 <= '37a' ", "y") - verify_eval("'37a' >= INT_37", "y") - verify_eval("INT_37 >= '37a' ", "n") - verify_eval("INT_37 > '37a' ", "n") - verify_eval("'37a' < INT_37", "n") - verify_eval("'37a' <= INT_37", "n") + verify_eval("INT_37 < '37a' ", 2) + verify_eval("'37a' > INT_37", 2) + verify_eval("INT_37 <= '37a' ", 2) + verify_eval("'37a' >= INT_37", 2) + verify_eval("INT_37 >= '37a' ", 0) + verify_eval("INT_37 > '37a' ", 0) + verify_eval("'37a' < INT_37", 0) + verify_eval("'37a' <= INT_37", 0) def verify_eval_bad(expr): try: @@ -519,6 +484,8 @@ def run_selftests(): c = kconfiglib.Config("Kconfiglib/tests/Kstr", warn=False) + c.modules.set_value("y") + verify_str(c.syms["UNDEFINED"], """ """) @@ -582,8 +549,10 @@ config INT # We still hardcode the modules symbol. Otherwise OPTIONS would have made # more sense as a name here. - verify_str(c.syms["MODULES"], """ + verify_str(c.modules, """ config MODULES + bool + prompt "MODULES" option modules """) @@ -619,59 +588,58 @@ choice c = kconfiglib.Config("Kconfiglib/tests/Krepr", warn=False) verify_repr(c.syms["UNDEFINED"], """ - + """) verify_repr(c.syms["BASIC"], """ - + """) verify_repr(c.syms["VISIBLE"], """ - + """) verify_repr(c.syms["DIR_DEP_N"], """ - + """) verify_repr(c.syms["OPTIONS"], """ - + """) verify_repr(c.syms["MULTI_DEF"], """ - + """) verify_repr(c.syms["CHOICE_1"], """ - + """) - verify_repr(c.syms["MODULES"], """ - + verify_repr(c.modules, """ + """) - print("Testing Choice.__repr__()") verify_repr(c.named_choices["CHOICE"], """ - + """) c.named_choices["CHOICE"].set_value("y") verify_repr(c.named_choices["CHOICE"], """ - + """) c.syms["CHOICE_2"].set_value("y") verify_repr(c.named_choices["CHOICE"], """ - + """) verify_repr(c.syms["CHOICE_HOOK"].nodes[0].next.item, """ - + """) @@ -830,13 +798,13 @@ g c = kconfiglib.Config("Kconfiglib/tests/Kvisibility") def verify_visibility(item, no_module_vis, module_vis): - c.syms["MODULES"].set_value("n") + c.modules.set_value("n") verify(item.visibility == no_module_vis, "expected {} to have visibility {} without modules, had " "visibility {}". format(repr(item), no_module_vis, item.visibility)) - c.syms["MODULES"].set_value("y") + c.modules.set_value("y") verify(item.visibility == module_vis, "expected {} to have visibility {} with modules, had " "visibility {}". @@ -844,72 +812,70 @@ g # Symbol visibility - verify_visibility(c.syms["NO_PROMPT"], "n", "n") - verify_visibility(c.syms["BOOL_N"], "n", "n") - verify_visibility(c.syms["BOOL_M"], "n", "y") - verify_visibility(c.syms["BOOL_MOD"], "y", "y") - verify_visibility(c.syms["BOOL_Y"], "y", "y") - verify_visibility(c.syms["TRISTATE_M"], "n", "m") - verify_visibility(c.syms["TRISTATE_MOD"], "y", "m") - verify_visibility(c.syms["TRISTATE_Y"], "y", "y") - verify_visibility(c.syms["BOOL_IF_N"], "n", "n") - verify_visibility(c.syms["BOOL_IF_M"], "n", "y") - verify_visibility(c.syms["BOOL_IF_Y"], "y", "y") - verify_visibility(c.syms["BOOL_MENU_N"], "n", "n") - verify_visibility(c.syms["BOOL_MENU_M"], "n", "y") - verify_visibility(c.syms["BOOL_MENU_Y"], "y", "y") - verify_visibility(c.syms["BOOL_CHOICE_N"], "n", "n") + verify_visibility(c.syms["NO_PROMPT"], 0, 0) + verify_visibility(c.syms["BOOL_N"], 0, 0) + verify_visibility(c.syms["BOOL_M"], 0, 2) + verify_visibility(c.syms["BOOL_MOD"], 2, 2) + verify_visibility(c.syms["BOOL_Y"], 2, 2) + verify_visibility(c.syms["TRISTATE_M"], 0, 1) + verify_visibility(c.syms["TRISTATE_MOD"], 2, 1) + verify_visibility(c.syms["TRISTATE_Y"], 2, 2) + verify_visibility(c.syms["BOOL_IF_N"], 0, 0) + verify_visibility(c.syms["BOOL_IF_M"], 0, 2) + verify_visibility(c.syms["BOOL_IF_Y"], 2, 2) + verify_visibility(c.syms["BOOL_MENU_N"], 0, 0) + verify_visibility(c.syms["BOOL_MENU_M"], 0, 2) + verify_visibility(c.syms["BOOL_MENU_Y"], 2, 2) + verify_visibility(c.syms["BOOL_CHOICE_N"], 0, 0) # Non-tristate symbols in tristate choices are only visible if the choice - # is in "y" mode - verify_visibility(c.syms["BOOL_CHOICE_M"], "n", "n") + # is in 2 mode + verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0) - # Tristate choices start out in "m" mode. When running without modules, - # their type gets adjusted to bool. - verify_visibility(c.syms["BOOL_CHOICE_Y"], "y", "n") + # Tristate choices start out in m mode. When running without modules, their + # type gets adjusted to bool. + verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 0) c.syms["TRISTATE_CHOICE_M"].set_value("y") c.syms["TRISTATE_CHOICE_Y"].set_value("y") # Still limited by the visibility of the choice - verify_visibility(c.syms["BOOL_CHOICE_M"], "n", "n") + verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0) # This one should become visible now - verify_visibility(c.syms["BOOL_CHOICE_Y"], "y", "y") - - verify_visibility(c.syms["TRISTATE_IF_N"], "n", "n") - verify_visibility(c.syms["TRISTATE_IF_M"], "n", "m") - verify_visibility(c.syms["TRISTATE_IF_Y"], "y", "y") - verify_visibility(c.syms["TRISTATE_MENU_N"], "n", "n") - verify_visibility(c.syms["TRISTATE_MENU_M"], "n", "m") - verify_visibility(c.syms["TRISTATE_MENU_Y"], "y", "y") - verify_visibility(c.syms["TRISTATE_CHOICE_N"], "n", "n") - verify_visibility(c.syms["TRISTATE_CHOICE_M"], "n", "m") - verify_visibility(c.syms["TRISTATE_CHOICE_Y"], "y", "y") - - verify_visibility(c.named_choices["BOOL_CHOICE_N"], "n", "n") - verify_visibility(c.named_choices["BOOL_CHOICE_M"], "n", "y") - verify_visibility(c.named_choices["BOOL_CHOICE_Y"], "y", "y") - verify_visibility(c.named_choices["TRISTATE_CHOICE_N"], "n", "n") - verify_visibility(c.named_choices["TRISTATE_CHOICE_M"], "n", "m") - verify_visibility(c.named_choices["TRISTATE_CHOICE_Y"], "y", "y") - - verify_visibility(c.named_choices["TRISTATE_CHOICE_IF_M_AND_Y"], - "n", "m") - verify_visibility(c.named_choices["TRISTATE_CHOICE_MENU_N_AND_Y"], - "n", "n") + verify_visibility(c.syms["BOOL_CHOICE_Y"], 2, 2) + + verify_visibility(c.syms["TRISTATE_IF_N"], 0, 0) + verify_visibility(c.syms["TRISTATE_IF_M"], 0, 1) + verify_visibility(c.syms["TRISTATE_IF_Y"], 2, 2) + verify_visibility(c.syms["TRISTATE_MENU_N"], 0, 0) + verify_visibility(c.syms["TRISTATE_MENU_M"], 0, 1) + verify_visibility(c.syms["TRISTATE_MENU_Y"], 2, 2) + verify_visibility(c.syms["TRISTATE_CHOICE_N"], 0, 0) + verify_visibility(c.syms["TRISTATE_CHOICE_M"], 0, 1) + verify_visibility(c.syms["TRISTATE_CHOICE_Y"], 2, 2) + + verify_visibility(c.named_choices["BOOL_CHOICE_N"], 0, 0) + verify_visibility(c.named_choices["BOOL_CHOICE_M"], 0, 2) + verify_visibility(c.named_choices["BOOL_CHOICE_Y"], 2, 2) + verify_visibility(c.named_choices["TRISTATE_CHOICE_N"], 0, 0) + verify_visibility(c.named_choices["TRISTATE_CHOICE_M"], 0, 1) + verify_visibility(c.named_choices["TRISTATE_CHOICE_Y"], 2, 2) + + verify_visibility(c.named_choices["TRISTATE_CHOICE_IF_M_AND_Y"], 0, 1) + verify_visibility(c.named_choices["TRISTATE_CHOICE_MENU_N_AND_Y"], 0, 0) # Menu visibility def verify_menu_visibility(menu, no_module_vis, module_vis): - c["MODULES"].set_value("n") + c.modules.set_value("n") menu_vis = kconfiglib.eval_expr(menu.node.dep) verify(menu_vis == no_module_vis, "menu \"{}\" should have visibility '{}' without modules, " "has visibility '{}'" .format(menu.title, no_module_vis, menu_vis)) - c["MODULES"].set_value("y") + c.modules.set_value("y") menu_vis = kconfiglib.eval_expr(menu.node.dep) verify(menu_vis == module_vis, "menu \"{}\" should have visibility '{}' with modules, " @@ -935,14 +901,14 @@ g menu_visible_if_m_2 = get_menus(c)[13:] def verify_visible_if_visibility(menu, no_module_vis, module_vis): - c["MODULES"].set_value("n") + c.modules.set_value("n") menu_vis = menu.get_visible_if_visibility() verify(menu_vis == no_module_vis, "menu \"{}\" should have 'visible if' visibility '{}' " "without modules, has 'visible if' visibility '{}'". format(menu.title, no_module_vis, menu_vis)) - c["MODULES"].set_value("y") + c.modules.set_value("y") menu_vis = menu.get_visible_if_visibility() verify(menu_vis == module_vis, "menu \"{}\" should have 'visible if' visibility '{}' " @@ -963,15 +929,15 @@ g #verify_visible_if_visibility(menu_visible_if_m_2, "n", "m") # Verify that 'visible if' visibility gets propagated to prompts - verify_visibility(c.syms["VISIBLE_IF_N"], "n", "n") - verify_visibility(c.syms["VISIBLE_IF_M"], "n", "m") - verify_visibility(c.syms["VISIBLE_IF_Y"], "y", "y") - verify_visibility(c.syms["VISIBLE_IF_M_2"], "n", "m") + verify_visibility(c.syms["VISIBLE_IF_N"], 0, 0) + verify_visibility(c.syms["VISIBLE_IF_M"], 0, 1) + verify_visibility(c.syms["VISIBLE_IF_Y"], 2, 2) + verify_visibility(c.syms["VISIBLE_IF_M_2"], 0, 1) # Comment visibility def verify_comment_visibility(comment, no_module_vis, module_vis): - c["MODULES"].set_value("n") + c.modules.set_value("n") # TODO: uninternalize comment_vis = kconfiglib.eval_expr(comment.node.dep) verify(comment_vis == no_module_vis, @@ -979,7 +945,7 @@ g "modules, has visibility '{}'". format(comment.text, no_module_vis, comment_vis)) - c["MODULES"].set_value("y") + c.modules.set_value("y") comment_vis = kconfiglib.eval_expr(comment.node.dep) verify(comment_vis == module_vis, "comment \"{}\" should have visibility '{}' with " @@ -1251,7 +1217,7 @@ g ("BOOL", "TRISTATE", "STRING", "INT", "HEX")] for sym in syms: - verify(sym.user_value is None, + verify(sym.user_str_value is None and sym.user_tri_value is None, "{} should not have a user value to begin with") # Assign valid values for the types @@ -1278,7 +1244,7 @@ g for s in syms: s.unset_value() - verify(s.user_value is None, + verify(s.user_str_value is None and s.user_tri_value is None, "{} should not have a user value after being reset". format(s.name)) @@ -1399,7 +1365,7 @@ g # .config # - print("Testing .config...") + print("Testing Config separation...") c1 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False) c2 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False) @@ -1547,9 +1513,8 @@ g def select_and_verify(sym): choice = get_parent(sym) sym.set_value("y") - verify(choice.value == "y", - 'The mode of the choice should be "y" after selecting a ' - "symbol") + verify(choice.str_value == "y", + "The mode of the choice should be y after selecting a symbol") verify(sym.choice.selection is sym, "{} should be the selected choice symbol" .format(sym.name)) @@ -1568,25 +1533,24 @@ g select_and_verify(choice.syms[i]) def verify_mode(choice, no_modules_mode, modules_mode): - c.syms["MODULES"].set_value("n") - choice_mode = choice.value + c.modules.set_value("n") + choice_mode = choice.tri_value verify(choice_mode == no_modules_mode, - 'Wrong mode for choice {} with no modules. Expected "{}", ' - 'got "{}".'.format(choice.name, no_modules_mode, choice_mode)) + 'Wrong mode for choice {} with no modules. Expected {}, got {}.' + .format(choice.name, no_modules_mode, choice_mode)) - c.syms["MODULES"].set_value("y") - choice_mode = choice.value + c.modules.set_value("y") + choice_mode = choice.tri_value verify(choice_mode == modules_mode, - 'Wrong mode for choice {} with modules. Expected "{}", ' - 'got "{}".'.format(choice.name, modules_mode, - choice_mode)) + 'Wrong mode for choice {} with modules. Expected {}, got {}.' + .format(choice.name, modules_mode, choice_mode)) - verify_mode(choice_bool, "y", "y") - verify_mode(choice_bool_opt, "n", "n") - verify_mode(choice_tristate, "y", "m") - verify_mode(choice_tristate_opt, "n", "n") - verify_mode(choice_bool_m, "y", "y") - verify_mode(choice_tristate_m, "y", "m") + verify_mode(choice_bool, 2, 2) + verify_mode(choice_bool_opt, 0, 0) + verify_mode(choice_tristate, 2, 1) + verify_mode(choice_tristate_opt, 0, 0) + verify_mode(choice_bool_m, 2, 2) + verify_mode(choice_tristate_m, 2, 1) # Test defaults @@ -1606,7 +1570,7 @@ g # Test "y" mode selection - c.syms["MODULES"].set_value("y") + c.modules.set_value("y") select_and_verify_all(choice_bool) select_and_verify_all(choice_bool_opt) @@ -1621,12 +1585,12 @@ g for sym_name in ("T_1", "T_2"): assign_and_verify_value(sym_name, "m", "m") - verify(choice_tristate.value == "m", + verify(choice_tristate.tri_value == 1, 'Selecting {} to "m" should have changed the mode of the ' 'choice to "m"'.format(sym_name)) assign_and_verify_value(sym_name, "y", "y") - verify(choice_tristate.value == "y" and + verify(choice_tristate.tri_value == 2 and choice_tristate.selection is c.syms[sym_name], 'Selecting {} to "y" should have changed the mode of the ' 'choice to "y" and made it the selection'.format(sym_name)) @@ -1638,7 +1602,7 @@ g assign_and_verify_value(sym_name, "n", "n") # "y" should be truncated assign_and_verify_value(sym_name, "y", "m") - verify(choice_tristate_m.value == "m", + verify(choice_tristate_m.tri_value == 1, 'A choice that can only be in "m" mode was not') # Verify that choices with no explicitly specified type get the type of the @@ -1746,8 +1710,10 @@ g "\nSome selftests failed\n") def run_compatibility_tests(): - """Runs tests on configurations from the kernel. Tests compability with the - C implementation by comparing outputs.""" + """ + Runs tests on configurations from the kernel. Tests compability with the + C implementation by comparing outputs. + """ os.environ.pop("ARCH", None) os.environ.pop("SRCARCH", None) @@ -1798,9 +1764,9 @@ def run_compatibility_tests(): if compare_configs: if equal_confs(): - print(" {:14}OK".format(arch)) + print("{:14}OK".format(arch)) else: - print(" {:14}FAIL".format(arch)) + print("{:14}FAIL".format(arch)) fail() if all_passed: @@ -1810,8 +1776,9 @@ def run_compatibility_tests(): print("Some tests failed") def get_arch_srcarch_list(): - """Returns a list of (ARCH, SRCARCH) tuples to test.""" - + """ + Returns a list of (ARCH, SRCARCH) tuples to test. + """ res = [] def add_arch(arch): @@ -1837,16 +1804,17 @@ def get_arch_srcarch_list(): return res def test_load(conf, arch): - """Load all arch Kconfigs to make sure we don't throw any errors""" - print(" {:14}OK".format(arch)) + """ + Load all arch Kconfigs to make sure we don't throw any errors + """ + print("{:14}OK".format(arch)) -# The weird docstring formatting is to get the format right when we print the -# docstring ourselves def test_all_no(conf, arch): """ Verify that our examples/allnoconfig.py script generates the same .config as 'make allnoconfig', for each architecture. Runs the script via - 'make scriptconfig', so kinda slow even in speedy mode.""" + 'make scriptconfig', so kinda slow even in speedy mode. + """ # TODO: Support speedy mode for running the script shell("make scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py " @@ -1861,7 +1829,8 @@ def test_all_no_simpler(conf, arch): """ Verify that our examples/allnoconfig_simpler.py script generates the same .config as 'make allnoconfig', for each architecture. Runs the script via - 'make scriptconfig', so kinda slow even in speedy mode.""" + 'make scriptconfig', so kinda slow even in speedy mode. + """ # TODO: Support speedy mode for running the script shell("make scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig_simpler.py " @@ -1876,7 +1845,8 @@ def test_all_yes(conf, arch): """ Verify that our examples/allyesconfig.py script generates the same .config as 'make allyesconfig', for each architecture. Runs the script via - 'make scriptconfig', so kinda slow even in speedy mode.""" + 'make scriptconfig', so kinda slow even in speedy mode. + """ # TODO: Support speedy mode for running the script shell("make scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py " @@ -1889,11 +1859,12 @@ def test_all_yes(conf, arch): def test_call_all(conf, arch): """ - Call all public methods on all symbols, menus, choices, and comments for + Call all public methods on all symbols, choices, and TODO menu nodes for all architectures to make sure we never crash or hang. (Nearly all public methods: some are hard to test like this, but are exercised by other - tests.)""" - print(" For {}...".format(arch)) + tests.) + """ + print("For {}...".format(arch)) conf.defconfig_filename conf.mainmenu_text @@ -1909,7 +1880,8 @@ def test_call_all(conf, arch): s.__repr__() s.assignable s.type - s.value + s.str_value + s.tri_value s.visibility s.unset_value() @@ -1933,7 +1905,8 @@ def test_call_all(conf, arch): for c in conf._choices: c.__str__() c.__repr__() - c.value + c.str_value + c.tri_value c.assignable c.selection c.default_selection @@ -1943,7 +1916,8 @@ def test_call_all(conf, arch): def test_config_absent(conf, arch): """ Verify that Kconfiglib generates the same .config as 'make alldefconfig', - for each architecture""" + for each architecture + """ conf.write_config("._config") if speedy: shell("scripts/kconfig/conf --alldefconfig Kconfig") @@ -1959,7 +1933,8 @@ def test_defconfig(conf, arch): run. With logging enabled, this test appends any failures to a file - test_defconfig_fails in the root.""" + test_defconfig_fails in the root. + """ global nconfigs defconfigs = [] @@ -2037,8 +2012,10 @@ def test_defconfig(conf, arch): # def rm_configs(): - """Delete any old ".config" (generated by the C implementation) and - "._config" (generated by us), if present.""" + """ + Delete any old ".config" (generated by the C implementation) and + "._config" (generated by us), if present. + """ def rm_if_exists(f): if os.path.exists(f): os.remove(f) -- cgit v1.2.3 From 989e9f77cfe8caabc7ac241572e9b52682901135 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Mon, 30 Oct 2017 00:50:09 +0100 Subject: Consistently use 0/1/2 for tristate values Easier to work with, allowing e.g. direct comparisons with < and >. Make set_value() take 0, 1, 2 for bool and tristate symbols, and fix other APIs to match. Also: - Add introductions to various concepts in the module docstring. Document some more attributes. Still TODOs. - Rename the Config class to Kconfig. - Escape " and \ in the name of constant symbols when printing them. Also make the (un)escaping 100% consistent with how the C tools do it (\ before non-magic character should be unescaped too). - Clean up the escaping/unescaping code and provide two public escape()/unescape() functions. - Export the original MODULES-independent type in orig_type. It's needed for printing symbols in the reparsable __str__() Kconfig format with just public APIs. - Lots of other minor reorganizing and nits all over. --- examples/allnoconfig.py | 8 +- examples/allnoconfig_simpler.py | 8 +- examples/allyesconfig.py | 22 +- examples/defconfig.py | 2 +- examples/defconfig_oldconfig.py | 10 +- examples/eval_expr.py | 4 +- examples/help_grep.py | 4 +- examples/print_sym_info.py | 63 +- examples/print_tree.py | 4 +- kconfiglib.py | 1566 +++++++++++++++++++++++---------------- testsuite.py | 350 +++++---- 11 files changed, 1188 insertions(+), 853 deletions(-) (limited to 'examples') diff --git a/examples/allnoconfig.py b/examples/allnoconfig.py index 2348da4..be36b47 100644 --- a/examples/allnoconfig.py +++ b/examples/allnoconfig.py @@ -8,7 +8,7 @@ # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig.py -from kconfiglib import Config, Symbol, STR_TO_TRI +from kconfiglib import Kconfig, Symbol, STR_TO_TRI import sys def do_allnoconfig(node): @@ -27,7 +27,7 @@ def do_allnoconfig(node): if (sym.choice is None and not sym.is_allnoconfig_y and sym.assignable and - STR_TO_TRI[sym.assignable[0]] < sym.tri_value): + sym.assignable[0] < sym.tri_value): # Yup, lower it sym.set_value(sym.assignable[0]) @@ -39,12 +39,12 @@ def do_allnoconfig(node): node = node.next -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) # Do an initial pass to set 'option allnoconfig_y' symbols to 'y' for sym in conf.defined_syms: if sym.is_allnoconfig_y: - sym.set_value("y") + sym.set_value(2) while 1: # Changing later symbols in the configuration can sometimes allow earlier diff --git a/examples/allnoconfig_simpler.py b/examples/allnoconfig_simpler.py index e732669..59a1bd4 100644 --- a/examples/allnoconfig_simpler.py +++ b/examples/allnoconfig_simpler.py @@ -12,17 +12,17 @@ # Kconfiglib immediately invalidates (flags for recalculation) all (possibly) # dependent symbols when a value is assigned to a symbol, which slows this down # a bit (due to tons of redundant invalidation), but makes any assignment -# pattern safe ("just works"). Config.load_config() instead invalidates all +# pattern safe ("just works"). Kconfig.load_config() instead invalidates all # symbols up front, making it much faster. If you really need to eke out # performance, look at how load_config() does things (which involves internal # APIs that don't invalidate symbols). This has been fast enough for all cases # I've seen so far though (around 3 seconds for this particular script on my # Core i7 2600K, including the initial Kconfig parsing). -from kconfiglib import Config, BOOL, TRISTATE +from kconfiglib import Kconfig, BOOL, TRISTATE import sys -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) # Avoid warnings printed by Kconfiglib when assigning a value to a symbol that # has no prompt. Such assignments never have an effect. @@ -30,6 +30,6 @@ conf.disable_warnings() for sym in conf.defined_syms: if sym.type in (BOOL, TRISTATE): - sym.set_value("y" if sym.is_allnoconfig_y else "n") + 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 f91b6d7..6e2e065 100644 --- a/examples/allyesconfig.py +++ b/examples/allyesconfig.py @@ -8,20 +8,20 @@ # allyesconfig is a bit more involved than allnoconfig as we need to handle # choices in two different modes: # -# y: One symbol is "y", the rest are "n" -# m: Any number of symbols are "m", the rest are "n" +# y: One symbol is y, the rest are n +# m: Any number of symbols are m, the rest are n # -# Only tristate choices can be in "m" mode. No "m" mode choices seem to appear -# for allyesconfig on the kernel Kconfigs as of 4.14, but we still handle it. +# Only tristate choices can be in m mode. No m mode choices seem to appear for +# allyesconfig on the kernel Kconfigs as of 4.14, but we still handle it. # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allyesconfig.py -from kconfiglib import Config, Choice, STR_TO_TRI +from kconfiglib import Kconfig, Choice, STR_TO_TRI import sys -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) # Collect all the choices in the configuration. Demonstrates how the menu node # tree can be walked iteratively by using the parent pointers. @@ -70,14 +70,14 @@ while 1: for sym in non_choice_syms: # See allnoconfig example. [-1] gives the last (highest) assignable # value. - if sym.assignable and sym.tri_value < STR_TO_TRI[sym.assignable[-1]]: + if sym.assignable and sym.tri_value < sym.assignable[-1]: sym.set_value(sym.assignable[-1]) no_changes = False # Handle choices for choice in choices: - # Handle a choice whose visibility allows it to be in "y" mode + # Handle a choice whose visibility allows it to be in y mode if choice.visibility == 2: selection = choice.default_selection @@ -88,7 +88,7 @@ while 1: selection is not choice.user_selection: # Yup, select it - selection.set_value("y") + selection.set_value(2) no_changes = False # Handle a choice whose visibility only allows it to be in "m" mode. @@ -100,10 +100,10 @@ while 1: # Does the choice have a symbol that can be "m" that we haven't # already set to "m"? - if sym.user_tri_value != 1 and "m" in sym.assignable: + if sym.user_tri_value != 1 and 1 in sym.assignable: # Yup, set it - sym.set_value("m") + sym.set_value(1) no_changes = False if no_changes: diff --git a/examples/defconfig.py b/examples/defconfig.py index ce2bf6e..236db8d 100644 --- a/examples/defconfig.py +++ b/examples/defconfig.py @@ -8,7 +8,7 @@ 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") diff --git a/examples/defconfig_oldconfig.py b/examples/defconfig_oldconfig.py index 98173e7..8e72c8a 100644 --- a/examples/defconfig_oldconfig.py +++ b/examples/defconfig_oldconfig.py @@ -15,7 +15,7 @@ import kconfiglib import sys -conf = kconfiglib.Config(sys.argv[1]) +conf = kconfiglib.Kconfig(sys.argv[1]) # Mirrors defconfig conf.load_config("arch/x86/configs/x86_64_defconfig") @@ -23,15 +23,15 @@ conf.write_config(".config") # Mirrors the first oldconfig conf.load_config(".config") -conf.syms["ETHERNET"].set_value('n') +conf.syms["ETHERNET"].set_value(0) conf.write_config(".config") # Mirrors the second oldconfig conf.load_config(".config") -conf.syms["ETHERNET"].set_value('y') +conf.syms["ETHERNET"].set_value(2) for s in conf: - if s.user_value is None and 'n' in s.assignable: - s.set_value('n') + if s.user_value is None and 0 in s.assignable: + s.set_value(0) # Write the final configuration conf.write_config(".config") diff --git a/examples/eval_expr.py b/examples/eval_expr.py index a907f35..be02c77 100644 --- a/examples/eval_expr.py +++ b/examples/eval_expr.py @@ -13,10 +13,10 @@ if len(sys.argv) < 3: print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=NAME') sys.exit(1) -conf = kconfiglib.Config(sys.argv[1]) +conf = kconfiglib.Kconfig(sys.argv[1]) # Enable modules so that 'm' doesn't get demoted to 'n' -conf.syms["MODULES"].set_value("y") +conf.syms["MODULES"].set_value(2) print("the expression '{}' evaluates to {}" .format(sys.argv[2], conf.eval_string(sys.argv[2]))) diff --git a/examples/help_grep.py b/examples/help_grep.py index 6fcb08f..6f54583 100644 --- a/examples/help_grep.py +++ b/examples/help_grep.py @@ -33,7 +33,7 @@ # ... -from kconfiglib import Config, Symbol, Choice, MENU, COMMENT +from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT import re import sys @@ -68,5 +68,5 @@ def search_tree(node): node = node.next -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) search_tree(conf.top_node) diff --git a/examples/print_sym_info.py b/examples/print_sym_info.py index 2c1b0f0..cc9f50a 100644 --- a/examples/print_sym_info.py +++ b/examples/print_sym_info.py @@ -5,48 +5,49 @@ # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/print_sym_info.py SCRIPT_ARG= # # Example output for SCRIPT_ARG=modules: -# -# config 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:1678 +# 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 if len(sys.argv) < 3: print('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=') sys.exit(1) -conf = kconfiglib.Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) sym = conf.syms[sys.argv[2]] print(sym) -print("value = " + sym.value) -print("visibility = " + sym.visibility) -print("currently assignable values: " + ", ".join(sym.assignable)) +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 8d18ce9..9d9eac2 100644 --- a/examples/print_tree.py +++ b/examples/print_tree.py @@ -38,7 +38,7 @@ # config GENERIC_IRQ_PROBE # ... -from kconfiglib import Config, Symbol, Choice, MENU, COMMENT +from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT import sys def indent_print(s, indent): @@ -63,5 +63,5 @@ def print_items(node, indent): node = node.next -conf = Config(sys.argv[1]) +conf = Kconfig(sys.argv[1]) print_items(conf.top_node, 0) diff --git a/kconfiglib.py b/kconfiglib.py index 1bf61a6..96a421c 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -9,127 +9,290 @@ from Kconfig-based configuration systems. Features include the following: - Reading/writing of .config files - - Inspection of symbol properties: print()ing a symbol (which calls __str__()) - produces output which could be fed back into a Kconfig parser to redefine - the symbol, and __str__() is implemented with only public APIs. + - Inspection of symbol properties and expressions: printing a symbol (calling + Symbol.__str__()) gives output which could be fed back into a Kconfig parser + to redefine the symbol, and __str__() is implemented with only public APIs. - A helpful __repr__() is implemented on all objects as well. + A helpful __repr__() is implemented on all objects as well, also implemented + with public APIs. - - Expression inspection and evaluation: All expressions are exposed and use a - simple tuple-based format that can be processed manually if needed. + - Expressions use a simple tuple-based format and can be processed manually if + needed. - Menu tree inspection: The underlying menu tree is exposed, including submenus created implicitly from symbols depending on preceding symbols. This can be used e.g. to implement menuconfig-like functionality. - Runs under both Python 2 and 3. The code mostly uses basic Python features - (the most advanced things used are probably @property and __slots__). + and has no third-party dependencies (the most advanced things used are + @property and __slots__). - Robust and highly compatible with the standard Kconfig C tools: The test - suite automatically compares the output from Kconfiglib with the output from - the C tools on the real kernel Kconfig and defconfig files for all ARCHes. - The comparison is done by diffing the generated .config files to make sure - they're identical. All tests are expected to pass. + suite automatically compares output from Kconfiglib and the C tools (by + diffing generated .config files) on the real kernel Kconfig and defconfig + files, for all ARCHes. All tests are expected to pass. - A suite of self tests is also included. + A set of selftests is also included. - - Internals that (mostly) mirror the C implementation. A lot can indirectly be - learned about how it works by reading the Kconfiglib documentation and code. + - Not horribly slow despite being a pure Python implementation: Parses the x86 + Kconfigs in about a second on a Core i7 2600K (with a warm file cache). For + long-running jobs, PyPy gives a big performance boost. - - Pretty speedy by pure Python standards: Parses the x86 Kconfigs in about a - second on a Core i7 2600K (with a warm file cache). For long-running jobs, - PyPy gives a large performance boost. + - Internals that (mostly) mirror the C implementation while being simpler to + understand. -Using Kconfiglib on the Linux kernel -==================================== +Using Kconfiglib on the Linux kernel with the Makefile targets +============================================================== For the Linux kernel, a handy interface is provided by the -scripts/kconfig/Makefile patch. +scripts/kconfig/Makefile patch, which adds the following targets: -Use the 'iscriptconfig' target for experimentation. It gives an interactive -Python prompt where the configuration for ARCH has been preloaded. - $ make [ARCH=] [PYTHONCMD=] iscriptconfig +make iscriptconfig +------------------ -To run a script, use the 'scriptconfig' target. +This target gives an interactive Python prompt where the configuration for ARCH +has been preloaded and is available in 'kconf'. - $ make [ARCH=] [PYTHONCMD=] scriptconfig SCRIPT= [SCRIPT_ARG=] +To get a feel for the API, try evaluating and printing the symbols in +kconf.defined_syms, and explore the menu tree starting at kconf.top_node by +following 'next' and 'list' pointers. -PYTHONCMD is the Python interpreter to use. It defaults to "python". +The item contained in the menu node is found in MenuNode.item, and all symbols +and choices have a 'nodes' attribute which gives their menu nodes (usually only +one). + +If you want to look up a symbol by name, use the kconf.syms dictionary. + + +As usual, ARCH= can be passed to 'make' to select the arch. +PYTHONCMD= selects the Python executable to use (default: +"python"). Tip: IronPython (PYTHONCMD=ipython) autocompletion is handy when figuring out -the API. +the API, as it provides autocompletion for attributes. + +make scriptconfig SCRIPT=