summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Magnusson <ulfalizer@gmail.com>2017-10-30 00:50:09 +0100
committerUlf Magnusson <ulfalizer@gmail.com>2017-10-30 01:14:20 +0100
commit989e9f77cfe8caabc7ac241572e9b52682901135 (patch)
tree9c70f1d1c08efc19803b4fe741ab1dd2c69b42cd
parent7bbaf7e7cf131d83931bfda2d2e8e5d6ef1b235f (diff)
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.
-rw-r--r--examples/allnoconfig.py8
-rw-r--r--examples/allnoconfig_simpler.py8
-rw-r--r--examples/allyesconfig.py22
-rw-r--r--examples/defconfig.py2
-rw-r--r--examples/defconfig_oldconfig.py10
-rw-r--r--examples/eval_expr.py4
-rw-r--r--examples/help_grep.py4
-rw-r--r--examples/print_sym_info.py63
-rw-r--r--examples/print_tree.py4
-rw-r--r--kconfiglib.py1566
-rw-r--r--testsuite.py350
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=<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=<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=<arch>] scriptconfig SCRIPT=Kconfiglib/examples/print_sym_info.py SCRIPT_ARG=<name>
#
# 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=<name>')
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=<arch>] [PYTHONCMD=<python/python3/pypy/...>] 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=<arch>] [PYTHONCMD=<python/python3/pypy/...>] scriptconfig SCRIPT=<path to script> [SCRIPT_ARG=<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=<arch> can be passed to 'make' to select the arch.
+PYTHONCMD=<executable> 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=<script> [SCRIPT_ARG=<arg>]
+----------------------------------------------------
-Writing scriptconfig scripts
-----------------------------
+This target runs the Python script given by the SCRIPT parameter on the
+configuration. sys.argv[1] holds the name of the top-level Kconfig file, and
+sys.argv[2] the SCRIPT_ARG argument, if given.
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].
+Using Kconfiglib without the Makefile targets
+=============================================
+
+The make targets are only needed for a trivial reason: The Kbuild makefiles
+export environment variables which are referenced inside the Kconfig files (via
+'option env="ENV_VARIABLE"').
+
+In practice, the only variables referenced (as of writing, and for many years)
+are ARCH, SRCARCH, and KERNELVERSION. To run Kconfiglib without the Makefile
+patch, do this:
+
+ $ ARCH=x86 SRCARCH=x86 KERNELVERSION=`make kernelversion` python
+ >>> import kconfiglib
+ >>> kconf = kconfiglib.Kconfig() # filename defaults to "Kconfig"
+
+Search the top-level Makefile for "Additional ARCH settings" to see other
+possibilities for ARCH and SRCARCH. Kconfiglib will print a warning if an unset
+environment variable is referenced inside the Kconfig files.
+
+
+Gotcha
+******
+
+It's important to set $SRCARCH even if you don't care about values and only
+want to extract information from Kconfig files, because the top-level Makefile
+does this (as of writing):
+
+ source "arch/$SRCARCH/Kconfig"
+
+If $SRCARCH is not set, this expands to "arch//Kconfig", and arch/Kconfig
+happens to be an existing file, giving something that appears to work but is
+actually a truncated configuration.
+
+
+Intro to symbol values
+======================
+
+Kconfiglib has the same assignment semantics as the C implementation.
+
+Any symbol can be assigned a value by the user (via Kconfig.load_config() or
+Symbol.set_value()), but this user value is only respected if the symbol is
+visible, which corresponds to it (currently) being visible in the menuconfig
+interface.
+
+Symbols without prompts are never visible (setting a user value on them is
+pointless). For symbols with prompts, the visibility of the symbol is
+determined by the condition on the prompt.
+
+Dependencies from parents and 'if'/'depends on' are propagated to properties,
+including prompts, so these two configurations are logically equivalent:
+
+(1)
+
+ menu "menu"
+ depends on A
+
+ if B
+
+ config FOO
+ tristate "foo" if D
+ default y
+ depends on C
+
+ endif
+
+ endmenu
+
+(2)
+
+ menu "menu"
+ depends on A
+
+ config FOO
+ tristate "foo" if A && B && C && D
+ default y if A && B && C
+
+ endmenu
-Running Kconfiglib without the Makefile patch
----------------------------------------------
+In this example, A && B && C && D (the prompt condition) needs to be m or y for
+FOO to be visible (assignable). If the value is m, the symbol can only be
+assigned the value m. The visibility sets an upper bound on the value that can
+be assigned by the user. Any higher user value will be truncated down.
-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 and doesn't bother setting KERNELVERSION to a sane
-value to save some time on startup).
+'default' properties are independent of the visibility, though a 'default' will
+often get the same condition as the prompt due to dependency propagation.
+'default' properties are used if the symbol is not visible or has no user
+value.
- $ ARCH=x86 SRCARCH=x86 KERNELVERSION=`make kernelversion` python script.py
+Symbols with no (active) user value and no (active) 'default' default to n for
+bool/tristate symbols, and to the empty string for other symbols.
-ARCH and SRCARCH (the arch/ subdirectory) might differ in some cases. Search
-for "Additional ARCH settings for" in the top-level Makefile to see the
-possible variations. The value of KERNELVERSION doesn't seem to matter as of
-Linux 4.14.
+'select' works similarly to symbol visibility, but sets a lower bound on the
+value of the symbol instead. The lower bound is determined by the value of the
+select*ing* symbol. 'select' does not respect visibility, so non-visible
+symbols can be forced to a particular (minimum) value by a select as well.
-Kconfiglib will warn if you forget to set some environment variable that's
-referenced in a Kconfig file (via 'option env="ENV_VAR"').
+For non-bool/tristate choices, it only matters if the visibility is n or not: m
+visibility works the same as y visibility.
+Conditions on 'default' and 'select' work in mostly intuitive ways. If the
+condition is n, the 'default' or 'select' is disabled. If it is m, the
+'default' or 'select' value (the value of the selecting symbol) is truncated
+down to m.
-Expression format
-=================
+When writing a configuration with Kconfig.write_config(), only symbols that are
+visible, have an (active) default, or are selected will get written out (note
+that this includes all symbols that would accept user values). Kconfiglib
+matches the .config format produced by the C implementations down to the
+character. This eases testing.
+
+In Kconfiglib, the set of (currently) assignable values for a bool/tristate
+symbol appear in Symbol.assignable. For other symbol types, just check if
+sym.visibility is non-0 (non-n).
+
+
+Intro to the menu tree
+======================
+
+The menu structure, as seen in e.g. menuconfig, is represented by a tree of
+MenuNode objects. The top node of the configuration corresponds to an implicit
+top-level menu, the title of which is shown at the top in the menuconfig
+interface. (The title with variables expanded is available in
+Kconfig.mainmenu_text in Kconfiglib.)
+
+The top node is found in Kconfig.top_node. From there, you can visit child menu
+nodes by following the 'list' pointer, and any following menu nodes by
+following the 'next' pointer. Usually, a non-None 'list' pointer indicates a
+menu or Choice, but menu nodes for symbols can sometimes have a non-None 'list'
+pointer too due to menus created implicitly from dependencies.
+
+MenuNode.item is either a Symbol or a Choice object, or one of the constants
+MENU and COMMENT. The prompt of the menu node (which also holds the text for
+menus and comments) can be found in MenuNode.prompt. For Symbol and Choice,
+MenuNode.help holds the help text (if any, otherwise None).
+
+This organization mirrors the C implementation. MenuNode is called
+'struct menu' there, but I thought "menu" was a confusing name.
+
+Note that prompts and help texts for symbols and choices are stored in the menu
+node. This makes it possible to define a symbol in multiple locations with a
+different prompt or help text in each location.
+
+The list of menu nodes for a Symbol or Choice can be found in the
+Symbol/Choice.nodes attribute.
+
+It is possible to give a Choice a name and define it in multiple locations,
+hence why Choice.nodes is a list. You're unlikely to ever see a choice defined
+in multiple locations in practice though (I don't think I've even seen a named
+choice outside of the test suite).
+
+
+Intro to expressions
+====================
+
+Expressions can be evaluated with the expr_value() function and printed with
+the expr_str() function (these are used internally as well).
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.
+A, B, C, ... are symbols (Symbol instances), NOT is the kconfiglib.NOT
+constant, etc.
Expression Representation
---------- --------------
A A
+"A" A (constant symbol)
!A (NOT, A)
A && B (AND, A, B)
+A && B && C (AND, A, (AND, B, C))
A || B (OR, A, B)
+A || (B && C && D) (OR, A, (AND, B, (AND, C, D)))
A = B (EQUAL, A, B)
A != "foo" (UNEQUAL, A, "foo")
-A || (B && C && D) (OR, A, (AND, B, (AND, C, D)))
-y config.y
-"y" config.y
+A && B = C && D (AND, A, (AND, (EQUAL, B, C), D))
+n Kconfig.n (constant symbol)
+m Kconfig.m (constant symbol)
+y Kconfig.y (constant symbol)
+"y" Kconfig.y (constant symbol)
+
+Strings like "foo" in 'default "foo"' or 'depends on SYM = "foo"' are
+represented as constant symbols, so the only values that appear in expressions
+are symbols (this mirrors the C implementation).
+
+Manual evaluation examples:
+
+ - The value of A && B is min(A.tri_value, B.tri_value)
+
+ - The value of A || B is max(A.tri_value, B.tri_value)
+
+ - The value of A = B is y if A.str_value == B.str_value, and n otherwise.
+ Note that str_value is used here instead of tri_value.
+
+n/m/y are automatically converted to the corresponding constant symbols
+"n"/"m"/"y" (Kconfig.n/m/y) during parsing.
-TODO: show example for other constant symbols
+A "missing" condition (e.g. <cond> if the 'if <cond>' part is removed from
+'default A if <cond>') or other "missing" expression is represented as
+Kconfig.y. The standard __str__() functions avoid printing 'if y' conditions to
+give cleaner output.
-As seen in the final two examples, n/m/y are always represented as the constant
-symbols config.n/m/y, regardless of whether they're written with or without
-quotes. 'config' represents the Config instance of the configuration the
-expression appears in.
+Kconfig.const_syms is a dictionary like Kconfig.syms but for constant symbols.
-A missing expression (e.g. <cond> if the 'if <cond>' part is removed from
-'default A if <conf>') is represented as config.y. The standard __str__()
-functions avoid printing 'if y' conditions to give cleaner output.
+Feedback
+========
Send bug reports, suggestions, and questions to ulfalizer a.t Google's email
-service (or open a ticket on the GitHub page).
+service, or open a ticket on the GitHub page.
"""
-# TODO: document n/m/y
# TODO: consistent docstring format
import errno
@@ -152,73 +315,76 @@ import sys
# Public classes
#
-class Config(object):
+class Kconfig(object):
"""
Represents a Kconfig configuration, e.g. for x86 or ARM. This is the set of
symbols, choices, and menu nodes appearing in the configuration. Creating
- any number of Config objects (including for different architectures) is
+ any number of Kconfig objects (including for different architectures) is
safe. Kconfiglib doesn't keep any global state.
- The following attributes are available on Config instances. They should be
- viewed as read-only, and some are implemented through @property magic.
+ The following attributes are available. They should be treated as
+ read-only, and some are implemented through @property magic.
syms:
- A dictionary with all symbols in the configuration. The key is the name
- 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.
+ A dictionary with all symbols in the configuration, indexed by name. Also
+ includes all symbols that are referenced in expressions but never
+ defined, except for constant (quoted) symbols.
- defined_syms:
- A list of all defined symbols, in the same order as they appear in the
- Kconfig files. Provided as a convenience. The defined symbols are those
- whose 'nodes' attribute is non-empty.
+ const_syms:
+ A dictionary like 'syms' for constant (quoted) symbols.
named_choices:
A dictionary like 'syms' for named choices (choice FOO). This is for
- completeness. I've never seen named choices being used.
+ completeness. I've never seen a named choice outside of the test suite.
+
+ defined_syms:
+ A list with all defined symbols, in the same order as they appear in the
+ Kconfig files. Provided as a convenience.
+
+ n/m/y:
+ The predefined constant symbols n/m/y. Also available in const_syms.
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.
+ The Symbol instance for the modules symbol. Currently hardcoded to
+ MODULES, which is backwards compatible. Kconfiglib will warn if
+ 'option modules' is set on some other symbol. Tell me if you need proper
+ 'option modules' support.
+
+ 'modules' is never None. If the MODULES symbol is not explicitly defined,
+ it will get the value n as expected.
- Never None.
+ A simple way to enable modules is to do 'kconf.modules.set_value(2)'
+ (provided the MODULES symbol is defined and visible). Modules are
+ disabled by default in the kernel Kconfig files as of writing, though
+ nearly all defconfig files enable them (with 'CONFIG_MODULES=y').
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.
+ symbol can be found in Kconfig.defconfig_filename.
defconfig_filename:
- 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,
+ The filename given by the defconfig_list symbol. This is taken from the
+ first 'default' with a satisfied condition where the specified file
+ exists (can be opened for reading). If a defconfig file foo/defconfig is
+ not found and $srctree was set when the Kconfig was created,
$srctree/foo/defconfig is looked up as well.
- 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") in the 'default' properties of the
+ defconfig_filename symbol are are expanded before the file is looked up.
- References to Kconfig symbols ("$FOO") are expanded in 'default'
- properties.
+ 'defconfig_filename' is None if either no defconfig_list symbol exists,
+ or if the defconfig_list symbol has no 'default' with a satisfied
+ condition that specifies a file that exists.
- Something to look out for is that scripts/kconfig/Makefile might pass
- --defconfig=<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.
+ Gotcha: scripts/kconfig/Makefile might pass --defconfig=<defconfig> to
+ scripts/kconfig/conf when running e.g. 'make defconfig'. This option
+ overrides the defconfig_list symbol, meaning defconfig_filename might not
+ always match what 'make defconfig' would use.
top_node:
- The menu node (see the MenuNode class) of the top-level menu. Acts as the
- root of the menu tree.
+ The menu node (see the MenuNode class) of the implicit top-level menu.
+ Acts as the root of the menu tree.
mainmenu_text:
The prompt (title) of the top_node menu, with Kconfig variable references
@@ -230,10 +396,10 @@ class Config(object):
The value of the $srctree environment variable when the configuration was
loaded, or None if $srctree wasn't set. Kconfig and .config files are
looked up relative to $srctree if they are not found in the base path
- (unless absolute paths are specified). This is to support out-of-tree
- builds. The C tools use this variable in the same way.
+ (unless absolute paths are used). This is used to support out-of-tree
+ builds. The C tools use this environment variable in the same way.
- Changing $srctree after creating the Config instance has no effect. Only
+ Changing $srctree after creating the Kconfig 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.
@@ -284,7 +450,7 @@ class Config(object):
def __init__(self, filename="Kconfig", warn=True):
"""
- Creates a new Config object by parsing Kconfig files. Raises
+ Creates a new Kconfig object by parsing Kconfig files. Raises
KconfigSyntaxError on syntax errors. Note that Kconfig files are not
the same as .config files (which store configuration symbol values).
@@ -299,12 +465,12 @@ class Config(object):
always "Kconfig" in practice.
The $srctree environment variable is used to look up Kconfig files if
- set (see the class documentation).
+ set. See the class documentation.
warn (default: True):
True if warnings related to this configuration should be printed to
stderr. This can be changed later with
- Config.enable/disable_warnings(). It is provided as a constructor
+ Kconfig.enable/disable_warnings(). It is provided as a constructor
argument since warnings might be generated during parsing.
"""
@@ -337,7 +503,7 @@ class Config(object):
sym.config = self
sym.name = nmy
sym.is_constant = True
- sym._type = TRISTATE
+ sym.orig_type = TRISTATE
sym._cached_tri_val = STR_TO_TRI[nmy]
sym._cached_str_val = nmy
@@ -347,26 +513,28 @@ class Config(object):
self.m = self.const_syms["m"]
self.y = self.const_syms["y"]
- # Just to make n/m/y well-formed symbols
+ # Make n/m/y well-formed symbols
for nmy in "n", "m", "y":
sym = self.const_syms[nmy]
sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+ # This is used to determine whether previously unseen symbols should be
+ # registered. They shouldn't be if we parse expressions after parsing,
+ # as part of Kconfig.eval_string().
self._parsing_kconfigs = True
self.modules = self._lookup_sym("MODULES")
self.defconfig_list = None
- # Predefined symbol. DEFCONFIG_LIST uses this.
+ # The only predefined symbol besides n/m/y. DEFCONFIG_LIST uses this as
+ # of writing.
uname_sym = self._lookup_const_sym("UNAME_RELEASE")
- uname_sym._type = STRING
+ uname_sym.orig_type = STRING
# 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.
+ # just set it to something. The naming breaks a bit here.
uname_sym.env_var = "<uname release>"
uname_sym.defaults.append(
- (self._lookup_const_sym(platform.uname()[2]),
- self.y))
+ (self._lookup_const_sym(platform.uname()[2]), self.y))
self.syms["UNAME_RELEASE"] = uname_sym
self.top_node = MenuNode()
@@ -381,12 +549,20 @@ class Config(object):
# Parse the Kconfig files
+ # These implements a single line of "unget" for the parser
self._reuse_line = False
self._has_tokens = False
+ # Keeps track of the location in the parent Kconfig files. Kconfig
+ # files usually source other Kconfig files.
self._filestack = []
- self._enter_file_empty_stack(filename)
+ # The current parsing location
+ self._filename = filename
+ self._linenr = 0
+
+ self._file = self._open(filename)
+
self._parse_block(None, # end_token
self.top_node, # parent
self.y, # visible_if_deps
@@ -396,8 +572,7 @@ class Config(object):
self._parsing_kconfigs = False
- # Do various post-processing of the menu tree, e.g. to finalize
- # choices, flatten ifs, and implicitly create menus
+ # Do various post-processing of the menu tree
_finalize_tree(self.top_node)
# Build Symbol._direct_dependents for all symbols
@@ -408,7 +583,7 @@ class Config(object):
"""
See the class documentation.
"""
- return self._expand_sym_refs(self.top_node.prompt[0])
+ return self._expand_syms(self.top_node.prompt[0])
@property
def defconfig_filename(self):
@@ -420,9 +595,8 @@ class Config(object):
for filename, cond in self.defconfig_list.defaults:
if expr_value(cond):
- filename = self._expand_sym_refs(filename.str_value)
try:
- with self._open(filename) as f:
+ with self._open(self._expand_syms(filename.str_value)) as f:
return f.name
except IOError:
continue
@@ -434,12 +608,12 @@ class Config(object):
Loads symbol values from a file in the .config format. Equivalent to
calling Symbol.set_value() to set each of the values.
- "# CONFIG_FOO is not set" within a .config file is treated specially
- and sets the user value of FOO to 'n'. The C tools work the same way.
+ "# CONFIG_FOO is not set" within a .config file sets the user value of
+ FOO to n. The C tools work the same way.
filename:
- The .config file to load. The $srctree variable is used if set (see
- the class documentation).
+ The file to load. Respects $srctree if set (see the class
+ documentation).
replace (default: True): True if all existing user values should
be cleared before loading the .config.
@@ -470,26 +644,45 @@ class Config(object):
continue
sym = syms[name]
+ if not sym.nodes:
+ self._warn_undef_assign_load(name, val, filename,
+ linenr)
+ continue
- if sym._type == STRING and val.startswith('"'):
- if len(val) < 2 or val[-1] != '"':
- self._warn("malformed string literal",
- filename, linenr)
+ if sym.orig_type in (BOOL, TRISTATE):
+ # The C implementation only checks the first character
+ # to the right of '=', for whatever reason
+ if not ((sym.orig_type == BOOL and
+ val.startswith(("n", "y"))) or \
+ (sym.orig_type == TRISTATE and
+ val.startswith(("n", "m", "y")))):
+ self._warn("'{}' is not a valid value for the {} "
+ "symbol {}. Assignment ignored."
+ .format(val, _TYPENAME[sym.orig_type],
+ sym.name))
continue
- # Strip quotes and remove escapings. The unescaping
- # procedure should be safe since " can only appear as
- # \" inside the string.
- val = val[1:-1].replace('\\"', '"') \
- .replace("\\\\", "\\")
-
- if sym.choice is not None:
- 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 "{}".'
- .format(name, val, mode),
+ if sym.choice is not None:
+ 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 {}.'
+ .format(name, mode, val),
+ filename, linenr)
+
+ # We represent tristate values as 0, 1, 2
+ val = STR_TO_TRI[val[0]]
+
+ elif sym.orig_type == STRING:
+ string_match = _conf_string_re_match(val)
+ if not string_match:
+ self._warn("Malformed string literal in "
+ "assignment to {}. Assignment ignored."
+ .format(sym.name),
filename, linenr)
+ continue
+
+ val = unescape(string_match.group(1))
else:
unset_match = unset_re_match(line)
@@ -503,14 +696,24 @@ class Config(object):
continue
sym = syms[name]
- val = "n"
+ if sym.orig_type not in (BOOL, TRISTATE):
+ continue
+
+ val = 0
# Done parsing the assignment. Set the value.
if sym.user_str_value is not None:
+ # Make the format for the old and new value consistent in
+ # the warning
+ if sym.orig_type in (BOOL, TRISTATE):
+ display_val = TRI_TO_STR[val]
+ else:
+ display_val = val
+
self._warn('{} set more than once. Old value: "{}", new '
'value: "{}".'
- .format(name, sym.user_str_value, val),
+ .format(name, sym.user_str_value, display_val),
filename, linenr)
sym._set_value_no_invalidate(val, True)
@@ -518,17 +721,15 @@ class Config(object):
def write_config(self, filename,
header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"):
"""
- Writes out symbol values in .config format.
+ Writes out symbol values in the .config format.
- Kconfiglib makes sure the format matches what the C tools would
- generate, down to whitespace. This eases testing.
-
- filename: The filename under which to save the configuration.
+ filename:
+ Self-explanatory.
- header (default: "# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"):
- Text that will be inserted verbatim at the beginning of the file.
- You would usually want each line to start with '#' to make it a
- comment, and include a final terminating newline.
+ header (default: "# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\\n"):
+ Text that will be inserted verbatim at the beginning of the file. You
+ would usually want each line to start with '#' to make it a comment,
+ and include a final terminating newline.
"""
with open(filename, "w") as f:
f.write(header)
@@ -536,44 +737,52 @@ class Config(object):
def eval_string(self, s):
"""
- Returns the value of the expression 's', represented as a string, in
- the context of the configuration. Raises KconfigSyntaxError if syntax
- errors are detected in 's'.
+ Returns the tristate value of the expression 's' represented as an
+ integer, where 0, 1, 2 correspond to n, m, and y, respectively. Raises
+ KconfigSyntaxError if syntax errors are detected in 's'. Warns if
+ undefined symbols are referenced.
As an example, if FOO and BAR are tristate symbols at least one of
- which has the value "y", then config.eval_string("y && (FOO || BAR)")
- returns "y".
+ which has the value y, then config.eval_string("y && (FOO || BAR)")
+ returns 2 (y).
- This function always yields a tristate value. To get the value of
- non-bool, non-tristate symbols, use Symbol.str_value.
+ To get the string value of non-bool/tristate symbols, use
+ Symbol.str_value. eval_string() always returns a tristate value, and
+ all non-bool/tristate symbols have the tristate value 0 (n).
- The result of this function is consistent with how evaluation works for
- conditional ('if ...') expressions in the configuration (as well as in
- the C tools). m is rewritten to 'm && MODULES'.
+ The expression parsing is consistent with how parsing works for
+ conditional ('if ...') expressions in the configuration, and matches
+ the C implementation. m is rewritten to 'm && MODULES', so
+ eval_string("m") will return 0 (n) unless modules are enabled.
"""
- # TODO: explain
+ # The parser is optimized to be fast when parsing Kconfig files (where
+ # an expression can never appear at the beginning of a line). We have
+ # to monkey-patch things a bit here to reuse it.
+
self._filename = None
self._line = "if " + s
self._tokenize()
+ # Remove the "if " to avoid giving confusing error messages
self._line = s
+ # Remove the T_IF token
del self._tokens[0]
- return expr_value(self._parse_expr(True))
+ return expr_value(self._parse_expr(True)) # transform_m
def unset_values(self):
"""
- Resets the user values of all symbols, as if Config.load_config() or
+ Resets the user values of all symbols, as if Kconfig.load_config() or
Symbol.set_value() had never been called.
"""
- # set_value() already rejects undefined symbols, and they don't
- # need to be invalidated (because their value never changes), so we can
- # just iterate over defined symbols.
+ # set_value() already rejects undefined symbols, and they don't need to
+ # be invalidated (because their value never changes), so we can just
+ # iterate over defined symbols
for sym in self.defined_syms:
- # We're iterating over all symbols, so no need for symbols to
- # invalidate their dependent symbols
+ # We're iterating over all (defined) symbols, so no need for
+ # symbols to invalidate their dependent symbols
sym.user_str_value = sym.user_tri_value = None
sym._invalidate()
@@ -584,20 +793,20 @@ class Config(object):
def enable_warnings(self):
"""
- See Config.__init__().
+ See Kconfig.__init__().
"""
self._print_warnings = True
def disable_warnings(self):
"""
- See Config.__init__().
+ See Kconfig.__init__().
"""
self._print_warnings = False
def enable_undef_warnings(self):
"""
- Enables printing of warnings to stderr for assignments to undefined
- symbols. Disabled by default since it tends to be spammy for Kernel
+ Enables warnings for assignments to undefined symbols. Printed to
+ stderr. Disabled by default since they tend to be spammy for Kernel
configurations (and mostly suggests cleanups).
"""
self._print_undef_assign = True
@@ -610,9 +819,9 @@ class Config(object):
def __repr__(self):
"""
- Prints some general information when a Config object is evaluated.
+ Prints some general information when a Kconfig object is evaluated.
"""
- fields = (
+ return "<{}>".format(", ".join((
"configuration with {} symbols".format(len(self.syms)),
'main menu prompt "{}"'.format(self.mainmenu_text),
"srctree not set" if self.srctree is None else
@@ -621,14 +830,13 @@ class Config(object):
"warnings " + ("enabled" if self._print_warnings else "disabled"),
"undef. symbol assignment warnings " +
("enabled" if self._print_undef_assign else "disabled"),
- )
-
- return "<{}>".format(", ".join(fields))
+ )))
#
# Private methods
#
+
#
# File reading
#
@@ -655,24 +863,121 @@ class Config(object):
raise IOError(
'Could not open "{}" ({}: {}). Perhaps the $srctree '
"environment variable (which was {}) is set incorrectly. Note "
- "that the current value of $srctree is saved when the Config "
+ "that the current value of $srctree is saved when the Kconfig "
"instance is created (for consistency and to cleanly "
"separate instances)."
.format(filename, errno.errorcode[e.errno], e.strerror,
"unset" if self.srctree is None else
'"{}"'.format(self.srctree)))
+ def _enter_file(self, filename):
+ """
+ Jumps to the beginning of a sourced Kconfig file, saving the previous
+ position and file object.
+ """
+ self._filestack.append((self._file, self._filename, self._linenr))
+ try:
+ self._file = self._open(filename)
+ except IOError as e:
+ # Extend the error message a bit in this case
+ raise IOError(
+ "{}:{}: {} Also note that e.g. $FOO in a 'source' "
+ "statement does not refer to the environment "
+ "variable FOO, but rather to the Kconfig Symbol FOO "
+ "(which would commonly have 'option env=\"FOO\"' in "
+ "its definition)."
+ .format(self._filename, self._linenr, e.message))
+
+ self._filename = filename
+ self._linenr = 0
+
+ def _leave_file(self):
+ """
+ Returns from a Kconfig file to the file that sourced it.
+ """
+ self._file.close()
+ self._file, self._filename, self._linenr = self._filestack.pop()
+
+ def _next_line(self):
+ """
+ Returns the next line in the current file, or the empty string at EOF
+ (like the standard readline() function)
+ """
+ # This provides a single line of "unget" if _reuse_line is set to True
+ if not self._reuse_line:
+ self._line = self._file.readline()
+ self._linenr += 1
+
+ self._reuse_line = False
+
+ # Handle line joining
+ while self._line.endswith("\\\n"):
+ self._line = self._line[:-2] + self._file.readline()
+ self._linenr += 1
+
+ return self._line
+
+ def _next_line_no_join(self):
+ """
+ Used for help texts, which don't do line joining
+ """
+ self._line = self._file.readline()
+ self._linenr += 1
+ return self._line
+
+
#
- # Kconfig parsing
+ # Tokenization
#
+ def _lookup_sym(self, name):
+ """
+ Fetches the symbol 'name' from the symbol table, creating and
+ registering it if it does not exist. If '_parsing_configs' is False, it
+ means we're in eval_string(), and new symbols won't be registered.
+ """
+ if name in self.syms:
+ return self.syms[name]
+
+ sym = Symbol()
+ sym.config = self
+ sym.name = name
+ sym.is_constant = False
+ sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+
+ if self._parsing_kconfigs:
+ self.syms[name] = sym
+ else:
+ self._warn("no symbol {} in configuration".format(name))
+
+ return sym
+
+ def _lookup_const_sym(self, name):
+ """
+ Like _lookup_sym(), for constant (quoted) symbols
+ """
+ if name in self.const_syms:
+ return self.const_syms[name]
+
+ sym = Symbol()
+ sym.config = self
+ sym.name = name
+ sym.is_constant = True
+ sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+
+ if self._parsing_kconfigs:
+ self.const_syms[name] = sym
+
+ return sym
+
def _tokenize(self):
"""
- Parses Config._line, putting the tokens in Config._tokens. Registers
- any new symbols encountered (via _lookup_sym()).
+ Parses Kconfig._line, putting the tokens in Kconfig._tokens. Registers
+ any new symbols encountered with _lookup(_const)_sym().
Tries to be reasonably speedy by processing chunks of text via regexes
- and string operations where possible. This is a hotspot during parsing.
+ and string operations where possible. This is the biggest hotspot
+ during parsing.
"""
s = self._line
@@ -766,8 +1071,9 @@ class Config(object):
if c in "\"'":
# String literal/constant symbol
if "\\" not in s:
- # Fast path: If the string contains no backslashes, we
+ # Fast path: If the line contains no backslashes, we
# can just find the matching quote.
+
end = s.find(c, i)
if end == -1:
self._parse_error("unterminated string")
@@ -775,8 +1081,9 @@ class Config(object):
val = s[i:end]
i = end + 1
else:
- # Slow path: This could probably be sped up, but it's a
- # very unusual case anyway.
+ # Slow path for lines with backslashes (very rare,
+ # performance irrelevant)
+
quote = c
val = ""
@@ -809,7 +1116,7 @@ class Config(object):
self._lookup_const_sym(val)
elif c == "&":
- # Invalid characters are ignored
+ # Invalid characters are ignored (backwards-compatible)
if i >= len(s) or s[i] != "&":
continue
@@ -817,7 +1124,7 @@ class Config(object):
i += 1
elif c == "|":
- # Invalid characters are ignored
+ # Invalid characters are ignored (backwards-compatible)
if i >= len(s) or s[i] != "|":
continue
@@ -860,7 +1167,7 @@ class Config(object):
token = _T_GREATER
else:
- # Invalid characters are ignored
+ # Invalid characters are ignored (backwards-compatible)
continue
# Skip trailing whitespace
@@ -869,8 +1176,8 @@ class Config(object):
self._tokens.append(token)
- # TODO: say something about the None-termination
-
+ # None-terminating token streams makes the token fetching functions
+ # simpler/faster
self._tokens.append(None)
self._tokens_i = -1
@@ -882,56 +1189,48 @@ class Config(object):
return self._tokens[self._tokens_i + 1]
def _check_token(self, token):
+ """
+ Removes the next token if it's 'token' and returns True
+ """
if self._tokens[self._tokens_i + 1] == token:
self._tokens_i += 1
return True
return False
- def _enter_file_empty_stack(self, filename):
- try:
- # TODO: comment
- self._file = self._open(filename)
- except IOError as e:
- # Extend the error message a bit in this case
- # TODO: broken for top-level Kconfig (because
- # self._filename/_linenr isn't set)
- raise IOError(
- "{}:{}: {} Also note that e.g. $FOO in a 'source' "
- "statement does not refer to the environment "
- "variable FOO, but rather to the Kconfig Symbol FOO "
- "(which would commonly have 'option env=\"FOO\"' in "
- "its definition)."
- .format(self._filename, self._linenr, e.message))
- self._filename = filename
- self._linenr = 0
+ #
+ # Parsing
+ #
- def _enter_file(self, filename):
- self._filestack.append((self._file, self._filename, self._linenr))
- self._enter_file_empty_stack(filename)
+ def _make_and(self, e1, e2):
+ """
+ Constructs an AND (&&) expression. Performs trivial simplification.
+ """
+ if e1 is self.y:
+ return e2
- def _leave_file(self):
- self._file.close()
- self._file, self._filename, self._linenr = self._filestack.pop()
+ if e2 is self.y:
+ return e1
- def _next_line(self):
- # This provides a single line of "unget" if _reuse_line is set to True
- if not self._reuse_line:
- self._line = self._file.readline()
- self._linenr += 1
+ if e1 is self.n or e2 is self.n:
+ return self.n
- self._reuse_line = False
+ return (AND, e1, e2)
- while self._line.endswith("\\\n"):
- self._line = self._line[:-2] + self._file.readline()
- self._linenr += 1
+ def _make_or(self, e1, e2):
+ """
+ Constructs an OR (||) expression. Performs trivial simplification.
+ """
+ if e1 is self.n:
+ return e2
- return self._line
+ if e2 is self.n:
+ return e1
- def _next_line_no_join(self):
- self._line = self._file.readline()
- self._linenr += 1
- return self._line
+ if e1 is self.y or e2 is self.y:
+ return self.y
+
+ return (OR, e1, e2)
def _parse_block(self, end_token, parent, visible_if_deps, prev_node):
"""
@@ -953,21 +1252,23 @@ class Config(object):
prev_node:
The previous menu node. New nodes will be added after this one (by
- modifying its 'next' pointer).
+ modifying their 'next' pointer).
- Through a trick, prev_node is also used to parse a list of children
- (for a menu or Choice): After parsing the children, the 'next'
- pointer is assigned to the 'list' pointer to "tilt up" the children
- above the node.
+ prev_node is reused to parse a list of child menu nodes (for a menu
+ or Choice): After parsing the children, the 'next' pointer is
+ assigned to the 'list' pointer to "tilt up" the children above the
+ node.
Returns the final menu node in the block (or prev_node if the block is
- empty). This allows for easy chaining.
+ empty). This allows chaining.
"""
while 1:
+ # We might already have tokens from parsing a line to check if it's
+ # a property and discovering it isn't. This is a kind of "unget".
if not self._has_tokens:
- # Also advances to the next line
+ # Advance to the next line
if not self._next_line():
if end_token is not None:
raise KconfigSyntaxError("Unexpected end of file " +
@@ -987,9 +1288,8 @@ class Config(object):
continue
if t0 in (_T_CONFIG, _T_MENUCONFIG):
- # The tokenizer will automatically allocate a new Symbol object
- # for any new names it encounters, so we don't need to worry
- # about that here.
+ # The tokenizer allocates a Symbol objects the first time a
+ # symbol is seen
sym = self._next_token()
node = MenuNode()
@@ -1012,7 +1312,7 @@ class Config(object):
prev_node.next = prev_node = node
elif t0 == _T_SOURCE:
- self._enter_file(self._expand_sym_refs(self._next_token()))
+ self._enter_file(self._expand_syms(self._next_token()))
prev_node = self._parse_block(None, # end_token
parent,
visible_if_deps,
@@ -1133,19 +1433,17 @@ class Config(object):
"""
Parses properties for symbols, menus, choices, and comments. Also takes
care of propagating dependencies from the menu node to the properties
- of the item (this mirrors the inner working of the C tools).
+ of the item (this mirrors the C tools, though they do it after
+ parsing).
node:
- The menu node we're parsing properties on. Some properties (prompts,
- help texts, 'depends on') apply to the Menu node, while the others
- apply to the contained item.
+ The menu node we're parsing properties on. Prompt, help text,
+ 'depends on', and 'visible if' properties apply to the Menu node,
+ while the others apply to the contained item.
visible_if_deps:
'visible if' dependencies from enclosing menus. Propagated to Symbol
and Choice prompts.
-
- Stops when finding a line that isn't part of the properties, and
- returns a (line, tokens) tuple for it so it can be reused.
"""
# New properties encountered at this location. A local 'depends on'
@@ -1157,12 +1455,12 @@ class Config(object):
implies = []
ranges = []
- # Menu node dependency from 'depends on'. Will get propagated to the
+ # Menu node dependencies from 'depends on'. Will get propagated to the
# properties above.
node.dep = self.y
while 1:
- # Also advances to the next line
+ # Advance to the next line
if not self._next_line():
break
@@ -1205,10 +1503,12 @@ class Config(object):
help_lines = [_deindent(line, indent).rstrip()]
while 1:
line = self._next_line_no_join()
+
if not line or \
(not line.isspace() and _indentation(line) < indent):
node.help = "\n".join(help_lines).rstrip() + "\n"
break
+
help_lines.append(_deindent(line, indent).rstrip())
if not line:
@@ -1229,7 +1529,7 @@ class Config(object):
implies.append((self._next_token(), self._parse_cond()))
elif t0 in (_T_BOOL, _T_TRISTATE, _T_INT, _T_HEX, _T_STRING):
- node.item._type = _TOKEN_TO_TYPE[t0]
+ node.item.orig_type = _TOKEN_TO_TYPE[t0]
if self._peek_token() is not None:
prompt = (self._next_token(), self._parse_cond())
@@ -1238,7 +1538,7 @@ class Config(object):
defaults.append((self._parse_expr(False), self._parse_cond()))
elif t0 in (_T_DEF_BOOL, _T_DEF_TRISTATE):
- node.item._type = _TOKEN_TO_TYPE[t0]
+ node.item.orig_type = _TOKEN_TO_TYPE[t0]
defaults.append((self._parse_expr(False), self._parse_cond()))
@@ -1288,13 +1588,13 @@ class Config(object):
# keep being called "MODULES".
if node.item is not self.modules:
self._warn("the 'modules' option is not supported. "
- "Let me know if this is a problem for you; "
- "it shouldn't be that hard to implement. "
- "(Note that modules are still supported -- "
+ "Let me know if this is a problem for you, "
+ "as it wouldn't be that hard to implement. "
+ "Note that modules are supported -- "
"Kconfiglib just assumes the symbol name "
"MODULES, like older versions of the C "
"implementation did when 'option modules' "
- "wasn't used.)",
+ "wasn't used.",
self._filename, self._linenr)
elif self._check_token(_T_ALLNOCONFIG_Y):
@@ -1322,6 +1622,7 @@ class Config(object):
else:
self._tokens_i = -1
+ # Reuse the tokens for the non-property line later
self._has_tokens = True
break
@@ -1358,7 +1659,7 @@ class Config(object):
# Handle selects
for target, cond in selects:
- # Only stored for convenience. Not used during evaluation.
+ # Only stored for inspection. Not used during evaluation.
node.item.selects.append(
(target, self._make_and(cond, node.dep)))
@@ -1371,7 +1672,7 @@ class Config(object):
# Handle implies
for target, cond in implies:
- # Only stored for convenience. Not used during evaluation.
+ # Only stored for inspection. Not used during evaluation.
node.item.implies.append(
(target, self._make_and(cond, node.dep)))
@@ -1384,19 +1685,12 @@ class Config(object):
def _parse_expr(self, transform_m):
"""
- Parses an expression from the tokens in Config._tokens using a simple
- top-down approach. The result has the form
- '(<operator> <operand 1> <operand 2>)' where <operator> is e.g.
- 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.
+ Parses an expression from the tokens in Kconfig._tokens using a simple
+ top-down approach. See the module docs for the expression format.
transform_m:
- True if 'm' should be rewritten to 'm && MODULES'. See
- the Config.eval_string() documentation.
+ True if m should be rewritten to m && MODULES. See the
+ Kconfig.eval_string() documentation.
"""
# Grammar:
@@ -1453,8 +1747,7 @@ class Config(object):
# Plain symbol
# For conditional expressions ('depends on <expr>',
- # '... if <expr>', etc.), "m" and m are rewritten to
- # "m" && MODULES.
+ # '... if <expr>', etc.), m is rewritten to m && MODULES.
if transform_m and token is self.m:
return (AND, self.m, self.modules)
@@ -1477,52 +1770,78 @@ class Config(object):
self._parse_error("malformed expression")
#
- # Symbol lookup
+ # Caching and invalidation
#
- def _lookup_sym(self, name):
+ def _build_dep(self):
"""
- Fetches the symbol 'name' from the symbol table, creating and
- registering it if it does not exist. TODO If 'for_eval_string' is True,
- the symbol won't be added to the symbol table if it does not exist.
- This is for Config.eval_string().
+ Populates the Symbol._direct_dependents sets, which link symbols to the
+ symbols that immediately depend on them in the sense that changing the
+ value of the symbol might affect the values of the dependent symbols.
+ This is used for caching/invalidation.
+
+ The calculated sets might be larger than necessary as we don't do any
+ complex analysis of the expressions.
"""
- if name in self.syms:
- return self.syms[name]
- sym = Symbol()
- sym.config = self
- sym.name = name
- sym.is_constant = False
- sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+ # The directly dependent symbols of a symbol S are:
+ #
+ # - Any symbols whose prompts, default values, rev_dep (select
+ # condition), weak_rev_dep (imply condition), or ranges depend on S
+ #
+ # - Any symbol that has S as a direct dependency (has S in
+ # direct_dep). This is needed to get invalidation right for
+ # 'imply'.
+ #
+ # - Any symbols that belong to the same choice statement as S. These
+ # won't be included in S._direct_dependents as it creates dependency
+ # loops, but S._get_dependent() includes them.
+ #
+ # - Any symbols in a choice statement that depends on S
- if self._parsing_kconfigs:
- self.syms[name] = sym
- else:
- self._warn("no symbol {} in configuration".format(name))
+ # Only calculate _direct_dependents for defined symbols. Constant and
+ # undefined symbols could theoretically be selected/implied, but it
+ # shouldn't change their value, so it's not a true dependency.
+ for sym in self.defined_syms:
+ for node in sym.nodes:
+ if node.prompt is not None:
+ _make_depend_on(sym, node.prompt[1])
- return sym
+ for value, cond in sym.defaults:
+ _make_depend_on(sym, value)
+ _make_depend_on(sym, cond)
- def _lookup_const_sym(self, name):
- """
- TODO: say something
- """
- if name in self.const_syms:
- return self.const_syms[name]
+ _make_depend_on(sym, sym.rev_dep)
+ _make_depend_on(sym, sym.weak_rev_dep)
- sym = Symbol()
- sym.config = self
- sym.name = name
- sym.is_constant = True
- sym.rev_dep = sym.weak_rev_dep = sym.direct_dep = self.n
+ for low, high, cond in sym.ranges:
+ _make_depend_on(sym, low)
+ _make_depend_on(sym, high)
+ _make_depend_on(sym, cond)
- if self._parsing_kconfigs:
- self.const_syms[name] = sym
+ _make_depend_on(sym, sym.direct_dep)
+
+ if sym.choice is not None:
+ for node in sym.choice.nodes:
+ if node.prompt is not None:
+ _make_depend_on(sym, node.prompt[1])
+
+ for _, cond in sym.choice.defaults:
+ _make_depend_on(sym, cond)
+
+ def _invalidate_all(self):
+ # Undefined symbols never change value and don't need to be
+ # invalidated, so we can just iterate over defined symbols.
+ # Invalidating constant symbols would break things horribly.
+ for sym in self.defined_syms:
+ sym._invalidate()
+
+ for choice in self._choices:
+ choice._invalidate()
- return sym
#
- # .config generation
+ # .config writing
#
def _get_config_strings(self):
@@ -1535,7 +1854,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_node).
+ # rooted at Kconfig.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
@@ -1549,6 +1868,7 @@ class Config(object):
return []
config_strings = []
+ # Small optimization
add_fn = config_strings.append
while 1:
@@ -1581,82 +1901,12 @@ class Config(object):
else:
return config_strings
- #
- # Dependency tracking (for caching and invalidation)
- #
-
- def _build_dep(self):
- """
- Populates the Symbol._direct_dependents sets, which link symbols to the
- symbols that immediately depend on them in the sense that changing the
- value of the symbol might affect the values of the dependent symbols.
- This is used for caching/invalidation purposes.
-
- The calculated sets might be larger than necessary as we don't do any
- complex analysis of the expressions.
- """
-
- # The directly dependent symbols of a symbol S are:
- #
- # - Any symbols whose prompts, default values, rev_dep (select
- # condition), weak_rev_dep (imply condition), or ranges depend on S
- #
- # - Any symbol that has S as a direct dependency (has S in
- # direct_dep). This is needed to get invalidation right for
- # 'imply'.
- #
- # - Any symbols that belong to the same choice statement as S. These
- # won't be included in S._direct_dependents as it creates dependency
- # loops, but S._get_dependent() includes them.
- #
- # - Any symbols in a choice statement that depends on S
-
- # Only calculate _direct_dependents for defined symbols. Undefined
- # symbols could theoretically be selected/implied, but it wouldn't
- # change their value (they always evaluate to their name), so it's not
- # a true dependency.
-
- for sym in self.defined_syms:
- for node in sym.nodes:
- if node.prompt is not None:
- _make_depend_on(sym, node.prompt[1])
-
- for value, cond in sym.defaults:
- _make_depend_on(sym, value)
- _make_depend_on(sym, cond)
-
- _make_depend_on(sym, sym.rev_dep)
- _make_depend_on(sym, sym.weak_rev_dep)
-
- for low, high, cond in sym.ranges:
- _make_depend_on(sym, low)
- _make_depend_on(sym, high)
- _make_depend_on(sym, cond)
-
- _make_depend_on(sym, sym.direct_dep)
-
- if sym.choice is not None:
- for node in sym.choice.nodes:
- if node.prompt is not None:
- _make_depend_on(sym, node.prompt[1])
-
- for _, cond in sym.choice.defaults:
- _make_depend_on(sym, cond)
-
- def _invalidate_all(self):
- # Undefined symbols never change value and don't need to be
- # invalidated, so we can just iterate over defined symbols
- for sym in self.defined_syms:
- sym._invalidate()
-
- for choice in self._choices:
- choice._invalidate()
#
- # Printing and misc.
+ # Misc.
#
- def _expand_sym_refs(self, s):
+ def _expand_syms(self, s):
"""
Expands $-references to symbols in 's' to symbol values, or to the
empty string for undefined symbols.
@@ -1673,44 +1923,6 @@ class Config(object):
(sym.str_value if sym is not None else "") + \
s[sym_ref_match.end():]
- #
- # Expression construction
- #
-
- def _make_and(self, e1, e2):
- """
- Constructs an AND (&&) expression. Performs trivial simplification.
- """
- if e1 is self.y:
- return e2
-
- if e2 is self.y:
- return e1
-
- if e1 is self.n or e2 is self.n:
- return self.n
-
- return (AND, e1, e2)
-
- def _make_or(self, e1, e2):
- """
- Constructs an OR (||) expression. Performs trivial simplification.
- """
- if e1 is self.n:
- return e2
-
- if e2 is self.n:
- return e1
-
- if e1 is self.y or e2 is self.y:
- return self.y
-
- return (OR, e1, e2)
-
- #
- # Errors and warnings
- #
-
def _parse_error(self, msg):
if self._filename is None:
loc = ""
@@ -1740,6 +1952,7 @@ class Config(object):
'attempt to assign the value "{}" to the undefined symbol {}' \
.format(val, name), filename, linenr)
+
class Symbol(object):
"""
Represents a configuration symbol:
@@ -1747,151 +1960,159 @@ class Symbol(object):
(menu)config FOO
...
- The following attributes are available on Symbol instances. They should be
- viewed as read-only, and some are implemented through @property magic (but
- are still efficient to access due to internal caching).
+ The following attributes are available. They should be viewed as read-only,
+ and some are implemented through @property magic (but are still efficient
+ to access due to internal caching).
- (Note: Prompts and help texts are stored in the Symbol's MenuNode(s) rather
- than the Symbol itself. This matches the C tools.)
+ Note: Prompts and help texts are stored in the Symbol's MenuNode(s) rather
+ than the Symbol itself. Check the 'nodes' attribute. This matches the C
+ tools.
name:
The name of the symbol, e.g. "FOO" for 'config FOO'.
type:
The type of the symbol. One of BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN.
- UNKNOWN is for undefined symbols and symbols defined without a type.
+ UNKNOWN is for undefined symbols, (non-special) constant symbols, and
+ symbols defined without a type.
- When running without modules (CONFIG_MODULES=n), TRISTATE symbols
- magically change type to BOOL. This also happens for symbols within
- choices in "y" mode. This matches the C tools, and makes sense for
- menuconfig-like functionality. (Check the implementation of the property
- if you need to get the original type.)
+ When running without modules (MODULES having the value n), TRISTATE
+ symbols magically change type to BOOL. This also happens for symbols
+ within choices in "y" mode. This matches the C tools, and makes sense for
+ menuconfig-like functionality.
+
+ orig_type:
+ The type as given in the Kconfig file, without any magic applied. Used
+ when printing the symbol.
str_value:
- TODO
- The current value of the symbol. Automatically recalculated as
- dependencies change.
+ The value of the symbol as a string. Gives the string value for
+ string/int/hex symbols. For bool/tristate symbols, gives "n", "m", or
+ "y".
+
+ This is the symbol value that's used in relational expressions
+ (A = B, A != B, etc.)
tri_value:
- TODO
+ The tristate value of the symbol as an integer. One of 0, 1, 2,
+ representing n, m, y. Always 0 (n) for non-bool/tristate symbols.
+
+ This is the symbol value that's used outside of relation expressions
+ (A, !A, A && B, A || B).
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
- selections available in the 'menuconfig' interface. The assignable
- values are calculated from the Symbol's visibility and selects/implies.
+ A tuple containing the tristate user values that can currently be
+ assigned to the symbol (that will be respected), ordered from lowest (0,
+ representing n) to highest (2, representing y). This corresponds to the
+ selections available in the menuconfig interface. The set of assignable
+ values is calculated from the symbol's visibility and selects/implies.
- Returns the empty string for non-BOOL/TRISTATE and symbols with
- visibility "n". The other possible values are "ny", "nmy", "my", "m",
- and "y". A "m" or "y" result means the symbol is visible but "locked" to
- that particular value (through a select, perhaps in combination with a
- prompt dependency). menuconfig seems to represent this as -M- and -*-,
- respectively.
+ Returns the empty set for non-bool/tristate symbols and for symbols with
+ visibility n. The other possible values are (0, 2), (0, 1, 2), (1, 2),
+ (1,), and (2,). A (1,) or (2,) result means the symbol is visible but
+ "locked" to that particular tristate value (through a select, perhaps in
+ combination with a prompt condition). menuconfig represents this as -M-
+ and -*-, respectively.
- Some handy 'assignable' idioms:
+ For string/hex/int symbols, check if Symbol.visibility is non-0 (non-n)
+ instead to determine if the value can be changed.
- # Is the symbol assignable (visible)?
- if sym.assignable:
- # What's the highest value it can be assigned? [-1] in Python
- # gives the last element.
- sym_high = sym.assignable[-1]
+ Some handy 'assignable' idioms:
- # The lowest?
- sym_low = sym.assignable[0]
+ # Is the symbol assignable (visible)?
+ if sym.assignable:
+ # What's the highest value it can be assigned? [-1] in Python
+ # gives the last element.
+ sym_high = sym.assignable[-1]
- # Can the symbol be assigned the value "m"?
- if "m" in sym.assignable:
- ...
+ # The lowest?
+ sym_low = sym.assignable[0]
- visibility:
- The visibility of the symbol's prompt(s): one of "n", "m", or "y". This
- acts as an upper bound on the values the user can set for the symbol (via
- Symbol.set_value() or a .config file). User values higher than the
- visibility are truncated down to the visibility.
+ # Can the symbol be set to at least m?
+ if sym.assignable[-1] >= 1:
+ ...
- If the visibility is "n", the user value is ignored, and the symbol is
- not visible in e.g. the menuconfig interface. The visibility of symbols
- without prompts is always "n". Symbols with "n" visibility can only get a
- non-"n" value through a default, select, or imply.
+ # Can the symbol be set to m?
+ if 1 in sym.assignable:
+ ...
- Note that 'depends on' and parent dependencies (including 'visible if'
- dependencies) are propagated to the prompt dependencies. Additional
- dependencies can be specified with e.g. 'bool "foo" if <cond>".
+ visibility:
+ The visibility of the symbol. One of 0, 1, 2, representing n, m, y. See
+ the module documentation for an overview of symbol values and visibility.
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.
+ The user value of the symbol as a string, or None if no user value has
+ been assigned (via Kconfig.load_value() or Symbol.set_value()). See
+ str_value.
- 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.
+ Warning: Do not assign directly to this. It will break things. Use
+ Symbol.set_value().
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.
+ The user value of the symbol as a tristate value (0, 1, 2), or None if no
+ user value has been assigned (via Kconfig.load_value() or
+ Symbol.set_value()). See tri_value.
+
+ Always 0 (n) for non-bool/tristate symbols (or None if no user value has
+ been set).
+
+ Warning: Do not assign directly to this. It will break things. Use
+ Symbol.set_value().
config_string:
The .config assignment string that would get written out for the symbol
- by Config.write_config(). None if no .config assignment would get written
- out. In general, visible symbols, symbols with (active) defaults, and
- selected symbols get written out.
+ by Kconfig.write_config(). None if no .config assignment would get
+ written out. In general, visible symbols, symbols with (active) defaults,
+ and selected symbols get written out.
nodes:
- A list of MenuNode's for this symbol. For most symbols, this list will
- contain a single MenuNode. Undefined symbols get an empty list, and
- symbols defined in multiple locations get one node for each location.
+ A list of MenuNodes for this symbol. Will contain a single MenuNode for
+ most symbols. Undefined and constant symbols have an empty nodes list.
+ Symbols defined in multiple locations get one node for each location.
choice:
Holds the parent Choice for choice symbols, and None for non-choice
symbols. Doubles as a flag for whether a symbol is a choice symbol.
defaults:
- List of (default, cond) tuples for the symbol's 'default's. For example,
- 'default A && B if C || D' is represented as ((AND, A, B), (OR, C, D)).
- If no condition was given, 'cond' is self.config.y.
+ List of (default, cond) tuples for the symbol's 'default' properties. For
+ example, 'default A && B if C || D' is represented as
+ ((AND, A, B), (OR, C, D)). If no condition was given, 'cond' is
+ self.config.y.
Note that 'depends on' and parent dependencies are propagated to
'default' conditions.
selects:
- List of (symbol, cond) tuples for the symbol's 'select's. For example,
- 'select A if B' is represented as (A, B). If no condition was given,
- 'cond' is self.config.y.
+ List of (symbol, cond) tuples for the symbol's 'select' properties. For
+ example, 'select A if B' is represented as (A, B). If no condition was
+ given, 'cond' is self.config.y.
Note that 'depends on' and parent dependencies are propagated to 'select'
conditions.
implies:
- List of (symbol, cond) tuples for the symbol's 'imply's. For example,
- 'imply A if B' is represented as (A, B). If no condition was given,
- 'cond' is self.config.y.
-
- Note that 'depends on' and parent dependencies are propagated to 'imply'
- conditions.
+ Same format as 'selects', for imply.
ranges:
- List of (low, high, cond) tuples for the symbol's 'range's. For example,
- 'range 1 2 if A' is represented as (1, 2, A). If there is no condition,
- 'cond' is self.config.y.
+ List of (low, high, cond) tuples for the symbol's 'range' properties. For
+ example, 'range 1 2 if A' is represented as (1, 2, A). If there is no
+ condition, 'cond' is self.config.y.
Note that 'depends on' and parent dependencies are propagated to 'range'
conditions.
- Gotcha: Integers are represented as Symbols too. Undefined symbols get
- their name as their value, so this works out. The C tools work the same
- way.
+ Gotcha: 1 and 2 above will be represented as (undefined) Symbols rather
+ than plain integers. Undefined symbols get their name as their string
+ value, so this works out. The C tools work the same way.
rev_dep:
- Reverse dependency expression from being 'select'ed by other symbols.
+ Reverse dependency expression from other symbols selecting this symbol.
Multiple selections get ORed together. A condition on a select is ANDed
- with the selecting symbol. For example, if A has 'select FOO' and B has
- 'select FOO if C', then FOO's rev_dep will be '(OR, A, (AND, B, C))'.
+ with the selecting symbol.
+
+ For example, if A has 'select FOO' and B has 'select FOO if C', then
+ FOO's rev_dep will be (OR, A, (AND, B, C)).
weak_rev_dep:
Like rev_dep, for imply.
@@ -1899,6 +2120,8 @@ class Symbol(object):
direct_dep:
The 'depends on' dependencies. If a symbol is defined in multiple
locations, the dependencies at each location are ORed together.
+ Internally, this is only used to implement 'imply', which only applies if
+ the implied symbol has expr_value(self.direct_dep) != 0.
env_var:
If the Symbol has an 'option env="FOO"' option, this contains the name
@@ -1908,18 +2131,23 @@ class Symbol(object):
'option env="FOO"' acts as a 'default' property whose value is the value
of $FOO.
- env_var is also set (to "<uname release>") on the predefined symbol
+ env_var is set to "<uname release>" for 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.
+ files, even if they are visible. env_var corresponds to a flag called
+ SYMBOL_AUTO in the C implementation.
is_allnoconfig_y:
True if the symbol has 'option allnoconfig_y' set on it. This has no
- effect internally, but can be checked by scripts.
+ effect internally (except when printing symbols), but can be checked by
+ scripts.
+
+ is_constant:
+ True if the symbol is a constant (quoted) symbol.
config:
- The Config instance this symbol is from.
+ The Kconfig instance this symbol is from.
"""
__slots__ = (
@@ -1930,7 +2158,6 @@ class Symbol(object):
"_cached_tri_val",
"_cached_vis",
"_direct_dependents",
- "_type",
"_write_to_conf",
"choice",
"config",
@@ -1942,6 +2169,7 @@ class Symbol(object):
"is_constant",
"name",
"nodes",
+ "orig_type",
"ranges",
"rev_dep",
"selects",
@@ -1960,11 +2188,12 @@ class Symbol(object):
See the class documentation.
"""
- if self._type == TRISTATE and \
+ if self.orig_type == TRISTATE and \
((self.choice is not None and self.choice.tri_value == 2) or
not self.config.modules.tri_value):
return BOOL
- return self._type
+
+ return self.orig_type
@property
def str_value(self):
@@ -1975,14 +2204,14 @@ class Symbol(object):
if self._cached_str_val is not None:
return self._cached_str_val
- if self._type in (BOOL, TRISTATE):
+ if self.orig_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
# string value. This is why things like "FOO = bar" work for seeing if
# FOO has the value "bar".
- if self._type == UNKNOWN:
+ if self.orig_type == UNKNOWN:
self._cached_str_val = self.name
return self.name
@@ -1991,8 +2220,8 @@ class Symbol(object):
self._write_to_conf = (vis != 0)
- if self._type in (INT, HEX):
- base = _TYPE_TO_BASE[self._type]
+ if self.orig_type in (INT, HEX):
+ base = _TYPE_TO_BASE[self.orig_type]
# Check if a range is in effect
for low_expr, high_expr, cond in self.ranges:
@@ -2044,7 +2273,7 @@ class Symbol(object):
if clamped_val is not None:
val = (hex(clamped_val)
- if self._type == HEX else
+ if self.orig_type == HEX else
str(clamped_val))
break
@@ -2054,12 +2283,14 @@ class Symbol(object):
# constraint, then the low end of the range is used,
# provided it's > 0, with "0x" prepended as appropriate.
if has_active_range and low > 0:
- val = (hex(low) if self._type == HEX else str(low))
+ val = (hex(low) if self.orig_type == HEX else str(low))
- elif self._type == STRING:
+ elif self.orig_type == STRING:
if vis and self.user_str_value is not None:
+ # If the symbol is visible and has a user value, use that
val = self.user_str_value
else:
+ # Otherwise, look at defaults
for val_expr, cond in self.defaults:
if expr_value(cond):
self._write_to_conf = True
@@ -2078,7 +2309,7 @@ class Symbol(object):
if self._cached_tri_val is not None:
return self._cached_tri_val
- if self._type not in (BOOL, TRISTATE):
+ if self.orig_type not in (BOOL, TRISTATE):
self._cached_tri_val = 0
return self._cached_tri_val
@@ -2089,11 +2320,11 @@ class Symbol(object):
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
+ # If the symbol is visible and has a user value, use that
val = min(self.user_tri_value, vis)
else:
- # Otherwise, we look at defaults and weak reverse dependencies
+ # Otherwise, look at defaults and weak reverse dependencies
# (implies)
for default, cond in self.defaults:
@@ -2136,9 +2367,8 @@ class Symbol(object):
# 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
+ # m is promoted to y for (1) bool symbols and (2) symbols with a
+ # weak_rev_dep (from imply) of y
if val == 1 and \
(self.type == BOOL or expr_value(self.weak_rev_dep) == 2):
val = 2
@@ -2175,8 +2405,7 @@ class Symbol(object):
"""
if self.env_var is not None:
- # Variables with 'option env' never get written out. This
- # corresponds to the SYMBOL_AUTO flag in the C implementation.
+ # Corresponds to SYMBOL_AUTO being set in the C implementation
return None
# Note: _write_to_conf is determined when the value is calculated. This
@@ -2185,44 +2414,51 @@ class Symbol(object):
if not self._write_to_conf:
return None
- if self._type in (BOOL, TRISTATE):
+ if self.orig_type in (BOOL, TRISTATE):
return "{}{}={}\n" \
.format(self.config.config_prefix, self.name, val) \
if val != "n" else \
"# {}{} is not set\n" \
.format(self.config.config_prefix, self.name)
- if self._type in (INT, HEX):
+ if self.orig_type in (INT, HEX):
return "{}{}={}\n" \
.format(self.config.config_prefix, self.name, val)
- if self._type == STRING:
+ if self.orig_type == STRING:
# Escape \ and "
return '{}{}="{}"\n' \
- .format(self.config.config_prefix, self.name,
- val.replace("\\", "\\\\").replace('"', '\\"'))
+ .format(self.config.config_prefix, self.name, escape(val))
_internal_error("Internal error while creating .config: unknown "
- 'type "{}".'.format(self._type))
+ 'type "{}".'.format(self.orig_type))
def set_value(self, value):
"""
Sets the user value of the symbol.
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_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.
+ file. For bool and tristate symbols, use the 'assignable' attribute to
+ check which values can currently be assigned. Setting values outside
+ 'assignable' will cause 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 (Kconfiglib will print a warning by
+ default).
- The values of other symbols that depend on this symbol are
- automatically recalculated to reflect the new value.
+ Other symbols that depend (possibly indirectly) on this symbol are
+ automatically recalculated to reflect the assigned value.
value:
The user value to give to the symbol.
+
+ A a convenience, set_value() accepts both 0/1/2 (plain python
+ integers) and "n"/"m"/"y" as the the format of the 'value' argument
+ for bool and tristate symbols. This was added so that 'assignable'
+ could return integers (which are easier to work with) while at the
+ same time being able to assign a value from 'assignable' without an
+ ugly conversion. Accepting strings for all symbols streamlines
+ .config handling.
"""
self._set_value_no_invalidate(value, False)
@@ -2235,7 +2471,7 @@ class Symbol(object):
def unset_value(self):
"""
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().
+ a user value via Kconfig.load_config() or Symbol.set_value().
"""
self.user_str_value = self.user_tri_value = None
self._rec_invalidate()
@@ -2328,7 +2564,7 @@ class Symbol(object):
# rev_dep
# weak_rev_dep
- self._type = UNKNOWN
+ self.orig_type = UNKNOWN
self.defaults = []
self.selects = []
self.implies = []
@@ -2338,7 +2574,7 @@ class Symbol(object):
self.user_str_value = self.user_tri_value = None
- # Populated in Config._build_dep() after parsing. Links the symbol to
+ # Populated in Kconfig._build_dep() after parsing. Links the symbol to
# the symbols that immediately depend on it (in a caching/invalidation
# sense). The total set of dependent symbols for the symbol is
# calculated as needed in _get_dependent().
@@ -2364,42 +2600,42 @@ class Symbol(object):
Worker function for the 'assignable' attribute.
"""
- if self._type not in (BOOL, TRISTATE):
- return ""
+ if self.orig_type not in (BOOL, TRISTATE):
+ return ()
vis = self.visibility
if not vis:
- return ""
+ return ()
rev_dep_val = expr_value(self.rev_dep)
if vis == 2:
if not rev_dep_val:
if self.type == BOOL or expr_value(self.weak_rev_dep) == 2:
- return "ny"
- return "nmy"
+ return (0, 2)
+ return (0, 1, 2)
if rev_dep_val == 2:
- return "y"
+ return (2,)
# rev_dep_val == 1
if self.type == BOOL or expr_value(self.weak_rev_dep) == 2:
- return "y"
- return "my"
+ return (2,)
+ return (1, 2)
# vis == 1
if not rev_dep_val:
- return "m" if expr_value(self.weak_rev_dep) != 2 else "y"
+ return (1,) if expr_value(self.weak_rev_dep) != 2 else (2,)
if rev_dep_val == 2:
- return "y"
+ return (2,)
# vis == rev_dep_val == 1
- return "m"
+ return (1,)
def _set_value_no_invalidate(self, value, suppress_prompt_warning):
"""
@@ -2412,14 +2648,23 @@ class Symbol(object):
"""
# Check if the value is valid for our type
- if not ((self._type == BOOL and value in ("n", "y") ) or
- (self._type == TRISTATE and value in ("n", "m", "y")) or
- (self._type == STRING ) or
- (self._type == INT and _is_base_n(value, 10) ) or
- (self._type == HEX and _is_base_n(value, 16) )):
- self.config._warn('the value "{}" is invalid for {}, which has '
- "type {}. Assignment ignored."
- .format(value, self.name, _TYPENAME[self._type]))
+ if not ((self.orig_type == BOOL and value in (0, 2) ) or
+ (self.orig_type == TRISTATE and value in (0, 1, 2) ) or
+ (self.orig_type == STRING and isinstance(value, str)) or
+ (self.orig_type == INT and isinstance(value, str)
+ and _is_base_n(value, 10) ) or
+ (self.orig_type == HEX and isinstance(value, str)
+ and _is_base_n(value, 16))):
+
+ warning = "the value '{}' is invalid for {}, which has type {}" \
+ .format(value, self.name, _TYPENAME[self.orig_type])
+
+ if self.orig_type in (BOOL, TRISTATE) and \
+ value in ("n", "m", "y"):
+ warning += ' (pass 0, 1, 2 for n, m, y, respectively)'
+
+ self.config._warn(warning)
+
return
if not self.nodes:
@@ -2436,11 +2681,12 @@ class Symbol(object):
"promptless symbol {} will have no effect"
.format(value, self.name))
- self.user_str_value = value
- self.user_tri_value = \
- STR_TO_TRI[value] \
- if self._type in (BOOL, TRISTATE) else \
- 0
+ if self.orig_type in (BOOL, TRISTATE):
+ self.user_str_value = TRI_TO_STR[value]
+ self.user_tri_value = value
+ else:
+ self.user_str_value = value
+ self.user_tri_value = 0
# TODO: assigning automatically changes choice yada yada
@@ -2524,9 +2770,10 @@ class Choice(object):
choice
...
+ endchoice
The following attributes are available on Choice instances. They should be
- viewed as read-only, and some are implemented through @property magic (but
+ treated as read-only, and some are implemented through @property magic (but
are still efficient to access due to internal caching).
name:
@@ -2542,29 +2789,40 @@ class Choice(object):
When running without modules (CONFIG_MODULES=n), TRISTATE choices
magically change type to BOOL. This matches the C tools, and makes sense
- for menuconfig-like functionality. (Check the implementation of the
- property if you need to get the original type.)
+ for menuconfig-like functionality.
+
+ orig_type:
+ The type as given in the Kconfig file, without any magic applied. Used
+ when printing the choice.
- value:
+ tri_value:
The tristate value (mode) of the choice. A choice can be in one of three
modes:
- "n" - The choice is not visible and no symbols can be selected.
+ 0 (n) - The choice is disabled and no symbols can be selected. This
+ mode is only possible for choices with the 'optional' flag set
+ (see kconfig-language.txt).
- "m" - Any number of symbols can be set to "m". The rest will be "n".
+ 1 (m) - Any number of choice symbols can be set to "m", the rest will
+ be "n".
- "y" - One symbol will be "y" while the rest are "n".
+ 2 (y) - One symbol will be "y", the rest "n".
- Only tristate choices can be in "m" mode, and the visibility of the
- choice is an upper bound on the mode.
+ Only tristate choices can be in "m" mode. The visibility of the choice is
+ an upper bound on the mode.
The mode changes automatically when a value is assigned to a symbol
within the choice (this makes .config loading "just work"), and can also
be changed via Choice.set_value().
- See the implementation note at the end for one reason why it makes sense
- to call this 'value' rather than e.g. 'mode'. It also makes the Choice
- and Symbol interfaces consistent.
+ Implementation note: The C tools internally represent choices as a type
+ of symbol, with special-casing in many code paths. This is why there is a
+ lot of similarity to Symbol. The value (mode) of a choice is really just
+ a normal symbol value, and an implicit reverse dependency forces its
+ lower bound to 'm' for non-optional choices. Kconfiglib uses a separate
+ Choice class only because it makes the code and interface less confusing
+ (especially in a user-facing interface). Corresponding attributes have
+ the same name in the Symbol and Choice classes, for consistency.
assignable:
See the symbol class documentation. Gives the assignable values (modes).
@@ -2577,17 +2835,27 @@ class Choice(object):
has no selected symbol (due to unsatisfied dependencies on choice
symbols).
+ Warning: Do not assign directly to this. It will break things. Call
+ sym.set_value(2) on the choice symbol you want to select instead.
+
default_selection:
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_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_str_value might not match
- Symbol.value.
+ user_str_value:
+ The value (mode) selected by the user (through Choice.set_value() or by
+ assigning a value to a symbol within the choice), represented as a
+ string. See Symbol.user_str_value.
+
+ Warning: Do not assign directly to this. It will break things. Use
+ Choice.set_value() or Symbol.set_value() instead.
+
+ user_tri_value:
+ The same value as user_str_value as a tristate (0, 1, 2). See
+ Symbol.user_tri_value.
- user_tri_value: TODO
+ Warning: Do not assign directly to this. It will break things. Use
+ Choice.set_value() or Symbol.set_value() instead.
user_selection:
The symbol selected by the user (by setting it to "y"). Ignored if the
@@ -2597,9 +2865,11 @@ class Choice(object):
syms:
List of symbols contained in the choice.
- Gotcha: If a symbol depends on a previous symbol within a choice so that
- an implicit menu is created, it won't be a choice symbol, and won't be
- included in 'syms'. There are real-world examples of this.
+ Gotcha: If a symbol depends on the previous symbol within a choice so
+ that an implicit menu is created, it won't be a choice symbol, and won't
+ be included in 'syms'. There are real-world examples of this, and it was
+ a PITA in older versions of Kconfiglib that didn't implement the menu
+ structure.
nodes:
A list of MenuNode's for this symbol. In practice, the list will probably
@@ -2607,35 +2877,28 @@ class Choice(object):
in multiple locations by giving it a name, which adds more nodes.
defaults:
- List of (symbol, cond) tuples for the choices 'defaults's. For example,
- 'default A if B && C' is represented as (A, (AND, B, C)). If there is no
- condition, 'cond' is self.config.y.
+ List of (symbol, cond) tuples for the choice's 'defaults' properties. For
+ example, 'default A if B && C' is represented as (A, (AND, B, C)). If
+ there is no condition, 'cond' is self.config.y.
Note that 'depends on' and parent dependencies are propagated to
'default' conditions.
is_optional:
- True if the choice has the 'optional' flag set on it.
-
- Implementation note: The C tools internally represent choices as a type of
- symbol, with special-casing in many code paths, which is why there is a lot
- of similarity to Symbol above. The value (mode) is really just a normal
- symbol value, and an implicit reverse dependency forces its lower bound to
- 'm' for non-optional choices. Kconfiglib uses a separate Choice class only
- because it makes the code and interface less confusing (especially in a
- user-facing interface).
+ True if the choice has the 'optional' flag set on it and can be in
+ n mode.
"""
__slots__ = (
"_cached_assignable",
"_cached_selection",
"_cached_vis",
- "_type",
"config",
"defaults",
"is_optional",
"name",
"nodes",
+ "orig_type",
"syms",
"user_selection",
"user_str_value",
@@ -2648,10 +2911,13 @@ class Choice(object):
@property
def type(self):
- """Returns the type of the choice. See Symbol.type."""
- if self._type == TRISTATE and not self.config.modules.tri_value:
+ """
+ Returns the type of the choice. See Symbol.type.
+ """
+ if self.orig_type == TRISTATE and not self.config.modules.tri_value:
return BOOL
- return self._type
+
+ return self.orig_type
@property
def str_value(self):
@@ -2745,14 +3011,15 @@ class Choice(object):
attribute (is_optional) can never be in "n" mode, but "n" is still
accepted (and ignored) since it's not a malformed value.
"""
- if not ((self._type == BOOL and value in ("n", "y") ) or
- (self._type == TRISTATE and value in ("n", "m", "y"))):
- self.config._warn('the value "{}" is invalid for the choice, '
+ if not ((self.orig_type == BOOL and value in (0, 2) ) or
+ (self.orig_type == TRISTATE and value in (0, 1, 2))):
+ self.config._warn("the value '{}' is invalid for the choice, "
"which has type {}. Assignment ignored"
- .format(value, _TYPENAME[self._type]))
+ .format(value, _TYPENAME[self.orig_type]))
+ return
- self.user_str_value = value
- self.user_tri_value = STR_TO_TRI[value]
+ self.user_str_value = TRI_TO_STR[value]
+ self.user_tri_value = value
if self.syms:
# Hackish way to invalidate the choice and all the choice symbols
@@ -2831,7 +3098,7 @@ class Choice(object):
# config
self.name = None
- self._type = UNKNOWN
+ self.orig_type = UNKNOWN
self.syms = []
self.defaults = []
@@ -2857,16 +3124,16 @@ class Choice(object):
vis = self.visibility
if not vis:
- return ""
+ return ()
if vis == 2:
if not self.is_optional:
- return "y" if self.type == BOOL else "my"
- return "y"
+ return (2,) if self.type == BOOL else (1, 2)
+ return (2,)
# vis == 1
- return "nm" if self.is_optional else "m"
+ return (0, 1) if self.is_optional else (1,)
def _invalidate(self):
self._cached_vis = self._cached_assignable = None
@@ -2881,7 +3148,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_node.
+ available in Kconfig.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
@@ -2947,7 +3214,7 @@ class MenuNode(object):
itself.
config:
- The Config the menu node is from.
+ The Kconfig the menu node is from.
filename/linenr:
The location where the menu node appears.
@@ -3067,14 +3334,14 @@ def expr_value(expr):
oper, op1, op2 = expr
# If both operands are strings...
- if op1._type == STRING and op2._type == STRING:
+ if op1.orig_type == STRING and op2.orig_type == STRING:
# ...then compare them lexicographically
comp = _strcmp(op1.str_value, op2.str_value)
else:
# Otherwise, try to compare them as numbers...
try:
- comp = int(op1.str_value, _TYPE_TO_BASE[op1._type]) - \
- int(op2.str_value, _TYPE_TO_BASE[op2._type])
+ comp = int(op1.str_value, _TYPE_TO_BASE[op1.orig_type]) - \
+ int(op2.str_value, _TYPE_TO_BASE[op2.orig_type])
except ValueError:
# Fall back on a lexicographic comparison if the operands don't
# parse as numbers
@@ -3097,7 +3364,9 @@ def expr_str(expr):
TODO
"""
if isinstance(expr, Symbol):
- return expr.name if not expr.is_constant else '"{}"'.format(expr.name)
+ if expr.is_constant:
+ return '"{}"'.format(escape(expr.name))
+ return expr.name
if expr[0] == NOT:
if isinstance(expr[1], Symbol):
@@ -3116,6 +3385,21 @@ def expr_str(expr):
_RELATION_TO_STR[expr[0]],
expr_str(expr[2]))
+_escape_re_sub = re.compile(r'(["\\])').sub
+_unescape_re_sub = re.compile(r"\\(.)").sub
+
+def escape(s):
+ """
+ TODO
+ """
+ return _escape_re_sub(r"\\\1", s)
+
+def unescape(s):
+ """
+ TODO
+ """
+ return _unescape_re_sub(r"\1", s)
+
#
# Internal functions
#
@@ -3134,13 +3418,13 @@ def _get_visibility(sc):
vis = max(vis, expr_value(node.prompt[1]))
if isinstance(sc, Symbol) and sc.choice is not None:
- if sc.choice._type == TRISTATE and sc._type != TRISTATE and \
+ if sc.choice.orig_type == TRISTATE and sc.orig_type != TRISTATE and \
sc.choice.tri_value != 2:
# Non-tristate choice symbols in tristate choices depend on the
# choice being in mode "y"
return 0
- if sc._type == TRISTATE and vis == 1 and sc.choice.tri_value == 2:
+ if sc.orig_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 0
@@ -3151,7 +3435,7 @@ def _get_visibility(sc):
# infinite recursion if something really weird is done with MODULES, but
# it's not a problem in practice.
if vis == 1 and \
- (sc._type != TRISTATE or not sc.config.modules.tri_value):
+ (sc.orig_type != TRISTATE or not sc.config.modules.tri_value):
return 2
return vis
@@ -3270,8 +3554,8 @@ def _sym_choice_str(sc):
else:
lines.append("choice " + sc.name)
- if node is sc.nodes[0] and sc.type != UNKNOWN:
- indent_add(_TYPENAME[sc.type])
+ if node is sc.nodes[0] and sc.orig_type != UNKNOWN:
+ indent_add(_TYPENAME[sc.orig_type])
if node.prompt is not None:
prompt_str = 'prompt "{}"'.format(node.prompt[0])
@@ -3458,16 +3742,16 @@ def _finalize_choice(node):
# If no type is specified for the choice, its type is that of
# the first choice item with a specified type
- if choice._type == UNKNOWN:
+ if choice.orig_type == UNKNOWN:
for item in choice.syms:
- if item._type != UNKNOWN:
- choice._type = item._type
+ if item.orig_type != UNKNOWN:
+ choice.orig_type = item.orig_type
break
# Each choice item of UNKNOWN type gets the type of the choice
for item in choice.syms:
- if item._type == UNKNOWN:
- item._type = choice._type
+ if item.orig_type == UNKNOWN:
+ item.orig_type = choice.orig_type
def _finalize_tree(node):
"""
@@ -3537,12 +3821,14 @@ def _finalize_tree(node):
COMMENT,
) = range(2)
+# TODO: unpublic?
TRI_TO_STR = {
0: "n",
1: "m",
2: "y",
}
+# TODO: unpublic?
STR_TO_TRI = {
"n": 0,
"m": 1,
@@ -3681,6 +3967,9 @@ _id_keyword_re_match = re.compile(r"([\w./-]+)\s*").match
# Regular expression for finding $-references to symbols in strings
_sym_ref_re_search = re.compile(r"\$([A-Za-z0-9_]+)").search
+# TODO
+_conf_string_re_match = re.compile(r'"((?:[^\\"]|\\.)*)"').match
+
# Strings to use for types
_TYPENAME = {
UNKNOWN: "unknown",
@@ -3719,13 +4008,6 @@ _TYPE_TO_BASE = {
UNKNOWN: 0,
}
-# Map from tristate values to integers
-_TRI_TO_INT = {
- "n": 0,
- "m": 1,
- "y": 2,
-}
-
_RELATIONS = frozenset((
EQUAL,
UNEQUAL,
diff --git a/testsuite.py b/testsuite.py
index 4af7c19..2e4b2ec 100644
--- a/testsuite.py
+++ b/testsuite.py
@@ -35,9 +35,11 @@
# All tests should pass. Report regressions to ulfalizer a.t Google's email
# service.
+from kconfiglib import Kconfig, Symbol, Choice, COMMENT, MENU, \
+ BOOL, TRISTATE, HEX, STRING, \
+ KconfigSyntaxError, expr_value
import difflib
import errno
-import kconfiglib
import os
import platform
import re
@@ -117,7 +119,7 @@ def get_comments(config):
items = []
def rec(node):
if node is not None:
- if node.item == kconfiglib.COMMENT:
+ if node.item == COMMENT:
items.append(node)
rec(node.list)
rec(node.next)
@@ -128,7 +130,7 @@ def get_menus(config):
items = []
def rec(node):
if node is not None:
- if node.item == kconfiglib.MENU:
+ if node.item == MENU:
items.append(node)
rec(node.list)
rec(node.next)
@@ -136,7 +138,7 @@ def get_menus(config):
return items
def get_choices(config):
- choices = get_items(config, kconfiglib.Choice)
+ choices = get_items(config, Choice)
unique_choices = []
for choice in choices:
if choice not in unique_choices:
@@ -144,7 +146,7 @@ def get_choices(config):
return unique_choices
def get_parent(item):
- if isinstance(item, (kconfiglib.Symbol, kconfiglib.Choice)):
+ if isinstance(item, (Symbol, Choice)):
if not item.nodes:
return None
return item.nodes[0].parent.item
@@ -157,6 +159,9 @@ def get_prompts(item):
prompts.append(node.prompt[0])
return prompts
+STR_TO_TRI = {"n": 0, "m": 1, "y": 2}
+TRI_TO_STR = {0: "n", 1: "m", 2: "y"}
+
def run_selftests():
#
# Common helper functions. These all expect 'c' to hold the current
@@ -167,6 +172,10 @@ def run_selftests():
"""
Verifies that a symbol has a particular value.
"""
+
+ if isinstance(val, int):
+ val = TRI_TO_STR[val]
+
sym = c.syms[sym_name]
verify(sym.str_value == val,
'expected {} to have the value "{}", had the value "{}"'
@@ -177,6 +186,10 @@ def run_selftests():
Assigns 'val' to a symbol and verifies that its value becomes
'new_val'.
"""
+
+ if isinstance(new_val, int):
+ new_val = TRI_TO_STR[new_val]
+
sym = c.syms[sym_name]
old_val = sym.str_value
sym.set_value(val)
@@ -194,9 +207,13 @@ def run_selftests():
assign_and_verify_value(sym_name, user_val, user_val)
def assign_and_verify_user_value(sym_name, val, user_val):
- """Assigns a user value to the symbol and verifies the new user
- value."""
+ """Assigns a user value to the symbol and verifies the new user value.
+ Also checks consistency of user_str_value and user_tri_value."""
sym = c.syms[sym_name]
+
+ if sym.type in (BOOL, TRISTATE):
+ user_val = TRI_TO_STR[user_val]
+
sym_old_user_val = sym.user_str_value
sym.set_value(val)
verify(sym.user_str_value == user_val,
@@ -206,14 +223,23 @@ def run_selftests():
.format(sym_name, user_val, user_val, sym.user_str_value,
sym_old_user_val))
+ if sym.type in (BOOL, TRISTATE):
+ verify(sym.user_tri_value == STR_TO_TRI[sym.user_str_value],
+ "{} has an inconsistent user value ('{}' vs '{}')"
+ .format(sym_name, sym.user_tri_value, sym.user_str_value))
+ else:
+ verify(sym.user_tri_value == 0,
+ "{} is non-bool/tristate and has the non-zero "
+ "user_tri_value {}".format(sym_name, sym.user_tri_value))
+
#
# Selftests
#
print("Testing string literal lexing")
- # Dummy empty configuration just to get a Config object
- c = kconfiglib.Config("Kconfiglib/tests/empty")
+ # Dummy empty configuration just to get a Kconfig object
+ c = Kconfig("Kconfiglib/tests/empty")
def verify_string_lex(s, res):
"""
@@ -261,7 +287,7 @@ def run_selftests():
"""
try:
c.eval_string(s)
- except kconfiglib.KconfigSyntaxError:
+ except KconfigSyntaxError:
pass
else:
fail("expected tokenization of {} to fail, didn't".format(s[1:-1]))
@@ -280,7 +306,7 @@ def run_selftests():
print("Testing expression evaluation")
- c = kconfiglib.Config("Kconfiglib/tests/Keval")
+ c = Kconfig("Kconfiglib/tests/Keval")
def verify_eval(expr, val):
res = c.eval_string(expr)
@@ -297,7 +323,7 @@ def run_selftests():
verify_eval("M", 2)
# Modules
- c.modules.set_value("y")
+ c.modules.set_value(2)
verify_eval("n", 0)
verify_eval("m", 1)
verify_eval("y", 2)
@@ -454,7 +480,7 @@ def run_selftests():
def verify_eval_bad(expr):
try:
c.eval_string(expr)
- except kconfiglib.KconfigSyntaxError:
+ except KconfigSyntaxError:
pass
else:
fail('expected eval_string("{}") to throw KconfigSyntaxError, ' \
@@ -482,9 +508,9 @@ def run_selftests():
def verify_str(item, s):
verify_equal(str(item), s[1:])
- c = kconfiglib.Config("Kconfiglib/tests/Kstr", warn=False)
+ c = Kconfig("Kconfiglib/tests/Kstr", warn=False)
- c.modules.set_value("y")
+ c.modules.set_value(2)
verify_str(c.syms["UNDEFINED"], """
""")
@@ -585,7 +611,7 @@ choice
def verify_repr(item, s):
verify_equal(repr(item) + "\n", s[1:])
- c = kconfiglib.Config("Kconfiglib/tests/Krepr", warn=False)
+ c = Kconfig("Kconfiglib/tests/Krepr", warn=False)
verify_repr(c.n, """
<symbol n, tristate, value "n", constant>
@@ -611,7 +637,7 @@ choice
<symbol VISIBLE, bool, "visible", value "n", visibility y, direct deps y, Kconfiglib/tests/Krepr:14>
""")
- c.syms["VISIBLE"].set_value("y")
+ c.syms["VISIBLE"].set_value(2)
verify_repr(c.syms["VISIBLE"], """
<symbol VISIBLE, bool, "visible", value "y", user value "y", visibility y, direct deps y, Kconfiglib/tests/Krepr:14>
@@ -644,19 +670,19 @@ choice
<choice CHOICE, tristate, "choice", mode m, visibility y, Kconfiglib/tests/Krepr:30>
""")
- c.named_choices["CHOICE"].set_value("y")
+ c.named_choices["CHOICE"].set_value(2)
verify_repr(c.named_choices["CHOICE"], """
<choice CHOICE, tristate, "choice", mode y, user mode y, CHOICE_1 selected, visibility y, Kconfiglib/tests/Krepr:30>
""")
- c.syms["CHOICE_2"].set_value("y")
+ c.syms["CHOICE_2"].set_value(2)
verify_repr(c.named_choices["CHOICE"], """
<choice CHOICE, tristate, "choice", mode y, user mode y, CHOICE_2 selected, CHOICE_2 selected by user, visibility y, Kconfiglib/tests/Krepr:30>
""")
- c.syms["CHOICE_1"].set_value("m")
+ c.syms["CHOICE_1"].set_value(1)
verify_repr(c.named_choices["CHOICE"], """
<choice CHOICE, tristate, "choice", mode m, user mode m, CHOICE_2 selected by user (overriden), visibility y, Kconfiglib/tests/Krepr:30>
@@ -710,7 +736,7 @@ choice
""")
- print("Testing Config.__repr__()")
+ print("Testing Kconfig.__repr__()")
verify_repr(c, """
<configuration with 15 symbols, main menu prompt "Linux Kernel Configuration", srctree not set, config symbol prefix "CONFIG_", warnings disabled, undef. symbol assignment warnings disabled>
@@ -719,7 +745,7 @@ choice
os.environ["srctree"] = "srctree value"
os.environ["CONFIG_"] = "CONFIG_ value"
- c = kconfiglib.Config("Kconfiglib/tests/Krepr", warn=False)
+ c = Kconfig("Kconfiglib/tests/Krepr", warn=False)
c.enable_warnings()
c.enable_undef_warnings()
@@ -733,7 +759,7 @@ choice
print("Testing tricky help strings")
- c = kconfiglib.Config("Kconfiglib/tests/Khelp")
+ c = Kconfig("Kconfiglib/tests/Khelp")
def verify_help(node, s):
verify_equal(node.help, s[1:])
@@ -794,7 +820,7 @@ g
os.environ["EXPANDED_FROM_ENV"] = "tests"
os.environ["srctree"] = "Kconfiglib/"
- c = kconfiglib.Config("tests/Klocation")
+ c = Kconfig("tests/Klocation")
os.environ.pop("EXPANDED_FROM_ENV", None)
os.environ.pop("srctree", None)
@@ -819,16 +845,16 @@ g
print("Testing visibility")
- c = kconfiglib.Config("Kconfiglib/tests/Kvisibility")
+ c = Kconfig("Kconfiglib/tests/Kvisibility")
def verify_visibility(item, no_module_vis, module_vis):
- c.modules.set_value("n")
+ c.modules.set_value(0)
verify(item.visibility == no_module_vis,
"expected {} to have visibility {} without modules, had "
"visibility {}".
format(repr(item), no_module_vis, item.visibility))
- c.modules.set_value("y")
+ c.modules.set_value(2)
verify(item.visibility == module_vis,
"expected {} to have visibility {} with modules, had "
"visibility {}".
@@ -860,8 +886,8 @@ g
# 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")
+ c.syms["TRISTATE_CHOICE_M"].set_value(2)
+ c.syms["TRISTATE_CHOICE_Y"].set_value(2)
# Still limited by the visibility of the choice
verify_visibility(c.syms["BOOL_CHOICE_M"], 0, 0)
@@ -892,15 +918,15 @@ g
# Menu visibility
def verify_menu_visibility(menu, no_module_vis, module_vis):
- c.modules.set_value("n")
- menu_vis = kconfiglib.expr_value(menu.node.dep)
+ c.modules.set_value(0)
+ menu_vis = expr_value(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")
- menu_vis = kconfiglib.expr_value(menu.node.dep)
+ c.modules.set_value(2)
+ menu_vis = expr_value(menu.node.dep)
verify(menu_vis == module_vis,
"menu \"{}\" should have visibility '{}' with modules, "
"has visibility '{}'".
@@ -925,14 +951,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(0)
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(2)
menu_vis = menu.get_visible_if_visibility()
verify(menu_vis == module_vis,
"menu \"{}\" should have 'visible if' visibility '{}' "
@@ -961,16 +987,16 @@ g
# Comment visibility
def verify_comment_visibility(comment, no_module_vis, module_vis):
- c.modules.set_value("n")
+ c.modules.set_value(0)
# TODO: uninternalize
- comment_vis = kconfiglib.expr_value(comment.node.dep)
+ comment_vis = expr_value(comment.node.dep)
verify(comment_vis == no_module_vis,
"comment \"{}\" should have visibility '{}' without "
"modules, has visibility '{}'".
format(comment.text, no_module_vis, comment_vis))
- c.modules.set_value("y")
- comment_vis = kconfiglib.expr_value(comment.node.dep)
+ c.modules.set_value(2)
+ comment_vis = expr_value(comment.node.dep)
verify(comment_vis == module_vis,
"comment \"{}\" should have visibility '{}' with "
"modules, has visibility '{}'".
@@ -999,7 +1025,7 @@ g
# Object relations
#
- c = kconfiglib.Config("Kconfiglib/tests/Krelation")
+ c = Kconfig("Kconfiglib/tests/Krelation")
UNDEFINED, A, B, C, D, E, F, G, H, I = \
c.syms["UNDEFINED"], c.syms["A"], c.syms["B"], c.syms["C"], \
@@ -1043,7 +1069,7 @@ g
print("Testing hex/int ranges...")
- c = kconfiglib.Config("Kconfiglib/tests/Krange")
+ c = Kconfig("Kconfiglib/tests/Krange")
for sym_name in "HEX_NO_RANGE", "INT_NO_RANGE", "HEX_40", "INT_40":
sym = c.syms[sym_name]
@@ -1093,7 +1119,7 @@ g
"""Tests that the values in the range 'low'-'high' can be assigned, and
that assigning values outside this range reverts the value back to
'default' (None if it should revert back to "")."""
- is_hex = (c.syms[sym_name].type == kconfiglib.HEX)
+ is_hex = (c.syms[sym_name].type == HEX)
for i in range(low, high + 1):
assign_and_verify_user_value(sym_name, str(i), str(i))
if is_hex:
@@ -1166,11 +1192,11 @@ g
print("Testing defconfig_filename...")
- c = kconfiglib.Config("Kconfiglib/tests/empty")
+ c = Kconfig("Kconfiglib/tests/empty")
verify(c.defconfig_filename is None,
"defconfig_filename should be None with no defconfig_list symbol")
- c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_nonexistent")
+ c = Kconfig("Kconfiglib/tests/Kdefconfig_nonexistent")
verify(c.defconfig_filename is None,
"defconfig_filename should be None when none of the files in the "
"defconfig_list symbol exist")
@@ -1178,12 +1204,12 @@ g
# Referenced in Kdefconfig_existent(_but_n)
os.environ["BAR"] = "defconfig_2"
- c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_existent_but_n")
+ c = Kconfig("Kconfiglib/tests/Kdefconfig_existent_but_n")
verify(c.defconfig_filename is None,
"defconfig_filename should be None when the condition is n for all "
"the defaults")
- c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_existent")
+ c = Kconfig("Kconfiglib/tests/Kdefconfig_existent")
verify(c.defconfig_filename == "Kconfiglib/tests/defconfig_2",
"defconfig_filename should return the existent file "
"Kconfiglib/tests/defconfig_2")
@@ -1191,12 +1217,12 @@ g
# Should also look relative to $srctree if the defconfig is an absolute
# path and not found
- c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_srctree")
+ c = Kconfig("Kconfiglib/tests/Kdefconfig_srctree")
verify(c.defconfig_filename == "Kconfiglib/tests/defconfig_2",
"defconfig_filename gave wrong file with $srctree unset")
os.environ["srctree"] = "Kconfiglib/tests"
- c = kconfiglib.Config("Kconfiglib/tests/Kdefconfig_srctree")
+ c = Kconfig("Kconfiglib/tests/Kdefconfig_srctree")
verify(c.defconfig_filename == "Kconfiglib/tests/sub/defconfig_in_sub",
"defconfig_filename gave wrong file with $srctree set")
@@ -1206,13 +1232,13 @@ g
print("Testing mainmenu_text...")
- c = kconfiglib.Config("Kconfiglib/tests/empty")
+ c = Kconfig("Kconfiglib/tests/empty")
verify(c.mainmenu_text == "Linux Kernel Configuration",
"An empty Kconfig should get a default main menu prompt")
# Expanded in the mainmenu text
os.environ["FOO"] = "bar baz"
- c = kconfiglib.Config("Kconfiglib/tests/Kmainmenu")
+ c = Kconfig("Kconfiglib/tests/Kmainmenu")
verify(c.mainmenu_text == "---bar baz---",
"Wrong mainmenu text")
@@ -1222,7 +1248,7 @@ g
os.environ["ENV_VAR"] = "foo"
# Contains reference to undefined environment variable, so disable warnings
- c = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False)
+ c = Kconfig("Kconfiglib/tests/Kmisc", warn=False)
print("Testing is_optional...")
@@ -1246,11 +1272,11 @@ g
# Assign valid values for the types
- assign_and_verify_user_value("BOOL", "n", "n")
- assign_and_verify_user_value("BOOL", "y", "y")
- assign_and_verify_user_value("TRISTATE", "n", "n")
- assign_and_verify_user_value("TRISTATE", "m", "m")
- assign_and_verify_user_value("TRISTATE", "y", "y")
+ assign_and_verify_user_value("BOOL", 0, 0)
+ assign_and_verify_user_value("BOOL", 2, 2)
+ assign_and_verify_user_value("TRISTATE", 0, 0)
+ assign_and_verify_user_value("TRISTATE", 1, 1)
+ assign_and_verify_user_value("TRISTATE", 2, 2)
assign_and_verify_user_value("STRING", "foo bar", "foo bar")
assign_and_verify_user_value("INT", "123", "123")
assign_and_verify_user_value("HEX", "0x123", "0x123")
@@ -1258,13 +1284,16 @@ g
# Assign invalid values for the types. They should retain their old user
# value.
- assign_and_verify_user_value("BOOL", "m", "y")
- assign_and_verify_user_value("BOOL", "foo", "y")
- assign_and_verify_user_value("BOOL", "1", "y")
- assign_and_verify_user_value("TRISTATE", "foo", "y")
- assign_and_verify_user_value("TRISTATE", "1", "y")
+ assign_and_verify_user_value("BOOL", 1, 2)
+ assign_and_verify_user_value("BOOL", "foo", 2)
+ assign_and_verify_user_value("BOOL", "1", 2)
+ assign_and_verify_user_value("TRISTATE", "foo", 2)
+ assign_and_verify_user_value("TRISTATE", "1", 2)
+ assign_and_verify_user_value("STRING", 0, "foo bar")
assign_and_verify_user_value("INT", "foo", "123")
+ assign_and_verify_user_value("INT", 0, "123")
assign_and_verify_user_value("HEX", "foo", "0x123")
+ assign_and_verify_user_value("HEX", 0, "0x123")
for s in syms:
s.unset_value()
@@ -1308,7 +1337,7 @@ g
verify_value("UNAME_RELEASE", platform.uname()[2])
ur = c.syms["UNAME_RELEASE"]
verify(ur.config is c and
- ur.type == kconfiglib.STRING and
+ ur.type == STRING and
ur.env_var == "<uname release>",
"UNAME_RELEASE has wrong fields")
@@ -1336,7 +1365,7 @@ g
# Writing/reading strings with characters that need to be escaped
- c = kconfiglib.Config("Kconfiglib/tests/Kescape")
+ c = Kconfig("Kconfiglib/tests/Kescape")
# Test the default value
c.write_config(config_test_file + "_from_def", header="")
@@ -1356,7 +1385,7 @@ g
# Appending values from a .config
- c = kconfiglib.Config("Kconfiglib/tests/Kappend")
+ c = Kconfig("Kconfiglib/tests/Kappend")
# Values before assigning
verify_value("BOOL", "n")
@@ -1391,8 +1420,8 @@ g
print("Testing Config separation...")
- c1 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False)
- c2 = kconfiglib.Config("Kconfiglib/tests/Kmisc", warn=False)
+ c1 = Kconfig("Kconfiglib/tests/Kmisc", warn=False)
+ c2 = Kconfig("Kconfiglib/tests/Kmisc", warn=False)
c1_undef, c1_bool, c1_choice, c1_menu, c1_comment = c1.syms["BOOL"], \
c1.syms["NOT_DEFINED_1"], get_choices(c1)[0], get_menus(c1)[0], \
@@ -1417,7 +1446,7 @@ g
print("Testing imply semantics...")
- c = kconfiglib.Config("Kconfiglib/tests/Kimply")
+ c = Kconfig("Kconfiglib/tests/Kimply")
verify_value("IMPLY_DIRECT_DEPS", "y")
verify_value("UNMET_DIRECT_1", "n")
@@ -1450,64 +1479,64 @@ g
# Verify that IMPLIED_TRISTATE is invalidated if the direct
# dependencies change
- assign_and_verify("IMPLY", "y")
- assign_and_verify("DIRECT_DEP", "y")
- verify_value("IMPLIED_TRISTATE", "y")
- assign_and_verify("DIRECT_DEP", "n")
- verify_value("IMPLIED_TRISTATE", "n")
+ assign_and_verify("IMPLY", 2)
+ assign_and_verify("DIRECT_DEP", 2)
+ verify_value("IMPLIED_TRISTATE", 2)
+ assign_and_verify("DIRECT_DEP", 0)
+ verify_value("IMPLIED_TRISTATE", 0)
# Set back for later tests
- assign_and_verify("DIRECT_DEP", "y")
+ assign_and_verify("DIRECT_DEP", 2)
# Verify that IMPLIED_TRISTATE can be set to anything when IMPLY has value
# "n", and that it gets the value "n" by default (for non-imply-related
# reasons)
- assign_and_verify("IMPLY", "n")
- assign_and_verify("IMPLIED_TRISTATE", "n")
- assign_and_verify("IMPLIED_TRISTATE", "m")
- assign_and_verify("IMPLIED_TRISTATE", "y")
+ assign_and_verify("IMPLY", 0)
+ assign_and_verify("IMPLIED_TRISTATE", 0)
+ assign_and_verify("IMPLIED_TRISTATE", 1)
+ assign_and_verify("IMPLIED_TRISTATE", 2)
c.syms["IMPLIED_TRISTATE"].unset_value()
verify_value("IMPLIED_TRISTATE", "n")
# Same as above for "m". Anything still goes, but "m" by default now.
- assign_and_verify("IMPLY", "m")
- assign_and_verify("IMPLIED_TRISTATE", "n")
- assign_and_verify("IMPLIED_TRISTATE", "m")
- assign_and_verify("IMPLIED_TRISTATE", "y")
+ assign_and_verify("IMPLY", 1)
+ assign_and_verify("IMPLIED_TRISTATE", 0)
+ assign_and_verify("IMPLIED_TRISTATE", 1)
+ assign_and_verify("IMPLIED_TRISTATE", 2)
c.syms["IMPLIED_TRISTATE"].unset_value()
- verify_value("IMPLIED_TRISTATE", "m")
+ verify_value("IMPLIED_TRISTATE", 1)
# Same as above for "y". Only "n" and "y" should be accepted. "m" gets
# promoted to "y". Default should be "y".
- assign_and_verify("IMPLY", "y")
- assign_and_verify("IMPLIED_TRISTATE", "n")
- assign_and_verify_value("IMPLIED_TRISTATE", "m", "y")
- assign_and_verify("IMPLIED_TRISTATE", "y")
+ assign_and_verify("IMPLY", 2)
+ assign_and_verify("IMPLIED_TRISTATE", 0)
+ assign_and_verify_value("IMPLIED_TRISTATE", 1, 2)
+ assign_and_verify("IMPLIED_TRISTATE", 2)
c.syms["IMPLIED_TRISTATE"].unset_value()
- verify_value("IMPLIED_TRISTATE", "y")
+ verify_value("IMPLIED_TRISTATE", 2)
# Being implied to either "m" or "y" should give a bool the value "y"
c.syms["IMPLY"].unset_value()
- verify_value("IMPLIED_BOOL", "n")
- assign_and_verify("IMPLY", "n")
- verify_value("IMPLIED_BOOL", "n")
- assign_and_verify("IMPLY", "m")
- verify_value("IMPLIED_BOOL", "y")
- assign_and_verify("IMPLY", "y")
- verify_value("IMPLIED_BOOL", "y")
+ verify_value("IMPLIED_BOOL", 0)
+ assign_and_verify("IMPLY", 0)
+ verify_value("IMPLIED_BOOL", 0)
+ assign_and_verify("IMPLY", 1)
+ verify_value("IMPLIED_BOOL", 2)
+ assign_and_verify("IMPLY", 2)
+ verify_value("IMPLIED_BOOL", 2)
# A bool implied to "m" or "y" can take the values "n" and "y"
- c.syms["IMPLY"].set_value("m")
- assign_and_verify("IMPLIED_BOOL", "n")
- assign_and_verify("IMPLIED_BOOL", "y")
+ c.syms["IMPLY"].set_value(1)
+ assign_and_verify("IMPLIED_BOOL", 0)
+ assign_and_verify("IMPLIED_BOOL", 2)
- c.syms["IMPLY"].set_value("y")
- assign_and_verify("IMPLIED_BOOL", "n")
- assign_and_verify("IMPLIED_BOOL", "y")
+ c.syms["IMPLY"].set_value(2)
+ assign_and_verify("IMPLIED_BOOL", 0)
+ assign_and_verify("IMPLIED_BOOL", 2)
#
# Choice semantics
@@ -1515,7 +1544,7 @@ g
print("Testing choice semantics...")
- c = kconfiglib.Config("Kconfiglib/tests/Kchoice")
+ c = Kconfig("Kconfiglib/tests/Kchoice")
choice_bool, choice_bool_opt, choice_tristate, choice_tristate_opt, \
choice_bool_m, choice_tristate_m, choice_defaults, \
@@ -1525,18 +1554,18 @@ g
for choice in (choice_bool, choice_bool_opt, choice_bool_m,
choice_defaults):
- verify(choice.type == kconfiglib.BOOL,
+ verify(choice.type == BOOL,
"choice {} should have type bool".format(choice.name))
# TODO: fix this laters. type automatically changed.
#for choice in (choice_tristate, choice_tristate_opt, choice_tristate_m):
- # verify(choice.type == kconfiglib.TRISTATE,
+ # verify(choice.type == TRISTATE,
# "choice {} should have type tristate"
# .format(choice.name))
def select_and_verify(sym):
choice = get_parent(sym)
- sym.set_value("y")
+ sym.set_value(2)
verify(choice.str_value == "y",
"The mode of the choice should be y after selecting a symbol")
verify(sym.choice.selection is sym,
@@ -1557,13 +1586,13 @@ g
select_and_verify(choice.syms[i])
def verify_mode(choice, no_modules_mode, modules_mode):
- c.modules.set_value("n")
+ c.modules.set_value(0)
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))
- c.modules.set_value("y")
+ c.modules.set_value(2)
choice_mode = choice.tri_value
verify(choice_mode == modules_mode,
'Wrong mode for choice {} with modules. Expected {}, got {}.'
@@ -1578,13 +1607,13 @@ g
# Test defaults
- c.syms["TRISTATE_SYM"].set_value("n")
+ c.syms["TRISTATE_SYM"].set_value(0)
verify(choice_defaults.selection is c.syms["OPT_4"],
"Wrong choice default with TRISTATE_SYM = n")
- c.syms["TRISTATE_SYM"].set_value("y")
+ c.syms["TRISTATE_SYM"].set_value(2)
verify(choice_defaults.selection is c.syms["OPT_2"],
"Wrong choice default with TRISTATE_SYM = y")
- c.syms["OPT_1"].set_value("y")
+ c.syms["OPT_1"].set_value(2)
verify(choice_defaults.selection is c.syms["OPT_1"],
"User selection should override defaults")
@@ -1594,7 +1623,7 @@ g
# Test "y" mode selection
- c.modules.set_value("y")
+ c.modules.set_value(2)
select_and_verify_all(choice_bool)
select_and_verify_all(choice_bool_opt)
@@ -1608,12 +1637,12 @@ g
# ...for a choice that can also be in "y" mode
for sym_name in ("T_1", "T_2"):
- assign_and_verify_value(sym_name, "m", "m")
+ assign_and_verify_value(sym_name, 1, 1)
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")
+ assign_and_verify_value(sym_name, 2, 2)
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 '
@@ -1622,32 +1651,31 @@ g
# ...for a choice that can only be in "m" mode
for sym_name in ("TM_1", "TM_2"):
- assign_and_verify_value(sym_name, "m", "m")
- assign_and_verify_value(sym_name, "n", "n")
+ assign_and_verify_value(sym_name, 1, 1)
+ assign_and_verify_value(sym_name, 0, 0)
# "y" should be truncated
- assign_and_verify_value(sym_name, "y", "m")
+ assign_and_verify_value(sym_name, 2, 1)
verify(choice_tristate_m.tri_value == 1,
- 'A choice that can only be in "m" mode was not')
+ 'A choice that can only be in m mode was not')
# Verify that choices with no explicitly specified type get the type of the
# first contained symbol with a type
- verify(choice_no_type_bool.type == kconfiglib.BOOL,
+ verify(choice_no_type_bool.type == BOOL,
"Expected first choice without explicit type to have type bool")
- verify(choice_no_type_tristate.type == kconfiglib.TRISTATE,
+ verify(choice_no_type_tristate.type == TRISTATE,
"Expected second choice without explicit type to have type "
"tristate")
# Verify that symbols without a type in the choice get the type of the
# choice
- verify((c.syms["MMT_1"]._type, c.syms["MMT_2"]._type,
- c.syms["MMT_3"]._type) ==
- (kconfiglib.BOOL, kconfiglib.BOOL, kconfiglib.TRISTATE),
+ verify((c.syms["MMT_1"].orig_type, c.syms["MMT_2"].orig_type,
+ c.syms["MMT_3"].orig_type) == (BOOL, BOOL, TRISTATE),
"Wrong types for first choice with missing member types")
- verify((c.syms["MMT_4"]._type, c.syms["MMT_5"]._type) ==
- (kconfiglib.BOOL, kconfiglib.BOOL),
+ verify((c.syms["MMT_4"].orig_type, c.syms["MMT_5"].orig_type) ==
+ (BOOL, BOOL),
"Wrong types for second choice with missing member types")
# Verify that symbols in choices that depend on the preceding symbol aren't
@@ -1687,7 +1715,7 @@ g
# Note: This tests an internal API
- c = kconfiglib.Config("Kconfiglib/tests/Kdep")
+ c = Kconfig("Kconfiglib/tests/Kdep")
def verify_dependent(sym_name, deps_names):
sym = c.syms[sym_name]
@@ -1696,7 +1724,7 @@ g
verify(len(sym_deps) == len(set(sym_deps)),
"{}'s dependencies contains duplicates".format(sym_name))
sym_deps = [item for item in sym_deps
- if not isinstance(item, kconfiglib.Choice)]
+ if not isinstance(item, Choice)]
verify(len(sym_deps) == len(deps),
"Wrong number of dependent symbols for {}".format(sym_name))
for dep in deps:
@@ -1717,7 +1745,7 @@ g
# Verify that the last symbol depends on the first in a long chain of
# dependencies. Test twice to cover dependency caching.
- c = kconfiglib.Config("Kconfiglib/tests/Kchain")
+ c = Kconfig("Kconfiglib/tests/Kchain")
for i in range(0, 2):
verify(c.syms["CHAIN_26"] in c.syms["CHAIN_1"]._get_dependent(),
@@ -1728,7 +1756,7 @@ g
# Check that Kconfiglib doesn't crash for stuff like 'select n' (seen in
# U-Boot). These probably originate from misunderstandings of how Kconfig
# works.
- kconfiglib.Config("Kconfiglib/tests/Kwtf")
+ Kconfig("Kconfiglib/tests/Kwtf")
print("\nAll selftests passed\n" if all_passed else
"\nSome selftests failed\n")
@@ -1754,15 +1782,15 @@ def run_compatibility_tests():
# tree -- currently, all tests. The boolean flag indicates whether .config
# (generated by the C implementation) should be compared to ._config
# (generated by us) after each invocation.
- all_arch_tests = [(test_load, False),
+ all_arch_tests = ((test_load, False),
(test_alldefconfig, True),
+ (test_defconfig, False),
(test_sanity, False),
(test_all_no, True),
(test_all_no_simpler, True),
- (test_all_yes, True),
+ (test_all_yes, True))
# Needs to report success/failure for each arch/defconfig
# combo, hence False.
- (test_defconfig, False)]
arch_srcarch_list = get_arch_srcarch_list()
@@ -1779,7 +1807,7 @@ def run_compatibility_tests():
# Previously we used to load all the arches once and keep them
# around for the tests. That now uses a huge amount of memory (pypy
# helps a bit), so reload them for each test instead.
- test_fn(kconfiglib.Config(), arch)
+ test_fn(Kconfig(), arch)
# Let kbuild infer SRCARCH from ARCH if we aren't in speedy mode.
# This could detect issues with the test suite.
@@ -1900,7 +1928,9 @@ def test_sanity(conf, arch):
conf.unset_values()
# Python 2/3 compatible
- for _, sym in conf.syms.items():
+ for key, sym in conf.syms.items():
+ verify(isinstance(key, str), "weird key '{}' in syms dict".format(key))
+
if sym.name != "UNAME_RELEASE":
verify(not sym.is_constant, sym.name + " in 'syms' and constant")
@@ -1916,7 +1946,8 @@ def test_sanity(conf, arch):
sym.__str__()
sym.assignable
conf.disable_warnings()
- sym.set_value("y")
+ sym.set_value(2)
+ sym.set_value("foo")
conf.enable_warnings()
sym.str_value
sym.tri_value
@@ -1929,24 +1960,33 @@ def test_sanity(conf, arch):
for sym in conf.defined_syms:
verify(sym.nodes, sym.name + " is defined but lacks menu nodes")
- for _, sym in conf.const_syms.items():
+ verify(not (sym.orig_type not in (BOOL, TRISTATE) and sym.choice),
+ sym.name + " is a choice symbol but not bool/tristate")
+
+ for key, sym in conf.const_syms.items():
+ verify(isinstance(key, str),
+ "weird key '{}' in const_syms dict".format(key))
+
verify(sym.is_constant,
'"{}" is in const_syms but not marked constant'
.format(sym.name))
verify(not sym.nodes,
- '"{}" is constant but has menu nodes'
- .format(sym.name))
+ '"{}" is constant but has menu nodes'.format(sym.name))
verify(not sym._direct_dependents,
'"{}" is constant but is a dependency of some symbol'
.format(sym.name))
+ verify(not sym.choice,
+ '"{}" is constant and a choice symbol'.format(sym.name))
+
sym.__repr__()
sym.__str__()
sym.assignable
conf.disable_warnings()
- sym.set_value("y")
+ sym.set_value(2)
+ sym.set_value("foo")
conf.enable_warnings()
sym.str_value
sym.tri_value
@@ -1955,18 +1995,27 @@ def test_sanity(conf, arch):
sym.visibility
# Cheat with internals
- for c in conf._choices:
- c.__str__()
- c.__repr__()
- c.str_value
- c.tri_value
- c.user_str_value
- c.user_tri_value
- c.assignable
- c.selection
- c.default_selection
- c.type
- c.visibility
+ for choice in conf._choices:
+ for sym in choice.syms:
+ verify(sym.choice is choice,
+ "{0} is in choice.syms but 'sym.choice' is not the choice"
+ .format(sym.name))
+
+ verify(sym.type in (BOOL, TRISTATE),
+ "{} is a choice symbol but is not a bool/tristate"
+ .format(sym.name))
+
+ choice.__str__()
+ choice.__repr__()
+ choice.str_value
+ choice.tri_value
+ choice.user_str_value
+ choice.user_tri_value
+ choice.assignable
+ choice.selection
+ choice.default_selection
+ choice.type
+ choice.visibility
# Menu nodes
@@ -1975,6 +2024,9 @@ def test_sanity(conf, arch):
while 1:
# Everything else should be well exercised elsewhere
node.__repr__()
+ verify(isinstance(node.item, (Symbol, Choice)) or \
+ node.item in (MENU, COMMENT),
+ "'{}' appeared as a menu item".format(node.item))
if node.list is not None:
node = node.list