diff options
author | Tom Rini | 2019-10-08 18:37:19 -0400 |
---|---|---|
committer | Tom Rini | 2019-10-08 18:37:19 -0400 |
commit | ca88313dcd02b686851d7bc76fe941935fa014bc (patch) | |
tree | b27e0c9a838fb695879ee4b115e555024e806d24 | |
parent | 61ba1244b548463dbfb3c5285b6b22e7c772c5bd (diff) | |
parent | 8b13e9bf1df2397ae7a814c80d669772f7133b15 (diff) |
Merge branch '2019-10-08-master-imports'
- Python3 conversion of genboardscfg.py
- Resync Kconfiglib.py
- Switch to running CI on Ubuntu "bionic" to facilitate Python 3.6 being
the minimum we use and test.
-rw-r--r-- | .gitlab-ci.yml | 33 | ||||
-rw-r--r-- | .travis.yml | 9 | ||||
-rw-r--r-- | tools/buildman/func_test.py | 26 | ||||
-rw-r--r-- | tools/buildman/kconfiglib.py | 9644 | ||||
-rwxr-xr-x | tools/genboardscfg.py | 36 | ||||
-rwxr-xr-x | tools/moveconfig.py | 12 |
6 files changed, 6623 insertions, 3137 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0c4343468d1..967abed9f25 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ # Grab our configured image. The source for this is found at: # https://gitlab.denx.de/u-boot/gitlab-ci-runner -image: trini/u-boot-gitlab-ci-runner:xenial-20190720-02Aug2019 +image: trini/u-boot-gitlab-ci-runner:bionic-20190912.1-03Oct2019 # We run some tests in different order, to catch some failures quicker. stages: @@ -22,8 +22,9 @@ stages: - . /tmp/venv/bin/activate - pip install pytest==2.8.7 - pip install python-subunit - - grub-mkimage -o ~/grub_x86.efi -O i386-efi normal echo lsefimmap lsefi lsefisystab efinet tftp minicmd - - grub-mkimage -o ~/grub_x64.efi -O x86_64-efi normal echo lsefimmap lsefi lsefisystab efinet tftp minicmd + - pip install coverage + - grub-mkimage --prefix="" -o ~/grub_x86.efi -O i386-efi normal echo lsefimmap lsefi lsefisystab efinet tftp minicmd + - grub-mkimage --prefix="" -o ~/grub_x64.efi -O x86_64-efi normal echo lsefimmap lsefi lsefisystab efinet tftp minicmd - mkdir ~/grub2-arm - ( cd ~/grub2-arm; wget -O - http://download.opensuse.org/ports/armv7hl/distribution/leap/42.2/repo/oss/suse/armv7hl/grub2-arm-efi-2.02~beta2-87.1.armv7hl.rpm | rpm2cpio | cpio -di ) - mkdir ~/grub2-arm64 @@ -36,9 +37,9 @@ stages: # use clang only do one configuration. - if [[ "${BUILDMAN}" != "" ]]; then ret=0; - tools/buildman/buildman -P -E ${BUILDMAN} ${OVERRIDE}|| ret=$?; + tools/buildman/buildman -o /tmp -P -E ${BUILDMAN} ${OVERRIDE}|| ret=$?; if [[ $ret -ne 0 && $ret -ne 129 ]]; then - tools/buildman/buildman -sdeP ${BUILDMAN}; + tools/buildman/buildman -o /tmp -sdeP ${BUILDMAN}; exit $ret; fi; fi @@ -46,7 +47,7 @@ stages: # never prevent any test from running. That way, we can always pass # "-k something" even when $TEST_PY_TEST_SPEC doesnt need a custom # value. - - export UBOOT_TRAVIS_BUILD_DIR=`cd .. && pwd`/.bm-work/${TEST_PY_BD}; + - export UBOOT_TRAVIS_BUILD_DIR=/tmp/.bm-work/${TEST_PY_BD}; export PATH=/opt/qemu/bin:/tmp/uboot-test-hooks/bin:/usr/bin:/bin; export PYTHONPATH=/tmp/uboot-test-hooks/py/travis-ci; if [[ "${TEST_PY_BD}" != "" ]]; then @@ -64,9 +65,9 @@ build all 32bit ARM platforms: stage: world build script: - ret=0; - ./tools/buildman/buildman -P -E arm -x aarch64 || ret=$?; + ./tools/buildman/buildman -o /tmp -P -E arm -x aarch64 || ret=$?; if [[ $ret -ne 0 && $ret -ne 129 ]]; then - ./tools/buildman/buildman -sdeP; + ./tools/buildman/buildman -o /tmp -sdeP; exit $ret; fi; @@ -78,9 +79,9 @@ build all 64bit ARM platforms: - . /tmp/venv/bin/activate - pip install pyelftools - ret=0; - ./tools/buildman/buildman -P -E aarch64 || ret=$?; + ./tools/buildman/buildman -o /tmp -P -E aarch64 || ret=$?; if [[ $ret -ne 0 && $ret -ne 129 ]]; then - ./tools/buildman/buildman -sdeP; + ./tools/buildman/buildman -o /tmp -sdeP; exit $ret; fi; @@ -89,9 +90,9 @@ build all PowerPC platforms: stage: world build script: - ret=0; - ./tools/buildman/buildman -P -E powerpc || ret=$?; + ./tools/buildman/buildman -o /tmp -P -E powerpc || ret=$?; if [[ $ret -ne 0 && $ret -ne 129 ]]; then - ./tools/buildman/buildman -sdeP; + ./tools/buildman/buildman -o /tmp -sdeP; exit $ret; fi; @@ -100,9 +101,9 @@ build all other platforms: stage: world build script: - ret=0; - ./tools/buildman/buildman -P -E -x arm,powerpc || ret=$?; + ./tools/buildman/buildman -o /tmp -P -E -x arm,powerpc || ret=$?; if [[ $ret -ne 0 && $ret -ne 129 ]]; then - ./tools/buildman/buildman -sdeP; + ./tools/buildman/buildman -o /tmp -sdeP; exit $ret; fi; @@ -162,10 +163,10 @@ Run binman, buildman, dtoc and patman testsuites: virtualenv /tmp/venv; . /tmp/venv/bin/activate; pip install pyelftools; - export UBOOT_TRAVIS_BUILD_DIR=`cd .. && pwd`/.bm-work/sandbox_spl; + export UBOOT_TRAVIS_BUILD_DIR=/tmp/.bm-work/sandbox_spl; export PYTHONPATH="${UBOOT_TRAVIS_BUILD_DIR}/scripts/dtc/pylibfdt"; export PATH="${UBOOT_TRAVIS_BUILD_DIR}/scripts/dtc:${PATH}"; - ./tools/buildman/buildman -P sandbox_spl; + ./tools/buildman/buildman -o /tmp -P sandbox_spl; ./tools/binman/binman --toolpath ${UBOOT_TRAVIS_BUILD_DIR}/tools test; ./tools/buildman/buildman -t; ./tools/dtoc/dtoc -t; diff --git a/.travis.yml b/.travis.yml index 0ce09e35b7c..c48b711659e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ # build U-Boot on Travis CI - https://travis-ci.org/ sudo: required -dist: xenial +dist: bionic language: c @@ -12,7 +12,7 @@ addons: apt: sources: - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-7 + - llvm-toolchain-bionic-7 packages: - cppcheck - sloccount @@ -52,12 +52,13 @@ install: - pip install pytest==2.8.7 - pip install python-subunit - pip install pyelftools - - grub-mkimage -o ~/grub_x86.efi -O i386-efi normal echo lsefimmap lsefi lsefisystab efinet tftp minicmd - - grub-mkimage -o ~/grub_x64.efi -O x86_64-efi normal echo lsefimmap lsefi lsefisystab efinet tftp minicmd + - grub-mkimage --prefix="" -o ~/grub_x86.efi -O i386-efi normal echo lsefimmap lsefi lsefisystab efinet tftp minicmd + - grub-mkimage --prefix="" -o ~/grub_x64.efi -O x86_64-efi normal echo lsefimmap lsefi lsefisystab efinet tftp minicmd - mkdir ~/grub2-arm - ( cd ~/grub2-arm; wget -O - http://download.opensuse.org/ports/armv7hl/distribution/leap/42.2/repo/oss/suse/armv7hl/grub2-arm-efi-2.02~beta2-87.1.armv7hl.rpm | rpm2cpio | cpio -di ) - mkdir ~/grub2-arm64 - ( cd ~/grub2-arm64; wget -O - http://download.opensuse.org/ports/aarch64/distribution/leap/42.2/repo/oss/suse/aarch64/grub2-arm64-efi-2.02~beta2-87.1.aarch64.rpm | rpm2cpio | cpio -di ) + - wget http://mirrors.kernel.org/ubuntu/pool/main/m/mpfr4/libmpfr4_3.1.4-1_amd64.deb && sudo dpkg -i libmpfr4_3.1.4-1_amd64.deb && rm libmpfr4_3.1.4-1_amd64.deb env: global: diff --git a/tools/buildman/func_test.py b/tools/buildman/func_test.py index 119d02cbb2b..f90b8ea7f5d 100644 --- a/tools/buildman/func_test.py +++ b/tools/buildman/func_test.py @@ -175,6 +175,7 @@ class TestFunctional(unittest.TestCase): """ def setUp(self): self._base_dir = tempfile.mkdtemp() + self._output_dir = tempfile.mkdtemp() self._git_dir = os.path.join(self._base_dir, 'src') self._buildman_pathname = sys.argv[0] self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0])) @@ -207,6 +208,7 @@ class TestFunctional(unittest.TestCase): def tearDown(self): shutil.rmtree(self._base_dir) + shutil.rmtree(self._output_dir) def setupToolchains(self): self._toolchains = toolchain.Toolchains() @@ -421,7 +423,7 @@ class TestFunctional(unittest.TestCase): def testCurrentSource(self): """Very simple test to invoke buildman on the current source""" self.setupToolchains(); - self._RunControl() + self._RunControl('-o', self._output_dir) lines = terminal.GetPrintTestLines() self.assertIn('Building current source for %d boards' % len(boards), lines[0].text) @@ -434,7 +436,7 @@ class TestFunctional(unittest.TestCase): def testBadToolchain(self): """Test that missing toolchains are detected""" self.setupToolchains(); - ret_code = self._RunControl('-b', TEST_BRANCH) + ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir) lines = terminal.GetPrintTestLines() # Buildman always builds the upstream commit as well @@ -458,13 +460,13 @@ class TestFunctional(unittest.TestCase): def testBranch(self): """Test building a branch with all toolchains present""" - self._RunControl('-b', TEST_BRANCH) + self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir) self.assertEqual(self._builder.count, self._total_builds) self.assertEqual(self._builder.fail, 0) def testCount(self): """Test building a specific number of commitst""" - self._RunControl('-b', TEST_BRANCH, '-c2') + self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir) self.assertEqual(self._builder.count, 2 * len(boards)) self.assertEqual(self._builder.fail, 0) # Each board has a mrproper, config, and then one make per commit @@ -472,34 +474,34 @@ class TestFunctional(unittest.TestCase): def testIncremental(self): """Test building a branch twice - the second time should do nothing""" - self._RunControl('-b', TEST_BRANCH) + self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir) # Each board has a mrproper, config, and then one make per commit self.assertEqual(self._make_calls, len(boards) * (self._commits + 2)) self._make_calls = 0 - self._RunControl('-b', TEST_BRANCH, clean_dir=False) + self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False) self.assertEqual(self._make_calls, 0) self.assertEqual(self._builder.count, self._total_builds) self.assertEqual(self._builder.fail, 0) def testForceBuild(self): """The -f flag should force a rebuild""" - self._RunControl('-b', TEST_BRANCH) + self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir) self._make_calls = 0 - self._RunControl('-b', TEST_BRANCH, '-f', clean_dir=False) + self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False) # Each board has a mrproper, config, and then one make per commit self.assertEqual(self._make_calls, len(boards) * (self._commits + 2)) def testForceReconfigure(self): """The -f flag should force a rebuild""" - self._RunControl('-b', TEST_BRANCH, '-C') + self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir) # Each commit has a mrproper, config and make self.assertEqual(self._make_calls, len(boards) * self._commits * 3) def testErrors(self): """Test handling of build errors""" self._error['board2', 1] = 'fred\n' - self._RunControl('-b', TEST_BRANCH) + self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir) self.assertEqual(self._builder.count, self._total_builds) self.assertEqual(self._builder.fail, 1) @@ -507,13 +509,13 @@ class TestFunctional(unittest.TestCase): # not be rebuilt del self._error['board2', 1] self._make_calls = 0 - self._RunControl('-b', TEST_BRANCH, clean_dir=False) + self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False) self.assertEqual(self._builder.count, self._total_builds) self.assertEqual(self._make_calls, 0) self.assertEqual(self._builder.fail, 1) # Now use the -F flag to force rebuild of the bad commit - self._RunControl('-b', TEST_BRANCH, '-F', clean_dir=False) + self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False) self.assertEqual(self._builder.count, self._total_builds) self.assertEqual(self._builder.fail, 0) self.assertEqual(self._make_calls, 3) diff --git a/tools/buildman/kconfiglib.py b/tools/buildman/kconfiglib.py index d68af056b6b..3908985c7b2 100644 --- a/tools/buildman/kconfiglib.py +++ b/tools/buildman/kconfiglib.py @@ -1,3409 +1,6219 @@ +# Copyright (c) 2011-2019, Ulf Magnusson # SPDX-License-Identifier: ISC -# -# Author: Ulf Magnusson -# https://github.com/ulfalizer/Kconfiglib - -# This is Kconfiglib, a Python library for scripting, debugging, and extracting -# information from Kconfig-based configuration systems. To view the -# documentation, run -# -# $ pydoc kconfiglib -# -# or, if you prefer HTML, -# -# $ pydoc -w kconfiglib -# -# The examples/ subdirectory contains examples, to be run with e.g. -# -# $ make scriptconfig SCRIPT=Kconfiglib/examples/print_tree.py -# -# Look in testsuite.py for the test suite. """ -Kconfiglib is a Python library for scripting and extracting information from -Kconfig-based configuration systems. Features include the following: +Overview +======== + +Kconfiglib is a Python 2/3 library for scripting and extracting information +from Kconfig (https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt) +configuration systems. + +See the homepage at https://github.com/ulfalizer/Kconfiglib for a longer +overview. + +Since Kconfiglib 12.0.0, the library version is available in +kconfiglib.VERSION, which is a (<major>, <minor>, <patch>) tuple, e.g. +(12, 0, 0). + + +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, which can be applied with either 'git am' or +the 'patch' utility: + + $ wget -qO- https://raw.githubusercontent.com/ulfalizer/Kconfiglib/master/makefile.patch | git am + $ wget -qO- https://raw.githubusercontent.com/ulfalizer/Kconfiglib/master/makefile.patch | patch -p1 + +Warning: Not passing -p1 to patch will cause the wrong file to be patched. + +Please tell me if the patch does not apply. It should be trivial to apply +manually, as it's just a block of text that needs to be inserted near the other +*conf: targets in scripts/kconfig/Makefile. + +Look further down for a motivation for the Makefile patch and for instructions +on how you can use Kconfiglib without it. + +If you do not wish to install Kconfiglib via pip, the Makefile patch is set up +so that you can also just clone Kconfiglib into the kernel root: + + $ git clone git://github.com/ulfalizer/Kconfiglib.git + $ git am Kconfiglib/makefile.patch (or 'patch -p1 < Kconfiglib/makefile.patch') + +Warning: The directory name Kconfiglib/ is significant in this case, because +it's added to PYTHONPATH by the new targets in makefile.patch. + +The targets added by the Makefile patch are described in the following +sections. + + +make kmenuconfig +---------------- + +This target runs the curses menuconfig interface with Python 3. As of +Kconfiglib 12.2.0, both Python 2 and Python 3 are supported (previously, only +Python 3 was supported, so this was a backport). + + +make guiconfig +-------------- + +This target runs the Tkinter menuconfig interface. Both Python 2 and Python 3 +are supported. To change the Python interpreter used, pass +PYTHONCMD=<executable> to 'make'. The default is 'python'. + + +make [ARCH=<arch>] iscriptconfig +-------------------------------- + +This target gives an interactive Python prompt where a Kconfig instance has +been preloaded and is available in 'kconf'. To change the Python interpreter +used, pass PYTHONCMD=<executable> to 'make'. The default is 'python'. + +To get a feel for the API, try evaluating and printing the symbols in +kconf.defined_syms, and explore the MenuNode menu tree starting at +kconf.top_node by following 'next' and 'list' pointers. + +The item contained in a menu node is found in MenuNode.item (note that this can +be one of the constants kconfiglib.MENU and kconfiglib.COMMENT), and all +symbols and choices have a 'nodes' attribute containing their menu nodes +(usually only one). Printing a menu node will print its item, in Kconfig +format. + +If you want to look up a symbol by name, use the kconf.syms dictionary. + + +make scriptconfig SCRIPT=<script> [SCRIPT_ARG=<arg>] +---------------------------------------------------- + +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 +(currently always "Kconfig" in practice), and sys.argv[2] holds the SCRIPT_ARG +argument, if given. + +See the examples/ subdirectory for example scripts. + + +make dumpvarsconfig +------------------- + +This target prints a list of all environment variables referenced from the +Kconfig files, together with their values. See the +Kconfiglib/examples/dumpvars.py script. + +Only environment variables that are referenced via the Kconfig preprocessor +$(FOO) syntax are included. The preprocessor was added in Linux 4.18. + + +Using Kconfiglib without the Makefile targets +============================================= + +The make targets are only needed to pick up environment variables exported from +the Kbuild makefiles and referenced inside Kconfig files, via e.g. +'source "arch/$(SRCARCH)/Kconfig" and commands run via '$(shell,...)'. + +These variables are referenced as of writing (Linux 4.18), together with sample +values: + + srctree (.) + ARCH (x86) + SRCARCH (x86) + KERNELVERSION (4.18.0) + CC (gcc) + HOSTCC (gcc) + HOSTCXX (g++) + CC_VERSION_TEXT (gcc (Ubuntu 7.3.0-16ubuntu3) 7.3.0) + +Older kernels only reference ARCH, SRCARCH, and KERNELVERSION. + +If your kernel is recent enough (4.18+), you can get a list of referenced +environment variables via 'make dumpvarsconfig' (see above). Note that this +command is added by the Makefile patch. + +To run Kconfiglib without the Makefile patch, set the environment variables +manually: + + $ srctree=. ARCH=x86 SRCARCH=x86 KERNELVERSION=`make kernelversion` ... python(3) + >>> 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. + + +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. + +For symbols with prompts, the visibility of the symbol is determined by the +condition on the prompt. Symbols without prompts are never visible, so setting +a user value on them is pointless. A warning will be printed by default if +Symbol.set_value() is called on a promptless symbol. Assignments to promptless +symbols are normal within a .config file, so no similar warning will be printed +by load_config(). + +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 + +In this example, A && B && C && D (the prompt condition) needs to be non-n for +FOO to be visible (assignable). If its 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, and any higher user value will be truncated down. + +'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. + +Symbols with no user value (or that have a user value but are not visible) and +no (active) 'default' default to n for bool/tristate symbols, and to the empty +string for other symbol types. + +'select' works similarly to symbol visibility, but sets a lower bound on the +value of the symbol. 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. + +For non-bool/tristate symbols, it only matters whether the visibility is n or +non-n: m visibility acts 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. + +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. + +For a visible bool/tristate symbol FOO with value n, this line is written to +.config: + + # CONFIG_FOO is not set + +The point is to remember the user n selection (which might differ from the +default value the symbol would get), while at the same sticking to the rule +that undefined corresponds to n (.config uses Makefile format, making the line +above a comment). When the .config file is read back in, this line will be +treated the same as the following assignment: - - Symbol values and properties can be looked up and values assigned - programmatically. - - .config files can be read and written. - - Expressions can be evaluated in the context of a Kconfig configuration. - - Relations between symbols can be quickly determined, such as finding all - symbols that reference a particular symbol. - - Highly compatible with the scripts/kconfig/*conf utilities. The test suite - automatically compares outputs between Kconfiglib and the C implementation - for a large number of cases. + CONFIG_FOO=n -For the Linux kernel, scripts are run using +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) to see whether the user value will have an +effect. - $ make scriptconfig [ARCH=<arch>] SCRIPT=<path to script> [SCRIPT_ARG=<arg>] -Using the 'scriptconfig' target ensures that required environment variables -(SRCARCH, ARCH, srctree, KERNELVERSION, etc.) are set up correctly. +Intro to the menu tree +====================== -Scripts receive the name of the Kconfig file to load in sys.argv[1]. As of -Linux 4.1.0-rc5, this is always "Kconfig" from the kernel top-level directory. -If an argument is provided with SCRIPT_ARG, it appears as sys.argv[2]. +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 standard +menuconfig interface. (The title is also available in Kconfig.mainmenu_text in +Kconfiglib.) -To get an interactive Python prompt with Kconfiglib preloaded and a Config -object 'c' created, run +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 submenus created implicitly from dependencies. - $ make iscriptconfig [ARCH=<arch>] +MenuNode.item is either a Symbol or a Choice object, or one of the constants +MENU and COMMENT. The prompt of the menu node can be found in MenuNode.prompt, +which also holds the title for menus and comments. For Symbol and Choice, +MenuNode.help holds the help text (if any, otherwise None). -Kconfiglib supports both Python 2 and Python 3. For (i)scriptconfig, the Python -interpreter to use can be passed in PYTHONCMD, which defaults to 'python'. PyPy -works well too, and might give a nice speedup for long-running jobs. +Most symbols will only have a single menu node. A symbol defined in multiple +locations will have one menu node for each location. The list of menu nodes for +a Symbol or Choice can be found in the Symbol/Choice.nodes attribute. -The examples/ directory contains short example scripts, which can be run with -e.g. +Note that prompts and help texts for symbols and choices are stored in their +menu node(s) rather than in the Symbol or Choice objects themselves. This makes +it possible to define a symbol in multiple locations with a different prompt or +help text in each location. To get the help text or prompt for a symbol with a +single menu node, do sym.nodes[0].help and sym.nodes[0].prompt, respectively. +The prompt is a (text, condition) tuple, where condition determines the +visibility (see 'Intro to expressions' below). - $ make scriptconfig SCRIPT=Kconfiglib/examples/print_tree.py +This organization mirrors the C implementation. MenuNode is called +'struct menu' there, but I thought "menu" was a confusing name. -or +It is possible to give a Choice a name and define it in multiple locations, +hence why Choice.nodes is also a list. - $ make scriptconfig SCRIPT=Kconfiglib/examples/help_grep.py SCRIPT_ARG=kernel +As a convenience, the properties added at a particular definition location are +available on the MenuNode itself, in e.g. MenuNode.defaults. This is helpful +when generating documentation, so that symbols/choices defined in multiple +locations can be shown with the correct properties at each location. -testsuite.py contains the test suite. See the top of the script for how to run -it. -Credits: Written by Ulf "Ulfalizer" Magnusson +Intro to expressions +==================== -Send bug reports, suggestions and other feedback to ulfalizer a.t Google's -email service. Don't wrestle with internal APIs. Tell me what you need and I -might add it in a safe way as a client API instead.""" +Expressions can be evaluated with the expr_value() function and printed with +the expr_str() function (these are used internally as well). Evaluating an +expression always yields a tristate value, where n, m, and y are represented as +0, 1, and 2, respectively. +The following table should help you figure out how expressions are represented. +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 (constant symbol)) +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. + +***For choice symbols, the parent Choice will appear in expressions as well, +but it's usually invisible as the value interfaces of Symbol and Choice are +identical. This mirrors the C implementation and makes different choice modes +"just work". + +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 is 2 - A.tri_value + + - The value of A = B is 2 (y) if A.str_value == B.str_value, and 0 (n) + otherwise. Note that str_value is used here instead of tri_value. + + For constant (as well as undefined) symbols, str_value matches the name of + the symbol. This mirrors the C implementation and explains why + 'depends on SYM = "foo"' above works as expected. + +n/m/y are automatically converted to the corresponding constant symbols +"n"/"m"/"y" (Kconfig.n/m/y) during parsing. + +Kconfig.const_syms is a dictionary like Kconfig.syms but for constant symbols. + +If a condition is missing (e.g., <cond> when the 'if <cond>' is removed from +'default A if <cond>'), it is actually Kconfig.y. The standard __str__() +functions just avoid printing 'if y' conditions to give cleaner output. + + +Kconfig extensions +================== + +Kconfiglib includes a couple of Kconfig extensions: + +'source' with relative path +--------------------------- + +The 'rsource' statement sources Kconfig files with a path relative to directory +of the Kconfig file containing the 'rsource' statement, instead of relative to +the project root. + +Consider following directory tree: + + Project + +--Kconfig + | + +--src + +--Kconfig + | + +--SubSystem1 + +--Kconfig + | + +--ModuleA + +--Kconfig + +In this example, assume that src/SubSystem1/Kconfig wants to source +src/SubSystem1/ModuleA/Kconfig. + +With 'source', this statement would be used: + + source "src/SubSystem1/ModuleA/Kconfig" + +With 'rsource', this turns into + + rsource "ModuleA/Kconfig" + +If an absolute path is given to 'rsource', it acts the same as 'source'. + +'rsource' can be used to create "position-independent" Kconfig trees that can +be moved around freely. + + +Globbing 'source' +----------------- + +'source' and 'rsource' accept glob patterns, sourcing all matching Kconfig +files. They require at least one matching file, raising a KconfigError +otherwise. + +For example, the following statement might source sub1/foofoofoo and +sub2/foobarfoo: + + source "sub[12]/foo*foo" + +The glob patterns accepted are the same as for the standard glob.glob() +function. + +Two additional statements are provided for cases where it's acceptable for a +pattern to match no files: 'osource' and 'orsource' (the o is for "optional"). + +For example, the following statements will be no-ops if neither "foo" nor any +files matching "bar*" exist: + + osource "foo" + osource "bar*" + +'orsource' does a relative optional source. + +'source' and 'osource' are analogous to 'include' and '-include' in Make. + + +Generalized def_* keywords +-------------------------- + +def_int, def_hex, and def_string are available in addition to def_bool and +def_tristate, allowing int, hex, and string symbols to be given a type and a +default at the same time. + + +Extra optional warnings +----------------------- + +Some optional warnings can be controlled via environment variables: + + - KCONFIG_WARN_UNDEF: If set to 'y', warnings will be generated for all + references to undefined symbols within Kconfig files. The only gotcha is + that all hex literals must be prefixed with "0x" or "0X", to make it + possible to distinguish them from symbol references. + + Some projects (e.g. the Linux kernel) use multiple Kconfig trees with many + shared Kconfig files, leading to some safe undefined symbol references. + KCONFIG_WARN_UNDEF is useful in projects that only have a single Kconfig + tree though. + + KCONFIG_STRICT is an older alias for this environment variable, supported + for backwards compatibility. + + - KCONFIG_WARN_UNDEF_ASSIGN: If set to 'y', warnings will be generated for + all assignments to undefined symbols within .config files. By default, no + such warnings are generated. + + This warning can also be enabled/disabled via the Kconfig.warn_assign_undef + variable. + + +Preprocessor user functions defined in Python +--------------------------------------------- + +Preprocessor functions can be defined in Python, which makes it simple to +integrate information from existing Python tools into Kconfig (e.g. to have +Kconfig symbols depend on hardware information stored in some other format). + +Putting a Python module named kconfigfunctions(.py) anywhere in sys.path will +cause it to be imported by Kconfiglib (in Kconfig.__init__()). Note that +sys.path can be customized via PYTHONPATH, and includes the directory of the +module being run by default, as well as installation directories. + +If the KCONFIG_FUNCTIONS environment variable is set, it gives a different +module name to use instead of 'kconfigfunctions'. + +The imported module is expected to define a global dictionary named 'functions' +that maps function names to Python functions, as follows: + + def my_fn(kconf, name, arg_1, arg_2, ...): + # kconf: + # Kconfig instance + # + # name: + # Name of the user-defined function ("my-fn"). Think argv[0]. + # + # arg_1, arg_2, ...: + # Arguments passed to the function from Kconfig (strings) + # + # Returns a string to be substituted as the result of calling the + # function + ... + + def my_other_fn(kconf, name, arg_1, arg_2, ...): + ... + + functions = { + "my-fn": (my_fn, <min.args>, <max.args>/None), + "my-other-fn": (my_other_fn, <min.args>, <max.args>/None), + ... + } + + ... + +<min.args> and <max.args> are the minimum and maximum number of arguments +expected by the function (excluding the implicit 'name' argument). If +<max.args> is None, there is no upper limit to the number of arguments. Passing +an invalid number of arguments will generate a KconfigError exception. + +Functions can access the current parsing location as kconf.filename/linenr. +Accessing other fields of the Kconfig object is not safe. See the warning +below. + +Keep in mind that for a variable defined like 'foo = $(fn)', 'fn' will be +called only when 'foo' is expanded. If 'fn' uses the parsing location and the +intent is to use the location of the assignment, you want 'foo := $(fn)' +instead, which calls the function immediately. + +Once defined, user functions can be called from Kconfig in the same way as +other preprocessor functions: + + config FOO + ... + depends on $(my-fn,arg1,arg2) + +If my_fn() returns "n", this will result in + + config FOO + ... + depends on n + +Warning +******* + +User-defined preprocessor functions are called as they're encountered at parse +time, before all Kconfig files have been processed, and before the menu tree +has been finalized. There are no guarantees that accessing Kconfig symbols or +the menu tree via the 'kconf' parameter will work, and it could potentially +lead to a crash. + +Preferably, user-defined functions should be stateless. + + +Feedback +======== + +Send bug reports, suggestions, and questions to ulfalizer a.t Google's email +service, or open a ticket on the GitHub page. +""" +import errno +import importlib import os -import platform import re import sys +# Get rid of some attribute lookups. These are obvious in context. +from glob import iglob +from os.path import dirname, exists, expandvars, islink, join, realpath + + +VERSION = (12, 14, 0) + + # File layout: # # Public classes # Public functions -# Internal classes # Internal functions -# Internal global constants +# Global constants # Line length: 79 columns + # # Public classes # -class Config(object): - """Represents a Kconfig configuration, e.g. for i386 or ARM. This is the - set of symbols and other items appearing in the configuration together with - their values. Creating any number of Config objects -- including for - different architectures -- is safe; Kconfiglib has no global state.""" +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 Kconfig objects (including for different architectures) is + safe. Kconfiglib doesn't keep any global state. + + 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, indexed by name. Also + includes all symbols that are referenced in expressions but never + defined, except for constant (quoted) symbols. + + Undefined symbols can be recognized by Symbol.nodes being empty -- see + the 'Intro to the menu tree' section in the module docstring. + + const_syms: + A dictionary like 'syms' for constant (quoted) symbols + + named_choices: + A dictionary like 'syms' for named choices (choice FOO) + + defined_syms: + A list with all defined symbols, in the same order as they appear in the + Kconfig files. Symbols defined in multiple locations appear multiple + times. + + Note: You probably want to use 'unique_defined_syms' instead. This + attribute is mostly maintained for backwards compatibility. + + unique_defined_syms: + A list like 'defined_syms', but with duplicates removed. Just the first + instance is kept for symbols defined in multiple locations. Kconfig order + is preserved otherwise. + + Using this attribute instead of 'defined_syms' can save work, and + automatically gives reasonable behavior when writing configuration output + (symbols defined in multiple locations only generate output once, while + still preserving Kconfig order for readability). + + choices: + A list with all choices, in the same order as they appear in the Kconfig + files. + + Note: You probably want to use 'unique_choices' instead. This attribute + is mostly maintained for backwards compatibility. + + unique_choices: + Analogous to 'unique_defined_syms', for choices. Named choices can have + multiple definition locations. + + menus: + A list with all menus, in the same order as they appear in the Kconfig + files + + comments: + A list with all comments, in the same order as they appear in the Kconfig + files + + kconfig_filenames: + A list with the filenames of all Kconfig files included in the + configuration, relative to $srctree (or relative to the current directory + if $srctree isn't set), except absolute paths (e.g. + 'source "/foo/Kconfig"') are kept as-is. + + The files are listed in the order they are source'd, starting with the + top-level Kconfig file. If a file is source'd multiple times, it will + appear multiple times. Use set() to get unique filenames. + + Note that Kconfig.sync_deps() already indirectly catches any file + modifications that change configuration output. + + env_vars: + A set() with the names of all environment variables referenced in the + Kconfig files. + + Only environment variables referenced with the preprocessor $(FOO) syntax + will be registered. The older $FOO syntax is only supported for backwards + compatibility. + + Also note that $(FOO) won't be registered unless the environment variable + $FOO is actually set. If it isn't, $(FOO) is an expansion of an unset + preprocessor variable (which gives the empty string). + + Another gotcha is that environment variables referenced in the values of + recursively expanded preprocessor variables (those defined with =) will + only be registered if the variable is actually used (expanded) somewhere. + + The note from the 'kconfig_filenames' documentation applies here too. + + n/m/y: + The predefined constant symbols n/m/y. Also available in const_syms. + + modules: + 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, + its tri_value will be 0 (n), as expected. + + 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 Kconfig.defconfig_filename. + + defconfig_filename: + 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. + + '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. + + 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 implicit top-level menu. + Acts as the root of the menu tree. + + mainmenu_text: + The prompt (title) of the top menu (top_node). Defaults to "Main menu". + Can be changed with the 'mainmenu' statement (see kconfig-language.txt). + + variables: + A dictionary with all preprocessor variables, indexed by name. See the + Variable class. + + warn: + Set this variable to True/False to enable/disable warnings. See + Kconfig.__init__(). + + When 'warn' is False, the values of the other warning-related variables + are ignored. + + This variable as well as the other warn* variables can be read to check + the current warning settings. + + warn_to_stderr: + Set this variable to True/False to enable/disable warnings on stderr. See + Kconfig.__init__(). + + warn_assign_undef: + Set this variable to True to generate warnings for assignments to + undefined symbols in configuration files. + + This variable is False by default unless the KCONFIG_WARN_UNDEF_ASSIGN + environment variable was set to 'y' when the Kconfig instance was + created. + + warn_assign_override: + Set this variable to True to generate warnings for multiple assignments + to the same symbol in configuration files, where the assignments set + different values (e.g. CONFIG_FOO=m followed by CONFIG_FOO=y, where the + last value would get used). + + This variable is True by default. Disabling it might be useful when + merging configurations. + + warn_assign_redun: + Like warn_assign_override, but for multiple assignments setting a symbol + to the same value. + + This variable is True by default. Disabling it might be useful when + merging configurations. + + warnings: + A list of strings containing all warnings that have been generated, for + cases where more flexibility is needed. + + See the 'warn_to_stderr' parameter to Kconfig.__init__() and the + Kconfig.warn_to_stderr variable as well. Note that warnings still get + added to Kconfig.warnings when 'warn_to_stderr' is True. + + Just as for warnings printed to stderr, only warnings that are enabled + will get added to Kconfig.warnings. See the various Kconfig.warn* + variables. + + missing_syms: + A list with (name, value) tuples for all assignments to undefined symbols + within the most recently loaded .config file(s). 'name' is the symbol + name without the 'CONFIG_' prefix. 'value' is a string that gives the + right-hand side of the assignment verbatim. + + See Kconfig.load_config() as well. + + srctree: + The value of the $srctree environment variable when the configuration was + loaded, or the empty string if $srctree wasn't set. This gives nice + behavior with os.path.join(), which treats "" as the current directory, + without adding "./". + + Kconfig files are looked up relative to $srctree (unless absolute paths + are used), and .config files are looked up relative to $srctree if they + are not found in the current directory. 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 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. + + config_prefix: + The value of the $CONFIG_ environment variable when the configuration was + loaded. This is the prefix used (and expected) on symbol names in .config + files and C headers. Defaults to "CONFIG_". Used in the same way in the C + tools. + + Like for srctree, only the value of $CONFIG_ when the configuration is + loaded matters. + + filename/linenr: + The current parsing location, for use in Python preprocessor functions. + See the module docstring. + """ + __slots__ = ( + "_encoding", + "_functions", + "_set_match", + "_srctree_prefix", + "_unset_match", + "_warn_assign_no_prompt", + "choices", + "comments", + "config_prefix", + "const_syms", + "defconfig_list", + "defined_syms", + "env_vars", + "kconfig_filenames", + "m", + "menus", + "missing_syms", + "modules", + "n", + "named_choices", + "srctree", + "syms", + "top_node", + "unique_choices", + "unique_defined_syms", + "variables", + "warn", + "warn_assign_override", + "warn_assign_redun", + "warn_assign_undef", + "warn_to_stderr", + "warnings", + "y", + + # Parsing-related + "_parsing_kconfigs", + "_readline", + "filename", + "linenr", + "_include_path", + "_filestack", + "_line", + "_tokens", + "_tokens_i", + "_reuse_tokens", + ) # # Public interface # - def __init__(self, filename="Kconfig", base_dir=None, print_warnings=True, - print_undef_assign=False): - """Creates a new Config object, representing a Kconfig configuration. - Raises Kconfig_Syntax_Error on syntax errors. - - filename (default: "Kconfig"): The base Kconfig file of the - configuration. For the Linux kernel, you'll probably want "Kconfig" - from the top-level directory, as environment variables will make - sure the right Kconfig is included from there - (arch/<architecture>/Kconfig). If you are using Kconfiglib via 'make - scriptconfig', the filename of the base base Kconfig file will be in - sys.argv[1]. - - base_dir (default: None): The base directory relative to which 'source' - statements within Kconfig files will work. For the Linux kernel this - should be the top-level directory of the kernel tree. $-references - to existing environment variables will be expanded. - - If None (the default), the environment variable 'srctree' will be - used if set, and the current directory otherwise. 'srctree' is set - by the Linux makefiles to the top-level kernel directory. A default - of "." would not work with an alternative build directory. - - print_warnings (default: True): Set to True if warnings related to this - configuration should be printed to stderr. This can be changed later - with Config.set_print_warnings(). It is provided as a constructor - argument since warnings might be generated during parsing. - - print_undef_assign (default: False): Set to True if informational - messages related to assignments to undefined symbols should be - printed to stderr for this configuration. Can be changed later with - Config.set_print_undef_assign().""" - - # The set of all symbols, indexed by name (a string) - self.syms = {} - # Python 2/3 compatibility hack. This is the only one needed. - self.syms_iter = self.syms.values if sys.version_info[0] >= 3 else \ - self.syms.itervalues + def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, + encoding="utf-8"): + """ + Creates a new Kconfig object by parsing Kconfig files. + Note that Kconfig files are not the same as .config files (which store + configuration symbol values). + + See the module docstring for some environment variables that influence + default warning settings (KCONFIG_WARN_UNDEF and + KCONFIG_WARN_UNDEF_ASSIGN). + + Raises KconfigError on syntax/semantic errors, and OSError or (possibly + a subclass of) IOError on IO errors ('errno', 'strerror', and + 'filename' are available). Note that IOError is an alias for OSError on + Python 3, so it's enough to catch OSError there. If you need Python 2/3 + compatibility, it's easiest to catch EnvironmentError, which is a + common base class of OSError/IOError on Python 2 and an alias for + OSError on Python 3. + + filename (default: "Kconfig"): + The Kconfig file to load. For the Linux kernel, you'll want "Kconfig" + from the top-level directory, as environment variables will make sure + the right Kconfig is included from there (arch/$SRCARCH/Kconfig as of + writing). + + If $srctree is set, 'filename' will be looked up relative to it. + $srctree is also used to look up source'd files within Kconfig files. + See the class documentation. + + If you are using Kconfiglib via 'make scriptconfig', the filename of + the base base Kconfig file will be in sys.argv[1]. It's currently + always "Kconfig" in practice. + + warn (default: True): + True if warnings related to this configuration should be generated. + This can be changed later by setting Kconfig.warn to True/False. It + is provided as a constructor argument since warnings might be + generated during parsing. + + See the other Kconfig.warn_* variables as well, which enable or + suppress certain warnings when warnings are enabled. + + All generated warnings are added to the Kconfig.warnings list. See + the class documentation. + + warn_to_stderr (default: True): + True if warnings should be printed to stderr in addition to being + added to Kconfig.warnings. + + This can be changed later by setting Kconfig.warn_to_stderr to + True/False. + + encoding (default: "utf-8"): + The encoding to use when reading and writing files, and when decoding + output from commands run via $(shell). If None, the encoding + specified in the current locale will be used. + + The "utf-8" default avoids exceptions on systems that are configured + to use the C locale, which implies an ASCII encoding. + + This parameter has no effect on Python 2, due to implementation + issues (regular strings turning into Unicode strings, which are + distinct in Python 2). Python 2 doesn't decode regular strings + anyway. + + Related PEP: https://www.python.org/dev/peps/pep-0538/ + """ + self._encoding = encoding - # The set of all defined symbols in the configuration in the order they - # appear in the Kconfig files. This excludes the special symbols n, m, - # and y as well as symbols that are referenced but never defined. - self.kconfig_syms = [] + self.srctree = os.getenv("srctree", "") + # A prefix we can reliably strip from glob() results to get a filename + # relative to $srctree. relpath() can cause issues for symlinks, + # because it assumes symlink/../foo is the same as foo/. + self._srctree_prefix = realpath(self.srctree) + os.sep - # The set of all named choices (yes, choices can have names), indexed - # by name (a string) - self.named_choices = {} + self.warn = warn + self.warn_to_stderr = warn_to_stderr + self.warn_assign_undef = os.getenv("KCONFIG_WARN_UNDEF_ASSIGN") == "y" + self.warn_assign_override = True + self.warn_assign_redun = True + self._warn_assign_no_prompt = True - # Lists containing all choices, menus and comments in the configuration + self.warnings = [] + + self.config_prefix = os.getenv("CONFIG_", "CONFIG_") + # Regular expressions for parsing .config files + self._set_match = _re_match(self.config_prefix + r"([^=]+)=(.*)") + self._unset_match = _re_match(r"# {}([^ ]+) is not set".format( + self.config_prefix)) + + self.syms = {} + self.const_syms = {} + self.defined_syms = [] + self.missing_syms = [] + self.named_choices = {} self.choices = [] self.menus = [] self.comments = [] - def register_special_symbol(type_, name, val): + for nmy in "n", "m", "y": sym = Symbol() - sym.is_special_ = True - sym.is_defined_ = True - sym.config = self - sym.name = name - sym.type = type_ - sym.cached_val = val - self.syms[name] = sym - return sym - - # The special symbols n, m and y, used as shorthand for "n", "m" and - # "y" - self.n = register_special_symbol(TRISTATE, "n", "n") - self.m = register_special_symbol(TRISTATE, "m", "m") - self.y = register_special_symbol(TRISTATE, "y", "y") - # DEFCONFIG_LIST uses this - register_special_symbol(STRING, "UNAME_RELEASE", platform.uname()[2]) - - # The symbol with "option defconfig_list" set, containing a list of - # default .config files - self.defconfig_sym = None - - # See Symbol.get_(src)arch() - self.arch = os.environ.get("ARCH") - self.srcarch = os.environ.get("SRCARCH") - - # If you set CONFIG_ in the environment, Kconfig will prefix all symbols - # with its value when saving the configuration, instead of using the default, "CONFIG_". - self.config_prefix = os.environ.get("CONFIG_") - if self.config_prefix is None: - self.config_prefix = "CONFIG_" - - # See Config.__init__(). We need this for get_defconfig_filename(). - self.srctree = os.environ.get("srctree") - if self.srctree is None: - self.srctree = "." + sym.kconfig = self + sym.name = nmy + sym.is_constant = True + sym.orig_type = TRISTATE + sym._cached_tri_val = STR_TO_TRI[nmy] + + self.const_syms[nmy] = sym + + self.n = self.const_syms["n"] + self.m = self.const_syms["m"] + self.y = self.const_syms["y"] + + # 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 + + # Maps preprocessor variables names to Variable instances + self.variables = {} + + # Predefined preprocessor functions, with min/max number of arguments + self._functions = { + "info": (_info_fn, 1, 1), + "error-if": (_error_if_fn, 2, 2), + "filename": (_filename_fn, 0, 0), + "lineno": (_lineno_fn, 0, 0), + "shell": (_shell_fn, 1, 1), + "warning-if": (_warning_if_fn, 2, 2), + } + + # Add any user-defined preprocessor functions + try: + self._functions.update( + importlib.import_module( + os.getenv("KCONFIG_FUNCTIONS", "kconfigfunctions") + ).functions) + except ImportError: + pass + + # This determines whether previously unseen symbols are 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 + + self.top_node = MenuNode() + self.top_node.kconfig = self + self.top_node.item = MENU + self.top_node.is_menuconfig = True + self.top_node.visibility = self.y + self.top_node.prompt = ("Main menu", self.y) + self.top_node.parent = None + self.top_node.dep = self.y + self.top_node.filename = filename + self.top_node.linenr = 1 + self.top_node.include_path = () + + # Parse the Kconfig files + + # Not used internally. Provided as a convenience. + self.kconfig_filenames = [filename] + self.env_vars = set() + # Keeps track of the location in the parent Kconfig files. Kconfig + # files usually source other Kconfig files. See _enter_file(). + self._filestack = [] + self._include_path = () + + # The current parsing location self.filename = filename - self.base_dir = self.srctree if base_dir is None else \ - os.path.expandvars(base_dir) + self.linenr = 0 - # The 'mainmenu' text - self.mainmenu_text = None + # Used to avoid retokenizing lines when we discover that they're not + # part of the construct currently being parsed. This is kinda like an + # unget operation. + self._reuse_tokens = False - # The filename of the most recently loaded .config file - self.config_filename = None - # The textual header of the most recently loaded .config, uncommented - self.config_header = None + # Open the top-level Kconfig file. Store the readline() method directly + # as a small optimization. + self._readline = self._open(join(self.srctree, filename), "r").readline - self.print_warnings = print_warnings - self.print_undef_assign = print_undef_assign - self._warnings = [] + try: + # Parse the Kconfig files + self._parse_block(None, self.top_node, self.top_node) + self.top_node.list = self.top_node.next + self.top_node.next = None + except UnicodeDecodeError as e: + _decoding_error(e, self.filename) - # For parsing routines that stop when finding a line belonging to a - # different construct, these holds that line and the tokenized version - # of that line. The purpose is to avoid having to re-tokenize the line, - # which is inefficient and causes problems when recording references to - # symbols. - self.end_line = None - self.end_line_tokens = None + # Close the top-level Kconfig file. __self__ fetches the 'file' object + # for the method. + self._readline.__self__.close() - # See the comment in _parse_expr(). - self._cur_item = None - self._line = None - self._filename = None - self._linenr = None - self._transform_m = None + self._parsing_kconfigs = False - # Parse the Kconfig files - self.top_block = [] - self._parse_file(filename, None, None, None, self.top_block) + # Do various menu tree post-processing + self._finalize_node(self.top_node, self.y) + + self.unique_defined_syms = _ordered_unique(self.defined_syms) + self.unique_choices = _ordered_unique(self.choices) - # Build Symbol.dep for all symbols + # Do sanity checks. Some of these depend on everything being finalized. + self._check_sym_sanity() + self._check_choice_sanity() + + # KCONFIG_STRICT is an older alias for KCONFIG_WARN_UNDEF, supported + # for backwards compatibility + if os.getenv("KCONFIG_WARN_UNDEF") == "y" or \ + os.getenv("KCONFIG_STRICT") == "y": + + self._check_undef_syms() + + # Build Symbol._dependents for all symbols and choices self._build_dep() - def get_arch(self): - """Returns the value the environment variable ARCH had at the time the - Config instance was created, or None if ARCH was not set. For the - kernel, this corresponds to the architecture being built for, with - values such as "i386" or "mips".""" - return self.arch - - def get_srcarch(self): - """Returns the value the environment variable SRCARCH had at the time - the Config instance was created, or None if SRCARCH was not set. For - the kernel, this corresponds to the particular arch/ subdirectory - containing architecture-specific code.""" - return self.srcarch - - def get_srctree(self): - """Returns the value the environment variable srctree had at the time - the Config instance was created, or None if srctree was not defined. - This variable points to the source directory and is used when building - in a separate directory.""" - return self.srctree - - def get_base_dir(self): - """Returns the base directory relative to which 'source' statements - will work, passed as an argument to Config.__init__().""" - return self.base_dir - - def get_kconfig_filename(self): - """Returns the name of the (base) kconfig file this configuration was - loaded from.""" - return self.filename - - def get_config_filename(self): - """Returns the filename of the most recently loaded configuration file, - or None if no configuration has been loaded.""" - return self.config_filename - - def get_config_header(self): - """Returns the (uncommented) textual header of the .config file most - recently loaded with load_config(). Returns None if no .config file has - been loaded or if the most recently loaded .config file has no header. - The header consists of all lines up to but not including the first line - that either - - 1. Does not start with "#" - 2. Has the form "# CONFIG_FOO is not set." - """ - return self.config_header - - def get_mainmenu_text(self): - """Returns the text of the 'mainmenu' statement (with $-references to - symbols replaced by symbol values), or None if the configuration has no - 'mainmenu' statement.""" - return None if self.mainmenu_text is None else \ - self._expand_sym_refs(self.mainmenu_text) - - def get_defconfig_filename(self): - """Returns the name of the defconfig file, which is the first existing - file in the list given in a symbol having 'option defconfig_list' set. - $-references to symbols will be expanded ("$FOO bar" -> "foo bar" if - FOO has the value "foo"). Returns None in case of no defconfig file. - Setting 'option defconfig_list' on multiple symbols currently results - in undefined behavior. - - If the environment variable 'srctree' was set when the Config was - created, get_defconfig_filename() will first look relative to that - directory before looking in the current directory; see - Config.__init__(). - - WARNING: A wart here is that scripts/kconfig/Makefile sometimes uses - the --defconfig=<defconfig> option when calling the C implementation of - e.g. 'make defconfig'. This option overrides the 'option - defconfig_list' symbol, meaning the result from - get_defconfig_filename() might not match what 'make defconfig' would - use. That probably ought to be worked around somehow, so that this - function always gives the "expected" result.""" - if self.defconfig_sym is None: - return None - for filename, cond_expr in self.defconfig_sym.def_exprs: - if self._eval_expr(cond_expr) == "y": - filename = self._expand_sym_refs(filename) - # We first look in $srctree. os.path.join() won't work here as - # an absolute path in filename would override $srctree. - srctree_filename = os.path.normpath(self.srctree + "/" + - filename) - if os.path.exists(srctree_filename): - return srctree_filename - if os.path.exists(filename): - return filename - return None + # Check for dependency loops + check_dep_loop_sym = _check_dep_loop_sym # Micro-optimization + for sym in self.unique_defined_syms: + check_dep_loop_sym(sym, False) - def get_symbol(self, name): - """Returns the symbol with name 'name', or None if no such symbol - appears in the configuration. An alternative shorthand is conf[name], - where conf is a Config instance, though that will instead raise - KeyError if the symbol does not exist.""" - return self.syms.get(name) + # Add extra dependencies from choices to choice symbols that get + # awkward during dependency loop detection + self._add_choice_deps() - def __getitem__(self, name): - """Returns the symbol with name 'name'. Raises KeyError if the symbol - does not appear in the configuration.""" - return self.syms[name] + @property + def mainmenu_text(self): + """ + See the class documentation. + """ + return self.top_node.prompt[0] - def get_symbols(self, all_symbols=True): - """Returns a list of symbols from the configuration. An alternative for - iterating over all defined symbols (in the order of definition) is + @property + def defconfig_filename(self): + """ + See the class documentation. + """ + if self.defconfig_list: + for filename, cond in self.defconfig_list.defaults: + if expr_value(cond): + try: + with self._open_config(filename.str_value) as f: + return f.name + except EnvironmentError: + continue - for sym in config: - ... + return None - which relies on Config implementing __iter__() and is equivalent to + def load_config(self, filename=None, replace=True, verbose=None): + """ + Loads symbol values from a file in the .config format. Equivalent to + calling Symbol.set_value() to set each of the values. - for sym in config.get_symbols(False): - ... + "# CONFIG_FOO is not set" within a .config file sets the user value of + FOO to n. The C tools work the same way. - all_symbols (default: True): If True, all symbols -- including special - and undefined symbols -- will be included in the result, in an - undefined order. If False, only symbols actually defined and not - merely referred to in the configuration will be included in the - result, and will appear in the order that they are defined within - the Kconfig configuration files.""" - return list(self.syms.values()) if all_symbols else self.kconfig_syms + For each symbol, the Symbol.user_value attribute holds the value the + symbol was assigned in the .config file (if any). The user value might + differ from Symbol.str/tri_value if there are unsatisfied dependencies. - def __iter__(self): - """Convenience function for iterating over the set of all defined - symbols in the configuration, used like + Calling this function also updates the Kconfig.missing_syms attribute + with a list of all assignments to undefined symbols within the + configuration file. Kconfig.missing_syms is cleared if 'replace' is + True, and appended to otherwise. See the documentation for + Kconfig.missing_syms as well. - for sym in conf: - ... + See the Kconfig.__init__() docstring for raised exceptions + (OSError/IOError). KconfigError is never raised here. - The iteration happens in the order of definition within the Kconfig - configuration files. Symbols only referred to but not defined will not - be included, nor will the special symbols n, m, and y. If you want to - include such symbols as well, see config.get_symbols().""" - return iter(self.kconfig_syms) + filename (default: None): + Path to load configuration from (a string). Respects $srctree if set + (see the class documentation). - def get_choices(self): - """Returns a list containing all choice statements in the - configuration, in the order they appear in the Kconfig files.""" - return self.choices + If 'filename' is None (the default), the configuration file to load + (if any) is calculated automatically, giving the behavior you'd + usually want: - def get_menus(self): - """Returns a list containing all menus in the configuration, in the - order they appear in the Kconfig files.""" - return self.menus + 1. If the KCONFIG_CONFIG environment variable is set, it gives the + path to the configuration file to load. Otherwise, ".config" is + used. See standard_config_filename(). - def get_comments(self): - """Returns a list containing all comments in the configuration, in the - order they appear in the Kconfig files.""" - return self.comments + 2. If the path from (1.) doesn't exist, the configuration file + given by kconf.defconfig_filename is loaded instead, which is + derived from the 'option defconfig_list' symbol. - def get_top_level_items(self): - """Returns a list containing the items (symbols, menus, choices, and - comments) at the top level of the configuration -- that is, all items - that do not appear within a menu or choice. The items appear in the - same order as within the configuration.""" - return self.top_block + 3. If (1.) and (2.) fail to find a configuration file to load, no + configuration file is loaded, and symbols retain their current + values (e.g., their default values). This is not an error. - def load_config(self, filename, replace=True): - """Loads symbol values from a file in the familiar .config format. - Equivalent to calling Symbol.set_user_value() to set each of the - values. + See the return value as well. - "# CONFIG_FOO is not set" within a .config file is treated specially - and sets the user value of FOO to 'n'. The C implementation works the - same way. + replace (default: True): + If True, all existing user values will be cleared before loading the + .config. Pass False to merge configurations. - filename: The .config file to load. $-references to existing - environment variables will be expanded. For scripts to work even when - an alternative build directory is used with the Linux kernel, you - need to refer to the top-level kernel directory with "$srctree". + verbose (default: None): + Limited backwards compatibility to prevent crashes. A warning is + printed if anything but None is passed. - replace (default: True): True if the configuration should replace the - old configuration; False if it should add to it. + Prior to Kconfiglib 12.0.0, this option enabled printing of messages + to stdout when 'filename' was None. A message is (always) returned + now instead, which is more flexible. - Returns a list or warnings (hopefully empty) + Will probably be removed in some future version. + + Returns a string with a message saying which file got loaded (or + possibly that no file got loaded, when 'filename' is None). This is + meant to reduce boilerplate in tools, which can do e.g. + print(kconf.load_config()). The returned message distinguishes between + loading (replace == True) and merging (replace == False). """ + if verbose is not None: + _warn_verbose_deprecated("load_config") + + msg = None + if filename is None: + filename = standard_config_filename() + if not exists(filename) and \ + not exists(join(self.srctree, filename)): + defconfig = self.defconfig_filename + if defconfig is None: + return "Using default symbol values (no '{}')" \ + .format(filename) + + msg = " default configuration '{}' (no '{}')" \ + .format(defconfig, filename) + filename = defconfig + + if not msg: + msg = " configuration '{}'".format(filename) + + # Disable the warning about assigning to symbols without prompts. This + # is normal and expected within a .config file. + self._warn_assign_no_prompt = False + + # This stub only exists to make sure _warn_assign_no_prompt gets + # reenabled + try: + self._load_config(filename, replace) + except UnicodeDecodeError as e: + _decoding_error(e, filename) + finally: + self._warn_assign_no_prompt = True + + return ("Loaded" if replace else "Merged") + msg + + def _load_config(self, filename, replace): + with self._open_config(filename) as f: + if replace: + self.missing_syms = [] + + # If we're replacing the configuration, keep track of which + # symbols and choices got set so that we can unset the rest + # later. This avoids invalidating everything and is faster. + # Another benefit is that invalidation must be rock solid for + # it to work, making it a good test. + + for sym in self.unique_defined_syms: + sym._was_set = False + + for choice in self.unique_choices: + choice._was_set = False + + # Small optimizations + set_match = self._set_match + unset_match = self._unset_match + get_sym = self.syms.get + + for linenr, line in enumerate(f, 1): + # The C tools ignore trailing whitespace + line = line.rstrip() + + match = set_match(line) + if match: + name, val = match.groups() + sym = get_sym(name) + if not sym or not sym.nodes: + self._undef_assign(name, val, filename, linenr) + continue + + 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 is BOOL + and val.startswith(("y", "n")) or + sym.orig_type is TRISTATE + and val.startswith(("y", "m", "n"))): + self._warn("'{}' is not a valid value for the {} " + "symbol {}. Assignment ignored." + .format(val, TYPE_TO_STR[sym.orig_type], + _name_and_loc(sym)), + filename, linenr) + continue + + val = val[0] + + if sym.choice and val != "n": + # During .config loading, we infer the mode of the + # choice from the kind of values that are assigned + # to the choice symbols + + prev_mode = sym.choice.user_value + if prev_mode is not None and \ + TRI_TO_STR[prev_mode] != val: + + self._warn("both m and y assigned to symbols " + "within the same choice", + filename, linenr) + + # Set the choice's mode + sym.choice.set_value(val) + + elif sym.orig_type is STRING: + match = _conf_string_match(val) + if not match: + self._warn("malformed string literal in " + "assignment to {}. Assignment ignored." + .format(_name_and_loc(sym)), + filename, linenr) + continue + + val = unescape(match.group(1)) - self._warnings = [] - # Regular expressions for parsing .config files - _set_re_match = re.compile(r"{}(\w+)=(.*)".format(self.config_prefix)).match - _unset_re_match = re.compile(r"# {}(\w+) is not set".format(self.config_prefix)).match + else: + match = unset_match(line) + if not match: + # Print a warning for lines that match neither + # set_match() nor unset_match() and that are not blank + # lines or comments. 'line' has already been + # rstrip()'d, so blank lines show up as "" here. + if line and not line.lstrip().startswith("#"): + self._warn("ignoring malformed line '{}'" + .format(line), + filename, linenr) - # Put this first so that a missing file doesn't screw up our state - filename = os.path.expandvars(filename) - line_feeder = _FileFeed(filename) + continue - self.config_filename = filename + name = match.group(1) + sym = get_sym(name) + if not sym or not sym.nodes: + self._undef_assign(name, "n", filename, linenr) + continue - # - # Read header - # + if sym.orig_type not in _BOOL_TRISTATE: + continue - def is_header_line(line): - return line is not None and line.startswith("#") and \ - not _unset_re_match(line) + val = "n" - self.config_header = None + # Done parsing the assignment. Set the value. - line = line_feeder.peek_next() - if is_header_line(line): - self.config_header = "" - while is_header_line(line_feeder.peek_next()): - self.config_header += line_feeder.get_next()[1:] - # Remove trailing newline - if self.config_header.endswith("\n"): - self.config_header = self.config_header[:-1] + if sym._was_set: + self._assigned_twice(sym, val, filename, linenr) - # - # Read assignments. Hotspot for some workloads. - # + sym.set_value(val) - def warn_override(filename, linenr, name, old_user_val, new_user_val): - self._warn('overriding the value of {0}. ' - 'Old value: "{1}", new value: "{2}".' - .format(name, old_user_val, new_user_val), - filename, linenr) - - # Invalidate everything to keep things simple. It might be possible to - # improve performance for the case where multiple configurations are - # loaded by only invalidating a symbol (and its dependent symbols) if - # the new user value differs from the old. One complication would be - # that symbols not mentioned in the .config must lose their user value - # when replace = True, which is the usual case. if replace: - self.unset_user_values() + # If we're replacing the configuration, unset the symbols that + # didn't get set + + for sym in self.unique_defined_syms: + if not sym._was_set: + sym.unset_value() + + for choice in self.unique_choices: + if not choice._was_set: + choice.unset_value() + + def _undef_assign(self, name, val, filename, linenr): + # Called for assignments to undefined symbols during .config loading + + self.missing_syms.append((name, val)) + if self.warn_assign_undef: + self._warn( + "attempt to assign the value '{}' to the undefined symbol {}" + .format(val, name), filename, linenr) + + def _assigned_twice(self, sym, new_val, filename, linenr): + # Called when a symbol is assigned more than once in a .config file + + # Use strings for bool/tristate user values in the warning + if sym.orig_type in _BOOL_TRISTATE: + user_val = TRI_TO_STR[sym.user_value] else: - self._invalidate_all() + user_val = sym.user_value + + msg = '{} set more than once. Old value "{}", new value "{}".'.format( + _name_and_loc(sym), user_val, new_val) + + if user_val == new_val: + if self.warn_assign_redun: + self._warn(msg, filename, linenr) + elif self.warn_assign_override: + self._warn(msg, filename, linenr) + + def write_autoconf(self, filename, + header="/* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */\n"): + r""" + Writes out symbol values as a C header file, matching the format used + by include/generated/autoconf.h in the kernel. + + The ordering of the #defines matches the one generated by + write_config(). The order in the C implementation depends on the hash + table implementation as of writing, and so won't match. + + If 'filename' exists and its contents is identical to what would get + written out, it is left untouched. This avoids updating file metadata + like the modification time and possibly triggering redundant work in + build tools. + + 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 it enclosed in '/* */' to make it a C comment, + and include a final terminating newline. + """ + self._write_if_changed(filename, self._autoconf_contents(header)) + + def _autoconf_contents(self, header): + # write_autoconf() helper. Returns the contents to write as a string, + # with 'header' at the beginning. + + # "".join()ed later + chunks = [header] + add = chunks.append + + for sym in self.unique_defined_syms: + # _write_to_conf is determined when the value is calculated. This + # is a hidden function call due to property magic. + # + # Note: In client code, you can check if sym.config_string is empty + # instead, to avoid accessing the internal _write_to_conf variable + # (though it's likely to keep working). + val = sym.str_value + if not sym._write_to_conf: + continue + if sym.orig_type in _BOOL_TRISTATE: + if val == "y": + add("#define {}{} 1\n" + .format(self.config_prefix, sym.name)) + elif val == "m": + add("#define {}{}_MODULE 1\n" + .format(self.config_prefix, sym.name)) + + elif sym.orig_type is STRING: + add('#define {}{} "{}"\n' + .format(self.config_prefix, sym.name, escape(val))) + + else: # sym.orig_type in _INT_HEX: + if sym.orig_type is HEX and \ + not val.startswith(("0x", "0X")): + val = "0x" + val + + add("#define {}{} {}\n" + .format(self.config_prefix, sym.name, val)) + + return "".join(chunks) + + def write_config(self, filename=None, + header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n", + save_old=True, verbose=None): + r""" + Writes out symbol values in the .config format. The format matches the + C implementation, including ordering. + + Symbols appear in the same order in generated .config files as they do + in the Kconfig files. For symbols defined in multiple locations, a + single assignment is written out corresponding to the first location + where the symbol is defined. + + See the 'Intro to symbol values' section in the module docstring to + understand which symbols get written out. + + If 'filename' exists and its contents is identical to what would get + written out, it is left untouched. This avoids updating file metadata + like the modification time and possibly triggering redundant work in + build tools. + + See the Kconfig.__init__() docstring for raised exceptions + (OSError/IOError). KconfigError is never raised here. + + filename (default: None): + Filename to save configuration to (a string). + + If None (the default), the filename in the environment variable + KCONFIG_CONFIG is used if set, and ".config" otherwise. See + standard_config_filename(). + + 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. + + save_old (default: True): + If True and <filename> already exists, a copy of it will be saved to + <filename>.old in the same directory before the new configuration is + written. + + Errors are silently ignored if <filename>.old cannot be written (e.g. + due to being a directory, or <filename> being something like + /dev/null). + + verbose (default: None): + Limited backwards compatibility to prevent crashes. A warning is + printed if anything but None is passed. + + Prior to Kconfiglib 12.0.0, this option enabled printing of messages + to stdout when 'filename' was None. A message is (always) returned + now instead, which is more flexible. + + Will probably be removed in some future version. + + Returns a string with a message saying which file got saved. This is + meant to reduce boilerplate in tools, which can do e.g. + print(kconf.write_config()). + """ + if verbose is not None: + _warn_verbose_deprecated("write_config") + + if filename is None: + filename = standard_config_filename() + + contents = self._config_contents(header) + if self._contents_eq(filename, contents): + return "No change to '{}'".format(filename) + + if save_old: + _save_old(filename) + + with self._open(filename, "w") as f: + f.write(contents) + + return "Configuration saved to '{}'".format(filename) + + def _config_contents(self, header): + # write_config() helper. Returns the contents to write as a string, + # with 'header' at the beginning. + # + # More memory friendly would be to 'yield' the strings and + # "".join(_config_contents()), but it was a bit slower on my system. + + # node_iter() was used here before commit 3aea9f7 ("Add '# end of + # <menu>' after menus in .config"). Those comments get tricky to + # implement with it. + + for sym in self.unique_defined_syms: + sym._visited = False + + # Did we just print an '# end of ...' comment? + after_end_comment = False + + # "".join()ed later + chunks = [header] + add = chunks.append + + node = self.top_node while 1: - line = line_feeder.get_next() - if line is None: - return self._warnings + # Jump to the next node with an iterative tree walk + if node.list: + node = node.list + elif node.next: + node = node.next + else: + while node.parent: + node = node.parent + + # Add a comment when leaving visible menus + if node.item is MENU and expr_value(node.dep) and \ + expr_value(node.visibility) and \ + node is not self.top_node: + add("# end of {}\n".format(node.prompt[0])) + after_end_comment = True + + if node.next: + node = node.next + break + else: + # No more nodes + return "".join(chunks) + + # Generate configuration output for the node + + item = node.item + + if item.__class__ is Symbol: + if item._visited: + continue + item._visited = True + + conf_string = item.config_string + if not conf_string: + continue - line = line.rstrip() + if after_end_comment: + # Add a blank line before the first symbol printed after an + # '# end of ...' comment + after_end_comment = False + add("\n") + add(conf_string) + + elif expr_value(node.dep) and \ + ((item is MENU and expr_value(node.visibility)) or + item is COMMENT): + + add("\n#\n# {}\n#\n".format(node.prompt[0])) + after_end_comment = False + + def write_min_config(self, filename, + header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"): + """ + Writes out a "minimal" configuration file, omitting symbols whose value + matches their default value. The format matches the one produced by + 'make savedefconfig'. + + The resulting configuration file is incomplete, but a complete + configuration can be derived from it by loading it. Minimal + configuration files can serve as a more manageable configuration format + compared to a "full" .config file, especially when configurations files + are merged or edited by hand. + + See the Kconfig.__init__() docstring for raised exceptions + (OSError/IOError). KconfigError is never raised here. + + 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. + + Returns a string with a message saying which file got saved. This is + meant to reduce boilerplate in tools, which can do e.g. + print(kconf.write_min_config()). + """ + contents = self._min_config_contents(header) + if self._contents_eq(filename, contents): + return "No change to '{}'".format(filename) + + with self._open(filename, "w") as f: + f.write(contents) + + return "Minimal configuration saved to '{}'".format(filename) + + def _min_config_contents(self, header): + # write_min_config() helper. Returns the contents to write as a string, + # with 'header' at the beginning. + + chunks = [header] + add = chunks.append + + for sym in self.unique_defined_syms: + # Skip symbols that cannot be changed. Only check + # non-choice symbols, as selects don't affect choice + # symbols. + if not sym.choice and \ + sym.visibility <= expr_value(sym.rev_dep): + continue + + # Skip symbols whose value matches their default + if sym.str_value == sym._str_default(): + continue + + # Skip symbols that would be selected by default in a + # choice, unless the choice is optional or the symbol type + # isn't bool (it might be possible to set the choice mode + # to n or the symbol to m in those cases). + if sym.choice and \ + not sym.choice.is_optional and \ + sym.choice._selection_from_defaults() is sym and \ + sym.orig_type is BOOL and \ + sym.tri_value == 2: + continue + + add(sym.config_string) + + return "".join(chunks) + + def sync_deps(self, path): + """ + Creates or updates a directory structure that can be used to avoid + doing a full rebuild whenever the configuration is changed, mirroring + include/config/ in the kernel. - set_match = _set_re_match(line) - if set_match: - name, val = set_match.groups() + This function is intended to be called during each build, before + compiling source files that depend on configuration symbols. - if val.startswith('"'): - if len(val) < 2 or val[-1] != '"': - _parse_error(line, "malformed string literal", - line_feeder.filename, line_feeder.linenr) - # 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("\\\\", "\\") + See the Kconfig.__init__() docstring for raised exceptions + (OSError/IOError). KconfigError is never raised here. + path: + Path to directory + + sync_deps(path) does the following: + + 1. If the directory <path> does not exist, it is created. + + 2. If <path>/auto.conf exists, old symbol values are loaded from it, + which are then compared against the current symbol values. If a + symbol has changed value (would generate different output in + autoconf.h compared to before), the change is signaled by + touch'ing a file corresponding to the symbol. + + The first time sync_deps() is run on a directory, <path>/auto.conf + won't exist, and no old symbol values will be available. This + logically has the same effect as updating the entire + configuration. + + The path to a symbol's file is calculated from the symbol's name + by replacing all '_' with '/' and appending '.h'. For example, the + symbol FOO_BAR_BAZ gets the file <path>/foo/bar/baz.h, and FOO + gets the file <path>/foo.h. + + This scheme matches the C tools. The point is to avoid having a + single directory with a huge number of files, which the underlying + filesystem might not handle well. + + 3. A new auto.conf with the current symbol values is written, to keep + track of them for the next build. + + If auto.conf exists and its contents is identical to what would + get written out, it is left untouched. This avoids updating file + metadata like the modification time and possibly triggering + redundant work in build tools. + + + The last piece of the puzzle is knowing what symbols each source file + depends on. Knowing that, dependencies can be added from source files + to the files corresponding to the symbols they depends on. The source + file will then get recompiled (only) when the symbol value changes + (provided sync_deps() is run first during each build). + + The tool in the kernel that extracts symbol dependencies from source + files is scripts/basic/fixdep.c. Missing symbol files also correspond + to "not changed", which fixdep deals with by using the $(wildcard) Make + function when adding symbol prerequisites to source files. + + In case you need a different scheme for your project, the sync_deps() + implementation can be used as a template. + """ + if not exists(path): + os.mkdir(path, 0o755) + + # Load old values from auto.conf, if any + self._load_old_vals(path) + + for sym in self.unique_defined_syms: + # _write_to_conf is determined when the value is calculated. This + # is a hidden function call due to property magic. + # + # Note: In client code, you can check if sym.config_string is empty + # instead, to avoid accessing the internal _write_to_conf variable + # (though it's likely to keep working). + val = sym.str_value + + # n tristate values do not get written to auto.conf and autoconf.h, + # making a missing symbol logically equivalent to n + + if sym._write_to_conf: + if sym._old_val is None and \ + sym.orig_type in _BOOL_TRISTATE and \ + val == "n": + # No old value (the symbol was missing or n), new value n. + # No change. + continue + + if val == sym._old_val: + # New value matches old. No change. + continue + + elif sym._old_val is None: + # The symbol wouldn't appear in autoconf.h (because + # _write_to_conf is false), and it wouldn't have appeared in + # autoconf.h previously either (because it didn't appear in + # auto.conf). No change. + continue + + # 'sym' has a new value. Flag it. + _touch_dep_file(path, sym.name) + + # Remember the current values as the "new old" values. + # + # This call could go anywhere after the call to _load_old_vals(), but + # putting it last means _sync_deps() can be safely rerun if it fails + # before this point. + self._write_old_vals(path) + + def _load_old_vals(self, path): + # Loads old symbol values from auto.conf into a dedicated + # Symbol._old_val field. Mirrors load_config(). + # + # The extra field could be avoided with some trickery involving dumping + # symbol values and restoring them later, but this is simpler and + # faster. The C tools also use a dedicated field for this purpose. + + for sym in self.unique_defined_syms: + sym._old_val = None + + try: + auto_conf = self._open(join(path, "auto.conf"), "r") + except EnvironmentError as e: + if e.errno == errno.ENOENT: + # No old values + return + raise + + with auto_conf as f: + for line in f: + match = self._set_match(line) + if not match: + # We only expect CONFIG_FOO=... (and possibly a header + # comment) in auto.conf + continue + + name, val = match.groups() if name in self.syms: sym = self.syms[name] - if sym.user_val is not None: - warn_override(line_feeder.filename, line_feeder.linenr, - name, sym.user_val, val) - - if sym.is_choice_sym: - user_mode = sym.parent.user_mode - if user_mode is not None and user_mode != val: - self._warn("assignment to {0} changes mode of " - 'containing choice from "{1}" to "{2}".' - .format(name, val, user_mode), - line_feeder.filename, - line_feeder.linenr) - - sym._set_user_value_no_invalidate(val, True) + + if sym.orig_type is STRING: + match = _conf_string_match(val) + if not match: + continue + val = unescape(match.group(1)) + + self.syms[name]._old_val = val else: - if self.print_undef_assign: - _stderr_msg('note: attempt to assign the value "{0}" ' - "to the undefined symbol {1}." - .format(val, name), - line_feeder.filename, line_feeder.linenr) + # Flag that the symbol no longer exists, in + # case something still depends on it + _touch_dep_file(path, name) + + def _write_old_vals(self, path): + # Helper for writing auto.conf. Basically just a simplified + # write_config() that doesn't write any comments (including + # '# CONFIG_FOO is not set' comments). The format matches the C + # implementation, though the ordering is arbitrary there (depends on + # the hash table implementation). + # + # A separate helper function is neater than complicating write_config() + # by passing a flag to it, plus we only need to look at symbols here. + + self._write_if_changed( + os.path.join(path, "auto.conf"), + self._old_vals_contents()) + + def _old_vals_contents(self): + # _write_old_vals() helper. Returns the contents to write as a string. + + # Temporary list instead of generator makes this a bit faster + return "".join([ + sym.config_string for sym in self.unique_defined_syms + if not (sym.orig_type in _BOOL_TRISTATE and not sym.tri_value) + ]) + + def node_iter(self, unique_syms=False): + """ + Returns a generator for iterating through all MenuNode's in the Kconfig + tree. The iteration is done in Kconfig definition order (each node is + visited before its children, and the children of a node are visited + before the next node). + + The Kconfig.top_node menu node is skipped. It contains an implicit menu + that holds the top-level items. + + As an example, the following code will produce a list equal to + Kconfig.defined_syms: + + defined_syms = [node.item for node in kconf.node_iter() + if isinstance(node.item, Symbol)] + + unique_syms (default: False): + If True, only the first MenuNode will be included for symbols defined + in multiple locations. + + Using kconf.node_iter(True) in the example above would give a list + equal to unique_defined_syms. + """ + if unique_syms: + for sym in self.unique_defined_syms: + sym._visited = False + + node = self.top_node + while 1: + # Jump to the next node with an iterative tree walk + if node.list: + node = node.list + elif node.next: + node = node.next else: - unset_match = _unset_re_match(line) - if unset_match: - name = unset_match.group(1) - if name in self.syms: - sym = self.syms[name] - if sym.user_val is not None: - warn_override(line_feeder.filename, - line_feeder.linenr, - name, sym.user_val, "n") - - sym._set_user_value_no_invalidate("n", True) - - def write_config(self, filename, header=None): - """Writes out symbol values in the familiar .config format. - - Kconfiglib makes sure the format matches what the C implementation - would generate, down to whitespace. This eases testing. - - filename: The filename under which to save the configuration. - - header (default: None): A textual header that will appear at the - beginning of the file, with each line commented out automatically. - None means no header.""" - - for sym in self.syms_iter(): - sym.already_written = False - - with open(filename, "w") as f: - # Write header - if header is not None: - f.write(_comment(header) + "\n") - - # Build and write configuration - conf_strings = [] - _make_block_conf(self.top_block, conf_strings.append) - f.write("\n".join(conf_strings) + "\n") - - def eval(self, s): - """Returns the value of the expression 's' -- where 's' is represented - as a string -- in the context of the configuration. Raises - Kconfig_Syntax_Error if syntax errors are detected in 's'. - - For example, if FOO and BAR are tristate symbols at least one of which - has the value "y", then config.eval("y && (FOO || BAR)") => "y" - - This function always yields a tristate value. To get the value of - non-bool, non-tristate symbols, use Symbol.get_value(). - - The result of this function is consistent with how evaluation works for - conditional expressions in the configuration as well as in the C - implementation. "m" and m are rewritten as '"m" && MODULES' and 'm && - MODULES', respectively, and a result of "m" will get promoted to "y" if - we're running without modules. - - Syntax checking is somewhat lax, partly to be compatible with lax - parsing in the C implementation.""" - return self._eval_expr(self._parse_expr(self._tokenize(s, True), # Feed - None, # Current symbol/choice - s)) # line - - def unset_user_values(self): - """Resets the values of all symbols, as if Config.load_config() or - Symbol.set_user_value() had never been called.""" - for sym in self.syms_iter(): - sym._unset_user_value_no_recursive_invalidate() - - def set_print_warnings(self, print_warnings): - """Determines whether warnings related to this configuration (for - things like attempting to assign illegal values to symbols with - Symbol.set_user_value()) should be printed to stderr. - - print_warnings: True if warnings should be printed.""" - self.print_warnings = print_warnings - - def set_print_undef_assign(self, print_undef_assign): - """Determines whether informational messages related to assignments to - undefined symbols should be printed to stderr for this configuration. - - print_undef_assign: If True, such messages will be printed.""" - self.print_undef_assign = print_undef_assign + while node.parent: + node = node.parent + if node.next: + node = node.next + break + else: + # No more nodes + return - def __str__(self): - """Returns a string containing various information about the Config.""" - return _lines("Configuration", - "File : " + - self.filename, - "Base directory : " + - self.base_dir, - "Value of $ARCH at creation time : " + - ("(not set)" if self.arch is None else self.arch), - "Value of $SRCARCH at creation time : " + - ("(not set)" if self.srcarch is None else - self.srcarch), - "Source tree (derived from $srctree;", - "defaults to '.' if $srctree isn't set) : " + - self.srctree, - "Most recently loaded .config : " + - ("(no .config loaded)" - if self.config_filename is None else - self.config_filename), - "Print warnings : " + - BOOL_STR[self.print_warnings], - "Print assignments to undefined symbols : " + - BOOL_STR[self.print_undef_assign]) + if unique_syms and node.item.__class__ is Symbol: + if node.item._visited: + continue + node.item._visited = True + + yield node + + def eval_string(self, s): + """ + Returns the tristate value of the expression 's', represented as 0, 1, + and 2 for n, m, and y, respectively. Raises KconfigError on syntax + errors. 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 eval_string("y && (FOO || BAR)") returns + 2 (y). + + 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 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. + """ + # 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._tokens = self._tokenize("if " + s) + # Strip "if " to avoid giving confusing error messages + self._line = s + self._tokens_i = 1 # Skip the 'if' token + + return expr_value(self._expect_expr_and_eol()) + + def unset_values(self): + """ + Removes any user values from all symbols, as if Kconfig.load_config() + or Symbol.set_value() had never been called. + """ + self._warn_assign_no_prompt = False + try: + # 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.unique_defined_syms: + sym.unset_value() + + for choice in self.unique_choices: + choice.unset_value() + finally: + self._warn_assign_no_prompt = True + + def enable_warnings(self): + """ + Do 'Kconfig.warn = True' instead. Maintained for backwards + compatibility. + """ + self.warn = True + + def disable_warnings(self): + """ + Do 'Kconfig.warn = False' instead. Maintained for backwards + compatibility. + """ + self.warn = False + + def enable_stderr_warnings(self): + """ + Do 'Kconfig.warn_to_stderr = True' instead. Maintained for backwards + compatibility. + """ + self.warn_to_stderr = True + + def disable_stderr_warnings(self): + """ + Do 'Kconfig.warn_to_stderr = False' instead. Maintained for backwards + compatibility. + """ + self.warn_to_stderr = False + + def enable_undef_warnings(self): + """ + Do 'Kconfig.warn_assign_undef = True' instead. Maintained for backwards + compatibility. + """ + self.warn_assign_undef = True + + def disable_undef_warnings(self): + """ + Do 'Kconfig.warn_assign_undef = False' instead. Maintained for + backwards compatibility. + """ + self.warn_assign_undef = False + + def enable_override_warnings(self): + """ + Do 'Kconfig.warn_assign_override = True' instead. Maintained for + backwards compatibility. + """ + self.warn_assign_override = True + + def disable_override_warnings(self): + """ + Do 'Kconfig.warn_assign_override = False' instead. Maintained for + backwards compatibility. + """ + self.warn_assign_override = False + + def enable_redun_warnings(self): + """ + Do 'Kconfig.warn_assign_redun = True' instead. Maintained for backwards + compatibility. + """ + self.warn_assign_redun = True + + def disable_redun_warnings(self): + """ + Do 'Kconfig.warn_assign_redun = False' instead. Maintained for + backwards compatibility. + """ + self.warn_assign_redun = False + + def __repr__(self): + """ + Returns a string with information about the Kconfig object when it is + evaluated on e.g. the interactive Python prompt. + """ + def status(flag): + return "enabled" if flag else "disabled" + + return "<{}>".format(", ".join(( + "configuration with {} symbols".format(len(self.syms)), + 'main menu prompt "{}"'.format(self.mainmenu_text), + "srctree is current directory" if not self.srctree else + 'srctree "{}"'.format(self.srctree), + 'config symbol prefix "{}"'.format(self.config_prefix), + "warnings " + status(self.warn), + "printing of warnings to stderr " + status(self.warn_to_stderr), + "undef. symbol assignment warnings " + + status(self.warn_assign_undef), + "overriding symbol assignment warnings " + + status(self.warn_assign_override), + "redundant symbol assignment warnings " + + status(self.warn_assign_redun) + ))) # # Private methods # + # - # Kconfig parsing + # File reading # - def _parse_file(self, filename, parent, deps, visible_if_deps, block): - """Parses the Kconfig file 'filename'. Appends the Items in the file - (and any file it sources) to the list passed in the 'block' parameter. - See _parse_block() for the meaning of the parameters.""" - self._parse_block(_FileFeed(filename), None, parent, deps, - visible_if_deps, block) + def _open_config(self, filename): + # Opens a .config file. First tries to open 'filename', then + # '$srctree/filename' if $srctree was set when the configuration was + # loaded. + + try: + return self._open(filename, "r") + except EnvironmentError as e: + # This will try opening the same file twice if $srctree is unset, + # but it's not a big deal + try: + return self._open(join(self.srctree, filename), "r") + except EnvironmentError as e2: + # This is needed for Python 3, because e2 is deleted after + # the try block: + # + # https://docs.python.org/3/reference/compound_stmts.html#the-try-statement + e = e2 + + raise _KconfigIOError( + e, "Could not open '{}' ({}: {}). Check that the $srctree " + "environment variable ({}) is set correctly." + .format(filename, errno.errorcode[e.errno], e.strerror, + "set to '{}'".format(self.srctree) if self.srctree + else "unset or blank")) + + def _enter_file(self, filename): + # Jumps to the beginning of a sourced Kconfig file, saving the previous + # position and file object. + # + # filename: + # Absolute path to file + + # Path relative to $srctree, stored in e.g. self.filename (which makes + # it indirectly show up in MenuNode.filename). Equals 'filename' for + # absolute paths passed to 'source'. + if filename.startswith(self._srctree_prefix): + # Relative path (or a redundant absolute path to within $srctree, + # but it's probably fine to reduce those too) + rel_filename = filename[len(self._srctree_prefix):] + else: + # Absolute path + rel_filename = filename - def _parse_block(self, line_feeder, end_marker, parent, deps, - visible_if_deps, block): - """Parses a block, which is the contents of either a file or an if, - menu, or choice statement. Appends the Items to the list passed in the - 'block' parameter. + self.kconfig_filenames.append(rel_filename) - line_feeder: A _FileFeed instance feeding lines from a file. The - Kconfig language is line-based in practice. + # The parent Kconfig files are represented as a list of + # (<include path>, <Python 'file' object for Kconfig file>) tuples. + # + # <include path> is immutable and holds a *tuple* of + # (<filename>, <linenr>) tuples, giving the locations of the 'source' + # statements in the parent Kconfig files. The current include path is + # also available in Kconfig._include_path. + # + # The point of this redundant setup is to allow Kconfig._include_path + # to be assigned directly to MenuNode.include_path without having to + # copy it, sharing it wherever possible. + + # Save include path and 'file' object (via its 'readline' function) + # before entering the file + self._filestack.append((self._include_path, self._readline)) + + # _include_path is a tuple, so this rebinds the variable instead of + # doing in-place modification + self._include_path += ((self.filename, self.linenr),) + + # Check for recursive 'source' + for name, _ in self._include_path: + if name == rel_filename: + raise KconfigError( + "\n{}:{}: recursive 'source' of '{}' detected. Check that " + "environment variables are set correctly.\n" + "Include path:\n{}" + .format(self.filename, self.linenr, rel_filename, + "\n".join("{}:{}".format(name, linenr) + for name, linenr in self._include_path))) + + try: + self._readline = self._open(filename, "r").readline + except EnvironmentError as e: + # We already know that the file exists + raise _KconfigIOError( + e, "{}:{}: Could not open '{}' (in '{}') ({}: {})" + .format(self.filename, self.linenr, filename, + self._line.strip(), + errno.errorcode[e.errno], e.strerror)) + + self.filename = rel_filename + self.linenr = 0 - end_marker: The token that ends the block, e.g. T_ENDIF ("endif") for - ifs. None for files. + def _leave_file(self): + # Returns from a Kconfig file to the file that sourced it. See + # _enter_file(). + + # Restore location from parent Kconfig file + self.filename, self.linenr = self._include_path[-1] + # Restore include path and 'file' object + self._readline.__self__.close() # __self__ fetches the 'file' object + self._include_path, self._readline = self._filestack.pop() + + def _next_line(self): + # Fetches and tokenizes the next line from the current Kconfig file. + # Returns False at EOF and True otherwise. + + # We might already have tokens from parsing a line and discovering that + # it's part of a different construct + if self._reuse_tokens: + self._reuse_tokens = False + # self._tokens_i is known to be 1 here, because _parse_properties() + # leaves it like that when it can't recognize a line (or parses + # a help text) + return True - parent: The enclosing menu or choice, or None if we're at the top - level. + # readline() returns '' over and over at EOF, which we rely on for help + # texts at the end of files (see _line_after_help()) + line = self._readline() + if not line: + return False + self.linenr += 1 - deps: Dependencies from enclosing menus, choices and ifs. + # Handle line joining + while line.endswith("\\\n"): + line = line[:-2] + self._readline() + self.linenr += 1 + + self._tokens = self._tokenize(line) + # Initialize to 1 instead of 0 to factor out code from _parse_block() + # and _parse_properties(). They immediately fetch self._tokens[0]. + self._tokens_i = 1 + + return True + + def _line_after_help(self, line): + # Tokenizes a line after a help text. This case is special in that the + # line has already been fetched (to discover that it isn't part of the + # help text). + # + # An earlier version used a _saved_line variable instead that was + # checked in _next_line(). This special-casing gets rid of it and makes + # _reuse_tokens alone sufficient to handle unget. + + # Handle line joining + while line.endswith("\\\n"): + line = line[:-2] + self._readline() + self.linenr += 1 + + self._tokens = self._tokenize(line) + self._reuse_tokens = True - visible_if_deps (default: None): 'visible if' dependencies from - enclosing menus. + def _write_if_changed(self, filename, contents): + # Writes 'contents' into 'filename', but only if it differs from the + # current contents of the file. + # + # Another variant would be write a temporary file on the same + # filesystem, compare the files, and rename() the temporary file if it + # differs, but it breaks stuff like write_config("/dev/null"), which is + # used out there to force evaluation-related warnings to be generated. + # This simple version is pretty failsafe and portable. + + if not self._contents_eq(filename, contents): + with self._open(filename, "w") as f: + f.write(contents) + + def _contents_eq(self, filename, contents): + # Returns True if the contents of 'filename' is 'contents' (a string), + # and False otherwise (including if 'filename' can't be opened/read) + + try: + with self._open(filename, "r") as f: + # Robust re. things like encoding and line endings (mmap() + # trickery isn't) + return f.read(len(contents) + 1) == contents + except EnvironmentError: + # If the error here would prevent writing the file as well, we'll + # notice it later + return False + + # + # 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_kconfigs' 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.kconfig = 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.kconfig = 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, s): + # Parses 's', returning a None-terminated list of 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 the biggest + # hotspot during parsing. + # + # It might be possible to rewrite this to 'yield' tokens instead, + # working across multiple lines. Lookback and compatibility with old + # janky versions of the C tools complicate things though. + + self._line = s # Used for error reporting + + # Initial token on the line + match = _command_match(s) + if not match: + if s.isspace() or s.lstrip().startswith("#"): + return (None,) + self._parse_error("unknown token at start of line") + + # Tricky implementation detail: While parsing a token, 'token' refers + # to the previous token. See _STRING_LEX for why this is needed. + token = _get_keyword(match.group(1)) + if not token: + # Backwards compatibility with old versions of the C tools, which + # (accidentally) accepted stuff like "--help--" and "-help---". + # This was fixed in the C tools by commit c2264564 ("kconfig: warn + # of unhandled characters in Kconfig commands"), committed in July + # 2015, but it seems people still run Kconfiglib on older kernels. + if s.strip(" \t\n-") == "help": + return (_T_HELP, None) + + # If the first token is not a keyword (and not a weird help token), + # we have a preprocessor variable assignment (or a bare macro on a + # line) + self._parse_assignment(s) + return (None,) + + tokens = [token] + # The current index in the string being tokenized + i = match.end() + + # Main tokenization loop (for tokens past the first one) + while i < len(s): + # Test for an identifier/keyword first. This is the most common + # case. + match = _id_keyword_match(s, i) + if match: + # We have an identifier or keyword + + # Check what it is. lookup_sym() will take care of allocating + # new symbols for us the first time we see them. Note that + # 'token' still refers to the previous token. + + name = match.group(1) + keyword = _get_keyword(name) + if keyword: + # It's a keyword + token = keyword + # Jump past it + i = match.end() + + elif token not in _STRING_LEX: + # It's a non-const symbol, except we translate n, m, and y + # into the corresponding constant symbols, like the C + # implementation + + if "$" in name: + # Macro expansion within symbol name + name, s, i = self._expand_name(s, i) + else: + i = match.end() + + token = self.const_syms[name] if name in STR_TO_TRI else \ + self._lookup_sym(name) + + else: + # It's a case of missing quotes. For example, the + # following is accepted: + # + # menu unquoted_title + # + # config A + # tristate unquoted_prompt + # + # endmenu + # + # Named choices ('choice FOO') also end up here. + + if token is not _T_CHOICE: + self._warn("style: quotes recommended around '{}' in '{}'" + .format(name, self._line.strip()), + self.filename, self.linenr) + + token = name + i = match.end() + + else: + # Neither a keyword nor a non-const symbol + + # We always strip whitespace after tokens, so it is safe to + # assume that s[i] is the start of a token here. + c = s[i] + + if c in "\"'": + if "$" not in s and "\\" not in s: + # Fast path for lines without $ and \. Find the + # matching quote. + end_i = s.find(c, i + 1) + 1 + if not end_i: + self._parse_error("unterminated string") + val = s[i + 1:end_i - 1] + i = end_i + else: + # Slow path + s, end_i = self._expand_str(s, i) + + # os.path.expandvars() and the $UNAME_RELEASE replace() + # is a backwards compatibility hack, which should be + # reasonably safe as expandvars() leaves references to + # undefined env. vars. as is. + # + # The preprocessor functionality changed how + # environment variables are referenced, to $(FOO). + val = expandvars(s[i + 1:end_i - 1] + .replace("$UNAME_RELEASE", + _UNAME_RELEASE)) + + i = end_i + + # This is the only place where we don't survive with a + # single token of lookback: 'option env="FOO"' does not + # refer to a constant symbol named "FOO". + token = \ + val if token in _STRING_LEX or tokens[0] is _T_OPTION \ + else self._lookup_const_sym(val) + + elif s.startswith("&&", i): + token = _T_AND + i += 2 + + elif s.startswith("||", i): + token = _T_OR + i += 2 + + elif c == "=": + token = _T_EQUAL + i += 1 + + elif s.startswith("!=", i): + token = _T_UNEQUAL + i += 2 + + elif c == "!": + token = _T_NOT + i += 1 + + elif c == "(": + token = _T_OPEN_PAREN + i += 1 + + elif c == ")": + token = _T_CLOSE_PAREN + i += 1 + + elif c == "#": + break + + + # Very rare + + elif s.startswith("<=", i): + token = _T_LESS_EQUAL + i += 2 + + elif c == "<": + token = _T_LESS + i += 1 + + elif s.startswith(">=", i): + token = _T_GREATER_EQUAL + i += 2 + + elif c == ">": + token = _T_GREATER + i += 1 + + + else: + self._parse_error("unknown tokens in line") + + + # Skip trailing whitespace + while i < len(s) and s[i].isspace(): + i += 1 + + + # Add the token + tokens.append(token) + + # None-terminating the token list makes token fetching simpler/faster + tokens.append(None) + + return tokens + + # Helpers for syntax checking and token fetching. See the + # 'Intro to expressions' section for what a constant symbol is. + # + # More of these could be added, but the single-use cases are inlined as an + # optimization. + + def _expect_sym(self): + token = self._tokens[self._tokens_i] + self._tokens_i += 1 + + if token.__class__ is not Symbol: + self._parse_error("expected symbol") + + return token + + def _expect_nonconst_sym(self): + # Used for 'select' and 'imply' only. We know the token indices. + + token = self._tokens[1] + self._tokens_i = 2 + + if token.__class__ is not Symbol or token.is_constant: + self._parse_error("expected nonconstant symbol") + + return token + + def _expect_str_and_eol(self): + token = self._tokens[self._tokens_i] + self._tokens_i += 1 + + if token.__class__ is not str: + self._parse_error("expected string") + + if self._tokens[self._tokens_i] is not None: + self._trailing_tokens_error() + + return token + + def _expect_expr_and_eol(self): + expr = self._parse_expr(True) + + if self._tokens[self._tokens_i] is not None: + self._trailing_tokens_error() + + return expr + + def _check_token(self, token): + # If the next token is 'token', removes it and returns True + + if self._tokens[self._tokens_i] is token: + self._tokens_i += 1 + return True + return False + + # + # Preprocessor logic + # - block: The list to add items to.""" + def _parse_assignment(self, s): + # Parses a preprocessor variable assignment, registering the variable + # if it doesn't already exist. Also takes care of bare macros on lines + # (which are allowed, and can be useful for their side effects). + # Expand any macros in the left-hand side of the assignment (the + # variable name) + s = s.lstrip() + i = 0 while 1: - # Do we already have a tokenized line that we determined wasn't - # part of whatever we were parsing earlier? See comment in - # Config.__init__(). - if self.end_line is not None: - line = self.end_line - tokens = self.end_line_tokens - tokens.unget_all() - - self.end_line = None - self.end_line_tokens = None + i = _assignment_lhs_fragment_match(s, i).end() + if s.startswith("$(", i): + s, i = self._expand_macro(s, i, ()) else: - line = line_feeder.get_next() - if line is None: - if end_marker is not None: - raise Kconfig_Syntax_Error("Unexpected end of file {0}" - .format(line_feeder.filename)) - return + break - tokens = self._tokenize(line, False, line_feeder.filename, - line_feeder.linenr) + if s.isspace(): + # We also accept a bare macro on a line (e.g. + # $(warning-if,$(foo),ops)), provided it expands to a blank string + return - t0 = tokens.get_next() - if t0 is None: - continue + # Assigned variable + name = s[:i] - # Cases are ordered roughly by frequency, which speeds things up a - # bit - - if t0 == T_CONFIG or t0 == 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. - sym = tokens.get_next() - - # Symbols defined in multiple places get the parent of their - # first definition. However, for symbols whose parents are - # choice statements, the choice statement takes precedence. - if not sym.is_defined_ or isinstance(parent, Choice): - sym.parent = parent - sym.is_defined_ = True - - self._parse_properties(line_feeder, sym, deps, visible_if_deps) - - self.kconfig_syms.append(sym) - block.append(sym) - - elif t0 == T_SOURCE: - kconfig_file = tokens.get_next() - exp_kconfig_file = self._expand_sym_refs(kconfig_file) - f = os.path.join(self.base_dir, exp_kconfig_file) - if not os.path.exists(f): - raise IOError('{0}:{1}: sourced file "{2}" (expands to ' - '"{3}") not found. Perhaps base_dir ' - '(argument to Config.__init__(), currently ' - '"{4}") is set to the wrong value.' - .format(line_feeder.filename, - line_feeder.linenr, - kconfig_file, exp_kconfig_file, - self.base_dir)) - # Add items to the same block - self._parse_file(f, parent, deps, visible_if_deps, block) - - elif t0 == end_marker: - # We have reached the end of the block - return - elif t0 == T_IF: - # If statements are treated as syntactic sugar for adding - # dependencies to enclosed items and do not have an explicit - # object representation. - - dep_expr = self._parse_expr(tokens, None, line, - line_feeder.filename, - line_feeder.linenr) - # Add items to the same block - self._parse_block(line_feeder, T_ENDIF, parent, - _make_and(dep_expr, deps), - visible_if_deps, block) - - elif t0 == T_COMMENT: - comment = Comment() - comment.config = self - comment.parent = parent - comment.filename = line_feeder.filename - comment.linenr = line_feeder.linenr - comment.text = tokens.get_next() - - self._parse_properties(line_feeder, comment, deps, - visible_if_deps) - - self.comments.append(comment) - block.append(comment) - - elif t0 == T_MENU: - menu = Menu() - menu.config = self - menu.parent = parent - menu.filename = line_feeder.filename - menu.linenr = line_feeder.linenr - menu.title = tokens.get_next() - - self._parse_properties(line_feeder, menu, deps, - visible_if_deps) - - # This needs to go before _parse_block() so that we get the - # proper menu ordering in the case of nested functions - self.menus.append(menu) - # Parse contents and put Items in menu.block - self._parse_block(line_feeder, T_ENDMENU, menu, menu.dep_expr, - _make_and(visible_if_deps, - menu.visible_if_expr), - menu.block) - - block.append(menu) - - elif t0 == T_CHOICE: - name = tokens.get_next() - if name is None: + # Extract assignment operator (=, :=, or +=) and value + rhs_match = _assignment_rhs_match(s, i) + if not rhs_match: + self._parse_error("syntax error") + + op, val = rhs_match.groups() + + + if name in self.variables: + # Already seen variable + var = self.variables[name] + else: + # New variable + var = Variable() + var.kconfig = self + var.name = name + var._n_expansions = 0 + self.variables[name] = var + + # += acts like = on undefined variables (defines a recursive + # variable) + if op == "+=": + op = "=" + + if op == "=": + var.is_recursive = True + var.value = val + elif op == ":=": + var.is_recursive = False + var.value = self._expand_whole(val, ()) + else: # op == "+=" + # += does immediate expansion if the variable was last set + # with := + var.value += " " + (val if var.is_recursive else + self._expand_whole(val, ())) + + def _expand_whole(self, s, args): + # Expands preprocessor macros in all of 's'. Used whenever we don't + # have to worry about delimiters. See _expand_macro() re. the 'args' + # parameter. + # + # Returns the expanded string. + + i = 0 + while 1: + i = s.find("$(", i) + if i == -1: + break + s, i = self._expand_macro(s, i, args) + return s + + def _expand_name(self, s, i): + # Expands a symbol name starting at index 'i' in 's'. + # + # Returns the expanded name, the expanded 's' (including the part + # before the name), and the index of the first character in the next + # token after the name. + + s, end_i = self._expand_name_iter(s, i) + name = s[i:end_i] + # isspace() is False for empty strings + if not name.strip(): + # Avoid creating a Kconfig symbol with a blank name. It's almost + # guaranteed to be an error. + self._parse_error("macro expanded to blank string") + + # Skip trailing whitespace + while end_i < len(s) and s[end_i].isspace(): + end_i += 1 + + return name, s, end_i + + def _expand_name_iter(self, s, i): + # Expands a symbol name starting at index 'i' in 's'. + # + # Returns the expanded 's' (including the part before the name) and the + # index of the first character after the expanded name in 's'. + + while 1: + match = _name_special_search(s, i) + + if match.group() == "$(": + s, i = self._expand_macro(s, match.start(), ()) + else: + return (s, match.start()) + + def _expand_str(self, s, i): + # Expands a quoted string starting at index 'i' in 's'. Handles both + # backslash escapes and macro expansion. + # + # Returns the expanded 's' (including the part before the string) and + # the index of the first character after the expanded string in 's'. + + quote = s[i] + i += 1 # Skip over initial "/' + while 1: + match = _string_special_search(s, i) + if not match: + self._parse_error("unterminated string") + + + if match.group() == quote: + # Found the end of the string + return (s, match.end()) + + elif match.group() == "\\": + # Replace '\x' with 'x'. 'i' ends up pointing to the character + # after 'x', which allows macros to be canceled with '\$(foo)'. + i = match.end() + s = s[:match.start()] + s[i:] + + elif match.group() == "$(": + # A macro call within the string + s, i = self._expand_macro(s, match.start(), ()) + + else: + # A ' quote within " quotes or vice versa + i += 1 + + def _expand_macro(self, s, i, args): + # Expands a macro starting at index 'i' in 's'. If this macro resulted + # from the expansion of another macro, 'args' holds the arguments + # passed to that macro. + # + # Returns the expanded 's' (including the part before the macro) and + # the index of the first character after the expanded macro in 's'. + + start = i + i += 2 # Skip over "$(" + + # Start of current macro argument + arg_start = i + + # Arguments of this macro call + new_args = [] + + while 1: + match = _macro_special_search(s, i) + if not match: + self._parse_error("missing end parenthesis in macro expansion") + + + if match.group() == ")": + # Found the end of the macro + + new_args.append(s[arg_start:match.start()]) + + prefix = s[:start] + + # $(1) is replaced by the first argument to the function, etc., + # provided at least that many arguments were passed + + try: + # Does the macro look like an integer, with a corresponding + # argument? If so, expand it to the value of the argument. + prefix += args[int(new_args[0])] + except (ValueError, IndexError): + # Regular variables are just functions without arguments, + # and also go through the function value path + prefix += self._fn_val(new_args) + + return (prefix + s[match.end():], + len(prefix)) + + elif match.group() == ",": + # Found the end of a macro argument + new_args.append(s[arg_start:match.start()]) + arg_start = i = match.end() + + else: # match.group() == "$(" + # A nested macro call within the macro + s, i = self._expand_macro(s, match.start(), args) + + def _fn_val(self, args): + # Returns the result of calling the function args[0] with the arguments + # args[1..len(args)-1]. Plain variables are treated as functions + # without arguments. + + fn = args[0] + + if fn in self.variables: + var = self.variables[fn] + + if len(args) == 1: + # Plain variable + if var._n_expansions: + self._parse_error("Preprocessor variable {} recursively " + "references itself".format(var.name)) + elif var._n_expansions > 100: + # Allow functions to call themselves, but guess that functions + # that are overly recursive are stuck + self._parse_error("Preprocessor function {} seems stuck " + "in infinite recursion".format(var.name)) + + var._n_expansions += 1 + res = self._expand_whole(self.variables[fn].value, args) + var._n_expansions -= 1 + return res + + if fn in self._functions: + # Built-in or user-defined function + + py_fn, min_arg, max_arg = self._functions[fn] + + if len(args) - 1 < min_arg or \ + (max_arg is not None and len(args) - 1 > max_arg): + + if min_arg == max_arg: + expected_args = min_arg + elif max_arg is None: + expected_args = "{} or more".format(min_arg) + else: + expected_args = "{}-{}".format(min_arg, max_arg) + + raise KconfigError("{}:{}: bad number of arguments in call " + "to {}, expected {}, got {}" + .format(self.filename, self.linenr, fn, + expected_args, len(args) - 1)) + + return py_fn(self, *args) + + # Environment variables are tried last + if fn in os.environ: + self.env_vars.add(fn) + return os.environ[fn] + + return "" + + # + # Parsing + # + + 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) + + def _parse_block(self, end_token, parent, prev): + # Parses a block, which is the contents of either a file or an if, + # menu, or choice statement. + # + # end_token: + # The token that ends the block, e.g. _T_ENDIF ("endif") for ifs. + # None for files. + # + # parent: + # The parent menu node, corresponding to a menu, Choice, or 'if'. + # 'if's are flattened after parsing. + # + # prev: + # The previous menu node. New nodes will be added after this one (by + # modifying their 'next' pointer). + # + # 'prev' 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' if the block is + # empty). This allows chaining. + + while self._next_line(): + t0 = self._tokens[0] + + if t0 is _T_CONFIG or t0 is _T_MENUCONFIG: + # The tokenizer allocates Symbol objects for us + sym = self._tokens[1] + + if sym.__class__ is not Symbol or sym.is_constant: + self._parse_error("missing or bad symbol name") + + if self._tokens[2] is not None: + self._trailing_tokens_error() + + self.defined_syms.append(sym) + + node = MenuNode() + node.kconfig = self + node.item = sym + node.is_menuconfig = (t0 is _T_MENUCONFIG) + node.prompt = node.help = node.list = None + node.parent = parent + node.filename = self.filename + node.linenr = self.linenr + node.include_path = self._include_path + + sym.nodes.append(node) + + self._parse_properties(node) + + if node.is_menuconfig and not node.prompt: + self._warn("the menuconfig symbol {} has no prompt" + .format(_name_and_loc(sym))) + + # Equivalent to + # + # prev.next = node + # prev = node + # + # due to tricky Python semantics. The order matters. + prev.next = prev = node + + elif t0 is None: + # Blank line + continue + + elif t0 in _SOURCE_TOKENS: + pattern = self._expect_str_and_eol() + + if t0 in _REL_SOURCE_TOKENS: + # Relative source + pattern = join(dirname(self.filename), pattern) + + # - glob() doesn't support globbing relative to a directory, so + # we need to prepend $srctree to 'pattern'. Use join() + # instead of '+' so that an absolute path in 'pattern' is + # preserved. + # + # - Sort the glob results to ensure a consistent ordering of + # Kconfig symbols, which indirectly ensures a consistent + # ordering in e.g. .config files + filenames = sorted(iglob(join(self._srctree_prefix, pattern))) + + if not filenames and t0 in _OBL_SOURCE_TOKENS: + raise KconfigError( + "{}:{}: '{}' not found (in '{}'). Check that " + "environment variables are set correctly (e.g. " + "$srctree, which is {}). Also note that unset " + "environment variables expand to the empty string." + .format(self.filename, self.linenr, pattern, + self._line.strip(), + "set to '{}'".format(self.srctree) + if self.srctree else "unset or blank")) + + for filename in filenames: + self._enter_file(filename) + prev = self._parse_block(None, parent, prev) + self._leave_file() + + elif t0 is end_token: + # Reached the end of the block. Terminate the final node and + # return it. + + if self._tokens[1] is not None: + self._trailing_tokens_error() + + prev.next = None + return prev + + elif t0 is _T_IF: + node = MenuNode() + node.item = node.prompt = None + node.parent = parent + node.dep = self._expect_expr_and_eol() + + self._parse_block(_T_ENDIF, node, node) + node.list = node.next + + prev.next = prev = node + + elif t0 is _T_MENU: + node = MenuNode() + node.kconfig = self + node.item = t0 # _T_MENU == MENU + node.is_menuconfig = True + node.prompt = (self._expect_str_and_eol(), self.y) + node.visibility = self.y + node.parent = parent + node.filename = self.filename + node.linenr = self.linenr + node.include_path = self._include_path + + self.menus.append(node) + + self._parse_properties(node) + self._parse_block(_T_ENDMENU, node, node) + node.list = node.next + + prev.next = prev = node + + elif t0 is _T_COMMENT: + node = MenuNode() + node.kconfig = self + node.item = t0 # _T_COMMENT == COMMENT + node.is_menuconfig = False + node.prompt = (self._expect_str_and_eol(), self.y) + node.list = None + node.parent = parent + node.filename = self.filename + node.linenr = self.linenr + node.include_path = self._include_path + + self.comments.append(node) + + self._parse_properties(node) + + prev.next = prev = node + + elif t0 is _T_CHOICE: + if self._tokens[1] is None: choice = Choice() - self.choices.append(choice) + choice.direct_dep = self.n else: # Named choice + name = self._expect_str_and_eol() choice = self.named_choices.get(name) - if choice is None: + if not choice: choice = Choice() choice.name = name + choice.direct_dep = self.n self.named_choices[name] = choice - self.choices.append(choice) - choice.config = self - choice.parent = parent + self.choices.append(choice) - choice.def_locations.append((line_feeder.filename, - line_feeder.linenr)) + node = MenuNode() + node.kconfig = choice.kconfig = self + node.item = choice + node.is_menuconfig = True + node.prompt = node.help = None + node.parent = parent + node.filename = self.filename + node.linenr = self.linenr + node.include_path = self._include_path - self._parse_properties(line_feeder, choice, deps, - visible_if_deps) + choice.nodes.append(node) - # Parse contents and put Items in choice.block - self._parse_block(line_feeder, T_ENDCHOICE, choice, deps, - visible_if_deps, choice.block) + self._parse_properties(node) + self._parse_block(_T_ENDCHOICE, node, node) + node.list = node.next - choice._determine_actual_symbols() + prev.next = prev = 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: - for item in choice.actual_symbols: - if item.type != UNKNOWN: - choice.type = item.type - break + elif t0 is _T_MAINMENU: + self.top_node.prompt = (self._expect_str_and_eol(), self.y) - # Each choice item of UNKNOWN type gets the type of the choice - for item in choice.actual_symbols: - if item.type == UNKNOWN: - item.type = choice.type + else: + # A valid endchoice/endif/endmenu is caught by the 'end_token' + # check above + self._parse_error( + "no corresponding 'choice'" if t0 is _T_ENDCHOICE else + "no corresponding 'if'" if t0 is _T_ENDIF else + "no corresponding 'menu'" if t0 is _T_ENDMENU else + "unrecognized construct") + + # End of file reached. Terminate the final node and return it. + + if end_token: + raise KconfigError( + "expected '{}' at end of '{}'" + .format("endchoice" if end_token is _T_ENDCHOICE else + "endif" if end_token is _T_ENDIF else + "endmenu", + self.filename)) + + prev.next = None + return prev + + def _parse_cond(self): + # Parses an optional 'if <expr>' construct and returns the parsed + # <expr>, or self.y if the next token is not _T_IF + + expr = self._parse_expr(True) if self._check_token(_T_IF) else self.y + + if self._tokens[self._tokens_i] is not None: + self._trailing_tokens_error() + + return expr + + def _parse_properties(self, node): + # Parses and adds properties to the MenuNode 'node' (type, 'prompt', + # 'default's, etc.) Properties are later copied up to symbols and + # choices in a separate pass after parsing, in e.g. + # _add_props_to_sym(). + # + # An older version of this code added properties directly to symbols + # and choices instead of to their menu nodes (and handled dependency + # propagation simultaneously), but that loses information on where a + # property is added when a symbol or choice is defined in multiple + # locations. Some Kconfig configuration systems rely heavily on such + # symbols, and better docs can be generated by keeping track of where + # properties are added. + # + # node: + # The menu node we're parsing properties on - block.append(choice) + # Dependencies from 'depends on'. Will get propagated to the properties + # below. + node.dep = self.y - elif t0 == T_MAINMENU: - text = tokens.get_next() - if self.mainmenu_text is not None: - self._warn("overriding 'mainmenu' text. " - 'Old value: "{0}", new value: "{1}".' - .format(self.mainmenu_text, text), - line_feeder.filename, line_feeder.linenr) - self.mainmenu_text = text + while self._next_line(): + t0 = self._tokens[0] - else: - _parse_error(line, "unrecognized construct", - line_feeder.filename, line_feeder.linenr) - - def _parse_properties(self, line_feeder, stmt, deps, visible_if_deps): - """Parsing of properties for symbols, menus, choices, and comments. - Takes care of propagating dependencies from enclosing menus and ifs.""" - - def parse_val_and_cond(tokens, line, filename, linenr): - """Parses '<expr1> if <expr2>' constructs, where the 'if' part is - optional. Returns a tuple containing the parsed expressions, with - None as the second element if the 'if' part is missing.""" - return (self._parse_expr(tokens, stmt, line, filename, linenr, - False), - self._parse_expr(tokens, stmt, line, filename, linenr) - if tokens.check(T_IF) else None) - - # In case the symbol is defined in multiple locations, we need to - # remember what prompts, defaults, selects, and implies are new for - # this definition, as "depends on" should only apply to the local - # definition. - new_prompt = None - new_def_exprs = [] - new_selects = [] - new_implies = [] - - # Dependencies from 'depends on' statements - depends_on_expr = None + if t0 in _TYPE_TOKENS: + # Relies on '_T_BOOL is BOOL', etc., to save a conversion + self._set_type(node, t0) + if self._tokens[1] is not None: + self._parse_prompt(node) - while 1: - line = line_feeder.get_next() - if line is None: - break + elif t0 is _T_DEPENDS: + if not self._check_token(_T_ON): + self._parse_error("expected 'on' after 'depends'") - filename = line_feeder.filename - linenr = line_feeder.linenr + node.dep = self._make_and(node.dep, + self._expect_expr_and_eol()) - tokens = self._tokenize(line, False, filename, linenr) + elif t0 is _T_HELP: + self._parse_help(node) - t0 = tokens.get_next() - if t0 is None: + elif t0 is _T_SELECT: + if node.item.__class__ is not Symbol: + self._parse_error("only symbols can select") + + node.selects.append((self._expect_nonconst_sym(), + self._parse_cond())) + + elif t0 is None: + # Blank line continue - # Cases are ordered roughly by frequency, which speeds things up a - # bit + elif t0 is _T_DEFAULT: + node.defaults.append((self._parse_expr(False), + self._parse_cond())) - if t0 == T_DEPENDS: - if not tokens.check(T_ON): - _parse_error(line, 'expected "on" after "depends"', - filename, linenr) + elif t0 in _DEF_TOKEN_TO_TYPE: + self._set_type(node, _DEF_TOKEN_TO_TYPE[t0]) + node.defaults.append((self._parse_expr(False), + self._parse_cond())) - parsed_deps = self._parse_expr(tokens, stmt, line, filename, - linenr) + elif t0 is _T_PROMPT: + self._parse_prompt(node) - if isinstance(stmt, (Menu, Comment)): - stmt.orig_deps = _make_and(stmt.orig_deps, parsed_deps) - else: - depends_on_expr = _make_and(depends_on_expr, parsed_deps) - - elif t0 == T_HELP: - # Find first non-blank (not all-space) line and get its - # indentation - line = line_feeder.next_nonblank() - if line is None: - stmt.help = "" - break - indent = _indentation(line) - if indent == 0: - # If the first non-empty lines has zero indent, there is no - # help text - stmt.help = "" - line_feeder.unget() - break + elif t0 is _T_RANGE: + node.ranges.append((self._expect_sym(), self._expect_sym(), + self._parse_cond())) - # The help text goes on till the first non-empty line with less - # indent - help_lines = [_deindent(line, indent)] - while 1: - line = line_feeder.get_next() - if line is None or \ - (not line.isspace() and _indentation(line) < indent): - stmt.help = "".join(help_lines) - break - help_lines.append(_deindent(line, indent)) + elif t0 is _T_IMPLY: + if node.item.__class__ is not Symbol: + self._parse_error("only symbols can imply") - if line is None: - break + node.implies.append((self._expect_nonconst_sym(), + self._parse_cond())) - line_feeder.unget() - - elif t0 == T_SELECT: - target = tokens.get_next() - - stmt.referenced_syms.add(target) - stmt.selected_syms.add(target) - - new_selects.append( - (target, - self._parse_expr(tokens, stmt, line, filename, linenr) - if tokens.check(T_IF) else None)) - - elif t0 == T_IMPLY: - target = tokens.get_next() - - stmt.referenced_syms.add(target) - stmt.implied_syms.add(target) - - new_implies.append( - (target, - self._parse_expr(tokens, stmt, line, filename, linenr) - if tokens.check(T_IF) else None)) - - elif t0 in (T_BOOL, T_TRISTATE, T_INT, T_HEX, T_STRING): - stmt.type = TOKEN_TO_TYPE[t0] - if tokens.peek_next() is not None: - new_prompt = parse_val_and_cond(tokens, line, filename, - linenr) - - elif t0 == T_DEFAULT: - new_def_exprs.append(parse_val_and_cond(tokens, line, filename, - linenr)) - - elif t0 == T_DEF_BOOL: - stmt.type = BOOL - if tokens.peek_next() is not None: - new_def_exprs.append(parse_val_and_cond(tokens, line, - filename, linenr)) - - elif t0 == T_PROMPT: - # 'prompt' properties override each other within a single - # definition of a symbol, but additional prompts can be added - # by defining the symbol multiple times; hence 'new_prompt' - # instead of 'prompt'. - new_prompt = parse_val_and_cond(tokens, line, filename, linenr) - - elif t0 == T_RANGE: - low = tokens.get_next() - high = tokens.get_next() - stmt.referenced_syms.add(low) - stmt.referenced_syms.add(high) - - stmt.ranges.append( - (low, high, - self._parse_expr(tokens, stmt, line, filename, linenr) - if tokens.check(T_IF) else None)) - - elif t0 == T_DEF_TRISTATE: - stmt.type = TRISTATE - if tokens.peek_next() is not None: - new_def_exprs.append(parse_val_and_cond(tokens, line, - filename, linenr)) - - elif t0 == T_OPTION: - if tokens.check(T_ENV) and tokens.check(T_EQUAL): - env_var = tokens.get_next() - - stmt.is_special_ = True - stmt.is_from_env = True - - if env_var not in os.environ: - self._warn("The symbol {0} references the " - "non-existent environment variable {1} and " - "will get the empty string as its value. " - "If you're using Kconfiglib via " - "'make (i)scriptconfig', it should have " - "set up the environment correctly for you. " - "If you still got this message, that " - "might be an error, and you should email " - "ulfalizer a.t Google's email service.""" - .format(stmt.name, env_var), - filename, linenr) - - stmt.cached_val = "" - else: - stmt.cached_val = os.environ[env_var] + elif t0 is _T_VISIBLE: + if not self._check_token(_T_IF): + self._parse_error("expected 'if' after 'visible'") + + node.visibility = self._make_and(node.visibility, + self._expect_expr_and_eol()) + + elif t0 is _T_OPTION: + if self._check_token(_T_ENV): + if not self._check_token(_T_EQUAL): + self._parse_error("expected '=' after 'env'") - elif tokens.check(T_DEFCONFIG_LIST): - self.defconfig_sym = stmt + env_var = self._expect_str_and_eol() + node.item.env_var = env_var - elif tokens.check(T_MODULES): + if env_var in os.environ: + node.defaults.append( + (self._lookup_const_sym(os.environ[env_var]), + self.y)) + else: + self._warn("{1} has 'option env=\"{0}\"', " + "but the environment variable {0} is not " + "set".format(node.item.name, env_var), + self.filename, self.linenr) + + if env_var != node.item.name: + self._warn("Kconfiglib expands environment variables " + "in strings directly, meaning you do not " + "need 'option env=...' \"bounce\" symbols. " + "For compatibility with the C tools, " + "rename {} to {} (so that the symbol name " + "matches the environment variable name)." + .format(node.item.name, env_var), + self.filename, self.linenr) + + elif self._check_token(_T_DEFCONFIG_LIST): + if not self.defconfig_list: + self.defconfig_list = node.item + else: + self._warn("'option defconfig_list' set on multiple " + "symbols ({0} and {1}). Only {0} will be " + "used.".format(self.defconfig_list.name, + node.item.name), + self.filename, self.linenr) + + elif self._check_token(_T_MODULES): # To reduce warning spam, only warn if 'option modules' is # set on some symbol that isn't MODULES, which should be # safe. I haven't run into any projects that make use # modules besides the kernel yet, and there it's likely to # keep being called "MODULES". - if stmt.name != "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.)", - filename, linenr) + "wasn't used.", + self.filename, self.linenr) - elif tokens.check(T_ALLNOCONFIG_Y): - if not isinstance(stmt, Symbol): - _parse_error(line, - "the 'allnoconfig_y' option is only " - "valid for symbols", - filename, linenr) - stmt.allnoconfig_y = True + elif self._check_token(_T_ALLNOCONFIG_Y): + if node.item.__class__ is not Symbol: + self._parse_error("the 'allnoconfig_y' option is only " + "valid for symbols") + + node.item.is_allnoconfig_y = True else: - _parse_error(line, "unrecognized option", filename, linenr) - - elif t0 == T_VISIBLE: - if not tokens.check(T_IF): - _parse_error(line, 'expected "if" after "visible"', - filename, linenr) - if not isinstance(stmt, Menu): - _parse_error(line, - "'visible if' is only valid for menus", - filename, linenr) - - parsed_deps = self._parse_expr(tokens, stmt, line, filename, - linenr) - stmt.visible_if_expr = _make_and(stmt.visible_if_expr, - parsed_deps) - - elif t0 == T_OPTIONAL: - if not isinstance(stmt, Choice): - _parse_error(line, - '"optional" is only valid for choices', - filename, - linenr) - stmt.optional = True + self._parse_error("unrecognized option") + + elif t0 is _T_OPTIONAL: + if node.item.__class__ is not Choice: + self._parse_error('"optional" is only valid for choices') + + node.item.is_optional = True else: - # See comment in Config.__init__() - self.end_line = line - self.end_line_tokens = tokens - break + # Reuse the tokens for the non-property line later + self._reuse_tokens = True + return - # Done parsing properties. Now propagate 'depends on' and enclosing - # menu/if dependencies to expressions. + def _set_type(self, node, new_type): + # UNKNOWN is falsy + if node.item.orig_type and node.item.orig_type is not new_type: + self._warn("{} defined with multiple types, {} will be used" + .format(_name_and_loc(node.item), + TYPE_TO_STR[new_type])) - # The set of symbols referenced directly by the statement plus all - # symbols referenced by enclosing menus and ifs - stmt.all_referenced_syms = stmt.referenced_syms | _get_expr_syms(deps) + node.item.orig_type = new_type - # Save original dependencies from enclosing menus and ifs - stmt.deps_from_containing = deps + def _parse_prompt(self, node): + # 'prompt' properties override each other within a single definition of + # a symbol, but additional prompts can be added by defining the symbol + # multiple times - if isinstance(stmt, (Menu, Comment)): - stmt.dep_expr = _make_and(stmt.orig_deps, deps) - else: - # Symbol or Choice - - # See comment for 'menu_dep' - stmt.menu_dep = _make_and(deps, depends_on_expr) - - # Propagate dependencies to prompts - - if new_prompt is not None: - prompt, cond_expr = new_prompt - # Propagate 'visible if' dependencies from menus and local - # 'depends on' dependencies - cond_expr = _make_and(_make_and(cond_expr, visible_if_deps), - depends_on_expr) - # Save original - stmt.orig_prompts.append((prompt, cond_expr)) - # Finalize with dependencies from enclosing menus and ifs - stmt.prompts.append((prompt, _make_and(cond_expr, deps))) - - # Propagate dependencies to defaults - - # Propagate 'depends on' dependencies - new_def_exprs = [(val_expr, _make_and(cond_expr, depends_on_expr)) - for val_expr, cond_expr in new_def_exprs] - # Save original - stmt.orig_def_exprs.extend(new_def_exprs) - # Finalize with dependencies from enclosing menus and ifs - stmt.def_exprs.extend([(val_expr, _make_and(cond_expr, deps)) - for val_expr, cond_expr in new_def_exprs]) - - # Propagate dependencies to selects and implies - - # Only symbols can select and imply - if isinstance(stmt, Symbol): - # Propagate 'depends on' dependencies - new_selects = [(target, _make_and(cond_expr, depends_on_expr)) - for target, cond_expr in new_selects] - new_implies = [(target, _make_and(cond_expr, depends_on_expr)) - for target, cond_expr in new_implies] - # Save original - stmt.orig_selects.extend(new_selects) - stmt.orig_implies.extend(new_implies) - # Finalize with dependencies from enclosing menus and ifs - for target, cond in new_selects: - target.rev_dep = \ - _make_or(target.rev_dep, - _make_and(stmt, _make_and(cond, deps))) - for target, cond in new_implies: - target.weak_rev_dep = \ - _make_or(target.weak_rev_dep, - _make_and(stmt, _make_and(cond, deps))) - - def _parse_expr(self, feed, cur_item, line, filename=None, linenr=None, - transform_m=True): - """Parses an expression from the tokens in 'feed' using a simple - top-down approach. The result has the form - '(<operator>, [<parsed operands>])', 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. - - feed: _Feed instance containing the tokens for the expression. - - cur_item: The item (Symbol, Choice, Menu, or Comment) currently being - parsed, or None if we're not parsing an item. Used for recording - references to symbols. - - line: The line containing the expression being parsed. - - filename (default: None): The file containing the expression. - - linenr (default: None): The line number containing the expression. - - transform_m (default: False): Determines if 'm' should be rewritten to - 'm && MODULES' -- see parse_val_and_cond(). - - Expression grammar, in decreasing order of precedence: - - <expr> -> <symbol> - <symbol> '=' <symbol> - <symbol> '!=' <symbol> - '(' <expr> ')' - '!' <expr> - <expr> '&&' <expr> - <expr> '||' <expr>""" - - # Use instance variables to avoid having to pass these as arguments - # through the top-down parser in _parse_expr_rec(), which is tedious - # and obfuscates the code. A profiler run shows no noticeable - # performance difference. - self._cur_item = cur_item - self._transform_m = transform_m - self._line = line - self._filename = filename - self._linenr = linenr - - return self._parse_expr_rec(feed) - - def _parse_expr_rec(self, feed): - or_term = self._parse_or_term(feed) - if not feed.check(T_OR): - # Common case -- no need for an OR node since it's just a single - # operand - return or_term - or_terms = [or_term, self._parse_or_term(feed)] - while feed.check(T_OR): - or_terms.append(self._parse_or_term(feed)) - return (OR, or_terms) - - def _parse_or_term(self, feed): - and_term = self._parse_factor(feed) - if not feed.check(T_AND): - # Common case -- no need for an AND node since it's just a single - # operand - return and_term - and_terms = [and_term, self._parse_factor(feed)] - while feed.check(T_AND): - and_terms.append(self._parse_factor(feed)) - return (AND, and_terms) - - def _parse_factor(self, feed): - token = feed.get_next() - - if isinstance(token, (Symbol, str)): - if self._cur_item is not None and isinstance(token, Symbol): - self._cur_item.referenced_syms.add(token) - - next_token = feed.peek_next() - # For conditional expressions ('depends on <expr>', - # '... if <expr>', # etc.), "m" and m are rewritten to - # "m" && MODULES. - if next_token != T_EQUAL and next_token != T_UNEQUAL: - if self._transform_m and (token is self.m or token == "m"): - return (AND, ["m", self._sym_lookup("MODULES")]) - return token + if node.prompt: + self._warn(_name_and_loc(node.item) + + " defined with multiple prompts in single location") - relation = EQUAL if (feed.get_next() == T_EQUAL) else UNEQUAL - token_2 = feed.get_next() - if self._cur_item is not None and isinstance(token_2, Symbol): - self._cur_item.referenced_syms.add(token_2) - return (relation, token, token_2) + prompt = self._tokens[1] + self._tokens_i = 2 - if token == T_NOT: - return (NOT, self._parse_factor(feed)) + if prompt.__class__ is not str: + self._parse_error("expected prompt string") - if token == T_OPEN_PAREN: - expr_parse = self._parse_expr_rec(feed) - if not feed.check(T_CLOSE_PAREN): - _parse_error(self._line, "missing end parenthesis", - self._filename, self._linenr) - return expr_parse + if prompt != prompt.strip(): + self._warn(_name_and_loc(node.item) + + " has leading or trailing whitespace in its prompt") - _parse_error(self._line, "malformed expression", self._filename, - self._linenr) + # This avoid issues for e.g. reStructuredText documentation, where + # '*prompt *' is invalid + prompt = prompt.strip() - def _tokenize(self, s, for_eval, filename=None, linenr=None): - """Returns a _Feed instance containing tokens derived from the string - 's'. Registers any new symbols encountered (via _sym_lookup()). + node.prompt = (prompt, self._parse_cond()) - (I experimented with a pure regular expression implementation, but it - came out slower, less readable, and wouldn't have been as flexible.) + def _parse_help(self, node): + if node.help is not None: + self._warn(_name_and_loc(node.item) + " defined with more than " + "one help text -- only the last one will be used") - for_eval: True when parsing an expression for a call to Config.eval(), - in which case we should not treat the first token specially nor - register new symbols.""" + # Micro-optimization. This code is pretty hot. + readline = self._readline - s = s.strip() - if s == "" or s[0] == "#": - return _Feed([]) + # Find first non-blank (not all-space) line and get its + # indentation - if for_eval: - previous = None # The previous token seen - tokens = [] - i = 0 # The current index in the string being tokenized + while 1: + line = readline() + self.linenr += 1 + if not line: + self._empty_help(node, line) + return + if not line.isspace(): + break - else: - # The initial word on a line is parsed specially. Let - # command_chars = [A-Za-z0-9_]. Then - # - leading non-command_chars characters are ignored, and - # - the first token consists the following one or more - # command_chars characters. - # This is why things like "----help--" are accepted. - initial_token_match = _initial_token_re_match(s) - if initial_token_match is None: - return _Feed([]) - keyword = _get_keyword(initial_token_match.group(1)) - if keyword == T_HELP: - # Avoid junk after "help", e.g. "---", being registered as a - # symbol - return _Feed([T_HELP]) - if keyword is None: - # We expect a keyword as the first token - _tokenization_error(s, filename, linenr) - - previous = keyword - tokens = [keyword] - # The current index in the string being tokenized - i = initial_token_match.end() - - # _tokenize() is a hotspot during parsing, and this speeds things up a - # bit - strlen = len(s) - append = tokens.append - - # Main tokenization loop. (Handles tokens past the first one.) - while i < strlen: - # Test for an identifier/keyword preceded by whitespace first; this - # is the most common case. - id_keyword_match = _id_keyword_re_match(s, i) - if id_keyword_match: - # We have an identifier or keyword. The above also stripped any - # whitespace for us. - name = id_keyword_match.group(1) - # Jump past it - i = id_keyword_match.end() + len_ = len # Micro-optimization - keyword = _get_keyword(name) - if keyword is not None: - # It's a keyword - append(keyword) - elif previous in STRING_LEX: - # What would ordinarily be considered an identifier is - # treated as a string after certain tokens - append(name) - else: - # It's a symbol name. _sym_lookup() will take care of - # allocating a new Symbol instance if it's the first time - # we see it. - sym = self._sym_lookup(name, for_eval) - - if previous == T_CONFIG or previous == T_MENUCONFIG: - # If the previous token is T_(MENU)CONFIG - # ("(menu)config"), we're tokenizing the first line of - # a symbol definition, and should remember this as a - # location where the symbol is defined - sym.def_locations.append((filename, linenr)) - else: - # Otherwise, it's a reference to the symbol - sym.ref_locations.append((filename, linenr)) + # Use a separate 'expline' variable here and below to avoid stomping on + # any tabs people might've put deliberately into the first line after + # the help text + expline = line.expandtabs() + indent = len_(expline) - len_(expline.lstrip()) + if not indent: + self._empty_help(node, line) + return - append(sym) + # The help text goes on till the first non-blank line with less indent + # than the first line - else: - # Not an identifier/keyword + # Add the first line + lines = [expline[indent:]] + add_line = lines.append # Micro-optimization - while i < strlen and s[i].isspace(): - i += 1 - if i == strlen: + while 1: + line = readline() + if line.isspace(): + # No need to preserve the exact whitespace in these + add_line("\n") + elif not line: + # End of file + break + else: + expline = line.expandtabs() + if len_(expline) - len_(expline.lstrip()) < indent: break - c = s[i] - i += 1 + add_line(expline[indent:]) + + self.linenr += len_(lines) + node.help = "".join(lines).rstrip() + if line: + self._line_after_help(line) + + def _empty_help(self, node, line): + self._warn(_name_and_loc(node.item) + + " has 'help' but empty help text") + node.help = "" + if line: + self._line_after_help(line) + + def _parse_expr(self, transform_m): + # Parses an expression from the tokens in Kconfig._tokens using a + # simple top-down approach. See the module docstring for the expression + # format. + # + # transform_m: + # True if m should be rewritten to m && MODULES. See the + # Kconfig.eval_string() documentation. - # String literal (constant symbol) - if c == '"' or c == "'": - if "\\" in s: - # Slow path: This could probably be sped up, but it's a - # very unusual case anyway. - quote = c - val = "" - while 1: - if i >= len(s): - _tokenization_error(s, filename, linenr) - c = s[i] - if c == quote: - break - if c == "\\": - if i + 1 >= len(s): - _tokenization_error(s, filename, linenr) - val += s[i + 1] - i += 2 - else: - val += c - i += 1 - i += 1 - append(val) - else: - # Fast path: If the string contains no backslashes - # (almost always) we can simply look for the matching - # quote. - end = s.find(c, i) - if end == -1: - _tokenization_error(s, filename, linenr) - append(s[i:end]) - i = end + 1 - - elif c == "&": - # Invalid characters are ignored - if i >= len(s) or s[i] != "&": continue - append(T_AND) - i += 1 + # Grammar: + # + # expr: and_expr ['||' expr] + # and_expr: factor ['&&' and_expr] + # factor: <symbol> ['='/'!='/'<'/... <symbol>] + # '!' factor + # '(' expr ')' + # + # It helps to think of the 'expr: and_expr' case as a single-operand OR + # (no ||), and of the 'and_expr: factor' case as a single-operand AND + # (no &&). Parsing code is always a bit tricky. + + # Mind dump: parse_factor() and two nested loops for OR and AND would + # work as well. The straightforward implementation there gives a + # (op, (op, (op, A, B), C), D) parse for A op B op C op D. Representing + # expressions as (op, [list of operands]) instead goes nicely with that + # version, but is wasteful for short expressions and complicates + # expression evaluation and other code that works on expressions (more + # complicated code likely offsets any performance gain from less + # recursion too). If we also try to optimize the list representation by + # merging lists when possible (e.g. when ANDing two AND expressions), + # we end up allocating a ton of lists instead of reusing expressions, + # which is bad. + + and_expr = self._parse_and_expr(transform_m) + + # Return 'and_expr' directly if we have a "single-operand" OR. + # Otherwise, parse the expression on the right and make an OR node. + # This turns A || B || C || D into (OR, A, (OR, B, (OR, C, D))). + return and_expr if not self._check_token(_T_OR) else \ + (OR, and_expr, self._parse_expr(transform_m)) + + def _parse_and_expr(self, transform_m): + factor = self._parse_factor(transform_m) + + # Return 'factor' directly if we have a "single-operand" AND. + # Otherwise, parse the right operand and make an AND node. This turns + # A && B && C && D into (AND, A, (AND, B, (AND, C, D))). + return factor if not self._check_token(_T_AND) else \ + (AND, factor, self._parse_and_expr(transform_m)) + + def _parse_factor(self, transform_m): + token = self._tokens[self._tokens_i] + self._tokens_i += 1 + + if token.__class__ is Symbol: + # Plain symbol or relation + + if self._tokens[self._tokens_i] not in _RELATIONS: + # Plain symbol + + # For conditional expressions ('depends on <expr>', + # '... if <expr>', etc.), m is rewritten to m && MODULES. + if transform_m and token is self.m: + return (AND, self.m, self.modules) - elif c == "|": - # Invalid characters are ignored - if i >= len(s) or s[i] != "|": continue - append(T_OR) - i += 1 + return token - elif c == "!": - if i < len(s) and s[i] == "=": - append(T_UNEQUAL) - i += 1 - else: - append(T_NOT) + # Relation + # + # _T_EQUAL, _T_UNEQUAL, etc., deliberately have the same values as + # EQUAL, UNEQUAL, etc., so we can just use the token directly + self._tokens_i += 1 + return (self._tokens[self._tokens_i - 1], token, + self._expect_sym()) + + if token is _T_NOT: + # token == _T_NOT == NOT + return (token, self._parse_factor(transform_m)) - elif c == "=": append(T_EQUAL) - elif c == "(": append(T_OPEN_PAREN) - elif c == ")": append(T_CLOSE_PAREN) - elif c == "#": break # Comment + if token is _T_OPEN_PAREN: + expr_parse = self._parse_expr(transform_m) + if self._check_token(_T_CLOSE_PAREN): + return expr_parse - else: continue # Invalid characters are ignored + self._parse_error("malformed expression") - previous = tokens[-1] + # + # Caching and invalidation + # - return _Feed(tokens) + def _build_dep(self): + # Populates the Symbol/Choice._dependents sets, which contain all other + # items (symbols and choices) that immediately depend on the item in + # the sense that changing the value of the item might affect the value + # of the dependent items. 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. + + make_depend_on = _make_depend_on # Micro-optimization + + # Only calculate _dependents for defined symbols. Constant and + # undefined symbols could theoretically be selected/implied, but it + # wouldn't change their value, so it's not a true dependency. + for sym in self.unique_defined_syms: + # Symbols depend on the following: + + # The prompt conditions + for node in sym.nodes: + if node.prompt: + make_depend_on(sym, node.prompt[1]) + + # The default values and their conditions + for value, cond in sym.defaults: + make_depend_on(sym, value) + make_depend_on(sym, cond) + + # The reverse and weak reverse dependencies + make_depend_on(sym, sym.rev_dep) + make_depend_on(sym, sym.weak_rev_dep) + + # The ranges along with their conditions + for low, high, cond in sym.ranges: + make_depend_on(sym, low) + make_depend_on(sym, high) + make_depend_on(sym, cond) + + # The direct dependencies. This is usually redundant, as the direct + # dependencies get propagated to properties, but it's needed to get + # invalidation solid for 'imply', which only checks the direct + # dependencies (even if there are no properties to propagate it + # to). + make_depend_on(sym, sym.direct_dep) + + # In addition to the above, choice symbols depend on the choice + # they're in, but that's handled automatically since the Choice is + # propagated to the conditions of the properties before + # _build_dep() runs. + + for choice in self.unique_choices: + # Choices depend on the following: + + # The prompt conditions + for node in choice.nodes: + if node.prompt: + make_depend_on(choice, node.prompt[1]) + + # The default symbol conditions + for _, cond in choice.defaults: + make_depend_on(choice, cond) + + def _add_choice_deps(self): + # Choices also depend on the choice symbols themselves, because the + # y-mode selection of the choice might change if a choice symbol's + # visibility changes. + # + # We add these dependencies separately after dependency loop detection. + # The invalidation algorithm can handle the resulting + # <choice symbol> <-> <choice> dependency loops, but they make loop + # detection awkward. - def _sym_lookup(self, name, for_eval=False): - """Fetches the symbol 'name' from the symbol table, creating and - registering it if it does not exist. If 'for_eval' is True, the symbol - won't be added to the symbol table if it does not exist -- this is for - Config.eval().""" - if name in self.syms: - return self.syms[name] + for choice in self.unique_choices: + for sym in choice.syms: + sym._dependents.add(choice) - new_sym = Symbol() - new_sym.config = self - new_sym.name = name - if for_eval: - self._warn("no symbol {0} in configuration".format(name)) - else: - self.syms[name] = new_sym - return new_sym + 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.unique_defined_syms: + sym._invalidate() + + for choice in self.unique_choices: + choice._invalidate() # - # Expression evaluation + # Post-parsing menu tree processing, including dependency propagation and + # implicit submenu creation # - def _eval_expr(self, expr): - """Evaluates an expression to "n", "m", or "y".""" + def _finalize_node(self, node, visible_if): + # Finalizes a menu node and its children: + # + # - Copies properties from menu nodes up to their contained + # symbols/choices + # + # - Propagates dependencies from parent to child nodes + # + # - Creates implicit menus (see kconfig-language.txt) + # + # - Removes 'if' nodes + # + # - Sets 'choice' types and registers choice symbols + # + # menu_finalize() in the C implementation is similar. + # + # node: + # The menu node to finalize. This node and its children will have + # been finalized when the function returns, and any implicit menus + # will have been created. + # + # visible_if: + # Dependencies from 'visible if' on parent menus. These are added to + # the prompts of symbols and choices. + + if node.item.__class__ is Symbol: + # Copy defaults, ranges, selects, and implies to the Symbol + self._add_props_to_sym(node) + + # Find any items that should go in an implicit menu rooted at the + # symbol + cur = node + while cur.next and _auto_menu_dep(node, cur.next): + # This makes implicit submenu creation work recursively, with + # implicit menus inside implicit menus + self._finalize_node(cur.next, visible_if) + cur = cur.next + cur.parent = node + + if cur is not node: + # Found symbols that should go in an implicit submenu. Tilt + # them up above us. + node.list = node.next + node.next = cur.next + cur.next = None + + elif node.list: + # The menu node is a choice, menu, or if. Finalize each child node. + + if node.item is MENU: + visible_if = self._make_and(visible_if, node.visibility) + + # Propagate the menu node's dependencies to each child menu node. + # + # This needs to go before the recursive _finalize_node() call so + # that implicit submenu creation can look ahead at dependencies. + self._propagate_deps(node, visible_if) + + # Finalize the children + cur = node.list + while cur: + self._finalize_node(cur, visible_if) + cur = cur.next + + if node.list: + # node's children have been individually finalized. Do final steps + # to finalize this "level" in the menu tree. + _flatten(node.list) + _remove_ifs(node) + + # Empty choices (node.list None) are possible, so this needs to go + # outside + if node.item.__class__ is Choice: + # Add the node's non-node-specific properties to the choice, like + # _add_props_to_sym() does + choice = node.item + choice.direct_dep = self._make_or(choice.direct_dep, node.dep) + choice.defaults += node.defaults + + _finalize_choice(node) + + def _propagate_deps(self, node, visible_if): + # Propagates 'node's dependencies to its child menu nodes + + # If the parent node holds a Choice, we use the Choice itself as the + # parent dependency. This makes sense as the value (mode) of the choice + # limits the visibility of the contained choice symbols. The C + # implementation works the same way. + # + # Due to the similar interface, Choice works as a drop-in replacement + # for Symbol here. + basedep = node.item if node.item.__class__ is Choice else node.dep + + cur = node.list + while cur: + dep = cur.dep = self._make_and(cur.dep, basedep) + + if cur.item.__class__ in _SYMBOL_CHOICE: + # Propagate 'visible if' and dependencies to the prompt + if cur.prompt: + cur.prompt = (cur.prompt[0], + self._make_and( + cur.prompt[1], + self._make_and(visible_if, dep))) + + # Propagate dependencies to defaults + if cur.defaults: + cur.defaults = [(default, self._make_and(cond, dep)) + for default, cond in cur.defaults] + + # Propagate dependencies to ranges + if cur.ranges: + cur.ranges = [(low, high, self._make_and(cond, dep)) + for low, high, cond in cur.ranges] + + # Propagate dependencies to selects + if cur.selects: + cur.selects = [(target, self._make_and(cond, dep)) + for target, cond in cur.selects] + + # Propagate dependencies to implies + if cur.implies: + cur.implies = [(target, self._make_and(cond, dep)) + for target, cond in cur.implies] + + elif cur.prompt: # Not a symbol/choice + # Propagate dependencies to the prompt. 'visible if' is only + # propagated to symbols/choices. + cur.prompt = (cur.prompt[0], + self._make_and(cur.prompt[1], dep)) + + cur = cur.next + + def _add_props_to_sym(self, node): + # Copies properties from the menu node 'node' up to its contained + # symbol, and adds (weak) reverse dependencies to selected/implied + # symbols. + # + # This can't be rolled into _propagate_deps(), because that function + # traverses the menu tree roughly breadth-first, meaning properties on + # symbols defined in multiple locations could end up in the wrong + # order. + + sym = node.item + + # See the Symbol class docstring + sym.direct_dep = self._make_or(sym.direct_dep, node.dep) + + sym.defaults += node.defaults + sym.ranges += node.ranges + sym.selects += node.selects + sym.implies += node.implies + + # Modify the reverse dependencies of the selected symbol + for target, cond in node.selects: + target.rev_dep = self._make_or( + target.rev_dep, + self._make_and(sym, cond)) + + # Modify the weak reverse dependencies of the implied + # symbol + for target, cond in node.implies: + target.weak_rev_dep = self._make_or( + target.weak_rev_dep, + self._make_and(sym, cond)) - # Handles e.g. an "x if y" condition where the "if y" part is missing. - if expr is None: - return "y" + # + # Misc. + # - res = self._eval_expr_rec(expr) - if res == "m": - # Promote "m" to "y" if we're running without modules. + def _check_sym_sanity(self): + # Checks various symbol properties that are handiest to check after + # parsing. Only generates errors and warnings. + + def num_ok(sym, type_): + # Returns True if the (possibly constant) symbol 'sym' is valid as a value + # for a symbol of type type_ (INT or HEX) + + # 'not sym.nodes' implies a constant or undefined symbol, e.g. a plain + # "123" + if not sym.nodes: + return _is_base_n(sym.name, _TYPE_TO_BASE[type_]) + + return sym.orig_type is type_ + + for sym in self.unique_defined_syms: + if sym.orig_type in _BOOL_TRISTATE: + # A helper function could be factored out here, but keep it + # speedy/straightforward + + for target_sym, _ in sym.selects: + if target_sym.orig_type not in _BOOL_TRISTATE_UNKNOWN: + self._warn("{} selects the {} symbol {}, which is not " + "bool or tristate" + .format(_name_and_loc(sym), + TYPE_TO_STR[target_sym.orig_type], + _name_and_loc(target_sym))) + + for target_sym, _ in sym.implies: + if target_sym.orig_type not in _BOOL_TRISTATE_UNKNOWN: + self._warn("{} implies the {} symbol {}, which is not " + "bool or tristate" + .format(_name_and_loc(sym), + TYPE_TO_STR[target_sym.orig_type], + _name_and_loc(target_sym))) + + elif sym.orig_type: # STRING/INT/HEX + for default, _ in sym.defaults: + if default.__class__ is not Symbol: + raise KconfigError( + "the {} symbol {} has a malformed default {} -- expected " + "a single symbol" + .format(TYPE_TO_STR[sym.orig_type], _name_and_loc(sym), + expr_str(default))) + + if sym.orig_type is STRING: + if not default.is_constant and not default.nodes and \ + not default.name.isupper(): + # 'default foo' on a string symbol could be either a symbol + # reference or someone leaving out the quotes. Guess that + # the quotes were left out if 'foo' isn't all-uppercase + # (and no symbol named 'foo' exists). + self._warn("style: quotes recommended around " + "default value for string symbol " + + _name_and_loc(sym)) + + elif not num_ok(default, sym.orig_type): # INT/HEX + self._warn("the {0} symbol {1} has a non-{0} default {2}" + .format(TYPE_TO_STR[sym.orig_type], + _name_and_loc(sym), + _name_and_loc(default))) + + if sym.selects or sym.implies: + self._warn("the {} symbol {} has selects or implies" + .format(TYPE_TO_STR[sym.orig_type], + _name_and_loc(sym))) + + else: # UNKNOWN + self._warn("{} defined without a type" + .format(_name_and_loc(sym))) + + + if sym.ranges: + if sym.orig_type not in _INT_HEX: + self._warn( + "the {} symbol {} has ranges, but is not int or hex" + .format(TYPE_TO_STR[sym.orig_type], + _name_and_loc(sym))) + else: + for low, high, _ in sym.ranges: + if not num_ok(low, sym.orig_type) or \ + not num_ok(high, sym.orig_type): + + self._warn("the {0} symbol {1} has a non-{0} " + "range [{2}, {3}]" + .format(TYPE_TO_STR[sym.orig_type], + _name_and_loc(sym), + _name_and_loc(low), + _name_and_loc(high))) + + def _check_choice_sanity(self): + # Checks various choice properties that are handiest to check after + # parsing. Only generates errors and warnings. + + def warn_select_imply(sym, expr, expr_type): + msg = "the choice symbol {} is {} by the following symbols, but " \ + "select/imply has no effect on choice symbols" \ + .format(_name_and_loc(sym), expr_type) + + # si = select/imply + for si in split_expr(expr, OR): + msg += "\n - " + _name_and_loc(split_expr(si, AND)[0]) + + self._warn(msg) + + for choice in self.unique_choices: + if choice.orig_type not in _BOOL_TRISTATE: + self._warn("{} defined with type {}" + .format(_name_and_loc(choice), + TYPE_TO_STR[choice.orig_type])) + + for node in choice.nodes: + if node.prompt: + break + else: + self._warn(_name_and_loc(choice) + " defined without a prompt") + + for default, _ in choice.defaults: + if default.__class__ is not Symbol: + raise KconfigError( + "{} has a malformed default {}" + .format(_name_and_loc(choice), expr_str(default))) + + if default.choice is not choice: + self._warn("the default selection {} of {} is not " + "contained in the choice" + .format(_name_and_loc(default), + _name_and_loc(choice))) + + for sym in choice.syms: + if sym.defaults: + self._warn("default on the choice symbol {} will have " + "no effect, as defaults do not affect choice " + "symbols".format(_name_and_loc(sym))) + + if sym.rev_dep is not sym.kconfig.n: + warn_select_imply(sym, sym.rev_dep, "selected") + + if sym.weak_rev_dep is not sym.kconfig.n: + warn_select_imply(sym, sym.weak_rev_dep, "implied") + + for node in sym.nodes: + if node.parent.item is choice: + if not node.prompt: + self._warn("the choice symbol {} has no prompt" + .format(_name_and_loc(sym))) + + elif node.prompt: + self._warn("the choice symbol {} is defined with a " + "prompt outside the choice" + .format(_name_and_loc(sym))) + + def _parse_error(self, msg): + raise KconfigError("{}couldn't parse '{}': {}".format( + "" if self.filename is None else + "{}:{}: ".format(self.filename, self.linenr), + self._line.strip(), msg)) + + def _trailing_tokens_error(self): + self._parse_error("extra tokens at end of line") + + def _open(self, filename, mode): + # open() wrapper: + # + # - Enable universal newlines mode on Python 2 to ease + # interoperability between Linux and Windows. It's already the + # default on Python 3. + # + # The "U" flag would currently work for both Python 2 and 3, but it's + # deprecated on Python 3, so play it future-safe. + # + # io.open() defaults to universal newlines on Python 2 (and is an + # alias for open() on Python 3), but it returns 'unicode' strings and + # slows things down: + # + # Parsing x86 Kconfigs on Python 2 + # + # with open(..., "rU"): + # + # real 0m0.930s + # user 0m0.905s + # sys 0m0.025s + # + # with io.open(): + # + # real 0m1.069s + # user 0m1.040s + # sys 0m0.029s + # + # There's no appreciable performance difference between "r" and + # "rU" for parsing performance on Python 2. + # + # - For Python 3, force the encoding. Forcing the encoding on Python 2 + # turns strings into Unicode strings, which gets messy. Python 2 + # doesn't decode regular strings anyway. + return open(filename, "rU" if mode == "r" else mode) if _IS_PY2 else \ + open(filename, mode, encoding=self._encoding) + + def _check_undef_syms(self): + # Prints warnings for all references to undefined symbols within the + # Kconfig files + + def is_num(s): + # Returns True if the string 's' looks like a number. # - # Internally, "m" is often rewritten to "m" && MODULES by both the - # C implementation and Kconfiglib, which takes care of cases where - # "m" should be demoted to "n" instead. - modules_sym = self.syms.get("MODULES") - if modules_sym is None or modules_sym.get_value() != "y": - return "y" - return res + # Internally, all operands in Kconfig are symbols, only undefined symbols + # (which numbers usually are) get their name as their value. + # + # Only hex numbers that start with 0x/0X are classified as numbers. + # Otherwise, symbols whose names happen to contain only the letters A-F + # would trigger false positives. - def _eval_expr_rec(self, expr): - if isinstance(expr, Symbol): - # Non-bool/tristate symbols are always "n" in a tristate sense, - # regardless of their value - if expr.type != BOOL and expr.type != TRISTATE: - return "n" - return expr.get_value() - - if isinstance(expr, str): - return expr if (expr == "y" or expr == "m") else "n" - - # Ordered by frequency - - if expr[0] == AND: - res = "y" - for subexpr in expr[1]: - ev = self._eval_expr_rec(subexpr) - # Return immediately upon discovering an "n" term - if ev == "n": - return "n" - if ev == "m": - res = "m" - # 'res' is either "m" or "y" here; we already handled the - # short-circuiting "n" case in the loop. - return res + try: + int(s) + except ValueError: + if not s.startswith(("0x", "0X")): + return False - if expr[0] == NOT: - ev = self._eval_expr_rec(expr[1]) - if ev == "y": - return "n" - return "y" if (ev == "n") else "m" - - if expr[0] == OR: - res = "n" - for subexpr in expr[1]: - ev = self._eval_expr_rec(subexpr) - # Return immediately upon discovering a "y" term - if ev == "y": - return "y" - if ev == "m": - res = "m" - # 'res' is either "n" or "m" here; we already handled the - # short-circuiting "y" case in the loop. - return res + try: + int(s, 16) + except ValueError: + return False - if expr[0] == EQUAL: - return "y" if (_str_val(expr[1]) == _str_val(expr[2])) else "n" + return True - if expr[0] == UNEQUAL: - return "y" if (_str_val(expr[1]) != _str_val(expr[2])) else "n" + for sym in (self.syms.viewvalues if _IS_PY2 else self.syms.values)(): + # - sym.nodes empty means the symbol is undefined (has no + # definition locations) + # + # - Due to Kconfig internals, numbers show up as undefined Kconfig + # symbols, but shouldn't be flagged + # + # - The MODULES symbol always exists + if not sym.nodes and not is_num(sym.name) and \ + sym.name != "MODULES": - _internal_error("Internal error while evaluating expression: " - "unknown operation {0}.".format(expr[0])) + msg = "undefined symbol {}:".format(sym.name) + for node in self.node_iter(): + if sym in node.referenced: + msg += "\n\n- Referenced at {}:{}:\n\n{}" \ + .format(node.filename, node.linenr, node) + self._warn(msg) - def _eval_min(self, e1, e2): - """Returns the minimum value of the two expressions. Equates None with - 'y'.""" - e1_eval = self._eval_expr(e1) - e2_eval = self._eval_expr(e2) - return e1_eval if tri_less(e1_eval, e2_eval) else e2_eval + def _warn(self, msg, filename=None, linenr=None): + # For printing general warnings - def _eval_max(self, e1, e2): - """Returns the maximum value of the two expressions. Equates None with - 'y'.""" - e1_eval = self._eval_expr(e1) - e2_eval = self._eval_expr(e2) - return e1_eval if tri_greater(e1_eval, e2_eval) else e2_eval + if not self.warn: + return - # - # Dependency tracking (for caching and invalidation) - # + msg = "warning: " + msg + if filename is not None: + msg = "{}:{}: {}".format(filename, linenr, msg) - def _build_dep(self): - """Populates the Symbol.dep sets, linking the symbol to the symbols - that immediately depend on it in the sense that changing the value of - the symbol might affect the values of those other symbols. This is used - for caching/invalidation purposes. The calculated sets might be larger - than necessary as we don't do any complicated analysis of the - expressions.""" - - # Adds 'sym' as a directly dependent symbol to all symbols that appear - # in the expression 'e' - def add_expr_deps(e, sym): - for s in _get_expr_syms(e): - s.dep.add(sym) - - # The directly dependent symbols of a symbol are: - # - Any symbols whose prompts, default values, rev_dep (select - # condition), weak_rev_dep (imply condition) or ranges depend on the - # symbol - # - Any symbols that belong to the same choice statement as the symbol - # (these won't be included in 'dep' as that makes the dependency - # graph unwieldy, but Symbol._get_dependent() will include them) - # - Any symbols in a choice statement that depends on the symbol - for sym in self.syms_iter(): - for _, e in sym.prompts: - add_expr_deps(e, sym) - - for v, e in sym.def_exprs: - add_expr_deps(v, sym) - add_expr_deps(e, sym) - - add_expr_deps(sym.rev_dep, sym) - add_expr_deps(sym.weak_rev_dep, sym) - - for l, u, e in sym.ranges: - add_expr_deps(l, sym) - add_expr_deps(u, sym) - add_expr_deps(e, sym) - - if sym.is_choice_sym: - choice = sym.parent - for _, e in choice.prompts: - add_expr_deps(e, sym) - for _, e in choice.def_exprs: - add_expr_deps(e, sym) - - def _eq_to_sym(self, eq): - """_expr_depends_on() helper. For (in)equalities of the form sym = y/m - or sym != n, returns sym. For other (in)equalities, returns None.""" - relation, left, right = eq - - def transform_y_m_n(item): - if item is self.y: return "y" - if item is self.m: return "m" - if item is self.n: return "n" - return item - - left = transform_y_m_n(left) - right = transform_y_m_n(right) - - # Make sure the symbol (if any) appears to the left - if not isinstance(left, Symbol): - left, right = right, left - if not isinstance(left, Symbol): - return None - if (relation == EQUAL and (right == "y" or right == "m")) or \ - (relation == UNEQUAL and right == "n"): - return left - return None + self.warnings.append(msg) + if self.warn_to_stderr: + sys.stderr.write(msg + "\n") - def _expr_depends_on(self, expr, sym): - """Reimplementation of expr_depends_symbol() from mconf.c. Used to - determine if a submenu should be implicitly created, which influences - what items inside choice statements are considered choice items.""" - if expr is None: - return False - def rec(expr): - if isinstance(expr, str): - return False - if isinstance(expr, Symbol): - return expr is sym - - if expr[0] in (EQUAL, UNEQUAL): - return self._eq_to_sym(expr) is sym - if expr[0] == AND: - for and_expr in expr[1]: - if rec(and_expr): - return True - return False +class Symbol(object): + """ + Represents a configuration symbol: - return rec(expr) + (menu)config FOO + ... - def _invalidate_all(self): - for sym in self.syms_iter(): - sym._invalidate() + 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). - # - # Printing and misc. - # + Note: Prompts, help texts, and locations are stored in the Symbol's + MenuNode(s) rather than in the Symbol itself. Check the MenuNode class and + the Symbol.nodes attribute. This organization matches the C tools. - def _expand_sym_refs(self, s): - """Expands $-references to symbols in 's' to symbol values, or to the - empty string for undefined symbols.""" + name: + The name of the symbol, e.g. "FOO" for 'config FOO'. - while 1: - sym_ref_match = _sym_ref_re_search(s) - if sym_ref_match is None: - return s + type: + The type of the symbol. One of BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN. + UNKNOWN is for undefined symbols, (non-special) constant symbols, and + symbols defined without a type. - sym_name = sym_ref_match.group(0)[1:] - sym = self.syms.get(sym_name) - expansion = "" if sym is None else sym.get_value() + 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. - s = s[:sym_ref_match.start()] + \ - expansion + \ - s[sym_ref_match.end():] + orig_type: + The type as given in the Kconfig file, without any magic applied. Used + when printing the symbol. - def _expr_val_str(self, expr, no_value_str="(none)", - get_val_instead_of_eval=False): - """Printing helper. Returns a string with 'expr' and its value. + str_value: + The value of the symbol as a string. Gives the value for string/int/hex + symbols. For bool/tristate symbols, gives "n", "m", or "y". - no_value_str: String to return when 'expr' is missing (None). + This is the symbol value that's used in relational expressions + (A = B, A != B, etc.) - get_val_instead_of_eval: Assume 'expr' is a symbol or string (constant - symbol) and get its value directly instead of evaluating it to a - tristate value.""" + Gotcha: For int/hex symbols, the exact format of the value must often be + preserved (e.g., when writing a .config file), hence why you can't get it + directly as an int. Do int(int_sym.str_value) or + int(hex_sym.str_value, 16) to get the integer value. - if expr is None: - return no_value_str + tri_value: + 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. - if get_val_instead_of_eval: - if isinstance(expr, str): - return _expr_to_str(expr) - val = expr.get_value() - else: - val = self._eval_expr(expr) + This is the symbol value that's used outside of relation expressions + (A, !A, A && B, A || B). - return "{0} (value: {1})".format(_expr_to_str(expr), _expr_to_str(val)) + assignable: + A tuple containing the tristate user values that can currently be + assigned to the symbol (that would 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. - def _get_sym_or_choice_str(self, sc): - """Symbols and choices have many properties in common, so we factor out - common __str__() stuff here. "sc" is short for "symbol or choice".""" + 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 m or y through a select, perhaps in combination with the + visibility. menuconfig represents this as -M- and -*-, respectively. - # As we deal a lot with string representations here, use some - # convenient shorthand: - s = _expr_to_str + For string/hex/int symbols, check if Symbol.visibility is non-0 (non-n) + instead to determine if the value can be changed. - # - # Common symbol/choice properties - # + Some handy 'assignable' idioms: - user_val_str = "(no user value)" if sc.user_val is None else \ - s(sc.user_val) + # Is 'sym' an assignable (visible) bool/tristate symbol? + if sym.assignable: + # What's the highest value it can be assigned? [-1] in Python + # gives the last element. + sym_high = sym.assignable[-1] - # Build prompts string - if not sc.prompts: - prompts_str = " (no prompts)" - else: - prompts_str_rows = [] - for prompt, cond_expr in sc.orig_prompts: - prompts_str_rows.append( - ' "{0}"'.format(prompt) if cond_expr is None else - ' "{0}" if {1}'.format(prompt, - self._expr_val_str(cond_expr))) - prompts_str = "\n".join(prompts_str_rows) - - # Build locations string - locations_str = "(no locations)" if not sc.def_locations else \ - " ".join(["{0}:{1}".format(filename, linenr) for - filename, linenr in sc.def_locations]) - - # Build additional-dependencies-from-menus-and-ifs string - additional_deps_str = " " + \ - self._expr_val_str(sc.deps_from_containing, - "(no additional dependencies)") + # The lowest? + sym_low = sym.assignable[0] - # - # Symbol-specific stuff - # + # Can the symbol be set to at least m? + if sym.assignable[-1] >= 1: + ... - if isinstance(sc, Symbol): - # Build ranges string - if isinstance(sc, Symbol): - if not sc.ranges: - ranges_str = " (no ranges)" - else: - ranges_str_rows = [] - for l, u, cond_expr in sc.ranges: - ranges_str_rows.append( - " [{0}, {1}]".format(s(l), s(u)) - if cond_expr is None else - " [{0}, {1}] if {2}" - .format(s(l), s(u), self._expr_val_str(cond_expr))) - ranges_str = "\n".join(ranges_str_rows) - - # Build default values string - if not sc.def_exprs: - defaults_str = " (no default values)" - else: - defaults_str_rows = [] - for val_expr, cond_expr in sc.orig_def_exprs: - row_str = " " + self._expr_val_str(val_expr, "(none)", - sc.type == STRING) - defaults_str_rows.append(row_str) - defaults_str_rows.append(" Condition: " + - self._expr_val_str(cond_expr)) - defaults_str = "\n".join(defaults_str_rows) - - # Build selects string - if not sc.orig_selects: - selects_str = " (no selects)" - else: - selects_str_rows = [] - for target, cond_expr in sc.orig_selects: - selects_str_rows.append( - " {0}".format(target.name) if cond_expr is None else - " {0} if {1}".format(target.name, - self._expr_val_str(cond_expr))) - selects_str = "\n".join(selects_str_rows) - - # Build implies string - if not sc.orig_implies: - implies_str = " (no implies)" - else: - implies_str_rows = [] - for target, cond_expr in sc.orig_implies: - implies_str_rows.append( - " {0}".format(target.name) if cond_expr is None else - " {0} if {1}".format(target.name, - self._expr_val_str(cond_expr))) - implies_str = "\n".join(implies_str_rows) - - res = _lines("Symbol " + - ("(no name)" if sc.name is None else sc.name), - "Type : " + TYPENAME[sc.type], - "Value : " + s(sc.get_value()), - "User value : " + user_val_str, - "Visibility : " + s(_get_visibility(sc)), - "Is choice item : " + BOOL_STR[sc.is_choice_sym], - "Is defined : " + BOOL_STR[sc.is_defined_], - "Is from env. : " + BOOL_STR[sc.is_from_env], - "Is special : " + BOOL_STR[sc.is_special_] + "\n") - if sc.ranges: - res += _lines("Ranges:", ranges_str + "\n") - res += _lines("Prompts:", - prompts_str, - "Default values:", - defaults_str, - "Selects:", - selects_str, - "Implies:", - implies_str, - "Reverse (select-related) dependencies:", - " (no reverse dependencies)" - if sc.rev_dep == "n" - else " " + self._expr_val_str(sc.rev_dep), - "Weak reverse (imply-related) dependencies:", - " (no weak reverse dependencies)" - if sc.weak_rev_dep == "n" - else " " + self._expr_val_str(sc.weak_rev_dep), - "Additional dependencies from enclosing menus " - "and ifs:", - additional_deps_str, - "Locations: " + locations_str) + # Can the symbol be set to m? + if 1 in sym.assignable: + ... - return res + 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. - # - # Choice-specific stuff - # + user_value: + The user value of the symbol. None if no user value has been assigned + (via Kconfig.load_config() or Symbol.set_value()). - # Build selected symbol string - sel = sc.get_selection() - sel_str = "(no selection)" if sel is None else sel.name + Holds 0, 1, or 2 for bool/tristate symbols, and a string for the other + symbol types. - # Build default values string - if not sc.def_exprs: - defaults_str = " (no default values)" - else: - defaults_str_rows = [] - for sym, cond_expr in sc.orig_def_exprs: - defaults_str_rows.append( - " {0}".format(sym.name) if cond_expr is None else - " {0} if {1}".format(sym.name, - self._expr_val_str(cond_expr))) - defaults_str = "\n".join(defaults_str_rows) - - # Build contained symbols string - names = [sym.name for sym in sc.actual_symbols] - syms_string = " ".join(names) if names else "(empty)" - - return _lines("Choice", - "Name (for named choices): " + - ("(no name)" if sc.name is None else sc.name), - "Type : " + TYPENAME[sc.type], - "Selected symbol : " + sel_str, - "User value : " + user_val_str, - "Mode : " + s(sc.get_mode()), - "Visibility : " + s(_get_visibility(sc)), - "Optional : " + BOOL_STR[sc.optional], - "Prompts:", - prompts_str, - "Defaults:", - defaults_str, - "Choice symbols:", - " " + syms_string, - "Additional dependencies from enclosing menus and " - "ifs:", - additional_deps_str, - "Locations: " + locations_str) + WARNING: Do not assign directly to this. It will break things. Use + Symbol.set_value(). - def _warn(self, msg, filename=None, linenr=None): - """For printing warnings to stderr.""" - msg = _build_msg("warning: " + msg, filename, linenr) - if self.print_warnings: - sys.stderr.write(msg + "\n") - self._warnings.append(msg) + config_string: + The .config assignment string that would get written out for the symbol + by Kconfig.write_config(). Returns the empty string if no .config + assignment would get written out. -class Item(object): + In general, visible symbols, symbols with (active) defaults, and selected + symbols get written out. This includes all non-n-valued bool/tristate + symbols, and all visible string/int/hex symbols. - """Base class for symbols and other Kconfig constructs. Subclasses are - Symbol, Choice, Menu, and Comment.""" + Symbols with the (no longer needed) 'option env=...' option generate no + configuration output, and neither does the special + 'option defconfig_list' symbol. - def is_symbol(self): - """Returns True if the item is a symbol. Short for - isinstance(item, kconfiglib.Symbol).""" - return isinstance(self, Symbol) + Tip: This field is useful when generating custom configuration output, + even for non-.config-like formats. To write just the symbols that would + get written out to .config files, do this: - def is_choice(self): - """Returns True if the item is a choice. Short for - isinstance(item, kconfiglib.Choice).""" - return isinstance(self, Choice) + if sym.config_string: + *Write symbol, e.g. by looking sym.str_value* - def is_menu(self): - """Returns True if the item is a menu. Short for - isinstance(item, kconfiglib.Menu).""" - return isinstance(self, Menu) + This is a superset of the symbols written out by write_autoconf(). + That function skips all n-valued symbols. - def is_comment(self): - """Returns True if the item is a comment. Short for - isinstance(item, kconfiglib.Comment).""" - return isinstance(self, Comment) + There usually won't be any great harm in just writing all symbols either, + though you might get some special symbols and possibly some "redundant" + n-valued symbol entries in there. -class Symbol(Item): + nodes: + 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. - """Represents a configuration symbol - e.g. FOO for + 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. - config FOO - ...""" + defaults: + 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.kconfig.y. - # - # Public interface - # + Note that 'depends on' and parent dependencies are propagated to + 'default' conditions. + + selects: + List of (symbol, cond) tuples for the symbol's 'select' properties. For + example, 'select A if B && C' is represented as (A, (AND, B, C)). If no + condition was given, 'cond' is self.kconfig.y. + + Note that 'depends on' and parent dependencies are propagated to 'select' + conditions. + + implies: + Like 'selects', for imply. + + ranges: + 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.kconfig.y. + + Note that 'depends on' and parent dependencies are propagated to 'range' + conditions. + + 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. + + orig_defaults: + orig_selects: + orig_implies: + orig_ranges: + See the corresponding attributes on the MenuNode class. + + rev_dep: + 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)). + + weak_rev_dep: + Like rev_dep, for imply. + + direct_dep: + The direct ('depends on') dependencies for the symbol, or self.kconfig.y + if there are no direct dependencies. + + This attribute includes any dependencies from surrounding menus and ifs. + Those get propagated to the direct dependencies, and the resulting direct + dependencies in turn get propagated to the conditions of all properties. + + If the symbol is defined in multiple locations, the dependencies from the + different locations get ORed together. + + referenced: + A set() with all symbols and choices referenced in the properties and + property conditions of the symbol. + + Also includes dependencies from surrounding menus and ifs, because those + get propagated to the symbol (see the 'Intro to symbol values' section in + the module docstring). - def get_config(self): - """Returns the Config instance this symbol is from.""" - return self.config - - def get_name(self): - """Returns the name of the symbol.""" - return self.name - - def get_type(self): - """Returns the type of the symbol: one of UNKNOWN, BOOL, TRISTATE, - STRING, HEX, or INT. These are defined at the top level of the module, - so you'd do something like - - if sym.get_type() == kconfiglib.STRING: - ...""" - return self.type - - def get_prompts(self): - """Returns a list of prompts defined for the symbol, in the order they - appear in the configuration files. Returns the empty list for symbols - with no prompt. - - This list will have a single entry for the vast majority of symbols - having prompts, but having multiple prompts for a single symbol is - possible through having multiple 'config' entries for it.""" - return [prompt for prompt, _ in self.orig_prompts] - - def get_help(self): - """Returns the help text of the symbol, or None if the symbol has no - help text.""" - return self.help - - def get_parent(self): - """Returns the menu or choice statement that contains the symbol, or - None if the symbol is at the top level. Note that if statements are - treated as syntactic and do not have an explicit class - representation.""" - return self.parent - - def get_def_locations(self): - """Returns a list of (filename, linenr) tuples, where filename (string) - and linenr (int) represent a location where the symbol is defined. For - the vast majority of symbols this list will only contain one element. - For the following Kconfig, FOO would get two entries: the lines marked - with *. - - config FOO * - bool "foo prompt 1" - - config FOO * - bool "foo prompt 2" - """ - return self.def_locations - - def get_ref_locations(self): - """Returns a list of (filename, linenr) tuples, where filename (string) - and linenr (int) represent a location where the symbol is referenced in - the configuration. For example, the lines marked by * would be included - for FOO below: + Choices appear in the dependencies of choice symbols. + + For the following definitions, only B and not C appears in A's + 'referenced'. To get transitive references, you'll have to recursively + expand 'references' until no new items appear. config A - bool - default BAR || FOO * + bool + depends on B config B - tristate - depends on FOO * - default m if FOO * + bool + depends on C + + config C + bool + + See the Symbol.direct_dep attribute if you're only interested in the + direct dependencies of the symbol (its 'depends on'). You can extract the + symbols in it with the global expr_items() function. + + env_var: + If the Symbol has an 'option env="FOO"' option, this contains the name + ("FOO") of the environment variable. None for symbols without no + 'option env'. + + 'option env="FOO"' acts like a 'default' property whose value is the + value of $FOO. + + Symbols with 'option env' are never written out to .config 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 (except when printing symbols), but can be checked by + scripts. + + is_constant: + True if the symbol is a constant (quoted) symbol. + + kconfig: + The Kconfig instance this symbol is from. + """ + __slots__ = ( + "_cached_assignable", + "_cached_str_val", + "_cached_tri_val", + "_cached_vis", + "_dependents", + "_old_val", + "_visited", + "_was_set", + "_write_to_conf", + "choice", + "defaults", + "direct_dep", + "env_var", + "implies", + "is_allnoconfig_y", + "is_constant", + "kconfig", + "name", + "nodes", + "orig_type", + "ranges", + "rev_dep", + "selects", + "user_value", + "weak_rev_dep", + ) - if FOO * - config A - bool "A" - endif + # + # Public interface + # - config FOO (definition not included) - bool + @property + def type(self): + """ + See the class documentation. """ - return self.ref_locations + if self.orig_type is TRISTATE and \ + (self.choice and self.choice.tri_value == 2 or + not self.kconfig.modules.tri_value): + + return BOOL - def get_value(self): - """Calculate and return the value of the symbol. See also - Symbol.set_user_value().""" + return self.orig_type + + @property + def str_value(self): + """ + See the class documentation. + """ + if self._cached_str_val is not None: + return self._cached_str_val - if self.cached_val is not None: - return self.cached_val + if self.orig_type in _BOOL_TRISTATE: + # Also calculates the visibility, so invalidation safe + self._cached_str_val = TRI_TO_STR[self.tri_value] + return self._cached_str_val # As a quirk of Kconfig, undefined symbols get their name as their - # value. This is why things like "FOO = bar" work for seeing if FOO has - # the value "bar". - if self.type == UNKNOWN: - self.cached_val = self.name + # string value. This is why things like "FOO = bar" work for seeing if + # FOO has the value "bar". + if not self.orig_type: # UNKNOWN + self._cached_str_val = self.name return self.name - new_val = DEFAULT_VALUE[self.type] - vis = _get_visibility(self) + val = "" + # Warning: See Symbol._rec_invalidate(), and note that this is a hidden + # function call (property magic) + vis = self.visibility + + self._write_to_conf = (vis != 0) - # This is easiest to calculate together with the value - self.write_to_conf = False + if self.orig_type in _INT_HEX: + # The C implementation checks the user value against the range in a + # separate code path (post-processing after loading a .config). + # Checking all values here instead makes more sense for us. It + # requires that we check for a range first. - if self.type == BOOL or self.type == TRISTATE: - # The visibility and mode (modules-only or single-selection) of - # choice items will be taken into account in _get_visibility() - if self.is_choice_sym: - if vis != "n": - choice = self.parent - mode = choice.get_mode() + base = _TYPE_TO_BASE[self.orig_type] - self.write_to_conf = (mode != "n") + # Check if a range is in effect + for low_expr, high_expr, cond in self.ranges: + if expr_value(cond): + has_active_range = True - if mode == "y": - new_val = "y" if choice.get_selection() is self \ - else "n" - elif mode == "m": - if self.user_val == "m" or self.user_val == "y": - new_val = "m" + # The zeros are from the C implementation running strtoll() + # on empty strings + low = int(low_expr.str_value, base) if \ + _is_base_n(low_expr.str_value, base) else 0 + high = int(high_expr.str_value, base) if \ + _is_base_n(high_expr.str_value, base) else 0 + break else: - # If the symbol is visible and has a user value, use that. - # Otherwise, look at defaults and weak reverse dependencies - # (implies). - use_defaults_and_weak_rev_deps = True - - if vis != "n": - self.write_to_conf = True - if self.user_val is not None: - new_val = self.config._eval_min(self.user_val, vis) - use_defaults_and_weak_rev_deps = False - - if use_defaults_and_weak_rev_deps: - for val_expr, cond_expr in self.def_exprs: - cond_eval = self.config._eval_expr(cond_expr) - if cond_eval != "n": - self.write_to_conf = True - new_val = self.config._eval_min(val_expr, - cond_eval) - break - - weak_rev_dep_val = \ - self.config._eval_expr(self.weak_rev_dep) - if weak_rev_dep_val != "n": - self.write_to_conf = True - new_val = self.config._eval_max(new_val, - weak_rev_dep_val) - - # Reverse (select-related) dependencies take precedence - rev_dep_val = self.config._eval_expr(self.rev_dep) - if rev_dep_val != "n": - self.write_to_conf = True - new_val = self.config._eval_max(new_val, rev_dep_val) - - # We need to promote "m" to "y" in two circumstances: - # 1) If our type is boolean - # 2) If our weak_rev_dep (from IMPLY) is "y" - if new_val == "m" and \ - (self.type == BOOL or - self.config._eval_expr(self.weak_rev_dep) == "y"): - new_val = "y" - - elif self.type == INT or self.type == HEX: - has_active_range = False - low = None - high = None + has_active_range = False + + # Defaults are used if the symbol is invisible, lacks a user value, + # or has an out-of-range user value use_defaults = True - base = 16 if self.type == HEX else 10 + if vis and self.user_value: + user_val = int(self.user_value, base) + if has_active_range and not low <= user_val <= high: + num2str = str if base == 10 else hex + self.kconfig._warn( + "user value {} on the {} symbol {} ignored due to " + "being outside the active range ([{}, {}]) -- falling " + "back on defaults" + .format(num2str(user_val), TYPE_TO_STR[self.orig_type], + _name_and_loc(self), + num2str(low), num2str(high))) + else: + # If the user value is well-formed and satisfies range + # contraints, it is stored in exactly the same form as + # specified in the assignment (with or without "0x", etc.) + val = self.user_value + use_defaults = False - for l, h, cond_expr in self.ranges: - if self.config._eval_expr(cond_expr) != "n": - has_active_range = True + if use_defaults: + # No user value or invalid user value. Look at defaults. - low_str = _str_val(l) - high_str = _str_val(h) - low = int(low_str, base) if \ - _is_base_n(low_str, base) else 0 - high = int(high_str, base) if \ - _is_base_n(high_str, base) else 0 + # Used to implement the warning below + has_default = False - break + for sym, cond in self.defaults: + if expr_value(cond): + has_default = self._write_to_conf = True - if vis != "n": - self.write_to_conf = True + val = sym.str_value - if self.user_val is not None and \ - _is_base_n(self.user_val, base) and \ - (not has_active_range or - low <= int(self.user_val, base) <= high): + if _is_base_n(val, base): + val_num = int(val, base) + else: + val_num = 0 # strtoll() on empty string - # If the user value is OK, it is stored in exactly the same - # form as specified in the assignment (with or without - # "0x", etc). + break + else: + val_num = 0 # strtoll() on empty string + + # This clamping procedure runs even if there's no default + if has_active_range: + clamp = None + if val_num < low: + clamp = low + elif val_num > high: + clamp = high + + if clamp is not None: + # The value is rewritten to a standard form if it is + # clamped + val = str(clamp) \ + if self.orig_type is INT else \ + hex(clamp) + + if has_default: + num2str = str if base == 10 else hex + self.kconfig._warn( + "default value {} on {} clamped to {} due to " + "being outside the active range ([{}, {}])" + .format(val_num, _name_and_loc(self), + num2str(clamp), num2str(low), + num2str(high))) + + elif self.orig_type is STRING: + if vis and self.user_value is not None: + # If the symbol is visible and has a user value, use that + val = self.user_value + else: + # Otherwise, look at defaults + for sym, cond in self.defaults: + if expr_value(cond): + val = sym.str_value + self._write_to_conf = True + break - use_defaults = False - new_val = self.user_val + # env_var corresponds to SYMBOL_AUTO in the C implementation, and is + # also set on the defconfig_list symbol there. Test for the + # defconfig_list symbol explicitly instead here, to avoid a nonsensical + # env_var setting and the defconfig_list symbol being printed + # incorrectly. This code is pretty cold anyway. + if self.env_var is not None or self is self.kconfig.defconfig_list: + self._write_to_conf = False - if use_defaults: - for val_expr, cond_expr in self.def_exprs: - if self.config._eval_expr(cond_expr) != "n": - self.write_to_conf = True - - # If the default value is OK, it is stored in exactly - # the same form as specified. Otherwise, it is clamped - # to the range, and the output has "0x" as appropriate - # for the type. - - new_val = _str_val(val_expr) - - if _is_base_n(new_val, base): - new_val_num = int(new_val, base) - if has_active_range: - clamped_val = None - - if new_val_num < low: - clamped_val = low - elif new_val_num > high: - clamped_val = high - - if clamped_val is not None: - new_val = (hex(clamped_val) if \ - self.type == HEX else str(clamped_val)) - - break - else: # For the for loop - # If no user value or default kicks in but the hex/int has - # an active range, 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: - new_val = (hex(low) if self.type == HEX else str(low)) - - elif self.type == STRING: - use_defaults = True + self._cached_str_val = val + return val - if vis != "n": - self.write_to_conf = True - if self.user_val is not None: - new_val = self.user_val - use_defaults = False + @property + def tri_value(self): + """ + See the class documentation. + """ + if self._cached_tri_val is not None: + return self._cached_tri_val - if use_defaults: - for val_expr, cond_expr in self.def_exprs: - if self.config._eval_expr(cond_expr) != "n": - self.write_to_conf = True - new_val = _str_val(val_expr) - break + if self.orig_type not in _BOOL_TRISTATE: + if self.orig_type: # != UNKNOWN + # Would take some work to give the location here + self.kconfig._warn( + "The {} symbol {} is being evaluated in a logical context " + "somewhere. It will always evaluate to n." + .format(TYPE_TO_STR[self.orig_type], _name_and_loc(self))) - self.cached_val = new_val - return new_val + self._cached_tri_val = 0 + return 0 - def get_user_value(self): - """Returns the value assigned to the symbol in a .config or via - Symbol.set_user_value() (provided the value was valid for the type of - the symbol). Returns None in case of no user value.""" - return self.user_val + # Warning: See Symbol._rec_invalidate(), and note that this is a hidden + # function call (property magic) + vis = self.visibility + self._write_to_conf = (vis != 0) - def get_upper_bound(self): - """For string/hex/int symbols and for bool and tristate symbols that - cannot be modified (see is_modifiable()), returns None. + val = 0 - Otherwise, returns the highest value the symbol can be set to with - Symbol.set_user_value() (that will not be truncated): one of "m" or - "y", arranged from lowest to highest. This corresponds to the highest - value the symbol could be given in e.g. the 'make menuconfig' - interface. + if not self.choice: + # Non-choice symbol - See also the tri_less*() and tri_greater*() functions, which could come - in handy.""" - if self.type != BOOL and self.type != TRISTATE: - return None - rev_dep = self.config._eval_expr(self.rev_dep) - # A bool selected to "m" gets promoted to "y", pinning it - if rev_dep == "m" and self.type == BOOL: - return None - vis = _get_visibility(self) - if TRI_TO_INT[vis] > TRI_TO_INT[rev_dep]: - return vis - return None + if vis and self.user_value is not None: + # If the symbol is visible and has a user value, use that + val = min(self.user_value, vis) - def get_lower_bound(self): - """For string/hex/int symbols and for bool and tristate symbols that - cannot be modified (see is_modifiable()), returns None. + else: + # Otherwise, look at defaults and weak reverse dependencies + # (implies) + + for default, cond in self.defaults: + dep_val = expr_value(cond) + if dep_val: + val = min(expr_value(default), dep_val) + if val: + self._write_to_conf = True + break - Otherwise, returns the lowest value the symbol can be set to with - Symbol.set_user_value() (that will not be truncated): one of "n" or - "m", arranged from lowest to highest. This corresponds to the lowest - value the symbol could be given in e.g. the 'make menuconfig' - interface. + # Weak reverse dependencies are only considered if our + # direct dependencies are met + dep_val = expr_value(self.weak_rev_dep) + if dep_val and expr_value(self.direct_dep): + val = max(dep_val, val) + self._write_to_conf = True + + # Reverse (select-related) dependencies take precedence + dep_val = expr_value(self.rev_dep) + if dep_val: + if expr_value(self.direct_dep) < dep_val: + self._warn_select_unsatisfied_deps() + + val = max(dep_val, val) + self._write_to_conf = True + + # 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 is BOOL or expr_value(self.weak_rev_dep) == 2): + val = 2 + + elif vis == 2: + # Visible choice symbol in y-mode choice. The choice mode limits + # the visibility of choice symbols, so it's sufficient to just + # check the visibility of the choice symbols themselves. + val = 2 if self.choice.selection is self else 0 + + elif vis and self.user_value: + # Visible choice symbol in m-mode choice, with set non-0 user value + val = 1 + + self._cached_tri_val = val + return val + + @property + def assignable(self): + """ + See the class documentation. + """ + if self._cached_assignable is None: + self._cached_assignable = self._assignable() + return self._cached_assignable - See also the tri_less*() and tri_greater*() functions, which could come - in handy.""" - if self.type != BOOL and self.type != TRISTATE: - return None - rev_dep = self.config._eval_expr(self.rev_dep) - # A bool selected to "m" gets promoted to "y", pinning it - if rev_dep == "m" and self.type == BOOL: - return None - if TRI_TO_INT[_get_visibility(self)] > TRI_TO_INT[rev_dep]: - return rev_dep - return None + @property + def visibility(self): + """ + See the class documentation. + """ + if self._cached_vis is None: + self._cached_vis = _visibility(self) + return self._cached_vis - def get_assignable_values(self): - """For string/hex/int symbols and for bool and tristate symbols that - cannot be modified (see is_modifiable()), returns the empty list. - - Otherwise, returns a list containing the user values that can be - assigned to the symbol (that won't be truncated). Usage example: - - if "m" in sym.get_assignable_values(): - sym.set_user_value("m") - - This is basically a more convenient interface to - get_lower/upper_bound() when wanting to test if a particular tristate - value can be assigned.""" - if self.type != BOOL and self.type != TRISTATE: - return [] - rev_dep = self.config._eval_expr(self.rev_dep) - # A bool selected to "m" gets promoted to "y", pinning it - if rev_dep == "m" and self.type == BOOL: - return [] - res = ["n", "m", "y"][TRI_TO_INT[rev_dep] : - TRI_TO_INT[_get_visibility(self)] + 1] - return res if len(res) > 1 else [] - - def get_visibility(self): - """Returns the visibility of the symbol: one of "n", "m" or "y". For - bool and tristate symbols, this is an upper bound on the value users - can set for the symbol. For other types of symbols, a visibility of "n" - means the user value will be ignored. A visibility of "n" corresponds - to not being visible in the 'make *config' interfaces. - - Example (assuming we're running with modules enabled -- i.e., MODULES - set to 'y'): - - # Assume this has been assigned 'n' - config N_SYM - tristate "N_SYM" - - # Assume this has been assigned 'm' - config M_SYM - tristate "M_SYM" - - # Has visibility 'n' - config A - tristate "A" - depends on N_SYM + @property + def config_string(self): + """ + See the class documentation. + """ + # _write_to_conf is determined when the value is calculated. This is a + # hidden function call due to property magic. + val = self.str_value + if not self._write_to_conf: + return "" + + if self.orig_type in _BOOL_TRISTATE: + return "{}{}={}\n" \ + .format(self.kconfig.config_prefix, self.name, val) \ + if val != "n" else \ + "# {}{} is not set\n" \ + .format(self.kconfig.config_prefix, self.name) + + if self.orig_type in _INT_HEX: + return "{}{}={}\n" \ + .format(self.kconfig.config_prefix, self.name, val) + + # sym.orig_type is STRING + return '{}{}="{}"\n' \ + .format(self.kconfig.config_prefix, self.name, escape(val)) + + def set_value(self, value): + """ + Sets the user value of the symbol. - # Has visibility 'm' - config B - tristate "B" - depends on M_SYM + Equal in effect to assigning the value to the symbol within a .config + 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_value to differ from + Symbol.str/tri_value (be truncated down or up). + + Setting a choice symbol to 2 (y) sets Choice.user_selection to the + choice symbol in addition to setting Symbol.user_value. + Choice.user_selection is considered when the choice is in y mode (the + "normal" mode). + + 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. For bool and tristate symbols, + n/m/y can be specified either as 0/1/2 (the usual format for tristate + values in Kconfiglib) or as one of the strings "n"/"m"/"y". For other + symbol types, pass a string. + + Note that the value for an int/hex symbol is passed as a string, e.g. + "123" or "0x0123". The format of this string is preserved in the + output. + + Values that are invalid for the type (such as "foo" or 1 (m) for a + BOOL or "0x123" for an INT) are ignored and won't be stored in + Symbol.user_value. Kconfiglib will print a warning by default for + invalid assignments, and set_value() will return False. + + Returns True if the value is valid for the type of the symbol, and + False otherwise. This only looks at the form of the value. For BOOL and + TRISTATE symbols, check the Symbol.assignable attribute to see what + values are currently in range and would actually be reflected in the + value of the symbol. For other symbol types, check whether the + visibility is non-n. + """ + if self.orig_type in _BOOL_TRISTATE and value in STR_TO_TRI: + value = STR_TO_TRI[value] - # Has visibility 'y' - config C - tristate "C" - - # Has no prompt, and hence visibility 'n' - config D - tristate - - Having visibility be tri-valued ensures that e.g. a symbol cannot be - set to "y" by the user if it depends on a symbol with value "m", which - wouldn't be safe. - - You should probably look at get_lower/upper_bound(), - get_assignable_values() and is_modifiable() before using this.""" - return _get_visibility(self) - - def get_referenced_symbols(self, refs_from_enclosing=False): - """Returns the set() of all symbols referenced by this symbol. For - example, the symbol defined by - - config FOO - bool - prompt "foo" if A && B - default C if D - depends on E - select F if G - - references the symbols A through G. - - refs_from_enclosing (default: False): If True, the symbols referenced - by enclosing menus and ifs will be included in the result.""" - return self.all_referenced_syms if refs_from_enclosing else \ - self.referenced_syms - - def get_selected_symbols(self): - """Returns the set() of all symbols X for which this symbol has a - 'select X' or 'select X if Y' (regardless of whether Y is satisfied or - not). This is a subset of the symbols returned by - get_referenced_symbols().""" - return self.selected_syms - - def get_implied_symbols(self): - """Returns the set() of all symbols X for which this symbol has an - 'imply X' or 'imply X if Y' (regardless of whether Y is satisfied or - not). This is a subset of the symbols returned by - get_referenced_symbols().""" - return self.implied_syms - - def set_user_value(self, v): - """Sets the user value of the symbol. + # If the new user value matches the old, nothing changes, and we can + # avoid invalidating cached values. + # + # This optimization is skipped for choice symbols: Setting a choice + # symbol's user value to y might change the state of the choice, so it + # wouldn't be safe (symbol user values always match the values set in a + # .config file or via set_value(), and are never implicitly updated). + if value == self.user_value and not self.choice: + self._was_set = True + return True - Equal in effect to assigning the value to the symbol within a .config - file. Use get_lower/upper_bound() or get_assignable_values() to find - the range of currently assignable values for bool and tristate symbols; - setting values outside this range will cause the user value to differ - from the result of Symbol.get_value() (be truncated). Values that are - invalid for the type (such as a_bool.set_user_value("foo")) are - ignored, and a warning is emitted if an attempt is made to assign such - a value. - - For any type of symbol, is_modifiable() can be used to check if a user - value will currently have any effect on the symbol, as determined by - its visibility and range of assignable values. Any value that is valid - for the type (bool, tristate, etc.) will end up being reflected in - get_user_value() though, and might have an effect later if conditions - change. To get rid of the user value, use unset_user_value(). - - Any symbols dependent on the symbol are (recursively) invalidated, so - things will just work with regards to dependencies. - - v: The user value to give to the symbol.""" - self._set_user_value_no_invalidate(v, False) - - # There might be something more efficient you could do here, but play - # it safe. - if self.name == "MODULES": - self.config._invalidate_all() - return + # Check if the value is valid for our type + if not (self.orig_type is BOOL and value in (2, 0) or + self.orig_type is TRISTATE and value in TRI_TO_STR or + value.__class__ is str and + (self.orig_type is STRING or + self.orig_type is INT and _is_base_n(value, 10) or + self.orig_type is HEX and _is_base_n(value, 16) + and int(value, 16) >= 0)): + + # Display tristate values as n, m, y in the warning + self.kconfig._warn( + "the value {} is invalid for {}, which has type {} -- " + "assignment ignored" + .format(TRI_TO_STR[value] if value in TRI_TO_STR else + "'{}'".format(value), + _name_and_loc(self), TYPE_TO_STR[self.orig_type])) - self._invalidate() - self._invalidate_dependent() - - def unset_user_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_user_value().""" - self._unset_user_value_no_recursive_invalidate() - self._invalidate_dependent() - - def is_modifiable(self): - """Returns True if the value of the symbol could be modified by calling - Symbol.set_user_value(). - - For bools and tristates, this corresponds to the symbol being visible - in the 'make menuconfig' interface and not already being pinned to a - specific value (e.g. because it is selected by another symbol). - - For strings and numbers, this corresponds to just being visible. (See - Symbol.get_visibility().)""" - if self.is_special_: return False - if self.type == BOOL or self.type == TRISTATE: - rev_dep = self.config._eval_expr(self.rev_dep) - # A bool selected to "m" gets promoted to "y", pinning it - if rev_dep == "m" and self.type == BOOL: - return False - return TRI_TO_INT[_get_visibility(self)] > TRI_TO_INT[rev_dep] - return _get_visibility(self) != "n" - - def is_defined(self): - """Returns False if the symbol is referred to in the Kconfig but never - actually defined.""" - return self.is_defined_ - - def is_special(self): - """Returns True if the symbol is one of the special symbols n, m, y, or - UNAME_RELEASE, or gets its value from the environment.""" - return self.is_special_ - - def is_from_environment(self): - """Returns True if the symbol gets its value from the environment.""" - return self.is_from_env - - def has_ranges(self): - """Returns True if the symbol is of type INT or HEX and has ranges that - limit what values it can take on.""" - return bool(self.ranges) - - def is_choice_symbol(self): - """Returns True if the symbol is in a choice statement and is an actual - choice symbol (see Choice.get_symbols()).""" - return self.is_choice_sym - - def is_choice_selection(self): - """Returns True if the symbol is contained in a choice statement and is - the selected item. Equivalent to - - sym.is_choice_symbol() and sym.get_parent().get_selection() is sym""" - return self.is_choice_sym and self.parent.get_selection() is self - - def is_allnoconfig_y(self): - """Returns True if the symbol has the 'allnoconfig_y' option set.""" - return self.allnoconfig_y + + self.user_value = value + self._was_set = True + + if self.choice and value == 2: + # Setting a choice symbol to y makes it the user selection of the + # choice. Like for symbol user values, the user selection is not + # guaranteed to match the actual selection of the choice, as + # dependencies come into play. + self.choice.user_selection = self + self.choice._was_set = True + self.choice._rec_invalidate() + else: + self._rec_invalidate_if_has_prompt() + + return True + + def unset_value(self): + """ + Removes any user value from the symbol, as if the symbol had never + gotten a user value via Kconfig.load_config() or Symbol.set_value(). + """ + if self.user_value is not None: + self.user_value = None + self._rec_invalidate_if_has_prompt() + + @property + def referenced(self): + """ + See the class documentation. + """ + return {item for node in self.nodes for item in node.referenced} + + @property + def orig_defaults(self): + """ + See the class documentation. + """ + return [d for node in self.nodes for d in node.orig_defaults] + + @property + def orig_selects(self): + """ + See the class documentation. + """ + return [s for node in self.nodes for s in node.orig_selects] + + @property + def orig_implies(self): + """ + See the class documentation. + """ + return [i for node in self.nodes for i in node.orig_implies] + + @property + def orig_ranges(self): + """ + See the class documentation. + """ + return [r for node in self.nodes for r in node.orig_ranges] + + def __repr__(self): + """ + Returns a string with information about the symbol (including its name, + value, visibility, and location(s)) when it is evaluated on e.g. the + interactive Python prompt. + """ + fields = ["symbol " + self.name, TYPE_TO_STR[self.type]] + add = fields.append + + for node in self.nodes: + if node.prompt: + add('"{}"'.format(node.prompt[0])) + + # Only add quotes for non-bool/tristate symbols + add("value " + (self.str_value if self.orig_type in _BOOL_TRISTATE + else '"{}"'.format(self.str_value))) + + if not self.is_constant: + # These aren't helpful to show for constant symbols + + if self.user_value is not None: + # Only add quotes for non-bool/tristate symbols + add("user value " + (TRI_TO_STR[self.user_value] + if self.orig_type in _BOOL_TRISTATE + else '"{}"'.format(self.user_value))) + + add("visibility " + TRI_TO_STR[self.visibility]) + + if self.choice: + add("choice symbol") + + if self.is_allnoconfig_y: + add("allnoconfig_y") + + if self is self.kconfig.defconfig_list: + add("is the defconfig_list symbol") + + if self.env_var is not None: + add("from environment variable " + self.env_var) + + if self is self.kconfig.modules: + add("is the modules symbol") + + add("direct deps " + TRI_TO_STR[expr_value(self.direct_dep)]) + + if self.nodes: + for node in self.nodes: + add("{}:{}".format(node.filename, node.linenr)) + else: + add("constant" if self.is_constant else "undefined") + + return "<{}>".format(", ".join(fields)) def __str__(self): - """Returns a string containing various information about the symbol.""" - return self.config._get_sym_or_choice_str(self) + """ + Returns a string representation of the symbol when it is printed. + Matches the Kconfig format, with any parent dependencies propagated to + the 'depends on' condition. + + The string is constructed by joining the strings returned by + MenuNode.__str__() for each of the symbol's menu nodes, so symbols + defined in multiple locations will return a string with all + definitions. + + The returned string does not end in a newline. An empty string is + returned for undefined and constant symbols. + """ + return self.custom_str(standard_sc_expr_str) + + def custom_str(self, sc_expr_str_fn): + """ + Works like Symbol.__str__(), but allows a custom format to be used for + all symbol/choice references. See expr_str(). + """ + return "\n\n".join(node.custom_str(sc_expr_str_fn) + for node in self.nodes) # # Private methods # def __init__(self): - """Symbol constructor -- not intended to be called directly by - Kconfiglib clients.""" - - self.name = None - self.type = UNKNOWN - self.prompts = [] - self.def_exprs = [] # 'default' properties - self.ranges = [] # 'range' properties (for int and hex) - self.help = None # Help text - self.rev_dep = "n" # Reverse (select-related) dependencies - self.weak_rev_dep = "n" # Weak reverse (imply-related) dependencies - self.config = None - self.parent = None - - self.user_val = None # Value set by user - - # The prompt, default value, select, and imply conditions without any - # dependencies from menus and ifs propagated to them - self.orig_prompts = [] - self.orig_def_exprs = [] - self.orig_selects = [] - self.orig_implies = [] - - # Dependencies inherited from containing menus and ifs - self.deps_from_containing = None - # The set of symbols referenced by this symbol (see - # get_referenced_symbols()) - self.referenced_syms = set() - # The set of symbols selected by this symbol (see - # get_selected_symbols()) - self.selected_syms = set() - # The set of symbols implied by this symbol (see get_implied_symbols()) - self.implied_syms = set() - # Like 'referenced_syms', but includes symbols from - # dependencies inherited from enclosing menus and ifs - self.all_referenced_syms = set() - - # This records only dependencies from enclosing ifs and menus together - # with local 'depends on' dependencies. Needed when determining actual - # choice items (hrrrr...). See Choice._determine_actual_symbols(). - self.menu_dep = None - - # See Symbol.get_ref/def_locations(). - self.def_locations = [] - self.ref_locations = [] - - # Populated in Config._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 (the - # transitive closure) is calculated on an as-needed basis in - # _get_dependent(). - self.dep = set() - - # Cached values - - # Caches the calculated value - self.cached_val = None - # Caches the visibility, which acts as an upper bound on the value - self.cached_visibility = None - # Caches the total list of dependent symbols. Calculated in - # _get_dependent(). - self.cached_deps = None - - # Flags - - # Does the symbol have an entry in the Kconfig file? The trailing - # underscore avoids a collision with is_defined(). - self.is_defined_ = False - # Should the symbol get an entry in .config? - self.write_to_conf = False - # Set to true when _make_conf() is called on a symbol, so that symbols - # defined in multiple locations only get one .config entry. We need to - # reset it prior to writing out a new .config. - self.already_written = False - # This is set to True for "actual" choice symbols; see - # Choice._determine_actual_symbols(). - self.is_choice_sym = False - # Does the symbol get its value in some special way, e.g. from the - # environment or by being one of the special symbols n, m, and y? If - # so, the value is stored in self.cached_val, which is never - # invalidated. The trailing underscore avoids a collision with - # is_special(). - self.is_special_ = False - # Does the symbol get its value from the environment? - self.is_from_env = False - # Does the symbol have the 'allnoconfig_y' option set? - self.allnoconfig_y = False + """ + Symbol constructor -- not intended to be called directly by Kconfiglib + clients. + """ + # These attributes are always set on the instance from outside and + # don't need defaults: + # kconfig + # direct_dep + # is_constant + # name + # rev_dep + # weak_rev_dep - def _invalidate(self): - if self.is_special_: - return + # - UNKNOWN == 0 + # - _visited is used during tree iteration and dep. loop detection + self.orig_type = self._visited = 0 - if self.is_choice_sym: - self.parent._invalidate() + self.nodes = [] - self.cached_val = None - self.cached_visibility = None + self.defaults = [] + self.selects = [] + self.implies = [] + self.ranges = [] - def _invalidate_dependent(self): - for sym in self._get_dependent(): - sym._invalidate() + self.user_value = \ + self.choice = \ + self.env_var = \ + self._cached_str_val = self._cached_tri_val = self._cached_vis = \ + self._cached_assignable = None - def _set_user_value_no_invalidate(self, v, suppress_load_warnings): - """Like set_user_value(), but does not invalidate any symbols. + # _write_to_conf is calculated along with the value. If True, the + # Symbol gets a .config entry. - suppress_load_warnings: some warnings are annoying when loading a - .config that can be helpful when manually invoking set_user_value(). - This flag is set to True to suppress such warnings. + self.is_allnoconfig_y = \ + self._was_set = \ + self._write_to_conf = False - Perhaps this could be made optional for load_config() instead.""" + # See Kconfig._build_dep() + self._dependents = set() - if self.is_special_: - if self.is_from_env: - self.config._warn('attempt to assign the value "{0}" to the ' - 'symbol {1}, which gets its value from the ' - 'environment. Assignment ignored.' - .format(v, self.name)) - else: - self.config._warn('attempt to assign the value "{0}" to the ' - 'special symbol {1}. Assignment ignored.' - .format(v, self.name)) - return + def _assignable(self): + # Worker function for the 'assignable' attribute - if not self.is_defined_: - filename, linenr = self.ref_locations[0] - if self.config.print_undef_assign: - _stderr_msg('note: attempt to assign the value "{0}" to {1}, ' - "which is referenced at {2}:{3} but never " - "defined. Assignment ignored." - .format(v, self.name, filename, linenr)) - return + if self.orig_type not in _BOOL_TRISTATE: + return () - # Check if the value is valid for our type - if not ((self.type == BOOL and (v == "y" or v == "n") ) or - (self.type == TRISTATE and (v == "y" or v == "m" or - v == "n") ) or - (self.type == STRING ) or - (self.type == INT and _is_base_n(v, 10) ) or - (self.type == HEX and _is_base_n(v, 16) )): - self.config._warn('the value "{0}" is invalid for {1}, which has ' - "type {2}. Assignment ignored." - .format(v, self.name, TYPENAME[self.type])) - return + # Warning: See Symbol._rec_invalidate(), and note that this is a hidden + # function call (property magic) + vis = self.visibility + if not vis: + return () - if not self.prompts and not suppress_load_warnings: - self.config._warn('assigning "{0}" to the symbol {1} which ' - 'lacks prompts and thus has visibility "n". ' - 'The assignment will have no effect.' - .format(v, self.name)) + rev_dep_val = expr_value(self.rev_dep) - self.user_val = v + if vis == 2: + if self.choice: + return (2,) - if self.is_choice_sym and (self.type == BOOL or self.type == TRISTATE): - choice = self.parent - if v == "y": - choice.user_val = self - choice.user_mode = "y" - elif v == "m": - choice.user_val = None - choice.user_mode = "m" + if not rev_dep_val: + if self.type is BOOL or expr_value(self.weak_rev_dep) == 2: + return (0, 2) + return (0, 1, 2) - def _unset_user_value_no_recursive_invalidate(self): - self._invalidate() - self.user_val = None + if rev_dep_val == 2: + return (2,) - if self.is_choice_sym: - self.parent._unset_user_value() + # rev_dep_val == 1 - def _make_conf(self, append_fn): - if self.already_written: - return + if self.type is BOOL or expr_value(self.weak_rev_dep) == 2: + return (2,) + return (1, 2) - self.already_written = True + # vis == 1 - # Note: write_to_conf is determined in get_value() - val = self.get_value() - if not self.write_to_conf: - return + # Must be a tristate here, because bool m visibility gets promoted to y + + if not rev_dep_val: + return (0, 1) if expr_value(self.weak_rev_dep) != 2 else (0, 2) + + if rev_dep_val == 2: + return (2,) - if self.type == BOOL or self.type == TRISTATE: - append_fn("{0}{1}={2}".format(self.config.config_prefix, self.name, val) - if val == "y" or val == "m" else - "# {0}{1} is not set".format(self.config.config_prefix, self.name)) + # vis == rev_dep_val == 1 - elif self.type == INT or self.type == HEX: - append_fn("{0}{1}={2}".format(self.config.config_prefix, self.name, val)) + return (1,) - elif self.type == STRING: - # Escape \ and " - append_fn('{0}{1}="{2}"' - .format(self.config.config_prefix, self.name, - val.replace("\\", "\\\\").replace('"', '\\"'))) + def _invalidate(self): + # Marks the symbol as needing to be recalculated + + self._cached_str_val = self._cached_tri_val = self._cached_vis = \ + self._cached_assignable = None + + def _rec_invalidate(self): + # Invalidates the symbol and all items that (possibly) depend on it + if self is self.kconfig.modules: + # Invalidating MODULES has wide-ranging effects + self.kconfig._invalidate_all() else: - _internal_error("Internal error while creating .config: unknown " - 'type "{0}".'.format(self.type)) - - def _get_dependent(self): - """Returns the set of symbols that should be invalidated if the value - of the symbol changes, because they might be affected by the change. - Note that this is an internal API -- it's probably of limited - usefulness to clients.""" - if self.cached_deps is not None: - return self.cached_deps - - res = set(self.dep) - for s in self.dep: - res |= s._get_dependent() - - if self.is_choice_sym: - # Choice symbols also depend (recursively) on their siblings. The - # siblings are not included in 'dep' to avoid dependency loops. - for sibling in self.parent.actual_symbols: - if sibling is not self: - res.add(sibling) - res |= sibling.dep - for s in sibling.dep: - res |= s._get_dependent() - - self.cached_deps = res - return res + self._invalidate() + + for item in self._dependents: + # _cached_vis doubles as a flag that tells us whether 'item' + # has cached values, because it's calculated as a side effect + # of calculating all other (non-constant) cached values. + # + # If item._cached_vis is None, it means there can't be cached + # values on other items that depend on 'item', because if there + # were, some value on 'item' would have been calculated and + # item._cached_vis set as a side effect. It's therefore safe to + # stop the invalidation at symbols with _cached_vis None. + # + # This approach massively speeds up scripts that set a lot of + # values, vs simply invalidating all possibly dependent symbols + # (even when you already have a list of all the dependent + # symbols, because some symbols get huge dependency trees). + # + # This gracefully handles dependency loops too, which is nice + # for choices, where the choice depends on the choice symbols + # and vice versa. + if item._cached_vis is not None: + item._rec_invalidate() + + def _rec_invalidate_if_has_prompt(self): + # Invalidates the symbol and its dependent symbols, but only if the + # symbol has a prompt. User values never have an effect on promptless + # symbols, so we skip invalidation for them as an optimization. + # + # This also prevents constant (quoted) symbols from being invalidated + # if set_value() is called on them, which would make them lose their + # value and break things. + # + # Prints a warning if the symbol has no prompt. In some contexts (e.g. + # when loading a .config files) assignments to promptless symbols are + # normal and expected, so the warning can be disabled. - def _has_auto_menu_dep_on(self, on): - """See Choice._determine_actual_symbols().""" - if not isinstance(self.parent, Choice): - _internal_error("Attempt to determine auto menu dependency for " - "symbol ouside of choice.") + for node in self.nodes: + if node.prompt: + self._rec_invalidate() + return - if not self.prompts: - # If we have no prompt, use the menu dependencies instead (what was - # specified with 'depends on') - return self.menu_dep is not None and \ - self.config._expr_depends_on(self.menu_dep, on) + if self.kconfig._warn_assign_no_prompt: + self.kconfig._warn(_name_and_loc(self) + " has no prompt, meaning " + "user values have no effect on it") + + def _str_default(self): + # write_min_config() helper function. Returns the value the symbol + # would get from defaults if it didn't have a user value. Uses exactly + # the same algorithm as the C implementation (though a bit cleaned up), + # for compatibility. + + if self.orig_type in _BOOL_TRISTATE: + val = 0 + + # Defaults, selects, and implies do not affect choice symbols + if not self.choice: + for default, cond in self.defaults: + cond_val = expr_value(cond) + if cond_val: + val = min(expr_value(default), cond_val) + break - for _, cond_expr in self.prompts: - if self.config._expr_depends_on(cond_expr, on): - return True + val = max(expr_value(self.rev_dep), + expr_value(self.weak_rev_dep), + val) - return False + # Transpose mod to yes if type is bool (possibly due to modules + # being disabled) + if val == 1 and self.type is BOOL: + val = 2 + + return TRI_TO_STR[val] + + if self.orig_type: # STRING/INT/HEX + for default, cond in self.defaults: + if expr_value(cond): + return default.str_value + + return "" + + def _warn_select_unsatisfied_deps(self): + # Helper for printing an informative warning when a symbol with + # unsatisfied direct dependencies (dependencies from 'depends on', ifs, + # and menus) is selected by some other symbol. Also warn if a symbol + # whose direct dependencies evaluate to m is selected to y. + + msg = "{} has direct dependencies {} with value {}, but is " \ + "currently being {}-selected by the following symbols:" \ + .format(_name_and_loc(self), expr_str(self.direct_dep), + TRI_TO_STR[expr_value(self.direct_dep)], + TRI_TO_STR[expr_value(self.rev_dep)]) + + # The reverse dependencies from each select are ORed together + for select in split_expr(self.rev_dep, OR): + if expr_value(select) <= expr_value(self.direct_dep): + # Only include selects that exceed the direct dependencies + continue + + # - 'select A if B' turns into A && B + # - 'select A' just turns into A + # + # In both cases, we can split on AND and pick the first operand + selecting_sym = split_expr(select, AND)[0] + + msg += "\n - {}, with value {}, direct dependencies {} " \ + "(value: {})" \ + .format(_name_and_loc(selecting_sym), + selecting_sym.str_value, + expr_str(selecting_sym.direct_dep), + TRI_TO_STR[expr_value(selecting_sym.direct_dep)]) + + if select.__class__ is tuple: + msg += ", and select condition {} (value: {})" \ + .format(expr_str(select[2]), + TRI_TO_STR[expr_value(select[2])]) + + self.kconfig._warn(msg) + + +class Choice(object): + """ + Represents a choice statement: + + choice + ... + endchoice + + The following attributes are available on Choice instances. They should be + treated as read-only, and some are implemented through @property magic (but + are still efficient to access due to internal caching). -class Menu(Item): + Note: Prompts, help texts, and locations are stored in the Choice's + MenuNode(s) rather than in the Choice itself. Check the MenuNode class and + the Choice.nodes attribute. This organization matches the C tools. - """Represents a menu statement.""" + name: + The name of the choice, e.g. "FOO" for 'choice FOO', or None if the + Choice has no name. + + type: + The type of the choice. One of BOOL, TRISTATE, UNKNOWN. UNKNOWN is for + choices defined without a type where none of the contained symbols have a + type either (otherwise the choice inherits the type of the first symbol + defined with a type). + + 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. + + orig_type: + The type as given in the Kconfig file, without any magic applied. Used + when printing the choice. + + tri_value: + The tristate value (mode) of the choice. A choice can be in one of three + modes: + + 0 (n) - The choice is disabled and no symbols can be selected. For + visible choices, this mode is only possible for choices with + the 'optional' flag set (see kconfig-language.txt). + + 1 (m) - Any number of choice symbols can be set to m, the rest will + be n. + + 2 (y) - One symbol will be y, the rest n. + + Only tristate choices can be in m mode. The visibility of the choice is + an upper bound on the mode, and the mode in turn is an upper bound on the + visibility of the choice symbols. + + To change the mode, use Choice.set_value(). + + 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 visible non-optional choices (the reverse + dependency is 'm && <visibility>'). + + Symbols within choices get the choice propagated as a dependency to + their properties. This turns the mode of the choice into an upper bound + on e.g. the visibility of choice symbols, and explains the gotcha + related to printing choice symbols mentioned in the module docstring. + + 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 and compatibility. + + assignable: + See the symbol class documentation. Gives the assignable values (modes). + + visibility: + See the Symbol class documentation. Acts on the value (mode). + + selection: + The Symbol instance of the currently selected symbol. None if the Choice + is not in y mode or 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. + + user_value: + The value (mode) selected by the user through Choice.set_value(). Either + 0, 1, or 2, or None if the user hasn't selected a mode. See + Symbol.user_value. + + WARNING: Do not assign directly to this. It will break things. Use + Choice.set_value() instead. + + user_selection: + The symbol selected by the user (by setting it to y). Ignored if the + choice is not in y mode, but still remembered so that the choice "snaps + back" to the user selection if the mode is changed back to y. This might + differ from 'selection' due to unsatisfied dependencies. + + WARNING: Do not assign directly to this. It will break things. Call + sym.set_value(2) on the choice symbol to be selected instead. + + syms: + List of symbols contained in the choice. + + Obscure 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'. + + nodes: + A list of MenuNodes for this choice. In practice, the list will probably + always contain a single MenuNode, but it is possible to give a choice a + name and define it in multiple locations. + + defaults: + 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.kconfig.y. + + Note that 'depends on' and parent dependencies are propagated to + 'default' conditions. + + orig_defaults: + See the corresponding attribute on the MenuNode class. + + direct_dep: + See Symbol.direct_dep. + + referenced: + A set() with all symbols referenced in the properties and property + conditions of the choice. + + Also includes dependencies from surrounding menus and ifs, because those + get propagated to the choice (see the 'Intro to symbol values' section in + the module docstring). + + is_optional: + True if the choice has the 'optional' flag set on it and can be in + n mode. + + kconfig: + The Kconfig instance this choice is from. + """ + __slots__ = ( + "_cached_assignable", + "_cached_selection", + "_cached_vis", + "_dependents", + "_visited", + "_was_set", + "defaults", + "direct_dep", + "is_constant", + "is_optional", + "kconfig", + "name", + "nodes", + "orig_type", + "syms", + "user_selection", + "user_value", + ) # # Public interface # - def get_config(self): - """Return the Config instance this menu is from.""" - return self.config - - def get_title(self): - """Returns the title text of the menu.""" - return self.title - - def get_parent(self): - """Returns the menu or choice statement that contains the menu, or - None if the menu is at the top level. Note that if statements are - treated as syntactic sugar and do not have an explicit class - representation.""" - return self.parent - - def get_location(self): - """Returns the location of the menu as a (filename, linenr) tuple, - where filename is a string and linenr an int.""" - return (self.filename, self.linenr) - - def get_items(self, recursive=False): - """Returns a list containing the items (symbols, menus, choice - statements and comments) in in the menu, in the same order that the - items appear within the menu. - - recursive (default: False): True if items contained in items within the - menu should be included recursively (preorder).""" - - if not recursive: - return self.block - - res = [] - for item in self.block: - res.append(item) - if isinstance(item, Menu): - res.extend(item.get_items(True)) - elif isinstance(item, Choice): - res.extend(item.get_items()) - return res + @property + def type(self): + """ + Returns the type of the choice. See Symbol.type. + """ + if self.orig_type is TRISTATE and not self.kconfig.modules.tri_value: + return BOOL + return self.orig_type + + @property + def str_value(self): + """ + See the class documentation. + """ + return TRI_TO_STR[self.tri_value] + + @property + def tri_value(self): + """ + See the class documentation. + """ + # This emulates a reverse dependency of 'm && visibility' for + # non-optional choices, which is how the C implementation does it + + val = 0 if self.is_optional else 1 + + if self.user_value is not None: + val = max(val, self.user_value) + + # Warning: See Symbol._rec_invalidate(), and note that this is a hidden + # function call (property magic) + val = min(val, self.visibility) - def get_symbols(self, recursive=False): - """Returns a list containing the symbols in the menu, in the same order - that they appear within the menu. + # Promote m to y for boolean choices + return 2 if val == 1 and self.type is BOOL else val - recursive (default: False): True if symbols contained in items within - the menu should be included recursively.""" + @property + def assignable(self): + """ + See the class documentation. + """ + if self._cached_assignable is None: + self._cached_assignable = self._assignable() + return self._cached_assignable + + @property + def visibility(self): + """ + See the class documentation. + """ + if self._cached_vis is None: + self._cached_vis = _visibility(self) + return self._cached_vis - return [item for item in self.get_items(recursive) if - isinstance(item, Symbol)] + @property + def selection(self): + """ + See the class documentation. + """ + if self._cached_selection is _NO_CACHED_SELECTION: + self._cached_selection = self._selection() + return self._cached_selection - def get_visibility(self): - """Returns the visibility of the menu. This also affects the visibility - of subitems. See also Symbol.get_visibility().""" - return self.config._eval_expr(self.dep_expr) + def set_value(self, value): + """ + Sets the user value (mode) of the choice. Like for Symbol.set_value(), + the visibility might truncate the value. Choices without the 'optional' + attribute (is_optional) can never be in n mode, but 0/"n" is still + accepted since it's not a malformed value (though it will have no + effect). + + Returns True if the value is valid for the type of the choice, and + False otherwise. This only looks at the form of the value. Check the + Choice.assignable attribute to see what values are currently in range + and would actually be reflected in the mode of the choice. + """ + if value in STR_TO_TRI: + value = STR_TO_TRI[value] - def get_visible_if_visibility(self): - """Returns the visibility the menu gets from its 'visible if' - condition. "y" if the menu has no 'visible if' condition.""" - return self.config._eval_expr(self.visible_if_expr) + if value == self.user_value: + # We know the value must be valid if it was successfully set + # previously + self._was_set = True + return True + + if not (self.orig_type is BOOL and value in (2, 0) or + self.orig_type is TRISTATE and value in TRI_TO_STR): + + # Display tristate values as n, m, y in the warning + self.kconfig._warn( + "the value {} is invalid for {}, which has type {} -- " + "assignment ignored" + .format(TRI_TO_STR[value] if value in TRI_TO_STR else + "'{}'".format(value), + _name_and_loc(self), TYPE_TO_STR[self.orig_type])) + + return False + + self.user_value = value + self._was_set = True + self._rec_invalidate() + + return True + + def unset_value(self): + """ + Resets the user value (mode) and user selection of the Choice, as if + the user had never touched the mode or any of the choice symbols. + """ + if self.user_value is not None or self.user_selection: + self.user_value = self.user_selection = None + self._rec_invalidate() + + @property + def referenced(self): + """ + See the class documentation. + """ + return {item for node in self.nodes for item in node.referenced} + + @property + def orig_defaults(self): + """ + See the class documentation. + """ + return [d for node in self.nodes for d in node.orig_defaults] + + def __repr__(self): + """ + Returns a string with information about the choice when it is evaluated + on e.g. the interactive Python prompt. + """ + fields = ["choice " + self.name if self.name else "choice", + TYPE_TO_STR[self.type]] + add = fields.append - def get_referenced_symbols(self, refs_from_enclosing=False): - """See Symbol.get_referenced_symbols().""" - return self.all_referenced_syms if refs_from_enclosing else \ - self.referenced_syms + for node in self.nodes: + if node.prompt: + add('"{}"'.format(node.prompt[0])) + + add("mode " + self.str_value) + + if self.user_value is not None: + add('user mode {}'.format(TRI_TO_STR[self.user_value])) + + if self.selection: + add("{} selected".format(self.selection.name)) + + if self.user_selection: + user_sel_str = "{} selected by user" \ + .format(self.user_selection.name) + + if self.selection is not self.user_selection: + user_sel_str += " (overridden)" + + add(user_sel_str) + + add("visibility " + TRI_TO_STR[self.visibility]) + + if self.is_optional: + add("optional") + + for node in self.nodes: + add("{}:{}".format(node.filename, node.linenr)) + + return "<{}>".format(", ".join(fields)) def __str__(self): - """Returns a string containing various information about the menu.""" - depends_on_str = self.config._expr_val_str(self.orig_deps, - "(no dependencies)") - visible_if_str = self.config._expr_val_str(self.visible_if_expr, - "(no dependencies)") - - additional_deps_str = " " + \ - self.config._expr_val_str(self.deps_from_containing, - "(no additional dependencies)") - - return _lines("Menu", - "Title : " + self.title, - "'depends on' dependencies : " + depends_on_str, - "'visible if' dependencies : " + visible_if_str, - "Additional dependencies from enclosing menus and " - "ifs:", - additional_deps_str, - "Location: {0}:{1}".format(self.filename, self.linenr)) + """ + Returns a string representation of the choice when it is printed. + Matches the Kconfig format (though without the contained choice + symbols), with any parent dependencies propagated to the 'depends on' + condition. + + The returned string does not end in a newline. + + See Symbol.__str__() as well. + """ + return self.custom_str(standard_sc_expr_str) + + def custom_str(self, sc_expr_str_fn): + """ + Works like Choice.__str__(), but allows a custom format to be used for + all symbol/choice references. See expr_str(). + """ + return "\n\n".join(node.custom_str(sc_expr_str_fn) + for node in self.nodes) # # Private methods # def __init__(self): - """Menu constructor -- not intended to be called directly by - Kconfiglib clients.""" - - self.title = None - self.dep_expr = None - self.visible_if_expr = None - self.block = [] # List of contained items - self.config = None - self.parent = None - - # Dependency expression without dependencies from enclosing menus and - # ifs propagated - self.orig_deps = None - - # Dependencies inherited from containing menus and ifs - self.deps_from_containing = None - # The set of symbols referenced by this menu (see - # get_referenced_symbols()) - self.referenced_syms = set() - # Like 'referenced_syms', but includes symbols from - # dependencies inherited from enclosing menus and ifs - self.all_referenced_syms = None + """ + Choice constructor -- not intended to be called directly by Kconfiglib + clients. + """ + # These attributes are always set on the instance from outside and + # don't need defaults: + # direct_dep + # kconfig - self.filename = None - self.linenr = None + # - UNKNOWN == 0 + # - _visited is used during dep. loop detection + self.orig_type = self._visited = 0 - def _make_conf(self, append_fn): - if self.config._eval_expr(self.dep_expr) != "n" and \ - self.config._eval_expr(self.visible_if_expr) != "n": - append_fn("\n#\n# {0}\n#".format(self.title)) - _make_block_conf(self.block, append_fn) + self.nodes = [] -class Choice(Item): + self.syms = [] + self.defaults = [] - """Represents a choice statement. A choice can be in one of three modes: + self.name = \ + self.user_value = self.user_selection = \ + self._cached_vis = self._cached_assignable = None - "n" - The choice is not visible and no symbols can be selected. + self._cached_selection = _NO_CACHED_SELECTION - "m" - Any number of symbols can be set to "m". The rest will be "n". This - is safe since potentially conflicting options don't actually get - compiled into the kernel simultaneously with "m". + # is_constant is checked by _make_depend_on(). Just set it to avoid + # having to special-case choices. + self.is_constant = self.is_optional = False - "y" - One symbol will be "y" while the rest are "n". + # See Kconfig._build_dep() + self._dependents = set() - Only tristate choices can be in "m" mode, and the visibility of the choice - is an upper bound on the mode, so that e.g. a choice that depends on a - symbol with value "m" will be in "m" mode. + def _assignable(self): + # Worker function for the 'assignable' attribute - The mode changes automatically when a value is assigned to a symbol within - the choice. + # Warning: See Symbol._rec_invalidate(), and note that this is a hidden + # function call (property magic) + vis = self.visibility - See Symbol.get_visibility() too.""" + if not vis: + return () - # - # Public interface - # + if vis == 2: + if not self.is_optional: + return (2,) if self.type is BOOL else (1, 2) + return (0, 2) if self.type is BOOL else (0, 1, 2) + + # vis == 1 - def get_config(self): - """Returns the Config instance this choice is from.""" - return self.config - - def get_name(self): - """For named choices, returns the name. Returns None for unnamed - choices. No named choices appear anywhere in the kernel Kconfig files - as of Linux 3.7.0-rc8.""" - return self.name - - def get_type(self): - """Returns the type of the choice. See Symbol.get_type().""" - return self.type - - def get_prompts(self): - """Returns a list of prompts defined for the choice, in the order they - appear in the configuration files. Returns the empty list for choices - with no prompt. - - This list will have a single entry for the vast majority of choices - having prompts, but having multiple prompts for a single choice is - possible through having multiple 'choice' entries for it (though I'm - not sure if that ever happens in practice).""" - return [prompt for prompt, _ in self.orig_prompts] - - def get_help(self): - """Returns the help text of the choice, or None if the choice has no - help text.""" - return self.help - - def get_parent(self): - """Returns the menu or choice statement that contains the choice, or - None if the choice is at the top level. Note that if statements are - treated as syntactic sugar and do not have an explicit class - representation.""" - return self.parent - - def get_def_locations(self): - """Returns a list of (filename, linenr) tuples, where filename (string) - and linenr (int) represent a location where the choice is defined. For - the vast majority of choices (all of them as of Linux 3.7.0-rc8) this - list will only contain one element, but its possible for named choices - to be defined in multiple locations.""" - return self.def_locations - - def get_selection(self): - """Returns the symbol selected (either by the user or through - defaults), or None if either no symbol is selected or the mode is not - "y".""" - if self.cached_selection is not None: - if self.cached_selection == NO_SELECTION: - return None - return self.cached_selection - - if self.get_mode() != "y": - return self._cache_ret(None) - - # User choice available? - if self.user_val is not None and _get_visibility(self.user_val) == "y": - return self._cache_ret(self.user_val) - - if self.optional: - return self._cache_ret(None) - - return self._cache_ret(self.get_selection_from_defaults()) - - def get_selection_from_defaults(self): - """Like Choice.get_selection(), but acts as if no symbol has been - selected by the user and no 'optional' flag is in effect.""" - - if not self.actual_symbols: + return (0, 1) if self.is_optional else (1,) + + def _selection(self): + # Worker function for the 'selection' attribute + + # Warning: See Symbol._rec_invalidate(), and note that this is a hidden + # function call (property magic) + if self.tri_value != 2: + # Not in y mode, so no selection return None - for symbol, cond_expr in self.def_exprs: - if self.config._eval_expr(cond_expr) != "n": - chosen_symbol = symbol - break - else: - chosen_symbol = self.actual_symbols[0] - - # Is the chosen symbol visible? - if _get_visibility(chosen_symbol) != "n": - return chosen_symbol - # Otherwise, pick the first visible symbol - for sym in self.actual_symbols: - if _get_visibility(sym) != "n": + # Use the user selection if it's visible + if self.user_selection and self.user_selection.visibility: + return self.user_selection + + # Otherwise, check if we have a default + return self._selection_from_defaults() + + def _selection_from_defaults(self): + # Check if we have a default + for sym, cond in self.defaults: + # The default symbol must be visible too + if expr_value(cond) and sym.visibility: + return sym + + # Otherwise, pick the first visible symbol, if any + for sym in self.syms: + if sym.visibility: return sym + + # Couldn't find a selection return None - def get_user_selection(self): - """If the choice is in "y" mode and has a user-selected symbol, returns - that symbol. Otherwise, returns None.""" - return self.user_val - - def get_items(self): - """Gets all items contained in the choice in the same order as within - the configuration ("items" instead of "symbols" since choices and - comments might appear within choices. This only happens in one place as - of Linux 3.7.0-rc8, in drivers/usb/gadget/Kconfig).""" - return self.block - - def get_symbols(self): - """Returns a list containing the choice's symbols. - - A quirk (perhaps a bug) of Kconfig is that you can put items within a - choice that will not be considered members of the choice insofar as - selection is concerned. This happens for example if one symbol within a - choice 'depends on' the symbol preceding it, or if you put non-symbol - items within choices. - - As of Linux 3.7.0-rc8, this seems to be used intentionally in one - place: drivers/usb/gadget/Kconfig. - - This function returns the "proper" symbols of the choice in the order - they appear in the choice, excluding such items. If you want all items - in the choice, use get_items().""" - return self.actual_symbols - - def get_referenced_symbols(self, refs_from_enclosing=False): - """See Symbol.get_referenced_symbols().""" - return self.all_referenced_syms if refs_from_enclosing else \ - self.referenced_syms - - def get_visibility(self): - """Returns the visibility of the choice statement: one of "n", "m" or - "y". This acts as an upper limit on the mode of the choice (though bool - choices can only have the mode "y"). See the class documentation for an - explanation of modes.""" - return _get_visibility(self) - - def get_mode(self): - """Returns the mode of the choice. See the class documentation for - an explanation of modes.""" - minimum_mode = "n" if self.optional else "m" - mode = self.user_mode if self.user_mode is not None else minimum_mode - mode = self.config._eval_min(mode, _get_visibility(self)) - - # Promote "m" to "y" for boolean choices - if mode == "m" and self.type == BOOL: - return "y" - - return mode - - def is_optional(self): - """Returns True if the choice has the 'optional' flag set (and so will - default to "n" mode).""" - return self.optional + def _invalidate(self): + self._cached_vis = self._cached_assignable = None + self._cached_selection = _NO_CACHED_SELECTION - def __str__(self): - """Returns a string containing various information about the choice - statement.""" - return self.config._get_sym_or_choice_str(self) + def _rec_invalidate(self): + # See Symbol._rec_invalidate() - # - # Private methods - # + self._invalidate() + + for item in self._dependents: + if item._cached_vis is not None: + item._rec_invalidate() + + +class MenuNode(object): + """ + Represents a menu node in the configuration. This corresponds to an entry + in e.g. the 'make menuconfig' interface, though non-visible choices, menus, + and comments also get menu nodes. If a symbol or choice is defined in + multiple locations, it gets one menu node for each location. + + The top-level menu node, corresponding to the implicit top-level menu, is + available in Kconfig.top_node. + + The menu nodes for a Symbol or Choice can be found in the + Symbol/Choice.nodes attribute. Menus and comments are represented as plain + menu nodes, with their text stored in the prompt attribute (prompt[0]). + This mirrors the C implementation. + + The following attributes are available on MenuNode instances. They should + be viewed as read-only. + + item: + Either a Symbol, a Choice, or one of the constants MENU and COMMENT. + Menus and comments are represented as plain menu nodes. Ifs are collapsed + (matching the C implementation) and do not appear in the final menu tree. + + next: + The following menu node. None if there is no following node. + + list: + The first child menu node. None if there are no children. + + Choices and menus naturally have children, but Symbols can also have + children because of menus created automatically from dependencies (see + kconfig-language.txt). + + parent: + The parent menu node. None if there is no parent. + + prompt: + A (string, cond) tuple with the prompt for the menu node and its + conditional expression (which is self.kconfig.y if there is no + condition). None if there is no prompt. + + For symbols and choices, the prompt is stored in the MenuNode rather than + the Symbol or Choice instance. For menus and comments, the prompt holds + the text. + + defaults: + The 'default' properties for this particular menu node. See + symbol.defaults. + + When evaluating defaults, you should use Symbol/Choice.defaults instead, + as it include properties from all menu nodes (a symbol/choice can have + multiple definition locations/menu nodes). MenuNode.defaults is meant for + documentation generation. + + selects: + Like MenuNode.defaults, for selects. + + implies: + Like MenuNode.defaults, for implies. + + ranges: + Like MenuNode.defaults, for ranges. + + orig_prompt: + orig_defaults: + orig_selects: + orig_implies: + orig_ranges: + These work the like the corresponding attributes without orig_*, but omit + any dependencies propagated from 'depends on' and surrounding 'if's (the + direct dependencies, stored in MenuNode.dep). + + One use for this is generating less cluttered documentation, by only + showing the direct dependencies in one place. + + help: + The help text for the menu node for Symbols and Choices. None if there is + no help text. Always stored in the node rather than the Symbol or Choice. + It is possible to have a separate help text at each location if a symbol + is defined in multiple locations. + + Trailing whitespace (including a final newline) is stripped from the help + text. This was not the case before Kconfiglib 10.21.0, where the format + was undocumented. + + dep: + The direct ('depends on') dependencies for the menu node, or + self.kconfig.y if there are no direct dependencies. + + This attribute includes any dependencies from surrounding menus and ifs. + Those get propagated to the direct dependencies, and the resulting direct + dependencies in turn get propagated to the conditions of all properties. + + If a symbol or choice is defined in multiple locations, only the + properties defined at a particular location get the corresponding + MenuNode.dep dependencies propagated to them. + + visibility: + The 'visible if' dependencies for the menu node (which must represent a + menu), or self.kconfig.y if there are no 'visible if' dependencies. + 'visible if' dependencies are recursively propagated to the prompts of + symbols and choices within the menu. + + referenced: + A set() with all symbols and choices referenced in the properties and + property conditions of the menu node. + + Also includes dependencies inherited from surrounding menus and ifs. + Choices appear in the dependencies of choice symbols. + + is_menuconfig: + Set to True if the children of the menu node should be displayed in a + separate menu. This is the case for the following items: + + - Menus (node.item == MENU) + + - Choices + + - Symbols defined with the 'menuconfig' keyword. The children come from + implicitly created submenus, and should be displayed in a separate + menu rather than being indented. + + 'is_menuconfig' is just a hint on how to display the menu node. It's + ignored internally by Kconfiglib, except when printing symbols. + + filename/linenr: + The location where the menu node appears. The filename is relative to + $srctree (or to the current directory if $srctree isn't set), except + absolute paths are used for paths outside $srctree. + + include_path: + A tuple of (filename, linenr) tuples, giving the locations of the + 'source' statements via which the Kconfig file containing this menu node + was included. The first element is the location of the 'source' statement + in the top-level Kconfig file passed to Kconfig.__init__(), etc. + + Note that the Kconfig file of the menu node itself isn't included. Check + 'filename' and 'linenr' for that. + + kconfig: + The Kconfig instance the menu node is from. + """ + __slots__ = ( + "dep", + "filename", + "help", + "include_path", + "is_menuconfig", + "item", + "kconfig", + "linenr", + "list", + "next", + "parent", + "prompt", + "visibility", + + # Properties + "defaults", + "selects", + "implies", + "ranges", + ) def __init__(self): - """Choice constructor -- not intended to be called directly by - Kconfiglib clients.""" - - self.name = None # Yes, choices can be named - self.type = UNKNOWN - self.prompts = [] - self.def_exprs = [] # 'default' properties - self.help = None # Help text - self.block = [] # List of contained items - self.config = None - self.parent = None - - self.user_val = None - self.user_mode = None - - # We need to filter out symbols that appear within the choice block but - # are not considered choice items (see - # Choice._determine_actual_symbols()) This list holds the "actual" - # choice items. - self.actual_symbols = [] - - # The prompts and default values without any dependencies from - # enclosing menus and ifs propagated - self.orig_prompts = [] - self.orig_def_exprs = [] - - # Dependencies inherited from containing menus and ifs - self.deps_from_containing = None - # The set of symbols referenced by this choice (see - # get_referenced_symbols()) - self.referenced_syms = set() - # Like 'referenced_syms', but includes symbols from - # dependencies inherited from enclosing menus and ifs - self.all_referenced_syms = set() - - # See Choice.get_def_locations() - self.def_locations = [] - - # Cached values - self.cached_selection = None - self.cached_visibility = None - - self.optional = False - - def _determine_actual_symbols(self): - """If a symbol's visibility depends on the preceding symbol within a - choice, it is no longer viewed as a choice item. (This is quite - possibly a bug, but some things consciously use it... ugh. It stems - from automatic submenu creation.) In addition, it's possible to have - choices and comments within choices, and those shouldn't be considered - choice items either. Only drivers/usb/gadget/Kconfig seems to depend on - any of this. This method computes the "actual" items in the choice and - sets the is_choice_sym flag on them (retrieved via is_choice_symbol()). - - Don't let this scare you: an earlier version simply checked for a - sequence of symbols where all symbols after the first appeared in the - 'depends on' expression of the first, and that worked fine. The added - complexity is to be future-proof in the event that - drivers/usb/gadget/Kconfig turns even more sinister. It might very well - be overkilling things (especially if that file is refactored ;).""" - - # Items might depend on each other in a tree structure, so we need a - # stack to keep track of the current tentative parent - stack = [] - - for item in self.block: - if not isinstance(item, Symbol): - stack = [] - continue + # Properties defined on this particular menu node. A local 'depends on' + # only applies to these, in case a symbol is defined in multiple + # locations. + self.defaults = [] + self.selects = [] + self.implies = [] + self.ranges = [] + + @property + def orig_prompt(self): + """ + See the class documentation. + """ + if not self.prompt: + return None + return (self.prompt[0], self._strip_dep(self.prompt[1])) - while stack: - if item._has_auto_menu_dep_on(stack[-1]): - # The item should not be viewed as a choice item, so don't - # set item.is_choice_sym - stack.append(item) - break - else: - stack.pop() - else: - item.is_choice_sym = True - self.actual_symbols.append(item) - stack.append(item) - - def _cache_ret(self, selection): - # As None is used to indicate the lack of a cached value we can't use - # that to cache the fact that the choice has no selection. Instead, we - # use the symbolic constant NO_SELECTION. - if selection is None: - self.cached_selection = NO_SELECTION - else: - self.cached_selection = selection + @property + def orig_defaults(self): + """ + See the class documentation. + """ + return [(default, self._strip_dep(cond)) + for default, cond in self.defaults] - return selection + @property + def orig_selects(self): + """ + See the class documentation. + """ + return [(select, self._strip_dep(cond)) + for select, cond in self.selects] - def _invalidate(self): - self.cached_selection = None - self.cached_visibility = None + @property + def orig_implies(self): + """ + See the class documentation. + """ + return [(imply, self._strip_dep(cond)) + for imply, cond in self.implies] - def _unset_user_value(self): - self._invalidate() - self.user_val = None - self.user_mode = None + @property + def orig_ranges(self): + """ + See the class documentation. + """ + return [(low, high, self._strip_dep(cond)) + for low, high, cond in self.ranges] - def _make_conf(self, append_fn): - _make_block_conf(self.block, append_fn) + @property + def referenced(self): + """ + See the class documentation. + """ + # self.dep is included to catch dependencies from a lone 'depends on' + # when there are no properties to propagate it to + res = expr_items(self.dep) -class Comment(Item): + if self.prompt: + res |= expr_items(self.prompt[1]) - """Represents a comment statement.""" + if self.item is MENU: + res |= expr_items(self.visibility) - # - # Public interface - # + for value, cond in self.defaults: + res |= expr_items(value) + res |= expr_items(cond) - def get_config(self): - """Returns the Config instance this comment is from.""" - return self.config + for value, cond in self.selects: + res.add(value) + res |= expr_items(cond) - def get_text(self): - """Returns the text of the comment.""" - return self.text + for value, cond in self.implies: + res.add(value) + res |= expr_items(cond) - def get_parent(self): - """Returns the menu or choice statement that contains the comment, or - None if the comment is at the top level. Note that if statements are - treated as syntactic sugar and do not have an explicit class - representation.""" - return self.parent + for low, high, cond in self.ranges: + res.add(low) + res.add(high) + res |= expr_items(cond) - def get_location(self): - """Returns the location of the comment as a (filename, linenr) tuple, - where filename is a string and linenr an int.""" - return (self.filename, self.linenr) + return res - def get_visibility(self): - """Returns the visibility of the comment. See also - Symbol.get_visibility().""" - return self.config._eval_expr(self.dep_expr) + def __repr__(self): + """ + Returns a string with information about the menu node when it is + evaluated on e.g. the interactive Python prompt. + """ + fields = [] + add = fields.append + + if self.item.__class__ is Symbol: + add("menu node for symbol " + self.item.name) + + elif self.item.__class__ is Choice: + s = "menu node for choice" + if self.item.name is not None: + s += " " + self.item.name + add(s) + + elif self.item is MENU: + add("menu node for menu") + + else: # self.item is COMMENT + add("menu node for comment") + + if self.prompt: + add('prompt "{}" (visibility {})'.format( + self.prompt[0], TRI_TO_STR[expr_value(self.prompt[1])])) + + if self.item.__class__ is Symbol and self.is_menuconfig: + add("is menuconfig") + + add("deps " + TRI_TO_STR[expr_value(self.dep)]) + + if self.item is MENU: + add("'visible if' deps " + TRI_TO_STR[expr_value(self.visibility)]) - def get_referenced_symbols(self, refs_from_enclosing=False): - """See Symbol.get_referenced_symbols().""" - return self.all_referenced_syms if refs_from_enclosing else \ - self.referenced_syms + if self.item.__class__ in _SYMBOL_CHOICE and self.help is not None: + add("has help") + + if self.list: + add("has child") + + if self.next: + add("has next") + + add("{}:{}".format(self.filename, self.linenr)) + + return "<{}>".format(", ".join(fields)) def __str__(self): - """Returns a string containing various information about the - comment.""" - dep_str = self.config._expr_val_str(self.orig_deps, - "(no dependencies)") - - additional_deps_str = " " + \ - self.config._expr_val_str(self.deps_from_containing, - "(no additional dependencies)") - - return _lines("Comment", - "Text: " + str(self.text), - "Dependencies: " + dep_str, - "Additional dependencies from enclosing menus and " - "ifs:", - additional_deps_str, - "Location: {0}:{1}".format(self.filename, self.linenr)) + """ + Returns a string representation of the menu node. Matches the Kconfig + format, with any parent dependencies propagated to the 'depends on' + condition. - # - # Private methods - # + The output could (almost) be fed back into a Kconfig parser to redefine + the object associated with the menu node. See the module documentation + for a gotcha related to choice symbols. - def __init__(self): - """Comment constructor -- not intended to be called directly by - Kconfiglib clients.""" - - self.text = None - self.dep_expr = None - self.config = None - self.parent = None - - # Dependency expression without dependencies from enclosing menus and - # ifs propagated - self.orig_deps = None - - # Dependencies inherited from containing menus and ifs - self.deps_from_containing = None - # The set of symbols referenced by this comment (see - # get_referenced_symbols()) - self.referenced_syms = set() - # Like 'referenced_syms', but includes symbols from - # dependencies inherited from enclosing menus and ifs - self.all_referenced_syms = None + For symbols and choices with multiple menu nodes (multiple definition + locations), properties that aren't associated with a particular menu + node are shown on all menu nodes ('option env=...', 'optional' for + choices, etc.). - self.filename = None - self.linenr = None + The returned string does not end in a newline. + """ + return self.custom_str(standard_sc_expr_str) + + def custom_str(self, sc_expr_str_fn): + """ + Works like MenuNode.__str__(), but allows a custom format to be used + for all symbol/choice references. See expr_str(). + """ + return self._menu_comment_node_str(sc_expr_str_fn) \ + if self.item in _MENU_COMMENT else \ + self._sym_choice_node_str(sc_expr_str_fn) + + def _menu_comment_node_str(self, sc_expr_str_fn): + s = '{} "{}"'.format("menu" if self.item is MENU else "comment", + self.prompt[0]) + + if self.dep is not self.kconfig.y: + s += "\n\tdepends on {}".format(expr_str(self.dep, sc_expr_str_fn)) + + if self.item is MENU and self.visibility is not self.kconfig.y: + s += "\n\tvisible if {}".format(expr_str(self.visibility, + sc_expr_str_fn)) + + return s + + def _sym_choice_node_str(self, sc_expr_str_fn): + def indent_add(s): + lines.append("\t" + s) + + def indent_add_cond(s, cond): + if cond is not self.kconfig.y: + s += " if " + expr_str(cond, sc_expr_str_fn) + indent_add(s) + + sc = self.item + + if sc.__class__ is Symbol: + lines = [("menuconfig " if self.is_menuconfig else "config ") + + sc.name] + else: + lines = ["choice " + sc.name if sc.name else "choice"] + + if sc.orig_type and not self.prompt: # sc.orig_type != UNKNOWN + # If there's a prompt, we'll use the '<type> "prompt"' shorthand + # instead + indent_add(TYPE_TO_STR[sc.orig_type]) + + if self.prompt: + if sc.orig_type: + prefix = TYPE_TO_STR[sc.orig_type] + else: + # Symbol defined without a type (which generates a warning) + prefix = "prompt" + + indent_add_cond(prefix + ' "{}"'.format(escape(self.prompt[0])), + self.orig_prompt[1]) + + if sc.__class__ is Symbol: + if sc.is_allnoconfig_y: + indent_add("option allnoconfig_y") - def _make_conf(self, append_fn): - if self.config._eval_expr(self.dep_expr) != "n": - append_fn("\n#\n# {0}\n#".format(self.text)) + if sc is sc.kconfig.defconfig_list: + indent_add("option defconfig_list") -class Kconfig_Syntax_Error(Exception): - """Exception raised for syntax errors.""" - pass + if sc.env_var is not None: + indent_add('option env="{}"'.format(sc.env_var)) + + if sc is sc.kconfig.modules: + indent_add("option modules") + + for low, high, cond in self.orig_ranges: + indent_add_cond( + "range {} {}".format(sc_expr_str_fn(low), + sc_expr_str_fn(high)), + cond) + + for default, cond in self.orig_defaults: + indent_add_cond("default " + expr_str(default, sc_expr_str_fn), + cond) + + if sc.__class__ is Choice and sc.is_optional: + indent_add("optional") + + if sc.__class__ is Symbol: + for select, cond in self.orig_selects: + indent_add_cond("select " + sc_expr_str_fn(select), cond) + + for imply, cond in self.orig_implies: + indent_add_cond("imply " + sc_expr_str_fn(imply), cond) + + if self.dep is not sc.kconfig.y: + indent_add("depends on " + expr_str(self.dep, sc_expr_str_fn)) + + if self.help is not None: + indent_add("help") + for line in self.help.splitlines(): + indent_add(" " + line) + + return "\n".join(lines) + + def _strip_dep(self, expr): + # Helper function for removing MenuNode.dep from 'expr'. Uses two + # pieces of internal knowledge: (1) Expressions are reused rather than + # copied, and (2) the direct dependencies always appear at the end. + + # ... if dep -> ... if y + if self.dep is expr: + return self.kconfig.y + + # (AND, X, dep) -> X + if expr.__class__ is tuple and expr[0] is AND and expr[2] is self.dep: + return expr[1] + + return expr + + +class Variable(object): + """ + Represents a preprocessor variable/function. + + The following attributes are available: + + name: + The name of the variable. + + value: + The unexpanded value of the variable. + + expanded_value: + The expanded value of the variable. For simple variables (those defined + with :=), this will equal 'value'. Accessing this property will raise a + KconfigError if the expansion seems to be stuck in a loop. + + Accessing this field is the same as calling expanded_value_w_args() with + no arguments. I hadn't considered function arguments when adding it. It + is retained for backwards compatibility though. + + is_recursive: + True if the variable is recursive (defined with =). + """ + __slots__ = ( + "_n_expansions", + "is_recursive", + "kconfig", + "name", + "value", + ) + + @property + def expanded_value(self): + """ + See the class documentation. + """ + return self.expanded_value_w_args() + + def expanded_value_w_args(self, *args): + """ + Returns the expanded value of the variable/function. Any arguments + passed will be substituted for $(1), $(2), etc. + + Raises a KconfigError if the expansion seems to be stuck in a loop. + """ + return self.kconfig._fn_val((self.name,) + args) + + def __repr__(self): + return "<variable {}, {}, value '{}'>" \ + .format(self.name, + "recursive" if self.is_recursive else "immediate", + self.value) + + +class KconfigError(Exception): + """ + Exception raised for Kconfig-related errors. + + KconfigError and KconfigSyntaxError are the same class. The + KconfigSyntaxError alias is only maintained for backwards compatibility. + """ + +KconfigSyntaxError = KconfigError # Backwards compatibility + + +class InternalError(Exception): + "Never raised. Kept around for backwards compatibility." + + +# Workaround: +# +# If 'errno' and 'strerror' are set on IOError, then __str__() always returns +# "[Errno <errno>] <strerror>", ignoring any custom message passed to the +# constructor. By defining our own subclass, we can use a custom message while +# also providing 'errno', 'strerror', and 'filename' to scripts. +class _KconfigIOError(IOError): + def __init__(self, ioerror, msg): + self.msg = msg + super(_KconfigIOError, self).__init__( + ioerror.errno, ioerror.strerror, ioerror.filename) + + def __str__(self): + return self.msg -class Internal_Error(Exception): - """Exception raised for internal errors.""" - pass # # Public functions # -def tri_less(v1, v2): - """Returns True if the tristate v1 is less than the tristate v2, where "n", - "m" and "y" are ordered from lowest to highest.""" - return TRI_TO_INT[v1] < TRI_TO_INT[v2] -def tri_less_eq(v1, v2): - """Returns True if the tristate v1 is less than or equal to the tristate - v2, where "n", "m" and "y" are ordered from lowest to highest.""" - return TRI_TO_INT[v1] <= TRI_TO_INT[v2] +def expr_value(expr): + """ + Evaluates the expression 'expr' to a tristate value. Returns 0 (n), 1 (m), + or 2 (y). -def tri_greater(v1, v2): - """Returns True if the tristate v1 is greater than the tristate v2, where - "n", "m" and "y" are ordered from lowest to highest.""" - return TRI_TO_INT[v1] > TRI_TO_INT[v2] + 'expr' must be an already-parsed expression from a Symbol, Choice, or + MenuNode property. To evaluate an expression represented as a string, use + Kconfig.eval_string(). -def tri_greater_eq(v1, v2): - """Returns True if the tristate v1 is greater than or equal to the tristate - v2, where "n", "m" and "y" are ordered from lowest to highest.""" - return TRI_TO_INT[v1] >= TRI_TO_INT[v2] + Passing subexpressions of expressions to this function works as expected. + """ + if expr.__class__ is not tuple: + return expr.tri_value -# -# Internal classes -# + if expr[0] is AND: + v1 = expr_value(expr[1]) + # Short-circuit the n case as an optimization (~5% faster + # allnoconfig.py and allyesconfig.py, as of writing) + return 0 if not v1 else min(v1, expr_value(expr[2])) -class _Feed(object): + if expr[0] is OR: + v1 = expr_value(expr[1]) + # Short-circuit the y case as an optimization + return 2 if v1 == 2 else max(v1, expr_value(expr[2])) - """Class for working with sequences in a stream-like fashion; handy for - tokens.""" + if expr[0] is NOT: + return 2 - expr_value(expr[1]) - # This would be more helpful on the item classes, but would remove some - # flexibility - __slots__ = ['items', 'length', 'i'] + # Relation + # + # Implements <, <=, >, >= comparisons as well. These were added to + # kconfig in 31847b67 (kconfig: allow use of relations other than + # (in)equality). - def __init__(self, items): - self.items = items - self.length = len(self.items) - self.i = 0 + rel, v1, v2 = expr - def get_next(self): - if self.i >= self.length: - return None - item = self.items[self.i] - self.i += 1 - return item - - def peek_next(self): - return None if self.i >= self.length else self.items[self.i] - - def check(self, token): - """Check if the next token is 'token'. If so, remove it from the token - feed and return True. Otherwise, leave it in and return False.""" - if self.i < self.length and self.items[self.i] == token: - self.i += 1 - return True - return False + # If both operands are strings... + if v1.orig_type is STRING and v2.orig_type is STRING: + # ...then compare them lexicographically + comp = _strcmp(v1.str_value, v2.str_value) + else: + # Otherwise, try to compare them as numbers + try: + comp = _sym_to_num(v1) - _sym_to_num(v2) + except ValueError: + # Fall back on a lexicographic comparison if the operands don't + # parse as numbers + comp = _strcmp(v1.str_value, v2.str_value) + + return 2*(comp == 0 if rel is EQUAL else + comp != 0 if rel is UNEQUAL else + comp < 0 if rel is LESS else + comp <= 0 if rel is LESS_EQUAL else + comp > 0 if rel is GREATER else + comp >= 0) + + +def standard_sc_expr_str(sc): + """ + Standard symbol/choice printing function. Uses plain Kconfig syntax, and + displays choices as <choice> (or <choice NAME>, for named choices). + + See expr_str(). + """ + if sc.__class__ is Symbol: + if sc.is_constant and sc.name not in STR_TO_TRI: + return '"{}"'.format(escape(sc.name)) + return sc.name + + return "<choice {}>".format(sc.name) if sc.name else "<choice>" + + +def expr_str(expr, sc_expr_str_fn=standard_sc_expr_str): + """ + Returns the string representation of the expression 'expr', as in a Kconfig + file. + + Passing subexpressions of expressions to this function works as expected. + + sc_expr_str_fn (default: standard_sc_expr_str): + This function is called for every symbol/choice (hence "sc") appearing in + the expression, with the symbol/choice as the argument. It is expected to + return a string to be used for the symbol/choice. + + This can be used e.g. to turn symbols/choices into links when generating + documentation, or for printing the value of each symbol/choice after it. + + Note that quoted values are represented as constants symbols + (Symbol.is_constant == True). + """ + if expr.__class__ is not tuple: + return sc_expr_str_fn(expr) + + if expr[0] is AND: + return "{} && {}".format(_parenthesize(expr[1], OR, sc_expr_str_fn), + _parenthesize(expr[2], OR, sc_expr_str_fn)) + + if expr[0] is OR: + # This turns A && B || C && D into "(A && B) || (C && D)", which is + # redundant, but more readable + return "{} || {}".format(_parenthesize(expr[1], AND, sc_expr_str_fn), + _parenthesize(expr[2], AND, sc_expr_str_fn)) + + if expr[0] is NOT: + if expr[1].__class__ is tuple: + return "!({})".format(expr_str(expr[1], sc_expr_str_fn)) + return "!" + sc_expr_str_fn(expr[1]) # Symbol + + # Relation + # + # Relation operands are always symbols (quoted strings are constant + # symbols) + return "{} {} {}".format(sc_expr_str_fn(expr[1]), REL_TO_STR[expr[0]], + sc_expr_str_fn(expr[2])) - def unget_all(self): - self.i = 0 -class _FileFeed(object): +def expr_items(expr): + """ + Returns a set() of all items (symbols and choices) that appear in the + expression 'expr'. - """Feeds lines from a file. Keeps track of the filename and current line - number. Joins any line ending in \\ with the following line. We need to be - careful to get the line number right in the presence of continuation - lines.""" + Passing subexpressions of expressions to this function works as expected. + """ + res = set() - __slots__ = ['filename', 'lines', 'length', 'linenr'] + def rec(subexpr): + if subexpr.__class__ is tuple: + # AND, OR, NOT, or relation - def __init__(self, filename): - self.filename = _clean_up_path(filename) - with open(filename, "r") as f: - # No interleaving of I/O and processing yet. Don't know if it would - # help. - self.lines = f.readlines() - self.length = len(self.lines) - self.linenr = 0 + rec(subexpr[1]) - def get_next(self): - if self.linenr >= self.length: - return None - line = self.lines[self.linenr] - self.linenr += 1 - while line.endswith("\\\n"): - line = line[:-2] + self.lines[self.linenr] - self.linenr += 1 - return line + # NOTs only have a single operand + if subexpr[0] is not NOT: + rec(subexpr[2]) - def peek_next(self): - linenr = self.linenr - if linenr >= self.length: - return None - line = self.lines[linenr] - while line.endswith("\\\n"): - linenr += 1 - line = line[:-2] + self.lines[linenr] - return line - - def unget(self): - self.linenr -= 1 - while self.lines[self.linenr].endswith("\\\n"): - self.linenr -= 1 - - def next_nonblank(self): - """Removes lines up to and including the next non-blank (not all-space) - line and returns it. Returns None if there are no more non-blank - lines.""" - while 1: - line = self.get_next() - if line is None or not line.isspace(): - return line + else: + # Symbol or choice + res.add(subexpr) -# -# Internal functions -# + rec(expr) + return res -def _get_visibility(sc): - """Symbols and Choices have a "visibility" that acts as an upper bound on - the values a user can set for them, corresponding to the visibility in e.g. - 'make menuconfig'. This function calculates the visibility for the Symbol - or Choice 'sc' -- the logic is nearly identical.""" - if sc.cached_visibility is None: - vis = "n" - for _, cond_expr in sc.prompts: - vis = sc.config._eval_max(vis, cond_expr) - - if isinstance(sc, Symbol) and sc.is_choice_sym: - if sc.type == TRISTATE and vis == "m" and \ - sc.parent.get_mode() == "y": - # Choice symbols with visibility "m" are not visible if the - # choice has mode "y" - vis = "n" - else: - vis = sc.config._eval_min(vis, _get_visibility(sc.parent)) - # Promote "m" to "y" if we're dealing with a non-tristate - if vis == "m" and sc.type != TRISTATE: - vis = "y" +def split_expr(expr, op): + """ + Returns a list containing the top-level AND or OR operands in the + expression 'expr', in the same (left-to-right) order as they appear in + the expression. - sc.cached_visibility = vis + This can be handy e.g. for splitting (weak) reverse dependencies + from 'select' and 'imply' into individual selects/implies. - return sc.cached_visibility + op: + Either AND to get AND operands, or OR to get OR operands. -def _make_and(e1, e2): - """Constructs an AND (&&) expression. Performs trivial simplification. - Nones equate to 'y'. + (Having this as an operand might be more future-safe than having two + hardcoded functions.) - Note: returns None if e1 == e2 == None.""" - if e1 is None or e1 == "y": - return e2 - if e2 is None or e2 == "y": - return e1 - # Prefer to merge argument lists if possible to reduce the number of nodes + Pseudo-code examples: - if isinstance(e1, tuple) and e1[0] == AND: - if isinstance(e2, tuple) and e2[0] == AND: - return (AND, e1[1] + e2[1]) - return (AND, e1[1] + [e2]) + split_expr( A , OR ) -> [A] + split_expr( A && B , OR ) -> [A && B] + split_expr( A || B , OR ) -> [A, B] + split_expr( A || B , AND ) -> [A || B] + split_expr( A || B || (C && D) , OR ) -> [A, B, C && D] - if isinstance(e2, tuple) and e2[0] == AND: - return (AND, e2[1] + [e1]) + # Second || is not at the top level + split_expr( A || (B && (C || D)) , OR ) -> [A, B && (C || D)] - return (AND, [e1, e2]) + # Parentheses don't matter as long as we stay at the top level (don't + # encounter any non-'op' nodes) + split_expr( (A || B) || C , OR ) -> [A, B, C] + split_expr( A || (B || C) , OR ) -> [A, B, C] + """ + res = [] + + def rec(subexpr): + if subexpr.__class__ is tuple and subexpr[0] is op: + rec(subexpr[1]) + rec(subexpr[2]) + else: + res.append(subexpr) + + rec(expr) + return res -def _make_or(e1, e2): - """Constructs an OR (||) expression. Performs trivial simplification and - avoids Nones. Nones equate to 'y', which is usually what we want, but needs - to be kept in mind.""" - # Perform trivial simplification and avoid None's (which - # correspond to y's) - if e1 is None or e2 is None or e1 == "y" or e2 == "y": - return "y" - if e1 == "n": - return e2 +def escape(s): + r""" + Escapes the string 's' in the same fashion as is done for display in + Kconfig format and when writing strings to a .config file. " and \ are + replaced by \" and \\, respectively. + """ + # \ must be escaped before " to avoid double escaping + return s.replace("\\", r"\\").replace('"', r'\"') - # Prefer to merge argument lists if possible to reduce the number of nodes - if isinstance(e1, tuple) and e1[0] == OR: - if isinstance(e2, tuple) and e2[0] == OR: - return (OR, e1[1] + e2[1]) - return (OR, e1[1] + [e2]) +def unescape(s): + r""" + Unescapes the string 's'. \ followed by any character is replaced with just + that character. Used internally when reading .config files. + """ + return _unescape_sub(r"\1", s) - if isinstance(e2, tuple) and e2[0] == OR: - return (OR, e2[1] + [e1]) +# unescape() helper +_unescape_sub = re.compile(r"\\(.)").sub - return (OR, [e1, e2]) -def _get_expr_syms_rec(expr, res): - """_get_expr_syms() helper. Recurses through expressions.""" - if isinstance(expr, Symbol): - res.add(expr) - elif isinstance(expr, str): +def standard_kconfig(): + """ + Helper for tools. Loads the top-level Kconfig specified as the first + command-line argument, or "Kconfig" if there are no command-line arguments. + Returns the Kconfig instance. + + Exits with sys.exit() (which raises a SystemExit exception) and prints a + usage note to stderr if more than one command-line argument is passed. + """ + if len(sys.argv) > 2: + sys.exit("usage: {} [Kconfig]".format(sys.argv[0])) + + # Only show backtraces for unexpected exceptions + try: + return Kconfig("Kconfig" if len(sys.argv) < 2 else sys.argv[1]) + except (EnvironmentError, KconfigError) as e: + # Some long exception messages have extra newlines for better + # formatting when reported as an unhandled exception. Strip them here. + sys.exit(str(e).strip()) + + +def standard_config_filename(): + """ + Helper for tools. Returns the value of KCONFIG_CONFIG (which specifies the + .config file to load/save) if it is set, and ".config" otherwise. + + Calling load_config() with filename=None might give the behavior you want, + without having to use this function. + """ + return os.getenv("KCONFIG_CONFIG", ".config") + + +def load_allconfig(kconf, filename): + """ + Helper for all*config. Loads (merges) the configuration file specified by + KCONFIG_ALLCONFIG, if any. See Documentation/kbuild/kconfig.txt in the + Linux kernel. + + Disables warnings for duplicated assignments within configuration files for + the duration of the call (kconf.warn_assign_override/warn_assign_redun = False), + and restores the previous warning settings at the end. The + KCONFIG_ALLCONFIG configuration file is expected to override symbols. + + Exits with sys.exit() (which raises a SystemExit exception) and prints an + error to stderr if KCONFIG_ALLCONFIG is set but the configuration file + can't be opened. + + kconf: + Kconfig instance to load the configuration in. + + filename: + Command-specific configuration filename - "allyes.config", + "allno.config", etc. + """ + allconfig = os.getenv("KCONFIG_ALLCONFIG") + if allconfig is None: return - elif expr[0] == AND or expr[0] == OR: - for term in expr[1]: - _get_expr_syms_rec(term, res) - elif expr[0] == NOT: - _get_expr_syms_rec(expr[1], res) - elif expr[0] == EQUAL or expr[0] == UNEQUAL: - if isinstance(expr[1], Symbol): - res.add(expr[1]) - if isinstance(expr[2], Symbol): - res.add(expr[2]) + + def std_msg(e): + # "Upcasts" a _KconfigIOError to an IOError, removing the custom + # __str__() message. The standard message is better here. + # + # This might also convert an OSError to an IOError in obscure cases, + # but it's probably not a big deal. The distinction is shaky (see + # PEP-3151). + return IOError(e.errno, e.strerror, e.filename) + + old_warn_assign_override = kconf.warn_assign_override + old_warn_assign_redun = kconf.warn_assign_redun + kconf.warn_assign_override = kconf.warn_assign_redun = False + + if allconfig in ("", "1"): + try: + print(kconf.load_config(filename, False)) + except EnvironmentError as e1: + try: + print(kconf.load_config("all.config", False)) + except EnvironmentError as e2: + sys.exit("error: KCONFIG_ALLCONFIG is set, but neither {} " + "nor all.config could be opened: {}, {}" + .format(filename, std_msg(e1), std_msg(e2))) else: - _internal_error("Internal error while fetching symbols from an " - "expression with token stream {0}.".format(expr)) + try: + print(kconf.load_config(allconfig, False)) + except EnvironmentError as e: + sys.exit("error: KCONFIG_ALLCONFIG is set to '{}', which " + "could not be opened: {}" + .format(allconfig, std_msg(e))) -def _get_expr_syms(expr): - """Returns the set() of symbols appearing in expr.""" - res = set() - if expr is not None: - _get_expr_syms_rec(expr, res) - return res + kconf.warn_assign_override = old_warn_assign_override + kconf.warn_assign_redun = old_warn_assign_redun -def _str_val(obj): - """Returns the value of obj as a string. If obj is not a string (constant - symbol), it must be a Symbol.""" - return obj if isinstance(obj, str) else obj.get_value() - -def _make_block_conf(block, append_fn): - """Returns a list of .config strings for a block (list) of items.""" - - # Collect the substrings in a list and later use join() instead of += to - # build the final .config contents. With older Python versions, this yields - # linear instead of quadratic complexity. - for item in block: - item._make_conf(append_fn) - -def _sym_str_string(sym_or_str): - if isinstance(sym_or_str, str): - return '"' + sym_or_str + '"' - return sym_or_str.name - -def _intersperse(lst, op): - """_expr_to_str() helper. Gets the string representation of each expression - in lst and produces a list where op has been inserted between the - elements.""" - if not lst: - return "" - res = [] +# +# Internal functions +# - def handle_sub_expr(expr): - no_parens = isinstance(expr, (str, Symbol)) or \ - expr[0] in (EQUAL, UNEQUAL) or \ - PRECEDENCE[op] <= PRECEDENCE[expr[0]] - if not no_parens: - res.append("(") - res.extend(_expr_to_str_rec(expr)) - if not no_parens: - res.append(")") - op_str = OP_TO_STR[op] +def _visibility(sc): + # Symbols and Choices have a "visibility" that acts as an upper bound on + # the values a user can set for them, corresponding to the visibility in + # e.g. 'make menuconfig'. This function calculates the visibility for the + # Symbol or Choice 'sc' -- the logic is nearly identical. - handle_sub_expr(lst[0]) - for expr in lst[1:]: - res.append(op_str) - handle_sub_expr(expr) + vis = 0 - return res + for node in sc.nodes: + if node.prompt: + vis = max(vis, expr_value(node.prompt[1])) -def _expr_to_str_rec(expr): - if expr is None: - return [""] + if sc.__class__ is Symbol and sc.choice: + if sc.choice.orig_type is TRISTATE and \ + sc.orig_type is not TRISTATE and sc.choice.tri_value != 2: + # Non-tristate choice symbols are only visible in y mode + return 0 - if isinstance(expr, (Symbol, str)): - return [_sym_str_string(expr)] + if sc.orig_type is TRISTATE and vis == 1 and sc.choice.tri_value == 2: + # Choice symbols with m visibility are not visible in y mode + return 0 - if expr[0] in (AND, OR): - return _intersperse(expr[1], expr[0]) + # Promote m to y if we're dealing with a non-tristate (possibly due to + # modules being disabled) + if vis == 1 and sc.type is not TRISTATE: + return 2 - if expr[0] == NOT: - need_parens = not isinstance(expr[1], (str, Symbol)) + return vis - res = ["!"] - if need_parens: - res.append("(") - res.extend(_expr_to_str_rec(expr[1])) - if need_parens: - res.append(")") - return res - if expr[0] in (EQUAL, UNEQUAL): - return [_sym_str_string(expr[1]), - OP_TO_STR[expr[0]], - _sym_str_string(expr[2])] +def _make_depend_on(sc, expr): + # Adds 'sc' (symbol or choice) as a "dependee" to all symbols in 'expr'. + # Constant symbols in 'expr' are skipped as they can never change value + # anyway. + + if expr.__class__ is tuple: + # AND, OR, NOT, or relation + + _make_depend_on(sc, expr[1]) + + # NOTs only have a single operand + if expr[0] is not NOT: + _make_depend_on(sc, expr[2]) + + elif not expr.is_constant: + # Non-constant symbol, or choice + expr._dependents.add(sc) + -def _expr_to_str(expr): - return "".join(_expr_to_str_rec(expr)) +def _parenthesize(expr, type_, sc_expr_str_fn): + # expr_str() helper. Adds parentheses around expressions of type 'type_'. -def _indentation(line): - """Returns the length of the line's leading whitespace, treating tab stops - as being spaced 8 characters apart.""" - line = line.expandtabs() - return len(line) - len(line.lstrip()) + if expr.__class__ is tuple and expr[0] is type_: + return "({})".format(expr_str(expr, sc_expr_str_fn)) + return expr_str(expr, sc_expr_str_fn) + + +def _ordered_unique(lst): + # Returns 'lst' with any duplicates removed, preserving order. This hacky + # version seems to be a common idiom. It relies on short-circuit evaluation + # and set.add() returning None, which is falsy. + + seen = set() + seen_add = seen.add + return [x for x in lst if x not in seen and not seen_add(x)] -def _deindent(line, indent): - """Deindent 'line' by 'indent' spaces.""" - line = line.expandtabs() - if len(line) <= indent: - return line - return line[indent:] def _is_base_n(s, n): try: @@ -3412,133 +6222,809 @@ def _is_base_n(s, n): except ValueError: return False -def _lines(*args): - """Returns a string consisting of all arguments, with newlines inserted - between them.""" - return "\n".join(args) - -def _comment(s): - """Returns a new string with "#" inserted before each line in 's'.""" - if not s: - return "#" - res = "".join(["#" + line for line in s.splitlines(True)]) - if s.endswith("\n"): - return res + "#" - return res -def _clean_up_path(path): - """Strips an initial "./" and any trailing slashes from 'path'.""" - if path.startswith("./"): - path = path[2:] - return path.rstrip("/") - -def _build_msg(msg, filename, linenr): - if filename is not None: - msg = "{0}:{1}: ".format(_clean_up_path(filename), linenr) + msg - return msg - -def _stderr_msg(msg, filename, linenr): - sys.stderr.write(_build_msg(msg, filename, linenr) + "\n") - -def _tokenization_error(s, filename, linenr): - loc = "" if filename is None else "{0}:{1}: ".format(filename, linenr) - raise Kconfig_Syntax_Error("{0}Couldn't tokenize '{1}'" - .format(loc, s.strip())) - -def _parse_error(s, msg, filename, linenr): - loc = "" if filename is None else "{0}:{1}: ".format(filename, linenr) - raise Kconfig_Syntax_Error("{0}Couldn't parse '{1}'{2}" - .format(loc, s.strip(), - "." if msg is None else ": " + msg)) - -def _internal_error(msg): - raise Internal_Error(msg + - "\nSorry! You may want to send an email to ulfalizer a.t Google's " - "email service to tell me about this. Include the message above and the " - "stack trace and describe what you were doing.") +def _strcmp(s1, s2): + # strcmp()-alike that returns -1, 0, or 1 + + return (s1 > s2) - (s1 < s2) + + +def _sym_to_num(sym): + # expr_value() helper for converting a symbol to a number. Raises + # ValueError for symbols that can't be converted. + + # For BOOL and TRISTATE, n/m/y count as 0/1/2. This mirrors 9059a3493ef + # ("kconfig: fix relational operators for bool and tristate symbols") in + # the C implementation. + return sym.tri_value if sym.orig_type in _BOOL_TRISTATE else \ + int(sym.str_value, _TYPE_TO_BASE[sym.orig_type]) + + +def _touch_dep_file(path, sym_name): + # If sym_name is MY_SYM_NAME, touches my/sym/name.h. See the sync_deps() + # docstring. + + sym_path = path + os.sep + sym_name.lower().replace("_", os.sep) + ".h" + sym_path_dir = dirname(sym_path) + if not exists(sym_path_dir): + os.makedirs(sym_path_dir, 0o755) + + # A kind of truncating touch, mirroring the C tools + os.close(os.open( + sym_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)) + + +def _save_old(path): + # See write_config() + + def copy(src, dst): + # Import as needed, to save some startup time + import shutil + shutil.copyfile(src, dst) + + if islink(path): + # Preserve symlinks + copy_fn = copy + elif hasattr(os, "replace"): + # Python 3 (3.3+) only. Best choice when available, because it + # removes <filename>.old on both *nix and Windows. + copy_fn = os.replace + elif os.name == "posix": + # Removes <filename>.old on POSIX systems + copy_fn = os.rename + else: + # Fall back on copying + copy_fn = copy + + try: + copy_fn(path, path + ".old") + except Exception: + # Ignore errors from 'path' missing as well as other errors. + # <filename>.old file is usually more of a nice-to-have, and not worth + # erroring out over e.g. if <filename>.old happens to be a directory or + # <filename> is something like /dev/null. + pass + + +def _name_and_loc(sc): + # Helper for giving the symbol/choice name and location(s) in e.g. warnings + + # Reuse the expression format. That way choices show up as + # '<choice (name, if any)>' + name = standard_sc_expr_str(sc) + + if not sc.nodes: + return name + " (undefined)" + + return "{} (defined at {})".format( + name, + ", ".join("{}:{}".format(node.filename, node.linenr) + for node in sc.nodes)) + + +# Menu manipulation + + +def _expr_depends_on(expr, sym): + # Reimplementation of expr_depends_symbol() from mconf.c. Used to determine + # if a submenu should be implicitly created. This also influences which + # items inside choice statements are considered choice items. + + if expr.__class__ is not tuple: + return expr is sym + + if expr[0] in _EQUAL_UNEQUAL: + # Check for one of the following: + # sym = m/y, m/y = sym, sym != n, n != sym + + left, right = expr[1:] + + if right is sym: + left, right = right, left + elif left is not sym: + return False + + return (expr[0] is EQUAL and right is sym.kconfig.m or + right is sym.kconfig.y) or \ + (expr[0] is UNEQUAL and right is sym.kconfig.n) + + return expr[0] is AND and \ + (_expr_depends_on(expr[1], sym) or + _expr_depends_on(expr[2], sym)) + + +def _auto_menu_dep(node1, node2): + # Returns True if node2 has an "automatic menu dependency" on node1. If + # node2 has a prompt, we check its condition. Otherwise, we look directly + # at node2.dep. + + return _expr_depends_on(node2.prompt[1] if node2.prompt else node2.dep, + node1.item) + + +def _flatten(node): + # "Flattens" menu nodes without prompts (e.g. 'if' nodes and non-visible + # symbols with children from automatic menu creation) so that their + # children appear after them instead. This gives a clean menu structure + # with no unexpected "jumps" in the indentation. + # + # Do not flatten promptless choices (which can appear "legitimately" if a + # named choice is defined in multiple locations to add on symbols). It + # looks confusing, and the menuconfig already shows all choice symbols if + # you enter the choice at some location with a prompt. + + while node: + if node.list and not node.prompt and \ + node.item.__class__ is not Choice: + + last_node = node.list + while 1: + last_node.parent = node.parent + if not last_node.next: + break + last_node = last_node.next + + last_node.next = node.next + node.next = node.list + node.list = None + + node = node.next + + +def _remove_ifs(node): + # Removes 'if' nodes (which can be recognized by MenuNode.item being None), + # which are assumed to already have been flattened. The C implementation + # doesn't bother to do this, but we expose the menu tree directly, and it + # makes it nicer to work with. + + cur = node.list + while cur and not cur.item: + cur = cur.next + + node.list = cur + + while cur: + next = cur.next + while next and not next.item: + next = next.next + + # Equivalent to + # + # cur.next = next + # cur = next + # + # due to tricky Python semantics. The order matters. + cur.next = cur = next + + +def _finalize_choice(node): + # Finalizes a choice, marking each symbol whose menu node has the choice as + # the parent as a choice symbol, and automatically determining types if not + # specified. + + choice = node.item + + cur = node.list + while cur: + if cur.item.__class__ is Symbol: + cur.item.choice = choice + choice.syms.append(cur.item) + cur = cur.next + + # If no type is specified for the choice, its type is that of + # the first choice item with a specified type + if not choice.orig_type: + for item in choice.syms: + if item.orig_type: + choice.orig_type = item.orig_type + break + + # Each choice item of UNKNOWN type gets the type of the choice + for sym in choice.syms: + if not sym.orig_type: + sym.orig_type = choice.orig_type + + +def _check_dep_loop_sym(sym, ignore_choice): + # Detects dependency loops using depth-first search on the dependency graph + # (which is calculated earlier in Kconfig._build_dep()). + # + # Algorithm: + # + # 1. Symbols/choices start out with _visited = 0, meaning unvisited. + # + # 2. When a symbol/choice is first visited, _visited is set to 1, meaning + # "visited, potentially part of a dependency loop". The recursive + # search then continues from the symbol/choice. + # + # 3. If we run into a symbol/choice X with _visited already set to 1, + # there's a dependency loop. The loop is found on the call stack by + # recording symbols while returning ("on the way back") until X is seen + # again. + # + # 4. Once a symbol/choice and all its dependencies (or dependents in this + # case) have been checked recursively without detecting any loops, its + # _visited is set to 2, meaning "visited, not part of a dependency + # loop". + # + # This saves work if we run into the symbol/choice again in later calls + # to _check_dep_loop_sym(). We just return immediately. + # + # Choices complicate things, as every choice symbol depends on every other + # choice symbol in a sense. When a choice is "entered" via a choice symbol + # X, we visit all choice symbols from the choice except X, and prevent + # immediately revisiting the choice with a flag (ignore_choice). + # + # Maybe there's a better way to handle this (different flags or the + # like...) + + if not sym._visited: + # sym._visited == 0, unvisited + + sym._visited = 1 + + for dep in sym._dependents: + # Choices show up in Symbol._dependents when the choice has the + # symbol in a 'prompt' or 'default' condition (e.g. + # 'default ... if SYM'). + # + # Since we aren't entering the choice via a choice symbol, all + # choice symbols need to be checked, hence the None. + loop = _check_dep_loop_choice(dep, None) \ + if dep.__class__ is Choice \ + else _check_dep_loop_sym(dep, False) + + if loop: + # Dependency loop found + return _found_dep_loop(loop, sym) + + if sym.choice and not ignore_choice: + loop = _check_dep_loop_choice(sym.choice, sym) + if loop: + # Dependency loop found + return _found_dep_loop(loop, sym) + + # The symbol is not part of a dependency loop + sym._visited = 2 + + # No dependency loop found + return None + + if sym._visited == 2: + # The symbol was checked earlier and is already known to not be part of + # a dependency loop + return None + + # sym._visited == 1, found a dependency loop. Return the symbol as the + # first element in it. + return (sym,) + + +def _check_dep_loop_choice(choice, skip): + if not choice._visited: + # choice._visited == 0, unvisited + + choice._visited = 1 + + # Check for loops involving choice symbols. If we came here via a + # choice symbol, skip that one, as we'd get a false positive + # '<sym FOO> -> <choice> -> <sym FOO>' loop otherwise. + for sym in choice.syms: + if sym is not skip: + # Prevent the choice from being immediately re-entered via the + # "is a choice symbol" path by passing True + loop = _check_dep_loop_sym(sym, True) + if loop: + # Dependency loop found + return _found_dep_loop(loop, choice) + + # The choice is not part of a dependency loop + choice._visited = 2 + + # No dependency loop found + return None + + if choice._visited == 2: + # The choice was checked earlier and is already known to not be part of + # a dependency loop + return None + + # choice._visited == 1, found a dependency loop. Return the choice as the + # first element in it. + return (choice,) + + +def _found_dep_loop(loop, cur): + # Called "on the way back" when we know we have a loop + + # Is the symbol/choice 'cur' where the loop started? + if cur is not loop[0]: + # Nope, it's just a part of the loop + return loop + (cur,) + + # Yep, we have the entire loop. Throw an exception that shows it. + + msg = "\nDependency loop\n" \ + "===============\n\n" + + for item in loop: + if item is not loop[0]: + msg += "...depends on " + if item.__class__ is Symbol and item.choice: + msg += "the choice symbol " + + msg += "{}, with definition...\n\n{}\n\n" \ + .format(_name_and_loc(item), item) + + # Small wart: Since we reuse the already calculated + # Symbol/Choice._dependents sets for recursive dependency detection, we + # lose information on whether a dependency came from a 'select'/'imply' + # condition or e.g. a 'depends on'. + # + # This might cause selecting symbols to "disappear". For example, + # a symbol B having 'select A if C' gives a direct dependency from A to + # C, since it corresponds to a reverse dependency of B && C. + # + # Always print reverse dependencies for symbols that have them to make + # sure information isn't lost. I wonder if there's some neat way to + # improve this. + + if item.__class__ is Symbol: + if item.rev_dep is not item.kconfig.n: + msg += "(select-related dependencies: {})\n\n" \ + .format(expr_str(item.rev_dep)) + + if item.weak_rev_dep is not item.kconfig.n: + msg += "(imply-related dependencies: {})\n\n" \ + .format(expr_str(item.rev_dep)) + + msg += "...depends again on {}".format(_name_and_loc(loop[0])) + + raise KconfigError(msg) + + +def _decoding_error(e, filename, macro_linenr=None): + # Gives the filename and context for UnicodeDecodeError's, which are a pain + # to debug otherwise. 'e' is the UnicodeDecodeError object. + # + # If the decoding error is for the output of a $(shell,...) command, + # macro_linenr holds the line number where it was run (the exact line + # number isn't available for decoding errors in files). + + raise KconfigError( + "\n" + "Malformed {} in {}\n" + "Context: {}\n" + "Problematic data: {}\n" + "Reason: {}".format( + e.encoding, + "'{}'".format(filename) if macro_linenr is None else + "output from macro at {}:{}".format(filename, macro_linenr), + e.object[max(e.start - 40, 0):e.end + 40], + e.object[e.start:e.end], + e.reason)) + + +def _warn_verbose_deprecated(fn_name): + sys.stderr.write( + "Deprecation warning: {0}()'s 'verbose' argument has no effect. Since " + "Kconfiglib 12.0.0, the message is returned from {0}() instead, " + "and is always generated. Do e.g. print(kconf.{0}()) if you want to " + "want to show a message like \"Loaded configuration '.config'\" on " + "stdout. The old API required ugly hacks to reuse messages in " + "configuration interfaces.\n".format(fn_name)) + + +# Predefined preprocessor functions + + +def _filename_fn(kconf, _): + return kconf.filename + + +def _lineno_fn(kconf, _): + return str(kconf.linenr) + + +def _info_fn(kconf, _, msg): + print("{}:{}: {}".format(kconf.filename, kconf.linenr, msg)) + + return "" + + +def _warning_if_fn(kconf, _, cond, msg): + if cond == "y": + kconf._warn(msg, kconf.filename, kconf.linenr) + + return "" + + +def _error_if_fn(kconf, _, cond, msg): + if cond == "y": + raise KconfigError("{}:{}: {}".format( + kconf.filename, kconf.linenr, msg)) + + return "" + + +def _shell_fn(kconf, _, command): + # Only import as needed, to save some startup time + import subprocess + + stdout, stderr = subprocess.Popen( + command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ).communicate() + + if not _IS_PY2: + try: + stdout = stdout.decode(kconf._encoding) + stderr = stderr.decode(kconf._encoding) + except UnicodeDecodeError as e: + _decoding_error(e, kconf.filename, kconf.linenr) + + if stderr: + kconf._warn("'{}' wrote to stderr: {}".format( + command, "\n".join(stderr.splitlines())), + kconf.filename, kconf.linenr) + + # Universal newlines with splitlines() (to prevent e.g. stray \r's in + # command output on Windows), trailing newline removal, and + # newline-to-space conversion. + # + # On Python 3 versions before 3.6, it's not possible to specify the + # encoding when passing universal_newlines=True to Popen() (the 'encoding' + # parameter was added in 3.6), so we do this manual version instead. + return "\n".join(stdout.splitlines()).rstrip("\n").replace("\n", " ") # -# Internal global constants +# Global constants # -# Tokens -(T_AND, T_OR, T_NOT, - T_OPEN_PAREN, T_CLOSE_PAREN, - T_EQUAL, T_UNEQUAL, - T_MAINMENU, T_MENU, T_ENDMENU, - T_SOURCE, T_CHOICE, T_ENDCHOICE, - T_COMMENT, T_CONFIG, T_MENUCONFIG, - T_HELP, T_IF, T_ENDIF, T_DEPENDS, T_ON, - T_OPTIONAL, T_PROMPT, T_DEFAULT, - T_BOOL, T_TRISTATE, T_HEX, T_INT, T_STRING, - T_DEF_BOOL, T_DEF_TRISTATE, - T_SELECT, T_IMPLY, T_RANGE, T_OPTION, T_ALLNOCONFIG_Y, T_ENV, - T_DEFCONFIG_LIST, T_MODULES, T_VISIBLE) = range(40) - -# The leading underscore before the function assignments below prevent pydoc -# from listing them. The constants could be hidden too, but they're fairly -# obviously internal anyway, so don't bother spamming the code. - -# Keyword to token map. Note that the get() method is assigned directly as a -# small optimization. -_get_keyword = \ - {"mainmenu": T_MAINMENU, "menu": T_MENU, "endmenu": T_ENDMENU, - "endif": T_ENDIF, "endchoice": T_ENDCHOICE, "source": T_SOURCE, - "choice": T_CHOICE, "config": T_CONFIG, "comment": T_COMMENT, - "menuconfig": T_MENUCONFIG, "help": T_HELP, "if": T_IF, - "depends": T_DEPENDS, "on": T_ON, "optional": T_OPTIONAL, - "prompt": T_PROMPT, "default": T_DEFAULT, "bool": T_BOOL, "boolean": T_BOOL, - "tristate": T_TRISTATE, "int": T_INT, "hex": T_HEX, "def_bool": T_DEF_BOOL, - "def_tristate": T_DEF_TRISTATE, "string": T_STRING, "select": T_SELECT, - "imply" : T_IMPLY, "range": T_RANGE, "option": T_OPTION, - "allnoconfig_y": T_ALLNOCONFIG_Y, "env": T_ENV, - "defconfig_list": T_DEFCONFIG_LIST, "modules": T_MODULES, - "visible": T_VISIBLE}.get - -# Strings to use for True and False -BOOL_STR = {False: "false", True: "true"} - -# Tokens after which identifier-like lexemes are treated as strings. T_CHOICE -# is included to avoid symbols being registered for named choices. -STRING_LEX = frozenset((T_BOOL, T_TRISTATE, T_INT, T_HEX, T_STRING, T_CHOICE, - T_PROMPT, T_MENU, T_COMMENT, T_SOURCE, T_MAINMENU)) - -# Matches the initial token on a line; see _tokenize(). Also eats trailing -# whitespace as an optimization. -_initial_token_re_match = re.compile(r"[^\w]*(\w+)\s*").match - -# Matches an identifier/keyword optionally preceded by whitespace. Also eats -# trailing whitespace as an optimization. -_id_keyword_re_match = re.compile(r"\s*([\w./-]+)\s*").match - -# Regular expression for finding $-references to symbols in strings -_sym_ref_re_search = re.compile(r"\$[A-Za-z0-9_]+").search - -# Integers representing symbol types -UNKNOWN, BOOL, TRISTATE, STRING, HEX, INT = range(6) - -# Strings to use for types -TYPENAME = {UNKNOWN: "unknown", BOOL: "bool", TRISTATE: "tristate", - STRING: "string", HEX: "hex", INT: "int"} - -# Token to type mapping -TOKEN_TO_TYPE = {T_BOOL: BOOL, T_TRISTATE: TRISTATE, T_STRING: STRING, - T_INT: INT, T_HEX: HEX} - -# Default values for symbols of different types (the value the symbol gets if -# it is not assigned a user value and none of its 'default' clauses kick in) -DEFAULT_VALUE = {BOOL: "n", TRISTATE: "n", STRING: "", INT: "", HEX: ""} - -# Indicates that no item is selected in a choice statement -NO_SELECTION = 0 - -# Integers representing expression types -AND, OR, NOT, EQUAL, UNEQUAL = range(5) - -# Map from tristate values to integers -TRI_TO_INT = {"n": 0, "m": 1, "y": 2} - -# Printing-related stuff - -OP_TO_STR = {AND: " && ", OR: " || ", EQUAL: " = ", UNEQUAL: " != "} -PRECEDENCE = {OR: 0, AND: 1, NOT: 2} +TRI_TO_STR = { + 0: "n", + 1: "m", + 2: "y", +} + +STR_TO_TRI = { + "n": 0, + "m": 1, + "y": 2, +} + +# Constant representing that there's no cached choice selection. This is +# distinct from a cached None (no selection). Any object that's not None or a +# Symbol will do. We test this with 'is'. +_NO_CACHED_SELECTION = 0 + +# Are we running on Python 2? +_IS_PY2 = sys.version_info[0] < 3 + +try: + _UNAME_RELEASE = os.uname()[2] +except AttributeError: + # Only import as needed, to save some startup time + import platform + _UNAME_RELEASE = platform.uname()[2] + +# The token and type constants below are safe to test with 'is', which is a bit +# faster (~30% faster on my machine, and a few % faster for total parsing +# time), even without assuming Python's small integer optimization (which +# caches small integer objects). The constants end up pointing to unique +# integer objects, and since we consistently refer to them via the names below, +# we always get the same object. +# +# Client code should use == though. + +# Tokens, with values 1, 2, ... . Avoiding 0 simplifies some checks by making +# all tokens except empty strings truthy. +( + _T_ALLNOCONFIG_Y, + _T_AND, + _T_BOOL, + _T_CHOICE, + _T_CLOSE_PAREN, + _T_COMMENT, + _T_CONFIG, + _T_DEFAULT, + _T_DEFCONFIG_LIST, + _T_DEF_BOOL, + _T_DEF_HEX, + _T_DEF_INT, + _T_DEF_STRING, + _T_DEF_TRISTATE, + _T_DEPENDS, + _T_ENDCHOICE, + _T_ENDIF, + _T_ENDMENU, + _T_ENV, + _T_EQUAL, + _T_GREATER, + _T_GREATER_EQUAL, + _T_HELP, + _T_HEX, + _T_IF, + _T_IMPLY, + _T_INT, + _T_LESS, + _T_LESS_EQUAL, + _T_MAINMENU, + _T_MENU, + _T_MENUCONFIG, + _T_MODULES, + _T_NOT, + _T_ON, + _T_OPEN_PAREN, + _T_OPTION, + _T_OPTIONAL, + _T_OR, + _T_ORSOURCE, + _T_OSOURCE, + _T_PROMPT, + _T_RANGE, + _T_RSOURCE, + _T_SELECT, + _T_SOURCE, + _T_STRING, + _T_TRISTATE, + _T_UNEQUAL, + _T_VISIBLE, +) = range(1, 51) + +# Keyword to token map, with the get() method assigned directly as a small +# optimization +_get_keyword = { + "---help---": _T_HELP, + "allnoconfig_y": _T_ALLNOCONFIG_Y, + "bool": _T_BOOL, + "boolean": _T_BOOL, + "choice": _T_CHOICE, + "comment": _T_COMMENT, + "config": _T_CONFIG, + "def_bool": _T_DEF_BOOL, + "def_hex": _T_DEF_HEX, + "def_int": _T_DEF_INT, + "def_string": _T_DEF_STRING, + "def_tristate": _T_DEF_TRISTATE, + "default": _T_DEFAULT, + "defconfig_list": _T_DEFCONFIG_LIST, + "depends": _T_DEPENDS, + "endchoice": _T_ENDCHOICE, + "endif": _T_ENDIF, + "endmenu": _T_ENDMENU, + "env": _T_ENV, + "grsource": _T_ORSOURCE, # Backwards compatibility + "gsource": _T_OSOURCE, # Backwards compatibility + "help": _T_HELP, + "hex": _T_HEX, + "if": _T_IF, + "imply": _T_IMPLY, + "int": _T_INT, + "mainmenu": _T_MAINMENU, + "menu": _T_MENU, + "menuconfig": _T_MENUCONFIG, + "modules": _T_MODULES, + "on": _T_ON, + "option": _T_OPTION, + "optional": _T_OPTIONAL, + "orsource": _T_ORSOURCE, + "osource": _T_OSOURCE, + "prompt": _T_PROMPT, + "range": _T_RANGE, + "rsource": _T_RSOURCE, + "select": _T_SELECT, + "source": _T_SOURCE, + "string": _T_STRING, + "tristate": _T_TRISTATE, + "visible": _T_VISIBLE, +}.get + +# The constants below match the value of the corresponding tokens to remove the +# need for conversion + +# Node types +MENU = _T_MENU +COMMENT = _T_COMMENT + +# Expression types +AND = _T_AND +OR = _T_OR +NOT = _T_NOT +EQUAL = _T_EQUAL +UNEQUAL = _T_UNEQUAL +LESS = _T_LESS +LESS_EQUAL = _T_LESS_EQUAL +GREATER = _T_GREATER +GREATER_EQUAL = _T_GREATER_EQUAL + +REL_TO_STR = { + EQUAL: "=", + UNEQUAL: "!=", + LESS: "<", + LESS_EQUAL: "<=", + GREATER: ">", + GREATER_EQUAL: ">=", +} + +# Symbol/choice types. UNKNOWN is 0 (falsy) to simplify some checks. +# Client code shouldn't rely on it though, as it was non-zero in +# older versions. +UNKNOWN = 0 +BOOL = _T_BOOL +TRISTATE = _T_TRISTATE +STRING = _T_STRING +INT = _T_INT +HEX = _T_HEX + +TYPE_TO_STR = { + UNKNOWN: "unknown", + BOOL: "bool", + TRISTATE: "tristate", + STRING: "string", + INT: "int", + HEX: "hex", +} + +# Used in comparisons. 0 means the base is inferred from the format of the +# string. +_TYPE_TO_BASE = { + HEX: 16, + INT: 10, + STRING: 0, + UNKNOWN: 0, +} + +# def_bool -> BOOL, etc. +_DEF_TOKEN_TO_TYPE = { + _T_DEF_BOOL: BOOL, + _T_DEF_HEX: HEX, + _T_DEF_INT: INT, + _T_DEF_STRING: STRING, + _T_DEF_TRISTATE: TRISTATE, +} + +# Tokens after which strings are expected. This is used to tell strings from +# constant symbol references during tokenization, both of which are enclosed in +# quotes. +# +# Identifier-like lexemes ("missing quotes") are also treated as strings after +# these tokens. _T_CHOICE is included to avoid symbols being registered for +# named choices. +_STRING_LEX = frozenset({ + _T_BOOL, + _T_CHOICE, + _T_COMMENT, + _T_HEX, + _T_INT, + _T_MAINMENU, + _T_MENU, + _T_ORSOURCE, + _T_OSOURCE, + _T_PROMPT, + _T_RSOURCE, + _T_SOURCE, + _T_STRING, + _T_TRISTATE, +}) + +# Various sets for quick membership tests. Gives a single global lookup and +# avoids creating temporary dicts/tuples. + +_TYPE_TOKENS = frozenset({ + _T_BOOL, + _T_TRISTATE, + _T_INT, + _T_HEX, + _T_STRING, +}) + +_SOURCE_TOKENS = frozenset({ + _T_SOURCE, + _T_RSOURCE, + _T_OSOURCE, + _T_ORSOURCE, +}) + +_REL_SOURCE_TOKENS = frozenset({ + _T_RSOURCE, + _T_ORSOURCE, +}) + +# Obligatory (non-optional) sources +_OBL_SOURCE_TOKENS = frozenset({ + _T_SOURCE, + _T_RSOURCE, +}) + +_BOOL_TRISTATE = frozenset({ + BOOL, + TRISTATE, +}) + +_BOOL_TRISTATE_UNKNOWN = frozenset({ + BOOL, + TRISTATE, + UNKNOWN, +}) + +_INT_HEX = frozenset({ + INT, + HEX, +}) + +_SYMBOL_CHOICE = frozenset({ + Symbol, + Choice, +}) + +_MENU_COMMENT = frozenset({ + MENU, + COMMENT, +}) + +_EQUAL_UNEQUAL = frozenset({ + EQUAL, + UNEQUAL, +}) + +_RELATIONS = frozenset({ + EQUAL, + UNEQUAL, + LESS, + LESS_EQUAL, + GREATER, + GREATER_EQUAL, +}) + +# Helper functions for getting compiled regular expressions, with the needed +# matching function returned directly as a small optimization. +# +# Use ASCII regex matching on Python 3. It's already the default on Python 2. + + +def _re_match(regex): + return re.compile(regex, 0 if _IS_PY2 else re.ASCII).match + + +def _re_search(regex): + return re.compile(regex, 0 if _IS_PY2 else re.ASCII).search + + +# Various regular expressions used during parsing + +# The initial token on a line. Also eats leading and trailing whitespace, so +# that we can jump straight to the next token (or to the end of the line if +# there is only one token). +# +# This regex will also fail to match for empty lines and comment lines. +# +# '$' is included to detect preprocessor variable assignments with macro +# expansions in the left-hand side. +_command_match = _re_match(r"\s*([A-Za-z0-9_$-]+)\s*") + +# An identifier/keyword after the first token. Also eats trailing whitespace. +# '$' is included to detect identifiers containing macro expansions. +_id_keyword_match = _re_match(r"([A-Za-z0-9_$/.-]+)\s*") + +# A fragment in the left-hand side of a preprocessor variable assignment. These +# are the portions between macro expansions ($(foo)). Macros are supported in +# the LHS (variable name). +_assignment_lhs_fragment_match = _re_match("[A-Za-z0-9_-]*") + +# The assignment operator and value (right-hand side) in a preprocessor +# variable assignment +_assignment_rhs_match = _re_match(r"\s*(=|:=|\+=)\s*(.*)") + +# Special characters/strings while expanding a macro (')', ',', and '$(') +_macro_special_search = _re_search(r"\)|,|\$\(") + +# Special characters/strings while expanding a string (quotes, '\', and '$(') +_string_special_search = _re_search(r'"|\'|\\|\$\(') + +# Special characters/strings while expanding a symbol name. Also includes +# end-of-line, in case the macro is the last thing on the line. +_name_special_search = _re_search(r'[^A-Za-z0-9_$/.-]|\$\(|$') + +# A valid right-hand side for an assignment to a string symbol in a .config +# file, including escaped characters. Extracts the contents. +_conf_string_match = _re_match(r'"((?:[^\\"]|\\.)*)"') diff --git a/tools/genboardscfg.py b/tools/genboardscfg.py index e9bbd15e15a..4ff0bffaefa 100755 --- a/tools/genboardscfg.py +++ b/tools/genboardscfg.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0+ # # Author: Masahiro Yamada <yamada.m@jp.panasonic.com> @@ -91,7 +91,7 @@ def output_is_new(output): # Detect a board that has been removed since the current board database # was generated - with open(output) as f: + with open(output, encoding="utf-8") as f: for line in f: if line[0] == '#' or line == '\n': continue @@ -118,12 +118,12 @@ class KconfigScanner: } def __init__(self): - """Scan all the Kconfig files and create a Config object.""" + """Scan all the Kconfig files and create a Kconfig object.""" # Define environment variables referenced from Kconfig os.environ['srctree'] = os.getcwd() os.environ['UBOOTVERSION'] = 'dummy' os.environ['KCONFIG_OBJDIR'] = '' - self._conf = kconfiglib.Config(print_warnings=False) + self._conf = kconfiglib.Kconfig(warn=False) def __del__(self): """Delete a leftover temporary file before exit. @@ -165,11 +165,7 @@ class KconfigScanner: else: f.write(line[colon + 1:]) - warnings = self._conf.load_config(self._tmpfile) - if warnings: - for warning in warnings: - print '%s: %s' % (defconfig, warning) - + self._conf.load_config(self._tmpfile) try_remove(self._tmpfile) self._tmpfile = None @@ -177,8 +173,8 @@ class KconfigScanner: # Get the value of CONFIG_SYS_ARCH, CONFIG_SYS_CPU, ... etc. # Set '-' if the value is empty. - for key, symbol in self._SYMBOL_TABLE.items(): - value = self._conf.get_symbol(symbol).get_value() + for key, symbol in list(self._SYMBOL_TABLE.items()): + value = self._conf.syms.get(symbol).str_value if value: params[key] = value else: @@ -242,8 +238,8 @@ def scan_defconfigs(jobs=1): processes = [] queues = [] for i in range(jobs): - defconfigs = all_defconfigs[total_boards * i / jobs : - total_boards * (i + 1) / jobs] + defconfigs = all_defconfigs[total_boards * i // jobs : + total_boards * (i + 1) // jobs] q = multiprocessing.Queue(maxsize=-1) p = multiprocessing.Process(target=scan_defconfigs_for_multiprocess, args=(q, defconfigs)) @@ -290,7 +286,7 @@ class MaintainersDatabase: 'Active', 'Orphan' or '-'. """ if not target in self.database: - print >> sys.stderr, "WARNING: no status info for '%s'" % target + print("WARNING: no status info for '%s'" % target, file=sys.stderr) return '-' tmp = self.database[target][0] @@ -301,8 +297,8 @@ class MaintainersDatabase: elif tmp.startswith('Orphan'): return 'Orphan' else: - print >> sys.stderr, ("WARNING: %s: unknown status for '%s'" % - (tmp, target)) + print(("WARNING: %s: unknown status for '%s'" % + (tmp, target)), file=sys.stderr) return '-' def get_maintainers(self, target): @@ -313,7 +309,7 @@ class MaintainersDatabase: they are separated with colons. """ if not target in self.database: - print >> sys.stderr, "WARNING: no maintainers for '%s'" % target + print("WARNING: no maintainers for '%s'" % target, file=sys.stderr) return '' return ':'.join(self.database[target][1]) @@ -330,7 +326,7 @@ class MaintainersDatabase: targets = [] maintainers = [] status = '-' - for line in open(file): + for line in open(file, encoding="utf-8"): # Check also commented maintainers if line[:3] == '#M:': line = line[1:] @@ -404,7 +400,7 @@ def format_and_output(params_list, output): # ignore case when sorting output_lines.sort(key=str.lower) - with open(output, 'w') as f: + with open(output, 'w', encoding="utf-8") as f: f.write(COMMENT_BLOCK + '\n'.join(output_lines) + '\n') def gen_boards_cfg(output, jobs=1, force=False): @@ -418,7 +414,7 @@ def gen_boards_cfg(output, jobs=1, force=False): check_top_directory() if not force and output_is_new(output): - print "%s is up to date. Nothing to do." % output + print("%s is up to date. Nothing to do." % output) sys.exit(0) params_list = scan_defconfigs(jobs) diff --git a/tools/moveconfig.py b/tools/moveconfig.py index 0bbc7c19911..b99417e9d63 100755 --- a/tools/moveconfig.py +++ b/tools/moveconfig.py @@ -851,7 +851,7 @@ class KconfigScanner: os.environ['srctree'] = os.getcwd() os.environ['UBOOTVERSION'] = 'dummy' os.environ['KCONFIG_OBJDIR'] = '' - self.conf = kconfiglib.Config() + self.conf = kconfiglib.Kconfig() class KconfigParser: @@ -1525,7 +1525,7 @@ def find_kconfig_rules(kconf, config, imply_config): """Check whether a config has a 'select' or 'imply' keyword Args: - kconf: Kconfig.Config object + kconf: Kconfiglib.Kconfig object config: Name of config to check (without CONFIG_ prefix) imply_config: Implying config (without CONFIG_ prefix) which may or may not have an 'imply' for 'config') @@ -1533,7 +1533,7 @@ def find_kconfig_rules(kconf, config, imply_config): Returns: Symbol object for 'config' if found, else None """ - sym = kconf.get_symbol(imply_config) + sym = kconf.syms.get(imply_config) if sym: for sel in sym.get_selected_symbols() | sym.get_implied_symbols(): if sel.get_name() == config: @@ -1547,7 +1547,7 @@ def check_imply_rule(kconf, config, imply_config): to add an 'imply' for 'config' to that part of the Kconfig. Args: - kconf: Kconfig.Config object + kconf: Kconfiglib.Kconfig object config: Name of config to check (without CONFIG_ prefix) imply_config: Implying config (without CONFIG_ prefix) which may or may not have an 'imply' for 'config') @@ -1558,7 +1558,7 @@ def check_imply_rule(kconf, config, imply_config): line number within the Kconfig file, or 0 if none message indicating the result """ - sym = kconf.get_symbol(imply_config) + sym = kconf.syms.get(imply_config) if not sym: return 'cannot find sym' locs = sym.get_def_locations() @@ -1784,7 +1784,7 @@ def do_imply_config(config_list, add_imply, imply_flags, skip_added, if skip_added: show = False else: - sym = kconf.get_symbol(iconfig[CONFIG_LEN:]) + sym = kconf.syms.get(iconfig[CONFIG_LEN:]) fname = '' if sym: locs = sym.get_def_locations() |