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(-) 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=