From 4cc8e1ede8e2431b457611aed877379a4413db30 Mon Sep 17 00:00:00 2001 From: latercomer Date: Sun, 24 Mar 2024 00:29:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86windows=E5=92=8Clinux=E4=B8=8B?= =?UTF-8?q?=E7=9A=84scons=20--menuconfig/--pyconfig/--pyconfig-silent?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=B8=BApython=E7=89=88=E6=9C=ACkconfiglib?= =?UTF-8?q?=EF=BC=8C=E5=90=8C=E6=97=B6=E5=85=BC=E5=AE=B9windows=20env?= =?UTF-8?q?=E4=B8=ADmenuconfig.exe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/Kconfiglib/.gitignore | 4 + tools/Kconfiglib/LICENSE.txt | 5 + tools/Kconfiglib/MANIFEST.in | 2 + tools/Kconfiglib/README.rst | 841 +++++ tools/Kconfiglib/__init__.py | 0 tools/Kconfiglib/alldefconfig.py | 27 + tools/Kconfiglib/allmodconfig.py | 46 + tools/Kconfiglib/allnoconfig.py | 45 + tools/Kconfiglib/allyesconfig.py | 56 + tools/{ => Kconfiglib}/defconfig.py | 6 +- tools/Kconfiglib/genconfig.py | 154 + .../guiconfig.py} | 42 +- tools/{ => Kconfiglib}/kconfiglib.py | 726 ++-- tools/Kconfiglib/listnewconfig.py | 76 + tools/Kconfiglib/makefile.patch | 48 + tools/Kconfiglib/menuconfig.py | 3278 +++++++++++++++++ tools/Kconfiglib/oldconfig.py | 246 ++ tools/Kconfiglib/olddefconfig.py | 28 + tools/Kconfiglib/savedefconfig.py | 49 + tools/Kconfiglib/setconfig.py | 92 + tools/Kconfiglib/setup.cfg | 7 + tools/Kconfiglib/setup.py | 96 + tools/Kconfiglib/testsuite.py | 3203 ++++++++++++++++ tools/building.py | 20 +- tools/{genconf.py => genconfig.py} | 0 tools/menuconfig.py | 18 +- tools/options.py | 15 +- 27 files changed, 8793 insertions(+), 337 deletions(-) create mode 100644 tools/Kconfiglib/.gitignore create mode 100644 tools/Kconfiglib/LICENSE.txt create mode 100644 tools/Kconfiglib/MANIFEST.in create mode 100644 tools/Kconfiglib/README.rst create mode 100644 tools/Kconfiglib/__init__.py create mode 100644 tools/Kconfiglib/alldefconfig.py create mode 100644 tools/Kconfiglib/allmodconfig.py create mode 100644 tools/Kconfiglib/allnoconfig.py create mode 100644 tools/Kconfiglib/allyesconfig.py rename tools/{ => Kconfiglib}/defconfig.py (86%) create mode 100644 tools/Kconfiglib/genconfig.py rename tools/{pyguiconfig.py => Kconfiglib/guiconfig.py} (92%) rename tools/{ => Kconfiglib}/kconfiglib.py (92%) create mode 100644 tools/Kconfiglib/listnewconfig.py create mode 100644 tools/Kconfiglib/makefile.patch create mode 100644 tools/Kconfiglib/menuconfig.py create mode 100644 tools/Kconfiglib/oldconfig.py create mode 100644 tools/Kconfiglib/olddefconfig.py create mode 100644 tools/Kconfiglib/savedefconfig.py create mode 100644 tools/Kconfiglib/setconfig.py create mode 100644 tools/Kconfiglib/setup.cfg create mode 100644 tools/Kconfiglib/setup.py create mode 100644 tools/Kconfiglib/testsuite.py rename tools/{genconf.py => genconfig.py} (100%) diff --git a/tools/Kconfiglib/.gitignore b/tools/Kconfiglib/.gitignore new file mode 100644 index 00000000000..4a18d79f037 --- /dev/null +++ b/tools/Kconfiglib/.gitignore @@ -0,0 +1,4 @@ +*.py[co] +build/ +*.egg-info/ +dist/ diff --git a/tools/Kconfiglib/LICENSE.txt b/tools/Kconfiglib/LICENSE.txt new file mode 100644 index 00000000000..8b31efca291 --- /dev/null +++ b/tools/Kconfiglib/LICENSE.txt @@ -0,0 +1,5 @@ +Copyright (c) 2011-2019, Ulf Magnusson + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/tools/Kconfiglib/MANIFEST.in b/tools/Kconfiglib/MANIFEST.in new file mode 100644 index 00000000000..881a7944b18 --- /dev/null +++ b/tools/Kconfiglib/MANIFEST.in @@ -0,0 +1,2 @@ +# Include the license file in source distributions +include LICENSE.txt diff --git a/tools/Kconfiglib/README.rst b/tools/Kconfiglib/README.rst new file mode 100644 index 00000000000..b59f04ecef8 --- /dev/null +++ b/tools/Kconfiglib/README.rst @@ -0,0 +1,841 @@ +.. contents:: Table of contents + :backlinks: none + +News +---- + +Dependency loop with recent linux-next kernels +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To fix issues with dependency loops on recent linux-next kernels, apply `this +patch `_. Hopefully, +it will be in ``linux-next`` soon. + +``windows-curses`` is no longer automatically installed on Windows +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Starting with Kconfiglib 13.0.0, the `windows-curses +`__ package is no longer +automatically installed on Windows, and needs to be installed manually for the +terminal ``menuconfig`` to work. + +This fixes installation of Kconfiglib on MSYS2, which is not compatible with +``windows-curses``. See `this issue +`__. + +The ``menuconfig`` now shows a hint re. installing ``windows-curses`` when the +``curses`` module can't be imported on Windows. + +Sorry if this change caused problems! + +Overview +-------- + +Kconfiglib is a `Kconfig +`__ +implementation in Python 2/3. It started out as a helper library, but now has a +enough functionality to also work well as a standalone Kconfig implementation +(including `terminal and GUI menuconfig interfaces `_ +and `Kconfig extensions`_). + +The entire library is contained in `kconfiglib.py +`_. The +bundled scripts are implemented on top of it. Implementing your own scripts +should be relatively easy, if needed. + +Kconfiglib is used exclusively by e.g. the `Zephyr +`__, `esp-idf +`__, and `ACRN +`__ projects. It is also used for many small helper +scripts in various projects. + +Since Kconfiglib is based around a library, it can be used e.g. to generate a +`Kconfig cross-reference +`_, using +the same robust Kconfig parser used for other Kconfig tools, instead of brittle +ad-hoc parsing. The documentation generation script can be found `here +`__. + +Kconfiglib implements the recently added `Kconfig preprocessor +`__. +For backwards compatibility, environment variables can be referenced both as +``$(FOO)`` (the new syntax) and as ``$FOO`` (the old syntax). The old syntax is +deprecated, but will probably be supported for a long time, as it's needed to +stay compatible with older Linux kernels. The major version will be increased +if support is ever dropped. Using the old syntax with an undefined environment +variable keeps the string as is. + +Note: See `this issue `__ if +you run into a "macro expanded to blank string" error with kernel 4.18+. + +See `this page +`__ for some +Kconfig tips and best practices. + +Installation +------------ + +Installation with pip +~~~~~~~~~~~~~~~~~~~~~ + +Kconfiglib is available on `PyPI `_ and can be +installed with e.g. + +.. code:: + + $ pip(3) install kconfiglib + +Microsoft Windows is supported. + +The ``pip`` installation will give you both the base library and the following +executables. All but two (``genconfig`` and ``setconfig``) mirror functionality +available in the C tools. + +- `menuconfig `_ + +- `guiconfig `_ + +- `oldconfig `_ + +- `olddefconfig `_ + +- `savedefconfig `_ + +- `defconfig `_ + +- `alldefconfig `_ + +- `allnoconfig `_ + +- `allmodconfig `_ + +- `allyesconfig `_ + +- `listnewconfig `_ + +- `genconfig `_ + +- `setconfig `_ + +``genconfig`` is intended to be run at build time. It generates a C header from +the configuration and (optionally) information that can be used to rebuild only +files that reference Kconfig symbols that have changed value. + +Starting with Kconfiglib version 12.2.0, all utilities are compatible with both +Python 2 and Python 3. Previously, ``menuconfig.py`` only ran under Python 3 +(i.e., it's now more backwards compatible than before). + +**Note:** If you install Kconfiglib with ``pip``'s ``--user`` flag, make sure +that your ``PATH`` includes the directory where the executables end up. You can +list the installed files with ``pip(3) show -f kconfiglib``. + +All releases have a corresponding tag in the git repository, e.g. ``v14.1.0`` +(the latest version). + +`Semantic versioning `_ is used. There's been ten small +changes to the behavior of the API, a Windows packaging change, and a hashbang +change to use ``python3`` +(`1 `_, +`2 `_, +`3 `_, +`4 `_, +`5 `_, +`6 `_, +`7 `_, +`8 `_, +`9 `_, +`10 `_, +`Windows packaging change `_, +`Python 3 hashbang change `_), +which is why the major version is at 14 rather than 2. I do major version bumps +for all behavior changes, even tiny ones, and most of these were fixes for baby +issues in the early days of the Kconfiglib 2 API. + +Manual installation +~~~~~~~~~~~~~~~~~~~ + +Just drop ``kconfiglib.py`` and the scripts you want somewhere. There are no +third-party dependencies, but the terminal ``menuconfig`` won't work on Windows +unless a package like `windows-curses +`__ is installed. + +Installation for the Linux kernel +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See the module docstring at the top of `kconfiglib.py `_. + +Python version compatibility (2.7/3.2+) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Kconfiglib and all utilities run under both Python 2.7 and Python 3.2 and +later. The code mostly uses basic Python features and has no third-party +dependencies, so keeping it backwards-compatible is pretty low effort. + +The 3.2 requirement comes from ``argparse``. ``format()`` with unnumbered +``{}`` is used as well. + +A recent Python 3 version is recommended if you have a choice, as it'll give +you better Unicode handling. + +Getting started +--------------- + +1. `Install `_ the library and the utilities. + +2. Write `Kconfig + `__ + files that describe the available configuration options. See `this page + `__ for some + general Kconfig advice. + +3. Generate an initial configuration with e.g. ``menuconfig``/``guiconfig`` or + ``alldefconfig``. The configuration is saved as ``.config`` by default. + + For more advanced projects, the ``defconfig`` utility can be used to + generate the initial configuration from an existing configuration file. + Usually, this existing configuration file would be a minimal configuration + file, as generated by e.g. ``savedefconfig``. + +4. Run ``genconfig`` to generate a header file. By default, it is saved as + ``config.h``. + + Normally, ``genconfig`` would be run automatically as part of the build. + + Before writing a header file or other configuration output, Kconfiglib + compares the old contents of the file against the new contents. If there's + no change, the write is skipped. This avoids updating file metadata like the + modification time, and might save work depending on your build setup. + + Adding new configuration output formats should be relatively straightforward. + See the implementation of ``write_config()`` in `kconfiglib.py + `_. + The documentation for the ``Symbol.config_string`` property has some tips as + well. + +5. To update an old ``.config`` file after the Kconfig files have changed (e.g. + to add new options), run ``oldconfig`` (prompts for values for new options) + or ``olddefconfig`` (gives new options their default value). Entering the + ``menuconfig`` or ``guiconfig`` interface and saving the configuration will + also update it (the configuration interfaces always prompt for saving + on exit if it would modify the contents of the ``.config`` file). + + Due to Kconfig semantics, simply loading an old ``.config`` file performs an + implicit ``olddefconfig``, so building will normally not be affected by + having an outdated configuration. + +Whenever ``.config`` is overwritten, the previous version of the file is saved +to ``.config.old`` (or, more generally, to ``$KCONFIG_CONFIG.old``). + +Using ``.config`` files as Make input +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``.config`` files use Make syntax and can be included directly in Makefiles to +read configuration values from there. This is why ``n``-valued +``bool``/``tristate`` values are written out as ``# CONFIG_FOO is not set`` (a +Make comment) in ``.config``, allowing them to be tested with ``ifdef`` in +Make. + +If you make use of this, you might want to pass ``--config-out `` to +``genconfig`` and include the configuration file it generates instead of +including ``.config`` directly. This has the advantage that the generated +configuration file will always be a "full" configuration file, even if +``.config`` is outdated. Otherwise, it might be necessary to run +``old(def)config`` or ``menuconfig``/``guiconfig`` before rebuilding with an +outdated ``.config``. + +If you use ``--sync-deps`` to generate incremental build information, you can +include ``deps/auto.conf`` instead, which is also a full configuration file. + +Useful helper macros +~~~~~~~~~~~~~~~~~~~~ + +The `include/linux/kconfig.h +`_ +header in the Linux kernel defines some useful helper macros for testing +Kconfig configuration values. + +``IS_ENABLED()`` is generally useful, allowing configuration values to be +tested in ``if`` statements with no runtime overhead. + +Incremental building +~~~~~~~~~~~~~~~~~~~~ + +See the docstring for ``Kconfig.sync_deps()`` in `kconfiglib.py +`_ for hints +on implementing incremental builds (rebuilding just source files that reference +changed configuration values). + +Running the ``scripts/basic/fixdep.c`` tool from the kernel on the output of +``gcc -MD `` might give you an idea of how it all fits together. + +Library documentation +--------------------- + +Kconfiglib comes with extensive documentation in the form of docstrings. To view it, run e.g. +the following command: + +.. code:: sh + + $ pydoc(3) kconfiglib + +For HTML output, add ``-w``: + +.. code:: sh + + $ pydoc(3) -w kconfiglib + +This will also work after installing Kconfiglib with ``pip(3)``. + +Documentation for other modules can be viewed in the same way (though a plain +``--help`` will work when they're run as executables): + +.. code:: sh + + $ pydoc(3) menuconfig/guiconfig/... + +A good starting point for learning the library is to read the module docstring +(which you could also just read directly at the beginning of `kconfiglib.py +`_). It +gives an introduction to symbol values, the menu tree, and expressions. + +After reading the module docstring, a good next step is to read the ``Kconfig`` +class documentation, and then the documentation for the ``Symbol``, ``Choice``, +and ``MenuNode`` classes. + +Please tell me if something is unclear or can be explained better. + +Library features +---------------- + +Kconfiglib can do the following, among other things: + +- **Programmatically get and set symbol values** + + See `allnoconfig.py + `_ and + `allyesconfig.py + `_, + which are automatically verified to produce identical output to the standard + ``make allnoconfig`` and ``make allyesconfig``. + +- **Read and write .config and defconfig files** + + The generated ``.config`` and ``defconfig`` (minimal configuration) files are + character-for-character identical to what the C implementation would generate + (except for the header comment). The test suite relies on this, as it + compares the generated files. + +- **Write C headers** + + The generated headers use the same format as ``include/generated/autoconf.h`` + from the Linux kernel. Output for symbols appears in the order that they're + defined, unlike in the C tools (where the order depends on the hash table + implementation). + +- **Implement incremental builds** + + This uses the same scheme as the ``include/config`` directory in the kernel: + Symbols are translated into files that are touched when the symbol's value + changes between builds, which can be used to avoid having to do a full + rebuild whenever the configuration is changed. + + See the ``sync_deps()`` function for more information. + +- **Inspect symbols** + + Printing a symbol or other item (which calls ``__str__()``) returns its + definition in Kconfig format. This also works for symbols defined in multiple + locations. + + A helpful ``__repr__()`` is on all objects too. + + All ``__str__()`` and ``__repr__()`` methods are deliberately implemented + with just public APIs, so all symbol information can be fetched separately as + well. + +- **Inspect expressions** + + Expressions use a simple tuple-based format that can be processed manually + if needed. Expression printing and evaluation functions are provided, + implemented with public APIs. + +- **Inspect the menu tree** + + The underlying menu tree is exposed, including submenus created implicitly + from symbols depending on preceding symbols. This can be used e.g. to + implement menuconfig-like functionality. + + See `menuconfig.py + `_/`guiconfig.py + `_ and the + minimalistic `menuconfig_example.py + `_ + example. + +Kconfig extensions +~~~~~~~~~~~~~~~~~~ + +The following Kconfig extensions are available: + +- ``source`` supports glob patterns and includes each matching file. A pattern + is required to match at least one file. + + A separate ``osource`` statement is available for cases where it's okay for + the pattern to match no files (in which case ``osource`` turns into a no-op). + +- A relative ``source`` statement (``rsource``) is available, where file paths + are specified relative to the directory of the current Kconfig file. An + ``orsource`` statement is available as well, analogous to ``osource``. + +- Preprocessor user 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). + + See the *Kconfig extensions* section in the + `kconfiglib.py `_ + module docstring for more information. + +- ``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. + + These can be useful in projects that make use of symbols defined in multiple + locations, and remove some Kconfig inconsistency. + +- Environment variables are expanded directly in e.g. ``source`` and + ``mainmenu`` statements, meaning ``option env`` symbols are redundant. + + This is the standard behavior with the new `Kconfig preprocessor + `__, + which Kconfiglib implements. + + ``option env`` symbols are accepted but ignored, which leads the caveat that + they must have the same name as the environment variables they reference + (Kconfiglib warns if the names differ). This keeps Kconfiglib compatible with + older Linux kernels, where the name of the ``option env`` symbol always + matched the environment variable. Compatibility with older Linux kernels is + the main reason ``option env`` is still supported. + + The C tools have dropped support for ``option env``. + +- Two extra optional warnings can be enabled by setting environment variables, + covering cases that are easily missed when making changes to Kconfig files: + + * ``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 by setting + ``Kconfig.warn_assign_undef`` to ``True``/``False``. + +Other features +-------------- + +- **Single-file implementation** + + The entire library is contained in `kconfiglib.py + `_. + + The tools implemented on top of it are one file each. + +- **Robust and highly compatible with the C Kconfig tools** + +  The `test suite `_ + automatically compares output from Kconfiglib and the C tools + by diffing the generated ``.config`` files for the real kernel Kconfig and + defconfig files, for all ARCHes. + + This currently involves comparing the output for 36 ARCHes and 498 defconfig + files (or over 18000 ARCH/defconfig combinations in "obsessive" test suite + mode). All tests are expected to pass. + + A comprehensive suite of selftests is included as well. + +- **Not horribly slow despite being a pure Python implementation** + + The `allyesconfig.py + `_ + script currently runs in about 1.3 seconds on the Linux kernel on a Core i7 + 2600K (with a warm file cache), including the ``make`` overhead from ``make + scriptconfig``. Note that the Linux kernel Kconfigs are absolutely massive + (over 14k symbols for x86) compared to most projects, and also have overhead + from running shell commands via the Kconfig preprocessor. + + Kconfiglib is especially speedy in cases where multiple ``.config`` files + need to be processed, because the ``Kconfig`` files will only need to be parsed + once. + + For long-running jobs, `PyPy `_ gives a big performance + boost. CPython is faster for short-running jobs as PyPy needs some time to + warm up. + + Kconfiglib also works well with the + `multiprocessing `_ + module. No global state is kept. + +- **Generates more warnings than the C implementation** + + Generates the same warnings as the C implementation, plus additional ones. + Also detects dependency and ``source`` loops. + + All warnings point out the location(s) in the ``Kconfig`` files where a + symbol is defined, where applicable. + +- **Unicode support** + + Unicode characters in string literals in ``Kconfig`` and ``.config`` files are + correctly handled. This support mostly comes for free from Python. + +- **Windows support** + + Nothing Linux-specific is used. Universal newlines mode is used for both + Python 2 and Python 3. + + The `Zephyr `_ project uses Kconfiglib to + generate ``.config`` files and C headers on Linux as well as Windows. + +- **Internals that (mostly) mirror the C implementation** + + While being simpler to understand and tweak. + +Menuconfig interfaces +--------------------- + +Three configuration interfaces are currently available: + +- `menuconfig.py `_ + is a terminal-based configuration interface implemented using the standard + Python ``curses`` module. ``xconfig`` features like showing invisible symbols and + showing symbol names are included, and it's possible to jump directly to a symbol + in the menu tree (even if it's currently invisible). + + .. image:: https://raw.githubusercontent.com/ulfalizer/Kconfiglib/screenshots/screenshots/menuconfig.gif + + *There is now also a show-help mode that shows the help text of the currently + selected symbol in the help window at the bottom.* + + Starting with Kconfiglib 12.2.0, ``menuconfig.py`` runs under both Python 2 + and Python 3 (previously, it only ran under Python 3, so this was a + backport). Running it under Python 3 provides better support for Unicode text + entry (``get_wch()`` is not available in the ``curses`` module on Python 2). + + There are no third-party dependencies on \*nix. On Windows, + the ``curses`` modules is not available by default, but support + can be added by installing the ``windows-curses`` package: + + .. code-block:: shell + + $ pip install windows-curses + + This uses wheels built from `this repository + `_, which is in turn + based on Christoph Gohlke's `Python Extension Packages for Windows + `_. + + See the docstring at the top of `menuconfig.py + `_ for + more information about the terminal menuconfig implementation. + +- `guiconfig.py + `_ is a + graphical configuration interface written in `Tkinter + `_. Like ``menuconfig.py``, + it supports showing all symbols (with invisible symbols in red) and jumping + directly to symbols. Symbol values can also be changed directly from the + jump-to dialog. + + When single-menu mode is enabled, a single menu is shown at a time, like in + the terminal menuconfig. Only this mode distinguishes between symbols defined + with ``config`` and symbols defined with ``menuconfig``. + + ``guiconfig.py`` has been tested on X11, Windows, and macOS, and is + compatible with both Python 2 and Python 3. + + Despite being part of the Python standard library, ``tkinter`` often isn't + included by default in Python installations on Linux. These commands will + install it on a few different distributions: + + - Ubuntu: ``sudo apt install python-tk``/``sudo apt install python3-tk`` + + - Fedora: ``dnf install python2-tkinter``/``dnf install python3-tkinter`` + + - Arch: ``sudo pacman -S tk`` + + - Clear Linux: ``sudo swupd bundle-add python3-tcl`` + + Screenshot below, with show-all mode enabled and the jump-to dialog open: + + .. image:: https://raw.githubusercontent.com/ulfalizer/Kconfiglib/screenshots/screenshots/guiconfig.png + + To avoid having to carry around a bunch of GIFs, the image data is embedded + in ``guiconfig.py``. To use separate GIF files instead, change + ``_USE_EMBEDDED_IMAGES`` to ``False`` in ``guiconfig.py``. The image files + can be found in the `screenshots + `_ + branch. + + I did my best with the images, but some are definitely only art adjacent. + Touch-ups are welcome. :) + +- `pymenuconfig `_, built by `RomaVis + `_, is an older portable Python 2/3 TkInter + menuconfig implementation. + + Screenshot below: + + .. image:: https://raw.githubusercontent.com/RomaVis/pymenuconfig/master/screenshot.PNG + + While working on the terminal menuconfig implementation, I added a few APIs + to Kconfiglib that turned out to be handy. ``pymenuconfig`` predates + ``menuconfig.py`` and ``guiconfig.py``, and so didn't have them available. + Blame me for any workarounds. + +Examples +-------- + +Example scripts +~~~~~~~~~~~~~~~ + +The `examples/ `_ directory contains some simple example scripts. Among these are the following ones. Make sure you run them with the latest version of Kconfiglib, as they might make use of newly added features. + +- `eval_expr.py `_ evaluates an expression in the context of a configuration. + +- `find_symbol.py `_ searches through expressions to find references to a symbol, also printing a "backtrace" with parents for each reference found. + +- `help_grep.py `_ searches for a string in all help texts. + +- `print_tree.py `_ prints a tree of all configuration items. + +- `print_config_tree.py `_ is similar to ``print_tree.py``, but dumps the tree as it would appear in ``menuconfig``, including values. This can be handy for visually diffing between ``.config`` files and different versions of ``Kconfig`` files. + +- `list_undefined.py `_ finds references to symbols that are not defined by any architecture in the Linux kernel. + +- `merge_config.py `_ merges configuration fragments to produce a complete .config, similarly to ``scripts/kconfig/merge_config.sh`` from the kernel. + +- `menuconfig_example.py `_ implements a configuration interface that uses notation similar to ``make menuconfig``. It's deliberately kept as simple as possible to demonstrate just the core concepts. + +Real-world examples +~~~~~~~~~~~~~~~~~~~ + +- `kconfig.py + `_ + from the `Zephyr `_ project handles + ``.config`` and header file generation, also doing configuration fragment + merging + +- `genrest.py + `_ + generates a Kconfig symbol cross-reference, which can be viewed `here + `__ + +- `CMake and IDE integration + `_ from + the ESP-IDF project, via a configuration server program. + +- `A script for turning on USB-related options + `_, + from the `syzkaller `_ project. + +- `Various automated checks + `_, + including a check for references to undefined Kconfig symbols in source code. + See the ``KconfigCheck`` class. + +- `Various utilities + `_ + from the `ACRN `_ project + +These use the older Kconfiglib 1 API, which was clunkier and not as general +(functions instead of properties, no direct access to the menu structure or +properties, uglier ``__str__()`` output): + +- `genboardscfg.py `_ from `Das U-Boot `_ generates some sort of legacy board database by pulling information from a newly added Kconfig-based configuration system (as far as I understand it :). + +- `gen-manual-lists.py `_ generated listings for an appendix in the `Buildroot `_ manual. (The listing has since been removed.) + +- `gen_kconfig_doc.py `_ from the `esp-idf `_ project generates documentation from Kconfig files. + +- `SConf `_ builds an interactive configuration interface (like ``menuconfig``) on top of Kconfiglib, for use e.g. with `SCons `_. + +- `kconfig-diff.py `_ -- a script by `dubiousjim `_ that compares kernel configurations. + +- Originally, Kconfiglib was used in chapter 4 of my `master's thesis `_ to automatically generate a "minimal" kernel for a given system. Parts of it bother me a bit now, but that's how it goes with old work. + +Sample ``make iscriptconfig`` session +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following log should give some idea of the functionality available in the API: + +.. code-block:: + + $ make iscriptconfig + A Kconfig instance 'kconf' for the architecture x86 has been created. + >>> kconf # Calls Kconfig.__repr__() + + >>> kconf.mainmenu_text # Expanded main menu text + 'Linux/x86 4.14.0-rc7 Kernel Configuration' + >>> kconf.top_node # The implicit top-level menu + + >>> kconf.top_node.list # First child menu node + + >>> print(kconf.top_node.list) # Calls MenuNode.__str__() + config SRCARCH + string + option env="SRCARCH" + default "x86" + >>> sym = kconf.top_node.list.next.item # Item contained in next menu node + >>> print(sym) # Calls Symbol.__str__() + config 64BIT + bool "64-bit kernel" if ARCH = "x86" + default ARCH != "i386" + help + Say yes to build a 64-bit kernel - formerly known as x86_64 + Say no to build a 32-bit kernel - formerly known as i386 + >>> sym # Calls Symbol.__repr__() + + >>> sym.assignable # Currently assignable values (0, 1, 2 = n, m, y) + (0, 2) + >>> sym.set_value(0) # Set it to n + True + >>> sym.tri_value # Check the new value + 0 + >>> sym = kconf.syms["X86_MPPARSE"] # Look up symbol by name + >>> print(sym) + config X86_MPPARSE + bool "Enable MPS table" if (ACPI || SFI) && X86_LOCAL_APIC + default y if X86_LOCAL_APIC + help + For old smp systems that do not have proper acpi support. Newer systems + (esp with 64bit cpus) with acpi support, MADT and DSDT will override it + >>> default = sym.defaults[0] # Fetch its first default + >>> sym = default[1] # Fetch the default's condition (just a Symbol here) + >>> print(sym) + config X86_LOCAL_APIC + bool + default y + select IRQ_DOMAIN_HIERARCHY + select PCI_MSI_IRQ_DOMAIN if PCI_MSI + depends on X86_64 || SMP || X86_32_NON_STANDARD || X86_UP_APIC || PCI_MSI + >>> sym.nodes # Show the MenuNode(s) associated with it + [] + >>> kconfiglib.expr_str(sym.defaults[0][1]) # Print the default's condition + 'X86_64 || SMP || X86_32_NON_STANDARD || X86_UP_APIC || PCI_MSI' + >>> kconfiglib.expr_value(sym.defaults[0][1]) # Evaluate it (0 = n) + 0 + >>> kconf.syms["64BIT"].set_value(2) + True + >>> kconfiglib.expr_value(sym.defaults[0][1]) # Evaluate it again (2 = y) + 2 + >>> kconf.write_config("myconfig") # Save a .config + >>> ^D + $ cat myconfig + # Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) + CONFIG_64BIT=y + CONFIG_X86_64=y + CONFIG_X86=y + CONFIG_INSTRUCTION_DECODER=y + CONFIG_OUTPUT_FORMAT="elf64-x86-64" + CONFIG_ARCH_DEFCONFIG="arch/x86/configs/x86_64_defconfig" + CONFIG_LOCKDEP_SUPPORT=y + CONFIG_STACKTRACE_SUPPORT=y + CONFIG_MMU=y + ... + +Test suite +---------- + +The test suite is run with + +.. code:: + + $ python(3) Kconfiglib/testsuite.py + +`pypy `_ works too, and is much speedier for everything except ``allnoconfig.py``/``allnoconfig_simpler.py``/``allyesconfig.py``, where it doesn't have time to warm up since +the scripts are run via ``make scriptconfig``. + +The test suite must be run from the top-level kernel directory. It requires that the +Kconfiglib git repository has been cloned into it and that the makefile patch has been applied. + +To get rid of warnings generated for the kernel ``Kconfig`` files, add ``2>/dev/null`` to the command to +discard ``stderr``. + +**NOTE: Forgetting to apply the Makefile patch will cause some tests that compare generated configurations to fail** + +**NOTE: The test suite overwrites .config in the kernel root, so make sure to back it up.** + +The test suite consists of a set of selftests and a set of compatibility tests that +compare configurations generated by Kconfiglib with +configurations generated by the C tools, for a number of cases. See +`testsuite.py `_ +for the available options. + +The `tests/reltest `_ script runs the test suite +and all the example scripts for both Python 2 and Python 3, verifying that everything works. + +Rarely, the output from the C tools is changed slightly (most recently due to a +`change `_ I added). +If you get test suite failures, try running the test suite again against the +`linux-next tree `_, +which has all the latest changes. I will make it clear if any +non-backwards-compatible changes appear. + +A lot of time is spent waiting around for ``make`` and the C utilities (which need to reparse all the +Kconfig files for each defconfig test). Adding some multiprocessing to the test suite would make sense +too. + +Notes +----- + +* This is version 2 of Kconfiglib, which is not backwards-compatible with + Kconfiglib 1. A summary of changes between Kconfiglib 1 and Kconfiglib + 2 can be found `here + `__. + +* I sometimes see people add custom output formats, which is pretty + straightforward to do (see the implementations of ``write_autoconf()`` and + ``write_config()`` for a template, and also the documentation of the + ``Symbol.config_string`` property). If you come up with something you think + might be useful to other people, I'm happy to take it in upstream. Batteries + included and all that. + +* Kconfiglib assumes the modules symbol is ``MODULES``, which is backwards-compatible. + A warning is printed by default if ``option modules`` is set on some other symbol. + + Let me know if you need proper ``option modules`` support. It wouldn't be that + hard to add. + +Thanks +------ + +- To `RomaVis `_, for making + `pymenuconfig `_ and suggesting + the ``rsource`` keyword. + +- To `Mitja Horvat `_, for adding support + for user-defined styles to the terminal menuconfig. + +- To `Philip Craig `_ for adding + support for the ``allnoconfig_y`` option and fixing an obscure issue + with ``comment``\s inside ``choice``\s (that didn't affect correctness but + made outputs differ). ``allnoconfig_y`` is used to force certain symbols + to ``y`` during ``make allnoconfig`` to improve coverage. + +License +------- + +See `LICENSE.txt `_. SPDX license identifiers are used in the +source code. diff --git a/tools/Kconfiglib/__init__.py b/tools/Kconfiglib/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/Kconfiglib/alldefconfig.py b/tools/Kconfiglib/alldefconfig.py new file mode 100644 index 00000000000..56c4caa92d7 --- /dev/null +++ b/tools/Kconfiglib/alldefconfig.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2019, Ulf Magnusson +# SPDX-License-Identifier: ISC + +""" +Writes a configuration file where all symbols are set to their their default +values. + +The default output filename is '.config'. A different filename can be passed in +the KCONFIG_CONFIG environment variable. + +Usage for the Linux kernel: + + $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/alldefconfig.py +""" +import kconfiglib + + +def main(): + kconf = kconfiglib.standard_kconfig(__doc__) + kconf.load_allconfig("alldef.config") + print(kconf.write_config()) + + +if __name__ == "__main__": + main() diff --git a/tools/Kconfiglib/allmodconfig.py b/tools/Kconfiglib/allmodconfig.py new file mode 100644 index 00000000000..bfb72b40252 --- /dev/null +++ b/tools/Kconfiglib/allmodconfig.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2019, Ulf Magnusson +# SPDX-License-Identifier: ISC + +""" +Writes a configuration file where as many symbols as possible are set to 'm'. + +The default output filename is '.config'. A different filename can be passed +in the KCONFIG_CONFIG environment variable. + +Usage for the Linux kernel: + + $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/allmodconfig.py +""" +import kconfiglib + + +def main(): + kconf = kconfiglib.standard_kconfig(__doc__) + + # See allnoconfig.py + kconf.warn = False + + for sym in kconf.unique_defined_syms: + if sym.orig_type == kconfiglib.BOOL: + # 'bool' choice symbols get their default value, as determined by + # e.g. 'default's on the choice + if not sym.choice: + # All other bool symbols get set to 'y', like for allyesconfig + sym.set_value(2) + elif sym.orig_type == kconfiglib.TRISTATE: + sym.set_value(1) + + for choice in kconf.unique_choices: + choice.set_value(2 if choice.orig_type == kconfiglib.BOOL else 1) + + kconf.warn = True + + kconf.load_allconfig("allmod.config") + + print(kconf.write_config()) + + +if __name__ == "__main__": + main() diff --git a/tools/Kconfiglib/allnoconfig.py b/tools/Kconfiglib/allnoconfig.py new file mode 100644 index 00000000000..de90d8bf46c --- /dev/null +++ b/tools/Kconfiglib/allnoconfig.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2019, Ulf Magnusson +# SPDX-License-Identifier: ISC + +""" +Writes a configuration file where as many symbols as possible are set to 'n'. + +The default output filename is '.config'. A different filename can be passed +in the KCONFIG_CONFIG environment variable. + +Usage for the Linux kernel: + + $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/allnoconfig.py +""" + +# See examples/allnoconfig_walk.py for another way to implement this script + +import kconfiglib + + +def main(): + kconf = kconfiglib.standard_kconfig(__doc__) + + # Avoid warnings that would otherwise get printed by Kconfiglib for the + # following: + # + # 1. Assigning a value to a symbol without a prompt, which never has any + # effect + # + # 2. Assigning values invalid for the type (only bool/tristate symbols + # accept 0/1/2, for n/m/y). The assignments will be ignored for other + # symbol types, which is what we want. + kconf.warn = False + for sym in kconf.unique_defined_syms: + sym.set_value(2 if sym.is_allnoconfig_y else 0) + kconf.warn = True + + kconf.load_allconfig("allno.config") + + print(kconf.write_config()) + + +if __name__ == "__main__": + main() diff --git a/tools/Kconfiglib/allyesconfig.py b/tools/Kconfiglib/allyesconfig.py new file mode 100644 index 00000000000..90eb9b8d67f --- /dev/null +++ b/tools/Kconfiglib/allyesconfig.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2019, Ulf Magnusson +# SPDX-License-Identifier: ISC + +""" +Writes a configuration file where as many symbols as possible are set to 'y'. + +The default output filename is '.config'. A different filename can be passed +in the KCONFIG_CONFIG environment variable. + +Usage for the Linux kernel: + + $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/allyesconfig.py +""" +import kconfiglib + + +def main(): + kconf = kconfiglib.standard_kconfig(__doc__) + + # See allnoconfig.py + kconf.warn = False + + # Try to set all symbols to 'y'. Dependencies might truncate the value down + # later, but this will at least give the highest possible value. + # + # Assigning 0/1/2 to non-bool/tristate symbols has no effect (int/hex + # symbols still take a string, because they preserve formatting). + for sym in kconf.unique_defined_syms: + # Set choice symbols to 'm'. This value will be ignored for choices in + # 'y' mode (the "normal" mode), which will instead just get their + # default selection, but will set all symbols in m-mode choices to 'm', + # which is as high as they can go. + # + # Here's a convoluted example of how you might get an m-mode choice + # even during allyesconfig: + # + # choice + # tristate "weird choice" + # depends on m + sym.set_value(1 if sym.choice else 2) + + # Set all choices to the highest possible mode + for choice in kconf.unique_choices: + choice.set_value(2) + + kconf.warn = True + + kconf.load_allconfig("allyes.config") + + print(kconf.write_config()) + + +if __name__ == "__main__": + main() diff --git a/tools/defconfig.py b/tools/Kconfiglib/defconfig.py similarity index 86% rename from tools/defconfig.py rename to tools/Kconfiglib/defconfig.py index d1b1e4ea739..b1792731f59 100644 --- a/tools/defconfig.py +++ b/tools/Kconfiglib/defconfig.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (c) 2019, Ulf Magnusson # SPDX-License-Identifier: ISC @@ -25,7 +25,7 @@ def main(): parser.add_argument( "--kconfig", default="Kconfig", - help="Base Kconfig file (default: Kconfig)") + help="Top-level Kconfig file (default: Kconfig)") parser.add_argument( "config", @@ -34,7 +34,7 @@ def main(): args = parser.parse_args() - kconf = kconfiglib.Kconfig(args.kconfig) + kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True) print(kconf.load_config(args.config)) print(kconf.write_config()) diff --git a/tools/Kconfiglib/genconfig.py b/tools/Kconfiglib/genconfig.py new file mode 100644 index 00000000000..62f065ba727 --- /dev/null +++ b/tools/Kconfiglib/genconfig.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2019, Ulf Magnusson +# SPDX-License-Identifier: ISC + +""" +Generates a header file with #defines from the configuration, matching the +format of include/generated/autoconf.h in the Linux kernel. + +Optionally, also writes the configuration output as a .config file. See +--config-out. + +The --sync-deps, --file-list, and --env-list options generate information that +can be used to avoid needless rebuilds/reconfigurations. + +Before writing a header or configuration file, Kconfiglib compares the old +contents of the file against the new contents. If there's no change, the write +is skipped. This avoids updating file metadata like the modification time, and +might save work depending on your build setup. + +By default, the configuration is generated from '.config'. A different +configuration file can be passed in the KCONFIG_CONFIG environment variable. + +A custom header string can be inserted at the beginning of generated +configuration and header files by setting the KCONFIG_CONFIG_HEADER and +KCONFIG_AUTOHEADER_HEADER environment variables, respectively (this also works +for other scripts). The string is not automatically made a comment (this is by +design, to allow anything to be added), and no trailing newline is added, so +add '/* */', '#', and newlines as appropriate. + +See https://www.gnu.org/software/make/manual/make.html#Multi_002dLine for a +handy way to define multi-line variables in makefiles, for use with custom +headers. Remember to export the variable to the environment. +""" +import argparse +import os +import sys + +import kconfiglib + + +DEFAULT_SYNC_DEPS_PATH = "deps/" + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=__doc__) + + parser.add_argument( + "--header-path", + metavar="HEADER_FILE", + help=""" +Path to write the generated header file to. If not specified, the path in the +environment variable KCONFIG_AUTOHEADER is used if it is set, and 'config.h' +otherwise. +""") + + parser.add_argument( + "--config-out", + metavar="CONFIG_FILE", + help=""" +Write the configuration to CONFIG_FILE. This is useful if you include .config +files in Makefiles, as the generated configuration file will be a full .config +file even if .config is outdated. The generated configuration matches what +olddefconfig would produce. If you use sync-deps, you can include +deps/auto.conf instead. --config-out is meant for cases where incremental build +information isn't needed. +""") + + parser.add_argument( + "--sync-deps", + metavar="OUTPUT_DIR", + nargs="?", + const=DEFAULT_SYNC_DEPS_PATH, + help=""" +Enable generation of symbol dependency information for incremental builds, +optionally specifying the output directory (default: {}). See the docstring of +Kconfig.sync_deps() in Kconfiglib for more information. +""".format(DEFAULT_SYNC_DEPS_PATH)) + + parser.add_argument( + "--file-list", + metavar="OUTPUT_FILE", + help=""" +Write a list of all Kconfig files to OUTPUT_FILE, with one file per line. The +paths are relative to $srctree (or to the current directory if $srctree is +unset). Files appear in the order they're 'source'd. +""") + + parser.add_argument( + "--env-list", + metavar="OUTPUT_FILE", + help=""" +Write a list of all environment variables referenced in Kconfig files to +OUTPUT_FILE, with one variable per line. Each line has the format NAME=VALUE. +Only environment variables referenced with the preprocessor $(VAR) syntax are +included, and not variables referenced with the older $VAR syntax (which is +only supported for backwards compatibility). +""") + + parser.add_argument( + "kconfig", + metavar="KCONFIG", + nargs="?", + default="Kconfig", + help="Top-level Kconfig file (default: Kconfig)") + + args = parser.parse_args() + + + kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True) + kconf.load_config() + + if args.header_path is None: + if "KCONFIG_AUTOHEADER" in os.environ: + kconf.write_autoconf() + else: + # Kconfiglib defaults to include/generated/autoconf.h to be + # compatible with the C tools. 'config.h' is used here instead for + # backwards compatibility. It's probably a saner default for tools + # as well. + kconf.write_autoconf("config.h") + else: + kconf.write_autoconf(args.header_path) + + if args.config_out is not None: + kconf.write_config(args.config_out, save_old=False) + + if args.sync_deps is not None: + kconf.sync_deps(args.sync_deps) + + if args.file_list is not None: + with _open_write(args.file_list) as f: + for path in kconf.kconfig_filenames: + f.write(path + "\n") + + if args.env_list is not None: + with _open_write(args.env_list) as f: + for env_var in kconf.env_vars: + f.write("{}={}\n".format(env_var, os.environ[env_var])) + + +def _open_write(path): + # Python 2/3 compatibility. io.open() is available on both, but makes + # write() expect 'unicode' strings on Python 2. + + if sys.version_info[0] < 3: + return open(path, "w") + return open(path, "w", encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/tools/pyguiconfig.py b/tools/Kconfiglib/guiconfig.py similarity index 92% rename from tools/pyguiconfig.py rename to tools/Kconfiglib/guiconfig.py index 59fbe877e29..7804fdc7c21 100644 --- a/tools/pyguiconfig.py +++ b/tools/Kconfiglib/guiconfig.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (c) 2019, Ulf Magnusson # SPDX-License-Identifier: ISC @@ -95,7 +95,7 @@ def _main(): - menuconfig(standard_kconfig()) + menuconfig(standard_kconfig(__doc__)) # Global variables used below: @@ -345,9 +345,9 @@ def load_image(name, data): # Note: Base64 data can be put on the clipboard with # $ base64 -w0 foo.gif | xclip - load_image("icon", "R0lGODlhIwAjAPcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAAjACMAAAj/AKPtE0hwoMGCAiUF28VwoaJIyg4a3LeMIkVly5QR00gsk8eNvBZGYrhr4SJhmixWJFgPkyReIyVFisTrEi9eu2aOXBhsZE5dkYYtK1gR00lhydoha5dMGFOkwhr6bEhSUTKLApVFEjZsHtdkw56CjRoJ0iJIOYHlJLlL0kqKmXZ1RdoO6bCkw+QJ86nrLK2duy7tAhpx6D5Jwujenat02DBhZx/2LSu54UmB+zTtsrt06eKkyKKSjNT34UO0gyOt1DpvGLvFSF/Lc7yXIVqdihYpyo120bCEXJHNbQ1WeF5JqW2fXbQITBjTkH5H45W4ujCFMGHOXOSTZ8iQwRRu/80ZLdr4tSPTo2e7ViT7YNfpHW4nmKQuhvfZily4NjDD+nsNtRlDPtXXU0mkTVXSew0Jw0tEkgxj4GgL6rfgTpHwN1JikigTzYAILqghTz4BRlJ9JTkI4TD88Uegi1S1WCFPm3X4oWgH9iSjYC4eWOGCie1SEWKiKRIGGLql11xzRx7JnHNQhsGQMJh4SJ1IYGzhnBY2LBLMllmCgSSUWjoZDHxCHuZgMLxoAUaGi2g5mps0XQiGFlNRZ6NcPMEgZU5+btZTGFsAyVAYWkR1JppDsriZFnguYiaBbtpEI6IZfkelh3zmFAYMYMTwJptoImqoJFl6x2hLwwiqRRiSauRx0oWvjifMQp+GFNU613Ea5C43gFESpqTy4pyIIYkZ0mZBDkXdrcK8ulkkhKYYiZY4QbsLorqmaCMvLIYkLa5IihZGGGgqemRUt7IT1TItuQufrVFhB5+Bwqzz3ZT6wmdjUAvdKuiit5KamMHwJfwsOxAd5q4w5eQbFTu8sLOZOcFYrDGbFq/JzpnvTsfVw4mRTLJ1JadcncWYDDVMJMiwk9TMeTHmlDDuPvxwxkEGtQ9cvLjUFiZtIScJ0S5dd13QNL0kU9CZYEYQRtFQTU80y0CzzDL6ZFR11ltrLfbW+9QTTUAAOw==") - load_image("n_bool", "R0lGODlhDgAOAHAAACH5BAEAAPwALAAAAAAOAA4AhwAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAAAgxAAEIHEiw4L6DCBMeFKiw4T6GDhNCjLgQAEWEEylmjLjRYceGHxWGlGjx4sOCKAcGBAA7") - load_image("y_bool", "R0lGODlhDwAPAHAAACH5BAEAAPwALAAAAAAPAA8AhwAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAAAhLAAEIHEiwIIB9CBMqTChwoUOEDR8iJHZjX0SJDS86HGjx4MGFBiJm/IhwxcWRB5WFJNkRosAYGlvuq0dwI0llMV1KXJjzocGfAwMCADs=") + load_image("icon", "R0lGODlhMAAwAPEDAAAAAADQAO7u7v///yH5BAUKAAMALAAAAAAwADAAAAL/nI+gy+2Pokyv2jazuZxryQjiSJZmyXxHeLbumH6sEATvW8OLNtf5bfLZRLFITzgEipDJ4mYxYv6A0ubuqYhWk66tVTE4enHer7jcKvt0LLUw6P45lvEprT6c0+v7OBuqhYdHohcoqIbSAHc4ljhDwrh1UlgSydRCWWlp5wiYZvmSuSh4IzrqV6p4cwhkCsmY+nhK6uJ6t1mrOhuJqfu6+WYiCiwl7HtLjNSZZZis/MeM7NY3TaRKS40ooDeoiVqIultsrav92bi9c3a5KkkOsOJZpSS99m4k/0zPng4Gks9JSbB+8DIcoQfnjwpZCHv5W+ip4aQrKrB0uOikYhiMCBw1/uPoQUMBADs=") + load_image("n_bool", "R0lGODdhEAAQAPAAAAgICP///ywAAAAAEAAQAAACIISPacHtvp5kcb5qG85hZ2+BkyiRF8BBaEqtrKkqslEAADs=") + load_image("y_bool", "R0lGODdhEAAQAPEAAAgICADQAP///wAAACwAAAAAEAAQAAACMoSPacLtvlh4YrIYsst2cV19AvaVF9CUXBNJJoum7ymrsKuCnhiupIWjSSjAFuWhSCIKADs=") load_image("n_tri", "R0lGODlhEAAQAPD/AAEBAf///yH5BAUKAAIALAAAAAAQABAAAAInlI+pBrAKQnCPSUlXvFhznlkfeGwjKZhnJ65h6nrfi6h0st2QXikFADs=") load_image("m_tri", "R0lGODlhEAAQAPEDAAEBAeQMuv///wAAACH5BAUKAAMALAAAAAAQABAAAAI5nI+pBrAWAhPCjYhiAJQCnWmdoElHGVBoiK5M21ofXFpXRIrgiecqxkuNciZIhNOZFRNI24PhfEoLADs=") load_image("y_tri", "R0lGODlhEAAQAPEDAAICAgDQAP///wAAACH5BAUKAAMALAAAAAAQABAAAAI0nI+pBrAYBhDCRRUypfmergmgZ4xjMpmaw2zmxk7cCB+pWiVqp4MzDwn9FhGZ5WFjIZeGAgA7") @@ -355,7 +355,7 @@ def load_image(name, data): load_image("y_my", "R0lGODlhEAAQAPH/AAAAAADQAAPRA////yH5BAUKAAQALAAAAAAQABAAAAM+SArcrhCMSSuIM9Q8rxxBWIXawIBkmWonupLd565Um9G1PIs59fKmzw8WnAlusBYR2SEIN6DmAmqBLBxYSAIAOw==") load_image("n_locked", "R0lGODlhEAAQAPABAAAAAP///yH5BAUKAAEALAAAAAAQABAAAAIgjB8AyKwN04pu0vMutpqqz4Hih4ydlnUpyl2r23pxUAAAOw==") load_image("m_locked", "R0lGODlhEAAQAPD/AAAAAOQMuiH5BAUKAAIALAAAAAAQABAAAAIylC8AyKwN04ohnGcqqlZmfXDWI26iInZoyiore05walolV39ftxsYHgL9QBBMBGFEFAAAOw==") - load_image("y_locked", "R0lGODlhDwAPAHAAACH5BAEAAPwALAAAAAAPAA8AhwAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAAAg1AAEIHEiwIIB9CBMqTChwoUOEDR8ujCiR4cGKFjFmNFhwX0OOBD1e1EgRY8mKJyWmfAgSZEAAOw==") + load_image("y_locked", "R0lGODlhEAAQAPD/AAAAAADQACH5BAUKAAIALAAAAAAQABAAAAIylC8AyKzNgnlCtoDTwvZwrHydIYpQmR3KWq4uK74IOnp0HQPmnD3cOVlUIAgKsShkFAAAOw==") load_image("not_selected", "R0lGODlhEAAQAPD/AAAAAP///yH5BAUKAAIALAAAAAAQABAAAAIrlA2px6IBw2IpWglOvTYhzmUbGD3kNZ5QqrKn2YrqigCxZoMelU6No9gdCgA7") load_image("selected", "R0lGODlhEAAQAPD/AAAAAP///yH5BAUKAAIALAAAAAAQABAAAAIzlA2px6IBw2IpWglOvTah/kTZhimASJomiqonlLov1qptHTsgKSEzh9H8QI0QzNPwmRoFADs=") load_image("edit", "R0lGODlhEAAQAPIFAAAAAKOLAMuuEPvXCvrxvgAAAAAAAAAAACH5BAUKAAUALAAAAAAQABAAAANCWLqw/gqMBp8cszJxcwVC2FEOEIAi5kVBi3IqWZhuCGMyfdpj2e4pnK+WAshmvxeAcETWlsxPkkBtsqBMa8TIBSQAADs=") @@ -403,9 +403,8 @@ def _init_misc_ui(): # Use the 'clam' theme on *nix if it's available. It looks nicer than the # 'default' theme. - style = ttk.Style() - style.theme_use("default") if _root.tk.call("tk", "windowingsystem") == "x11": + style = ttk.Style() if "clam" in style.theme_names(): style.theme_use("clam") @@ -556,7 +555,7 @@ def _create_kconfig_tree(parent): tree.tag_configure("y-locked", image=_y_locked_img) tree.tag_configure("not-selected", image=_not_selected_img) tree.tag_configure("selected", image=_selected_img) -# tree.tag_configure("edit", image=_edit_img) + tree.tag_configure("edit", image=_edit_img) tree.tag_configure("invisible", foreground="red") tree.grid(column=0, row=0, sticky="nsew") @@ -1476,9 +1475,8 @@ def _toggle_showall(_): def _do_showall(): # Updates the UI for the current show-all setting - # Don't allow turning off show-all if we're in single-menu mode and the - # current menu would become empty - if _single_menu and not _shown_menu_nodes(_cur_menu): + # Don't allow turning off show-all if we'd end up with no visible nodes + if _nothing_shown(): _show_all_var.set(True) return @@ -1513,6 +1511,17 @@ def _do_showall(): _tree.focus_set() +def _nothing_shown(): + # _do_showall() helper. Returns True if no nodes would get + # shown with the current show-all setting. Also handles the + # (obscure) case when there are no visible nodes in the entire + # tree, meaning guiconfig was automatically started in + # show-all mode, which mustn't be turned off. + + return not _shown_menu_nodes( + _cur_menu if _single_menu else _kconf.top_node) + + def _toggle_tree_mode(_): # Toggles single-menu mode on/off @@ -1952,9 +1961,7 @@ def _sorted_sc_nodes(cached_nodes=[]): key=lambda choice: choice.name or "") cached_nodes += sorted( - [node - for choice in choices - for node in choice.nodes], + [node for choice in choices for node in choice.nodes], key=lambda node: node.prompt[0] if node.prompt else "") return cached_nodes @@ -2120,7 +2127,10 @@ def _defaults_info(sc): if not sc.defaults: return "" - s = "Defaults:\n" + s = "Default" + if len(sc.defaults) > 1: + s += "s" + s += ":\n" for val, cond in sc.orig_defaults: s += " - " diff --git a/tools/kconfiglib.py b/tools/Kconfiglib/kconfiglib.py similarity index 92% rename from tools/kconfiglib.py rename to tools/Kconfiglib/kconfiglib.py index 9a9b2f03f78..c67895ced6b 100644 --- a/tools/kconfiglib.py +++ b/tools/Kconfiglib/kconfiglib.py @@ -503,6 +503,15 @@ def my_other_fn(kconf, name, arg_1, arg_2, ...): 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: @@ -523,8 +532,7 @@ def my_other_fn(kconf, name, arg_1, arg_2, ...): 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. The 'kconf' parameter is provided for future extension (and -because the predefined functions take it anyway). +lead to a crash. Preferably, user-defined functions should be stateless. @@ -546,7 +554,7 @@ def my_other_fn(kconf, name, arg_1, arg_2, ...): from os.path import dirname, exists, expandvars, islink, join, realpath -VERSION = (12, 12, 1) +VERSION = (14, 1, 0) # File layout: @@ -765,8 +773,8 @@ class Kconfig(object): 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 + The value the $srctree environment variable had when the Kconfig instance + was created, 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 "./". @@ -781,13 +789,26 @@ class Kconfig(object): 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. + The value the CONFIG_ environment variable had when the Kconfig instance + was created, or "CONFIG_" if CONFIG_ wasn't set. This is the prefix used + (and expected) on symbol names in .config files and C headers. Used in + the same way in the C tools. + + config_header: + The value the KCONFIG_CONFIG_HEADER environment variable had when the + Kconfig instance was created, or the empty string if + KCONFIG_CONFIG_HEADER wasn't set. This string is inserted verbatim at the + beginning of configuration files. See write_config(). + + header_header: + The value the KCONFIG_AUTOHEADER_HEADER environment variable had when the + Kconfig instance was created, or the empty string if + KCONFIG_AUTOHEADER_HEADER wasn't set. This string is inserted verbatim at + the beginning of header files. See write_autoconf(). - 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", @@ -798,11 +819,13 @@ class Kconfig(object): "_warn_assign_no_prompt", "choices", "comments", + "config_header", "config_prefix", "const_syms", "defconfig_list", "defined_syms", "env_vars", + "header_header", "kconfig_filenames", "m", "menus", @@ -827,8 +850,8 @@ class Kconfig(object): # Parsing-related "_parsing_kconfigs", "_readline", - "_filename", - "_linenr", + "filename", + "linenr", "_include_path", "_filestack", "_line", @@ -842,7 +865,7 @@ class Kconfig(object): # def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, - encoding="utf-8"): + encoding="utf-8", suppress_traceback=False): """ Creates a new Kconfig object by parsing Kconfig files. Note that Kconfig files are not the same as .config files (which store @@ -907,7 +930,35 @@ def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, anyway. Related PEP: https://www.python.org/dev/peps/pep-0538/ + + suppress_traceback (default: False): + Helper for tools. When True, any EnvironmentError or KconfigError + generated during parsing is caught, the exception message is printed + to stderr together with the command name, and sys.exit(1) is called + (which generates SystemExit). + + This hides the Python traceback for "expected" errors like syntax + errors in Kconfig files. + + Other exceptions besides EnvironmentError and KconfigError are still + propagated when suppress_traceback is True. """ + try: + self._init(filename, warn, warn_to_stderr, encoding) + except (EnvironmentError, KconfigError) as e: + if suppress_traceback: + cmd = sys.argv[0] # Empty string if missing + if cmd: + cmd += ": " + # Some long exception messages have extra newlines for better + # formatting when reported as an unhandled exception. Strip + # them here. + sys.exit(cmd + str(e).strip()) + raise + + def _init(self, filename, warn, warn_to_stderr, encoding): + # See __init__() + self._encoding = encoding self.srctree = os.getenv("srctree", "") @@ -931,6 +982,9 @@ def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, self._unset_match = _re_match(r"# {}([^ ]+) is not set".format( self.config_prefix)) + self.config_header = os.getenv("KCONFIG_CONFIG_HEADER", "") + self.header_header = os.getenv("KCONFIG_AUTOHEADER_HEADER", "") + self.syms = {} self.const_syms = {} self.defined_syms = [] @@ -1013,8 +1067,8 @@ def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, self._include_path = () # The current parsing location - self._filename = filename - self._linenr = 0 + self.filename = filename + self.linenr = 0 # Used to avoid retokenizing lines when we discover that they're not # part of the construct currently being parsed. This is kinda like an @@ -1026,12 +1080,13 @@ def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, self._readline = self._open(join(self.srctree, filename), "r").readline try: - # Parse the Kconfig files - self._parse_block(None, self.top_node, self.top_node) + # Parse the Kconfig files. Returns the last node, which we + # terminate with '.next = None'. + self._parse_block(None, self.top_node, self.top_node).next = None self.top_node.list = self.top_node.next self.top_node.next = None except UnicodeDecodeError as e: - _decoding_error(e, self._filename) + _decoding_error(e, self.filename) # Close the top-level Kconfig file. __self__ fetches the 'file' object # for the method. @@ -1233,7 +1288,7 @@ def _load_config(self, filename, replace): self._warn("'{}' is not a valid value for the {} " "symbol {}. Assignment ignored." .format(val, TYPE_TO_STR[sym.orig_type], - _name_and_loc(sym)), + sym.name_and_loc), filename, linenr) continue @@ -1260,7 +1315,7 @@ def _load_config(self, filename, replace): if not match: self._warn("malformed string literal in " "assignment to {}. Assignment ignored." - .format(_name_and_loc(sym)), + .format(sym.name_and_loc), filename, linenr) continue @@ -1329,7 +1384,7 @@ def _assigned_twice(self, sym, new_val, filename, linenr): user_val = sym.user_value msg = '{} set more than once. Old value "{}", new value "{}".'.format( - _name_and_loc(sym), user_val, new_val) + sym.name_and_loc, user_val, new_val) if user_val == new_val: if self.warn_assign_redun: @@ -1337,8 +1392,29 @@ def _assigned_twice(self, sym, new_val, 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"): + def load_allconfig(self, 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. + + filename: + Command-specific configuration filename - "allyes.config", + "allno.config", etc. + """ + load_allconfig(self, filename) + + def write_autoconf(self, filename=None, header=None): r""" Writes out symbol values as a C header file, matching the format used by include/generated/autoconf.h in the kernel. @@ -1352,22 +1428,43 @@ def write_autoconf(self, filename, like the modification time and possibly triggering redundant work in build tools. - filename: - Self-explanatory. + filename (default: None): + Path to write header to. + + If None (the default), the path in the environment variable + KCONFIG_AUTOHEADER is used if set, and "include/generated/autoconf.h" + otherwise. This is compatible with the C tools. + + header (default: None): + Text inserted verbatim at the beginning of the file. You would + usually want it enclosed in '/* */' to make it a C comment, and + include a trailing newline. - header (default: "/* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */\n"): - Text that will be inserted verbatim at the beginning of the file. You - would usually want it enclosed in '/* */' to make it a C comment, - and include a final terminating newline. + If None (the default), the value of the environment variable + KCONFIG_AUTOHEADER_HEADER had when the Kconfig instance was created + will be used if it was set, and no header otherwise. See the + Kconfig.header_header attribute. + + Returns a string with a message saying that the header got saved, or + that there were no changes to it. This is meant to reduce boilerplate + in tools, which can do e.g. print(kconf.write_autoconf()). """ - self._write_if_changed(filename, self._autoconf_contents(header)) + if filename is None: + filename = os.getenv("KCONFIG_AUTOHEADER", + "include/generated/autoconf.h") + + if self._write_if_changed(filename, self._autoconf_contents(header)): + return "Kconfig header saved to '{}'".format(filename) + return "No change to Kconfig header in '{}'".format(filename) def _autoconf_contents(self, header): # write_autoconf() helper. Returns the contents to write as a string, - # with 'header' at the beginning. + # with 'header' or KCONFIG_AUTOHEADER_HEADER at the beginning. + + if header is None: + header = self.header_header - # "".join()ed later - chunks = [header] + chunks = [header] # "".join()ed later add = chunks.append for sym in self.unique_defined_syms: @@ -1403,9 +1500,8 @@ def _autoconf_contents(self, header): return "".join(chunks) - def write_config(self, filename=None, - header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n", - save_old=True, verbose=None): + def write_config(self, filename=None, header=None, save_old=True, + verbose=None): r""" Writes out symbol values in the .config format. The format matches the C implementation, including ordering. @@ -1427,16 +1523,21 @@ def write_config(self, filename=None, (OSError/IOError). KconfigError is never raised here. filename (default: None): - Filename to save configuration to (a string). + Path to write configuration to (a string). - If None (the default), the filename in the environment variable + If None (the default), the path 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. + header (default: None): + Text 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 trailing newline. + + if None (the default), the value of the environment variable + KCONFIG_CONFIG_HEADER had when the Kconfig instance was created will + be used if it was set, and no header otherwise. See the + Kconfig.config_header attribute. save_old (default: True): If True and already exists, a copy of it will be saved to @@ -1469,7 +1570,7 @@ def write_config(self, filename=None, contents = self._config_contents(header) if self._contents_eq(filename, contents): - return "No change to '{}'".format(filename) + return "No change to configuration in '{}'".format(filename) if save_old: _save_old(filename) @@ -1481,7 +1582,7 @@ def write_config(self, filename=None, def _config_contents(self, header): # write_config() helper. Returns the contents to write as a string, - # with 'header' at the beginning. + # with 'header' or KCONFIG_CONFIG_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. @@ -1493,13 +1594,15 @@ def _config_contents(self, header): for sym in self.unique_defined_syms: sym._visited = False - # Did we just print an '# end of ...' comment? - after_end_comment = False + if header is None: + header = self.config_header - # "".join()ed later - chunks = [header] + chunks = [header] # "".join()ed later add = chunks.append + # Did we just print an '# end of ...' comment? + after_end_comment = False + node = self.top_node while 1: # Jump to the next node with an iterative tree walk @@ -1552,8 +1655,7 @@ def _config_contents(self, header): 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"): + def write_min_config(self, filename, header=None): """ Writes out a "minimal" configuration file, omitting symbols whose value matches their default value. The format matches the one produced by @@ -1569,31 +1671,35 @@ def write_min_config(self, filename, (OSError/IOError). KconfigError is never raised here. filename: - Self-explanatory. + Path to write minimal configuration to. - header (default: "# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"): - Text that will be inserted verbatim at the beginning of the file. You - would usually want each line to start with '#' to make it a comment, - and include a final terminating newline. + header (default: None): + Text 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. + if None (the default), the value of the environment variable + KCONFIG_CONFIG_HEADER had when the Kconfig instance was created will + be used if it was set, and no header otherwise. See the + Kconfig.config_header attribute. + + Returns a string with a message saying the minimal configuration got + saved, or that there were no changes to it. 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) + if self._write_if_changed(filename, self._min_config_contents(header)): + return "Minimal configuration saved to '{}'".format(filename) + return "No change to minimal configuration in '{}'".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. + # with 'header' or KCONFIG_CONFIG_HEADER at the beginning. - chunks = [header] + if header is None: + header = self.config_header + + chunks = [header] # "".join()ed later add = chunks.append for sym in self.unique_defined_syms: @@ -1872,7 +1978,7 @@ def eval_string(self, s): # 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.filename = None self._tokens = self._tokenize("if " + s) # Strip "if " to avoid giving confusing error messages @@ -2035,9 +2141,9 @@ def _enter_file(self, filename): # 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'. + # 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) @@ -2066,7 +2172,7 @@ def _enter_file(self, filename): # _include_path is a tuple, so this rebinds the variable instead of # doing in-place modification - self._include_path += ((self._filename, self._linenr),) + self._include_path += ((self.filename, self.linenr),) # Check for recursive 'source' for name, _ in self._include_path: @@ -2075,7 +2181,7 @@ def _enter_file(self, filename): "\n{}:{}: recursive 'source' of '{}' detected. Check that " "environment variables are set correctly.\n" "Include path:\n{}" - .format(self._filename, self._linenr, rel_filename, + .format(self.filename, self.linenr, rel_filename, "\n".join("{}:{}".format(name, linenr) for name, linenr in self._include_path))) @@ -2085,19 +2191,19 @@ def _enter_file(self, filename): # We already know that the file exists raise _KconfigIOError( e, "{}:{}: Could not open '{}' (in '{}') ({}: {})" - .format(self._filename, self._linenr, filename, + .format(self.filename, self.linenr, filename, self._line.strip(), errno.errorcode[e.errno], e.strerror)) - self._filename = rel_filename - self._linenr = 0 + self.filename = rel_filename + self.linenr = 0 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] + 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() @@ -2110,9 +2216,9 @@ def _next_line(self): # 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) + # self._tokens_i is known to be 1 here, because _parse_props() + # leaves it like that when it can't recognize a line (or parses a + # help text) return True # readline() returns '' over and over at EOF, which we rely on for help @@ -2120,16 +2226,16 @@ def _next_line(self): line = self._readline() if not line: return False - self._linenr += 1 + self.linenr += 1 # Handle line joining while line.endswith("\\\n"): line = line[:-2] + self._readline() - self._linenr += 1 + 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]. + # and _parse_props(). They immediately fetch self._tokens[0]. self._tokens_i = 1 return True @@ -2146,7 +2252,7 @@ def _line_after_help(self, line): # Handle line joining while line.endswith("\\\n"): line = line[:-2] + self._readline() - self._linenr += 1 + self.linenr += 1 self._tokens = self._tokenize(line) self._reuse_tokens = True @@ -2160,10 +2266,15 @@ def _write_if_changed(self, filename, contents): # 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. + # + # Returns True if the file has changed and is updated, and False + # otherwise. - if not self._contents_eq(filename, contents): - with self._open(filename, "w") as f: - f.write(contents) + if self._contents_eq(filename, contents): + return False + with self._open(filename, "w") as f: + f.write(contents) + return True def _contents_eq(self, filename, contents): # Returns True if the contents of 'filename' is 'contents' (a string), @@ -2314,7 +2425,7 @@ def _tokenize(self, s): if token is not _T_CHOICE: self._warn("style: quotes recommended around '{}' in '{}'" .format(name, self._line.strip()), - self._filename, self._linenr) + self.filename, self.linenr) token = name i = match.end() @@ -2591,10 +2702,9 @@ def _expand_name_iter(self, s, i): while 1: match = _name_special_search(s, i) - if match.group() == "$(": - s, i = self._expand_macro(s, match.start(), ()) - else: + if match.group() != "$(": return (s, match.start()) + s, i = self._expand_macro(s, match.start(), ()) def _expand_str(self, s, i): # Expands a quoted string starting at index 'i' in 's'. Handles both @@ -2637,14 +2747,12 @@ def _expand_macro(self, s, i, args): # 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 + res = s[:i] i += 2 # Skip over "$(" - # Start of current macro argument - arg_start = i - - # Arguments of this macro call - new_args = [] + arg_start = i # Start of current macro argument + new_args = [] # Arguments of this macro call + nesting = 0 # Current parentheses nesting level while 1: match = _macro_special_search(s, i) @@ -2652,32 +2760,42 @@ def _expand_macro(self, s, i, args): self._parse_error("missing end parenthesis in macro expansion") - if match.group() == ")": + if match.group() == "(": + nesting += 1 + i = match.end() + + elif match.group() == ")": + if nesting: + nesting -= 1 + i = match.end() + continue + # 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])] + res += 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) + res += self._fn_val(new_args) - return (prefix + s[match.end():], - len(prefix)) + return (res + s[match.end():], len(res)) elif match.group() == ",": + i = match.end() + if nesting: + continue + # Found the end of a macro argument new_args.append(s[arg_start:match.start()]) - arg_start = i = match.end() + arg_start = i else: # match.group() == "$(" # A nested macro call within the macro @@ -2726,7 +2844,7 @@ def _fn_val(self, args): raise KconfigError("{}:{}: bad number of arguments in call " "to {}, expected {}, got {}" - .format(self._filename, self._linenr, fn, + .format(self.filename, self.linenr, fn, expected_args, len(args) - 1)) return py_fn(self, *args) @@ -2784,7 +2902,7 @@ def _parse_block(self, end_token, parent, prev): # # prev: # The previous menu node. New nodes will be added after this one (by - # modifying their 'next' pointer). + # modifying 'next' pointers). # # '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 @@ -2814,23 +2932,17 @@ def _parse_block(self, end_token, parent, prev): 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.filename = self.filename + node.linenr = self.linenr node.include_path = self._include_path sym.nodes.append(node) - self._parse_properties(node) - - if node.item.env_var: - if node.item.env_var in os.environ: - os.environ[node.item.name] = os.environ[node.item.env_var] - else: - os.environ[node.item.name] = ((node.defaults[0])[0]).name + self._parse_props(node) if node.is_menuconfig and not node.prompt: self._warn("the menuconfig symbol {} has no prompt" - .format(_name_and_loc(sym))) + .format(sym.name_and_loc)) # Equivalent to # @@ -2849,7 +2961,7 @@ def _parse_block(self, end_token, parent, prev): if t0 in _REL_SOURCE_TOKENS: # Relative source - pattern = join(dirname(self._filename), pattern) + 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() @@ -2867,7 +2979,7 @@ def _parse_block(self, end_token, parent, prev): "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, + .format(self.filename, self.linenr, pattern, self._line.strip(), "set to '{}'".format(self.srctree) if self.srctree else "unset or blank")) @@ -2906,13 +3018,13 @@ def _parse_block(self, end_token, parent, prev): 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.filename = self.filename + node.linenr = self.linenr node.include_path = self._include_path self.menus.append(node) - self._parse_properties(node) + self._parse_props(node) self._parse_block(_T_ENDMENU, node, node) node.list = node.next @@ -2926,13 +3038,13 @@ def _parse_block(self, end_token, parent, prev): node.prompt = (self._expect_str_and_eol(), self.y) node.list = None node.parent = parent - node.filename = self._filename - node.linenr = self._linenr + node.filename = self.filename + node.linenr = self.linenr node.include_path = self._include_path self.comments.append(node) - self._parse_properties(node) + self._parse_props(node) prev.next = prev = node @@ -2958,13 +3070,13 @@ def _parse_block(self, end_token, parent, prev): node.is_menuconfig = True node.prompt = node.help = None node.parent = parent - node.filename = self._filename - node.linenr = self._linenr + node.filename = self.filename + node.linenr = self.linenr node.include_path = self._include_path choice.nodes.append(node) - self._parse_properties(node) + self._parse_props(node) self._parse_block(_T_ENDCHOICE, node, node) node.list = node.next @@ -2982,17 +3094,16 @@ def _parse_block(self, end_token, parent, prev): "no corresponding 'menu'" if t0 is _T_ENDMENU else "unrecognized construct") - # End of file reached. Terminate the final node and return it. + # End of file reached. Return the last node. if end_token: raise KconfigError( - "expected '{}' at end of '{}'" + "error: expected '{}' at end of '{}'" .format("endchoice" if end_token is _T_ENDCHOICE else "endif" if end_token is _T_ENDIF else "endmenu", - self._filename)) + self.filename)) - prev.next = None return prev def _parse_cond(self): @@ -3006,7 +3117,7 @@ def _parse_cond(self): return expr - def _parse_properties(self, node): + def _parse_props(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. @@ -3032,7 +3143,7 @@ def _parse_properties(self, node): if t0 in _TYPE_TOKENS: # Relies on '_T_BOOL is BOOL', etc., to save a conversion - self._set_type(node, t0) + self._set_type(node.item, t0) if self._tokens[1] is not None: self._parse_prompt(node) @@ -3062,7 +3173,7 @@ def _parse_properties(self, node): self._parse_cond())) elif t0 in _DEF_TOKEN_TO_TYPE: - self._set_type(node, _DEF_TOKEN_TO_TYPE[t0]) + self._set_type(node.item, _DEF_TOKEN_TO_TYPE[t0]) node.defaults.append((self._parse_expr(False), self._parse_cond())) @@ -3103,7 +3214,7 @@ def _parse_properties(self, node): 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) + self.filename, self.linenr) if env_var != node.item.name: self._warn("Kconfiglib expands environment variables " @@ -3113,7 +3224,7 @@ def _parse_properties(self, node): "rename {} to {} (so that the symbol name " "matches the environment variable name)." .format(node.item.name, env_var), - self._filename, self._linenr) + self.filename, self.linenr) elif self._check_token(_T_DEFCONFIG_LIST): if not self.defconfig_list: @@ -3123,7 +3234,7 @@ def _parse_properties(self, node): "symbols ({0} and {1}). Only {0} will be " "used.".format(self.defconfig_list.name, node.item.name), - self._filename, self._linenr) + self.filename, self.linenr) elif self._check_token(_T_MODULES): # To reduce warning spam, only warn if 'option modules' is @@ -3140,7 +3251,7 @@ def _parse_properties(self, node): "MODULES, like older versions of the C " "implementation did when 'option modules' " "wasn't used.", - self._filename, self._linenr) + self.filename, self.linenr) elif self._check_token(_T_ALLNOCONFIG_Y): if node.item.__class__ is not Symbol: @@ -3163,14 +3274,15 @@ def _parse_properties(self, node): self._reuse_tokens = True return - def _set_type(self, node, new_type): + def _set_type(self, sc, new_type): + # Sets the type of 'sc' (symbol or choice) to 'new_type' + # UNKNOWN is falsy - if node.item.orig_type and node.item.orig_type is not new_type: + if sc.orig_type and sc.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])) + .format(sc.name_and_loc, TYPE_TO_STR[new_type])) - node.item.orig_type = new_type + sc.orig_type = new_type def _parse_prompt(self, node): # 'prompt' properties override each other within a single definition of @@ -3178,7 +3290,7 @@ def _parse_prompt(self, node): # multiple times if node.prompt: - self._warn(_name_and_loc(node.item) + + self._warn(node.item.name_and_loc + " defined with multiple prompts in single location") prompt = self._tokens[1] @@ -3188,7 +3300,7 @@ def _parse_prompt(self, node): self._parse_error("expected prompt string") if prompt != prompt.strip(): - self._warn(_name_and_loc(node.item) + + self._warn(node.item.name_and_loc + " has leading or trailing whitespace in its prompt") # This avoid issues for e.g. reStructuredText documentation, where @@ -3199,7 +3311,7 @@ def _parse_prompt(self, node): def _parse_help(self, node): if node.help is not None: - self._warn(_name_and_loc(node.item) + " defined with more than " + self._warn(node.item.name_and_loc + " defined with more than " "one help text -- only the last one will be used") # Micro-optimization. This code is pretty hot. @@ -3210,7 +3322,7 @@ def _parse_help(self, node): while 1: line = readline() - self._linenr += 1 + self.linenr += 1 if not line: self._empty_help(node, line) return @@ -3249,13 +3361,13 @@ def _parse_help(self, node): break add_line(expline[indent:]) - self._linenr += len_(lines) + 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) + + self._warn(node.item.name_and_loc + " has 'help' but empty help text") node.help = "" if line: @@ -3360,7 +3472,7 @@ def _build_dep(self): # 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 + depend_on = _depend_on # Micro-optimization # Only calculate _dependents for defined symbols. Constant and # undefined symbols could theoretically be selected/implied, but it @@ -3371,29 +3483,29 @@ def _build_dep(self): # The prompt conditions for node in sym.nodes: if node.prompt: - make_depend_on(sym, node.prompt[1]) + 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) + depend_on(sym, value) + 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) + depend_on(sym, sym.rev_dep) + 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) + depend_on(sym, low) + depend_on(sym, high) + 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) + 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 @@ -3406,11 +3518,11 @@ def _build_dep(self): # The prompt conditions for node in choice.nodes: if node.prompt: - make_depend_on(choice, node.prompt[1]) + depend_on(choice, node.prompt[1]) # The default symbol conditions for _, cond in choice.defaults: - make_depend_on(choice, cond) + depend_on(choice, cond) def _add_choice_deps(self): # Choices also depend on the choice symbols themselves, because the @@ -3635,26 +3747,26 @@ def num_ok(sym, type_): 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), + .format(sym.name_and_loc, TYPE_TO_STR[target_sym.orig_type], - _name_and_loc(target_sym))) + target_sym.name_and_loc)) 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), + .format(sym.name_and_loc, TYPE_TO_STR[target_sym.orig_type], - _name_and_loc(target_sym))) + target_sym.name_and_loc)) 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))) + "the {} symbol {} has a malformed default {} -- " + "expected a single symbol" + .format(TYPE_TO_STR[sym.orig_type], + sym.name_and_loc, expr_str(default))) if sym.orig_type is STRING: if not default.is_constant and not default.nodes and \ @@ -3665,22 +3777,22 @@ def num_ok(sym, type_): # (and no symbol named 'foo' exists). self._warn("style: quotes recommended around " "default value for string symbol " - + _name_and_loc(sym)) + + sym.name_and_loc) 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))) + sym.name_and_loc, + default.name_and_loc)) 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))) + sym.name_and_loc)) else: # UNKNOWN self._warn("{} defined without a type" - .format(_name_and_loc(sym))) + .format(sym.name_and_loc)) if sym.ranges: @@ -3688,7 +3800,7 @@ def num_ok(sym, type_): self._warn( "the {} symbol {} has ranges, but is not int or hex" .format(TYPE_TO_STR[sym.orig_type], - _name_and_loc(sym))) + sym.name_and_loc)) else: for low, high, _ in sym.ranges: if not num_ok(low, sym.orig_type) or \ @@ -3697,9 +3809,9 @@ def num_ok(sym, 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))) + sym.name_and_loc, + low.name_and_loc, + high.name_and_loc)) def _check_choice_sanity(self): # Checks various choice properties that are handiest to check after @@ -3708,43 +3820,43 @@ def _check_choice_sanity(self): 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) + .format(sym.name_and_loc, expr_type) # si = select/imply for si in split_expr(expr, OR): - msg += "\n - " + _name_and_loc(split_expr(si, AND)[0]) + msg += "\n - " + split_expr(si, AND)[0].name_and_loc 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), + .format(choice.name_and_loc, 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") + self._warn(choice.name_and_loc + " 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))) + .format(choice.name_and_loc, 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))) + .format(default.name_and_loc, + choice.name_and_loc)) 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))) + "symbols".format(sym.name_and_loc)) if sym.rev_dep is not sym.kconfig.n: warn_select_imply(sym, sym.rev_dep, "selected") @@ -3756,17 +3868,17 @@ def warn_select_imply(sym, expr, expr_type): if node.parent.item is choice: if not node.prompt: self._warn("the choice symbol {} has no prompt" - .format(_name_and_loc(sym))) + .format(sym.name_and_loc)) elif node.prompt: self._warn("the choice symbol {} is defined with a " "prompt outside the choice" - .format(_name_and_loc(sym))) + .format(sym.name_and_loc)) def _parse_error(self, msg): - raise KconfigError("{}couldn't parse '{}': {}".format( - "" if self._filename is None else - "{}:{}: ".format(self._filename, self._linenr), + raise KconfigError("{}error: couldn't parse '{}': {}".format( + "" if self.filename is None else + "{}:{}: ".format(self.filename, self.linenr), self._line.strip(), msg)) def _trailing_tokens_error(self): @@ -3901,6 +4013,13 @@ class Symbol(object): The type as given in the Kconfig file, without any magic applied. Used when printing the symbol. + 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. + + This is the symbol value that's used outside of relation expressions + (A, !A, A && B, A || B). + 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". @@ -3908,17 +4027,20 @@ class Symbol(object): This is the symbol value that's used in relational expressions (A = B, A != B, etc.) - 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 + Gotcha: For int/hex symbols, the exact format of the value is often + 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. - 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. + user_value: + The user value of the symbol. None if no user value has been assigned + (via Kconfig.load_config() or Symbol.set_value()). - This is the symbol value that's used outside of relation expressions - (A, !A, A && B, A || B). + Holds 0, 1, or 2 for bool/tristate symbols, and a string for the other + symbol types. + + WARNING: Do not assign directly to this. It will break things. Use + Symbol.set_value(). assignable: A tuple containing the tristate user values that can currently be @@ -3959,16 +4081,6 @@ class Symbol(object): The visibility of the symbol. One of 0, 1, 2, representing n, m, y. See the module documentation for an overview of symbol values and visibility. - user_value: - The user value of the symbol. None if no user value has been assigned - (via Kconfig.load_config() or Symbol.set_value()). - - Holds 0, 1, or 2 for bool/tristate symbols, and a string for the other - symbol types. - - WARNING: Do not assign directly to this. It will break things. Use - Symbol.set_value(). - config_string: The .config assignment string that would get written out for the symbol by Kconfig.write_config(). Returns the empty string if no .config @@ -3996,6 +4108,15 @@ class Symbol(object): though you might get some special symbols and possibly some "redundant" n-valued symbol entries in there. + name_and_loc: + Holds a string like + + "MY_SYMBOL (defined at foo/Kconfig:12, bar/Kconfig:14)" + + , giving the name of the symbol and its definition location(s). + + If the symbol is undefined, the location is given as "(undefined)". + 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. @@ -4226,7 +4347,7 @@ def str_value(self): "being outside the active range ([{}, {}]) -- falling " "back on defaults" .format(num2str(user_val), TYPE_TO_STR[self.orig_type], - _name_and_loc(self), + self.name_and_loc, num2str(low), num2str(high))) else: # If the user value is well-formed and satisfies range @@ -4276,7 +4397,7 @@ def str_value(self): self.kconfig._warn( "default value {} on {} clamped to {} due to " "being outside the active range ([{}, {}])" - .format(val_num, _name_and_loc(self), + .format(val_num, self.name_and_loc, num2str(clamp), num2str(low), num2str(high))) @@ -4317,7 +4438,7 @@ def tri_value(self): 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))) + .format(TYPE_TO_STR[self.orig_type], self.name_and_loc)) self._cached_tri_val = 0 return 0 @@ -4427,6 +4548,13 @@ def config_string(self): return '{}{}="{}"\n' \ .format(self.kconfig.config_prefix, self.name, escape(val)) + @property + def name_and_loc(self): + """ + See the class documentation. + """ + return self.name + " " + _locs(self) + def set_value(self, value): """ Sets the user value of the symbol. @@ -4448,8 +4576,8 @@ def set_value(self, 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. + values in Kconfiglib) or as one of the strings "n", "m", or "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 @@ -4496,7 +4624,7 @@ def set_value(self, value): "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.name_and_loc, TYPE_TO_STR[self.orig_type])) return False @@ -4784,7 +4912,7 @@ def _rec_invalidate_if_has_prompt(self): return if self.kconfig._warn_assign_no_prompt: - self.kconfig._warn(_name_and_loc(self) + " has no prompt, meaning " + self.kconfig._warn(self.name_and_loc + " has no prompt, meaning " "user values have no effect on it") def _str_default(self): @@ -4830,7 +4958,7 @@ def _warn_select_unsatisfied_deps(self): 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), + .format(self.name_and_loc, expr_str(self.direct_dep), TRI_TO_STR[expr_value(self.direct_dep)], TRI_TO_STR[expr_value(self.rev_dep)]) @@ -4848,7 +4976,7 @@ def _warn_select_unsatisfied_deps(self): msg += "\n - {}, with value {}, direct dependencies {} " \ "(value: {})" \ - .format(_name_and_loc(selecting_sym), + .format(selecting_sym.name_and_loc, selecting_sym.str_value, expr_str(selecting_sym.direct_dep), TRI_TO_STR[expr_value(selecting_sym.direct_dep)]) @@ -4932,12 +5060,21 @@ class Choice(object): Corresponding attributes have the same name in the Symbol and Choice classes, for consistency and compatibility. + str_value: + Like choice.tri_value, but gives the value as one of the strings + "n", "m", or "y" + + 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. + 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 @@ -4946,14 +5083,6 @@ class Choice(object): 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 @@ -4963,6 +5092,19 @@ class Choice(object): WARNING: Do not assign directly to this. It will break things. Call sym.set_value(2) on the choice symbol to be selected instead. + visibility: + See the Symbol class documentation. Acts on the value (mode). + + name_and_loc: + Holds a string like + + " (defined at foo/Kconfig:12)" + + , giving the name of the choice and its definition location(s). If the + choice has no name (isn't defined with 'choice MY_CHOICE'), then it will + be shown as "" before the list of locations (always a single one + in that case). + syms: List of symbols contained in the choice. @@ -5082,6 +5224,14 @@ def visibility(self): self._cached_vis = _visibility(self) return self._cached_vis + @property + def name_and_loc(self): + """ + See the class documentation. + """ + # Reuse the expression format, which is ''. + return standard_sc_expr_str(self) + " " + _locs(self) + @property def selection(self): """ @@ -5122,7 +5272,7 @@ def set_value(self, value): "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.name_and_loc, TYPE_TO_STR[self.orig_type])) return False @@ -5245,8 +5395,8 @@ def __init__(self): self._cached_selection = _NO_CACHED_SELECTION - # is_constant is checked by _make_depend_on(). Just set it to avoid - # having to special-case choices. + # is_constant is checked by _depend_on(). Just set it to avoid having + # to special-case choices. self.is_constant = self.is_optional = False # See Kconfig._build_dep() @@ -6044,25 +6194,32 @@ def unescape(s): _unescape_sub = re.compile(r"\\(.)").sub -def standard_kconfig(): +def standard_kconfig(description=None): """ - 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. + Argument parsing helper for tools that take a single optional Kconfig file + argument (default: Kconfig). Returns the Kconfig instance for the parsed + configuration. Uses argparse internally. - 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. + Exits with sys.exit() (which raises SystemExit) on errors. + + description (default: None): + The 'description' passed to argparse.ArgumentParser(). + argparse.RawDescriptionHelpFormatter is used, so formatting is preserved. """ - if len(sys.argv) > 2: - sys.exit("usage: {} [Kconfig]".format(sys.argv[0])) + import argparse - # 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()) + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=description) + + parser.add_argument( + "kconfig", + metavar="KCONFIG", + default="Kconfig", + nargs="?", + help="Top-level Kconfig file (default: Kconfig)") + + return Kconfig(parser.parse_args().kconfig, suppress_traceback=True) def standard_config_filename(): @@ -6078,25 +6235,9 @@ def standard_config_filename(): 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. + Use Kconfig.load_allconfig() instead, which was added in Kconfiglib 13.4.0. + Supported for backwards compatibility. Might be removed at some point after + a long period of deprecation warnings. """ allconfig = os.getenv("KCONFIG_ALLCONFIG") if allconfig is None: @@ -6172,7 +6313,7 @@ def _visibility(sc): return vis -def _make_depend_on(sc, expr): +def _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. @@ -6180,11 +6321,11 @@ def _make_depend_on(sc, expr): if expr.__class__ is tuple: # AND, OR, NOT, or relation - _make_depend_on(sc, expr[1]) + _depend_on(sc, expr[1]) # NOTs only have a single operand if expr[0] is not NOT: - _make_depend_on(sc, expr[2]) + _depend_on(sc, expr[2]) elif not expr.is_constant: # Non-constant symbol, or choice @@ -6280,20 +6421,16 @@ def copy(src, dst): pass -def _name_and_loc(sc): - # Helper for giving the symbol/choice name and location(s) in e.g. warnings +def _locs(sc): + # Symbol/Choice.name_and_loc helper. Returns the "(defined at ...)" part of + # the string. 'sc' is a Symbol or Choice. - # Reuse the expression format. That way choices show up as - # '' - name = standard_sc_expr_str(sc) + if sc.nodes: + return "(defined at {})".format( + ", ".join("{0.filename}:{0.linenr}".format(node) + for node in sc.nodes)) - if not sc.nodes: - return name + " (undefined)" - - return "{} (defined at {})".format( - name, - ", ".join("{}:{}".format(node.filename, node.linenr) - for node in sc.nodes)) + return "(undefined)" # Menu manipulation @@ -6548,7 +6685,7 @@ def _found_dep_loop(loop, cur): msg += "the choice symbol " msg += "{}, with definition...\n\n{}\n\n" \ - .format(_name_and_loc(item), item) + .format(item.name_and_loc, item) # Small wart: Since we reuse the already calculated # Symbol/Choice._dependents sets for recursive dependency detection, we @@ -6572,7 +6709,7 @@ def _found_dep_loop(loop, cur): msg += "(imply-related dependencies: {})\n\n" \ .format(expr_str(item.rev_dep)) - msg += "...depends again on {}".format(_name_and_loc(loop[0])) + msg += "...depends again on " + loop[0].name_and_loc raise KconfigError(msg) @@ -6613,22 +6750,22 @@ def _warn_verbose_deprecated(fn_name): def _filename_fn(kconf, _): - return kconf._filename + return kconf.filename def _lineno_fn(kconf, _): - return str(kconf._linenr) + return str(kconf.linenr) def _info_fn(kconf, _, msg): - print("{}:{}: {}".format(kconf._filename, kconf._linenr, 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) + kconf._warn(msg, kconf.filename, kconf.linenr) return "" @@ -6636,14 +6773,13 @@ def _warning_if_fn(kconf, _, cond, msg): def _error_if_fn(kconf, _, cond, msg): if cond == "y": raise KconfigError("{}:{}: {}".format( - kconf._filename, kconf._linenr, msg)) + kconf.filename, kconf.linenr, msg)) return "" def _shell_fn(kconf, _, command): - # Only import as needed, to save some startup time - import subprocess + import subprocess # Only import as needed, to save some startup time stdout, stderr = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE @@ -6654,12 +6790,12 @@ def _shell_fn(kconf, _, command): stdout = stdout.decode(kconf._encoding) stderr = stderr.decode(kconf._encoding) except UnicodeDecodeError as e: - _decoding_error(e, kconf._filename, kconf._linenr) + _decoding_error(e, kconf.filename, kconf.linenr) if stderr: kconf._warn("'{}' wrote to stderr: {}".format( command, "\n".join(stderr.splitlines())), - kconf._filename, kconf._linenr) + kconf.filename, kconf.linenr) # Universal newlines with splitlines() (to prevent e.g. stray \r's in # command output on Windows), trailing newline removal, and @@ -7009,8 +7145,8 @@ def _re_search(regex): # 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 macro ('(', ')', ',', and '$(') +_macro_special_search = _re_search(r"\(|\)|,|\$\(") # Special characters/strings while expanding a string (quotes, '\', and '$(') _string_special_search = _re_search(r'"|\'|\\|\$\(') diff --git a/tools/Kconfiglib/listnewconfig.py b/tools/Kconfiglib/listnewconfig.py new file mode 100644 index 00000000000..8276de15243 --- /dev/null +++ b/tools/Kconfiglib/listnewconfig.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2019, Ulf Magnusson +# SPDX-License-Identifier: ISC + +""" +Lists all user-modifiable symbols that are not given a value in the +configuration file. Usually, these are new symbols that have been added to the +Kconfig files. + +The default configuration filename is '.config'. A different filename can be +passed in the KCONFIG_CONFIG environment variable. +""" +from __future__ import print_function + +import argparse +import sys + +from kconfiglib import Kconfig, BOOL, TRISTATE, INT, HEX, STRING, TRI_TO_STR + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=__doc__) + + parser.add_argument( + "--show-help", "-l", + action="store_true", + help="Show any help texts as well") + + parser.add_argument( + "kconfig", + metavar="KCONFIG", + nargs="?", + default="Kconfig", + help="Top-level Kconfig file (default: Kconfig)") + + args = parser.parse_args() + + kconf = Kconfig(args.kconfig, suppress_traceback=True) + # Make it possible to filter this message out + print(kconf.load_config(), file=sys.stderr) + + for sym in kconf.unique_defined_syms: + # Only show symbols that can be toggled. Choice symbols are a special + # case in that sym.assignable will be (2,) (length 1) for visible + # symbols in choices in y mode, but they can still be toggled by + # selecting some other symbol. + if sym.user_value is None and \ + (len(sym.assignable) > 1 or + (sym.visibility and (sym.orig_type in (INT, HEX, STRING) or + sym.choice))): + + # Don't reuse the 'config_string' format for bool/tristate symbols, + # to show n-valued symbols as 'CONFIG_FOO=n' instead of + # '# CONFIG_FOO is not set'. This matches the C tools. + if sym.orig_type in (BOOL, TRISTATE): + s = "{}{}={}\n".format(kconf.config_prefix, sym.name, + TRI_TO_STR[sym.tri_value]) + else: + s = sym.config_string + + print(s, end="") + if args.show_help: + for node in sym.nodes: + if node.help is not None: + # Indent by two spaces. textwrap.indent() is not + # available in Python 2 (it's 3.3+). + print("\n".join(" " + line + for line in node.help.split("\n"))) + break + + +if __name__ == "__main__": + main() diff --git a/tools/Kconfiglib/makefile.patch b/tools/Kconfiglib/makefile.patch new file mode 100644 index 00000000000..a617ebdf00c --- /dev/null +++ b/tools/Kconfiglib/makefile.patch @@ -0,0 +1,48 @@ +From 93daf46f309b0c8f86149ef58c4906387d054c22 Mon Sep 17 00:00:00 2001 +From: Ulf Magnusson +Date: Tue, 9 Jun 2015 13:01:34 +0200 +Subject: [PATCH] Kconfiglib scripts/kconfig/Makefile patch + +--- + scripts/kconfig/Makefile | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/scripts/kconfig/Makefile b/scripts/kconfig/Makefile +index 3f327e21f60e..8b7dd1292005 100644 +--- a/scripts/kconfig/Makefile ++++ b/scripts/kconfig/Makefile +@@ -27,2 +27,31 @@ gconfig: $(obj)/gconf + ++PHONY += scriptconfig iscriptconfig kmenuconfig guiconfig dumpvarsconfig ++ ++PYTHONCMD ?= python ++kpython := PYTHONPATH=$(srctree)/Kconfiglib:$$PYTHONPATH $(PYTHONCMD) ++ ++ifneq ($(filter scriptconfig,$(MAKECMDGOALS)),) ++ifndef SCRIPT ++$(error Use "make scriptconfig SCRIPT= [SCRIPT_ARG=]") ++endif ++endif ++ ++scriptconfig: ++ $(Q)$(kpython) $(SCRIPT) $(Kconfig) $(if $(SCRIPT_ARG),"$(SCRIPT_ARG)") ++ ++iscriptconfig: ++ $(Q)$(kpython) -i -c \ ++ "import kconfiglib; \ ++ kconf = kconfiglib.Kconfig('$(Kconfig)'); \ ++ print('A Kconfig instance \'kconf\' for the architecture $(ARCH) has been created.')" ++ ++kmenuconfig: ++ $(Q)$(kpython) $(srctree)/Kconfiglib/menuconfig.py $(Kconfig) ++ ++guiconfig: ++ $(Q)$(kpython) $(srctree)/Kconfiglib/guiconfig.py $(Kconfig) ++ ++dumpvarsconfig: ++ $(Q)$(kpython) $(srctree)/Kconfiglib/examples/dumpvars.py $(Kconfig) ++ + menuconfig: $(obj)/mconf +-- +2.20.1 + diff --git a/tools/Kconfiglib/menuconfig.py b/tools/Kconfiglib/menuconfig.py new file mode 100644 index 00000000000..7e765d36f98 --- /dev/null +++ b/tools/Kconfiglib/menuconfig.py @@ -0,0 +1,3278 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2019, Nordic Semiconductor ASA and Ulf Magnusson +# SPDX-License-Identifier: ISC + +""" +Overview +======== + +A curses-based Python 2/3 menuconfig implementation. The interface should feel +familiar to people used to mconf ('make menuconfig'). + +Supports the same keys as mconf, and also supports a set of keybindings +inspired by Vi: + + J/K : Down/Up + L : Enter menu/Toggle item + H : Leave menu + Ctrl-D/U: Page Down/Page Up + G/End : Jump to end of list + g/Home : Jump to beginning of list + +[Space] toggles values if possible, and enters menus otherwise. [Enter] works +the other way around. + +The mconf feature where pressing a key jumps to a menu entry with that +character in it in the current menu isn't supported. A jump-to feature for +jumping directly to any symbol (including invisible symbols), choice, menu or +comment (as in a Kconfig 'comment "Foo"') is available instead. + +A few different modes are available: + + F: Toggle show-help mode, which shows the help text of the currently selected + item in the window at the bottom of the menu display. This is handy when + browsing through options. + + C: Toggle show-name mode, which shows the symbol name before each symbol menu + entry + + A: Toggle show-all mode, which shows all items, including currently invisible + items and items that lack a prompt. Invisible items are drawn in a different + style to make them stand out. + + +Running +======= + +menuconfig.py can be run either as a standalone executable or by calling the +menuconfig() function with an existing Kconfig instance. The second option is a +bit inflexible in that it will still load and save .config, etc. + +When run in standalone mode, the top-level Kconfig file to load can be passed +as a command-line argument. With no argument, it defaults to "Kconfig". + +The KCONFIG_CONFIG environment variable specifies the .config file to load (if +it exists) and save. If KCONFIG_CONFIG is unset, ".config" is used. + +When overwriting a configuration file, the old version is saved to +.old (e.g. .config.old). + +$srctree is supported through Kconfiglib. + + +Color schemes +============= + +It is possible to customize the color scheme by setting the MENUCONFIG_STYLE +environment variable. For example, setting it to 'aquatic' will enable an +alternative, less yellow, more 'make menuconfig'-like color scheme, contributed +by Mitja Horvat (pinkfluid). + +This is the current list of built-in styles: + - default classic Kconfiglib theme with a yellow accent + - monochrome colorless theme (uses only bold and standout) attributes, + this style is used if the terminal doesn't support colors + - aquatic blue-tinted style loosely resembling the lxdialog theme + +It is possible to customize the current style by changing colors of UI +elements on the screen. This is the list of elements that can be stylized: + + - path Top row in the main display, with the menu path + - separator Separator lines between windows. Also used for the top line + in the symbol information display. + - list List of items, e.g. the main display + - selection Style for the selected item + - inv-list Like list, but for invisible items. Used in show-all mode. + - inv-selection Like selection, but for invisible items. Used in show-all + mode. + - help Help text windows at the bottom of various fullscreen + dialogs + - show-help Window showing the help text in show-help mode + - frame Frame around dialog boxes + - body Body of dialog boxes + - edit Edit box in pop-up dialogs + - jump-edit Edit box in jump-to dialog + - text Symbol information text + +The color definition is a comma separated list of attributes: + + - fg:COLOR Set the foreground/background colors. COLOR can be one of + * or * the basic 16 colors (black, red, green, yellow, blue, + - bg:COLOR magenta, cyan, white and brighter versions, for example, + brightred). On terminals that support more than 8 colors, + you can also directly put in a color number, e.g. fg:123 + (hexadecimal and octal constants are accepted as well). + Colors outside the range -1..curses.COLORS-1 (which is + terminal-dependent) are ignored (with a warning). The COLOR + can be also specified using a RGB value in the HTML + notation, for example #RRGGBB. If the terminal supports + color changing, the color is rendered accurately. + Otherwise, the visually nearest color is used. + + If the background or foreground color of an element is not + specified, it defaults to -1, representing the default + terminal foreground or background color. + + Note: On some terminals a bright version of the color + implies bold. + - bold Use bold text + - underline Use underline text + - standout Standout text attribute (reverse color) + +More often than not, some UI elements share the same color definition. In such +cases the right value may specify an UI element from which the color definition +will be copied. For example, "separator=help" will apply the current color +definition for "help" to "separator". + +A keyword without the '=' is assumed to be a style template. The template name +is looked up in the built-in styles list and the style definition is expanded +in-place. With this, built-in styles can be used as basis for new styles. + +For example, take the aquatic theme and give it a red selection bar: + +MENUCONFIG_STYLE="aquatic selection=fg:white,bg:red" + +If there's an error in the style definition or if a missing style is assigned +to, the assignment will be ignored, along with a warning being printed on +stderr. + +The 'default' theme is always implicitly parsed first, so the following two +settings have the same effect: + + MENUCONFIG_STYLE="selection=fg:white,bg:red" + MENUCONFIG_STYLE="default selection=fg:white,bg:red" + +If the terminal doesn't support colors, the 'monochrome' theme is used, and +MENUCONFIG_STYLE is ignored. The assumption is that the environment is broken +somehow, and that the important thing is to get something usable. + + +Other features +============== + + - Seamless terminal resizing + + - No dependencies on *nix, as the 'curses' module is in the Python standard + library + + - Unicode text entry + + - Improved information screen compared to mconf: + + * Expressions are split up by their top-level &&/|| operands to improve + readability + + * Undefined symbols in expressions are pointed out + + * Menus and comments have information displays + + * Kconfig definitions are printed + + * The include path is shown, listing the locations of the 'source' + statements that included the Kconfig file of the symbol (or other + item) + + +Limitations +=========== + +Doesn't work out of the box on Windows, but can be made to work with + + pip install windows-curses + +See the https://github.com/zephyrproject-rtos/windows-curses repository. +""" +from __future__ import print_function + +import os +import sys + +_IS_WINDOWS = os.name == "nt" # Are we running on Windows? + +try: + import curses +except ImportError as e: + if not _IS_WINDOWS: + raise + sys.exit("""\ +menuconfig failed to import the standard Python 'curses' library. Try +installing a package like windows-curses +(https://github.com/zephyrproject-rtos/windows-curses) by running this command +in cmd.exe: + + pip install windows-curses + +Starting with Kconfiglib 13.0.0, windows-curses is no longer automatically +installed when installing Kconfiglib via pip on Windows (because it breaks +installation on MSYS2). + +Exception: +{}: {}""".format(type(e).__name__, e)) + +import errno +import locale +import re +import textwrap + +from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \ + BOOL, TRISTATE, STRING, INT, HEX, \ + AND, OR, \ + expr_str, expr_value, split_expr, \ + standard_sc_expr_str, \ + TRI_TO_STR, TYPE_TO_STR, \ + standard_kconfig, standard_config_filename + + +# +# Configuration variables +# + +# If True, try to change LC_CTYPE to a UTF-8 locale if it is set to the C +# locale (which implies ASCII). This fixes curses Unicode I/O issues on systems +# with bad defaults. ncurses configures itself from the locale settings. +# +# Related PEP: https://www.python.org/dev/peps/pep-0538/ +_CHANGE_C_LC_CTYPE_TO_UTF8 = True + +# How many steps an implicit submenu will be indented. Implicit submenus are +# created when an item depends on the symbol before it. Note that symbols +# defined with 'menuconfig' create a separate menu instead of indenting. +_SUBMENU_INDENT = 4 + +# Number of steps for Page Up/Down to jump +_PG_JUMP = 6 + +# Height of the help window in show-help mode +_SHOW_HELP_HEIGHT = 8 + +# How far the cursor needs to be from the edge of the window before it starts +# to scroll. Used for the main menu display, the information display, the +# search display, and for text boxes. +_SCROLL_OFFSET = 5 + +# Minimum width of dialogs that ask for text input +_INPUT_DIALOG_MIN_WIDTH = 30 + +# Number of arrows pointing up/down to draw when a window is scrolled +_N_SCROLL_ARROWS = 14 + +# Lines of help text shown at the bottom of the "main" display +_MAIN_HELP_LINES = """ +[Space/Enter] Toggle/enter [ESC] Leave menu [S] Save +[O] Load [?] Symbol info [/] Jump to symbol +[F] Toggle show-help mode [C] Toggle show-name mode [A] Toggle show-all mode +[Q] Quit (prompts for save) [D] Save minimal config (advanced) +"""[1:-1].split("\n") + +# Lines of help text shown at the bottom of the information dialog +_INFO_HELP_LINES = """ +[ESC/q] Return to menu [/] Jump to symbol +"""[1:-1].split("\n") + +# Lines of help text shown at the bottom of the search dialog +_JUMP_TO_HELP_LINES = """ +Type text to narrow the search. Regexes are supported (via Python's 're' +module). The up/down cursor keys step in the list. [Enter] jumps to the +selected symbol. [ESC] aborts the search. Type multiple space-separated +strings/regexes to find entries that match all of them. Type Ctrl-F to +view the help of the selected item without leaving the dialog. +"""[1:-1].split("\n") + +# +# Styling +# + +_STYLES = { + "default": """ + path=fg:black,bg:white,bold + separator=fg:black,bg:yellow,bold + list=fg:black,bg:white + selection=fg:white,bg:blue,bold + inv-list=fg:red,bg:white + inv-selection=fg:red,bg:blue + help=path + show-help=list + frame=fg:black,bg:yellow,bold + body=fg:white,bg:black + edit=fg:white,bg:blue + jump-edit=edit + text=list + """, + + # This style is forced on terminals that do no support colors + "monochrome": """ + path=bold + separator=bold,standout + list= + selection=bold,standout + inv-list=bold + inv-selection=bold,standout + help=bold + show-help= + frame=bold,standout + body= + edit=standout + jump-edit= + text= + """, + + # Blue-tinted style loosely resembling lxdialog + "aquatic": """ + path=fg:white,bg:blue + separator=fg:white,bg:cyan + help=path + frame=fg:white,bg:cyan + body=fg:white,bg:blue + edit=fg:black,bg:white + """ +} + +_NAMED_COLORS = { + # Basic colors + "black": curses.COLOR_BLACK, + "red": curses.COLOR_RED, + "green": curses.COLOR_GREEN, + "yellow": curses.COLOR_YELLOW, + "blue": curses.COLOR_BLUE, + "magenta": curses.COLOR_MAGENTA, + "cyan": curses.COLOR_CYAN, + "white": curses.COLOR_WHITE, + + # Bright versions + "brightblack": curses.COLOR_BLACK + 8, + "brightred": curses.COLOR_RED + 8, + "brightgreen": curses.COLOR_GREEN + 8, + "brightyellow": curses.COLOR_YELLOW + 8, + "brightblue": curses.COLOR_BLUE + 8, + "brightmagenta": curses.COLOR_MAGENTA + 8, + "brightcyan": curses.COLOR_CYAN + 8, + "brightwhite": curses.COLOR_WHITE + 8, + + # Aliases + "purple": curses.COLOR_MAGENTA, + "brightpurple": curses.COLOR_MAGENTA + 8, +} + + +def _rgb_to_6cube(rgb): + # Converts an 888 RGB color to a 3-tuple (nice in that it's hashable) + # representing the closest xterm 256-color 6x6x6 color cube color. + # + # The xterm 256-color extension uses a RGB color palette with components in + # the range 0-5 (a 6x6x6 cube). The catch is that the mapping is nonlinear. + # Index 0 in the 6x6x6 cube is mapped to 0, index 1 to 95, then 135, 175, + # etc., in increments of 40. See the links below: + # + # https://commons.wikimedia.org/wiki/File:Xterm_256color_chart.svg + # https://github.com/tmux/tmux/blob/master/colour.c + + # 48 is the middle ground between 0 and 95. + return tuple(0 if x < 48 else int(round(max(1, (x - 55)/40))) for x in rgb) + + +def _6cube_to_rgb(r6g6b6): + # Returns the 888 RGB color for a 666 xterm color cube index + + return tuple(0 if x == 0 else 40*x + 55 for x in r6g6b6) + + +def _rgb_to_gray(rgb): + # Converts an 888 RGB color to the index of an xterm 256-color grayscale + # color with approx. the same perceived brightness + + # Calculate the luminance (gray intensity) of the color. See + # https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color + # and + # https://www.w3.org/TR/AERT/#color-contrast + luma = 0.299*rgb[0] + 0.587*rgb[1] + 0.114*rgb[2] + + # Closest index in the grayscale palette, which starts at RGB 0x080808, + # with stepping 0x0A0A0A + index = int(round((luma - 8)/10)) + + # Clamp the index to 0-23, corresponding to 232-255 + return max(0, min(index, 23)) + + +def _gray_to_rgb(index): + # Convert a grayscale index to its closet single RGB component + + return 3*(10*index + 8,) # Returns a 3-tuple + + +# Obscure Python: We never pass a value for rgb2index, and it keeps pointing to +# the same dict. This avoids a global. +def _alloc_rgb(rgb, rgb2index={}): + # Initialize a new entry in the xterm palette to the given RGB color, + # returning its index. If the color has already been initialized, the index + # of the existing entry is returned. + # + # ncurses is palette-based, so we need to overwrite palette entries to make + # new colors. + # + # The colors from 0 to 15 are user-defined, and there's no way to query + # their RGB values, so we better leave them untouched. Also leave any + # hypothetical colors above 255 untouched (though we're unlikely to + # allocate that many colors anyway). + + if rgb in rgb2index: + return rgb2index[rgb] + + # Many terminals allow the user to customize the first 16 colors. Avoid + # changing their values. + color_index = 16 + len(rgb2index) + if color_index >= 256: + _warn("Unable to allocate new RGB color ", rgb, ". Too many colors " + "allocated.") + return 0 + + # Map each RGB component from the range 0-255 to the range 0-1000, which is + # what curses uses + curses.init_color(color_index, *(int(round(1000*x/255)) for x in rgb)) + rgb2index[rgb] = color_index + + return color_index + + +def _color_from_num(num): + # Returns the index of a color that looks like color 'num' in the xterm + # 256-color palette (but that might not be 'num', if we're redefining + # colors) + + # - _alloc_rgb() won't touch the first 16 colors or any (hypothetical) + # colors above 255, so we can always return them as-is + # + # - If the terminal doesn't support changing color definitions, or if + # curses.COLORS < 256, _alloc_rgb() won't touch any color, and all colors + # can be returned as-is + if num < 16 or num > 255 or not curses.can_change_color() or \ + curses.COLORS < 256: + return num + + # _alloc_rgb() might redefine colors, so emulate the xterm 256-color + # palette by allocating new colors instead of returning color numbers + # directly + + if num < 232: + num -= 16 + return _alloc_rgb(_6cube_to_rgb(((num//36)%6, (num//6)%6, num%6))) + + return _alloc_rgb(_gray_to_rgb(num - 232)) + + +def _color_from_rgb(rgb): + # Returns the index of a color matching the 888 RGB color 'rgb'. The + # returned color might be an ~exact match or an approximation, depending on + # terminal capabilities. + + # Calculates the Euclidean distance between two RGB colors + def dist(r1, r2): return sum((x - y)**2 for x, y in zip(r1, r2)) + + if curses.COLORS >= 256: + # Assume we're dealing with xterm's 256-color extension + + if curses.can_change_color(): + # Best case -- the terminal supports changing palette entries via + # curses.init_color(). Initialize an unused palette entry and + # return it. + return _alloc_rgb(rgb) + + # Second best case -- pick between the xterm 256-color extension colors + + # Closest 6-cube "color" color + c6 = _rgb_to_6cube(rgb) + # Closest gray color + gray = _rgb_to_gray(rgb) + + if dist(rgb, _6cube_to_rgb(c6)) < dist(rgb, _gray_to_rgb(gray)): + # Use the "color" color from the 6x6x6 color palette. Calculate the + # color number from the 6-cube index triplet. + return 16 + 36*c6[0] + 6*c6[1] + c6[2] + + # Use the color from the gray palette + return 232 + gray + + # Terminal not in xterm 256-color mode. This is probably the best we can + # do, or is it? Submit patches. :) + min_dist = float('inf') + best = -1 + for color in range(curses.COLORS): + # ncurses uses the range 0..1000. Scale that down to 0..255. + d = dist(rgb, tuple(int(round(255*c/1000)) + for c in curses.color_content(color))) + if d < min_dist: + min_dist = d + best = color + + return best + + +def _parse_style(style_str, parsing_default): + # Parses a string with '=