pax_global_header00006660000000000000000000000064136147425570014530gustar00rootroot0000000000000052 comment=061e71f7d78cb057762d88de088055361863deff Kconfiglib-14.1.0/000077500000000000000000000000001361474255700136625ustar00rootroot00000000000000Kconfiglib-14.1.0/.gitignore000066400000000000000000000000421361474255700156460ustar00rootroot00000000000000*.py[co] build/ *.egg-info/ dist/ Kconfiglib-14.1.0/LICENSE.txt000066400000000000000000000013671361474255700155140ustar00rootroot00000000000000Copyright (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. Kconfiglib-14.1.0/MANIFEST.in000066400000000000000000000001071361474255700154160ustar00rootroot00000000000000# Include the license file in source distributions include LICENSE.txt Kconfiglib-14.1.0/README.rst000066400000000000000000001130401361474255700153500ustar00rootroot00000000000000.. 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. Kconfiglib-14.1.0/alldefconfig.py000077500000000000000000000011371361474255700166560ustar00rootroot00000000000000#!/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() Kconfiglib-14.1.0/allmodconfig.py000077500000000000000000000023061361474255700166760ustar00rootroot00000000000000#!/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() Kconfiglib-14.1.0/allnoconfig.py000077500000000000000000000023021361474255700165270ustar00rootroot00000000000000#!/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() Kconfiglib-14.1.0/allyesconfig.py000077500000000000000000000032261361474255700167210ustar00rootroot00000000000000#!/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() Kconfiglib-14.1.0/defconfig.py000077500000000000000000000022001361474255700161550ustar00rootroot00000000000000#!/usr/bin/env python3 # Copyright (c) 2019, Ulf Magnusson # SPDX-License-Identifier: ISC """ Reads a specified configuration file, then writes a new configuration file. This can be used to initialize the configuration from e.g. an arch-specific configuration file. This input configuration file would usually be a minimal configuration file, as generated by e.g. savedefconfig. The default output filename is '.config'. A different filename can be passed in the KCONFIG_CONFIG environment variable. """ import argparse import kconfiglib def main(): parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__) parser.add_argument( "--kconfig", default="Kconfig", help="Top-level Kconfig file (default: Kconfig)") parser.add_argument( "config", metavar="CONFIGURATION", help="Input configuration file") args = parser.parse_args() kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True) print(kconf.load_config(args.config)) print(kconf.write_config()) if __name__ == "__main__": main() Kconfiglib-14.1.0/examples/000077500000000000000000000000001361474255700155005ustar00rootroot00000000000000Kconfiglib-14.1.0/examples/Kmenuconfig000066400000000000000000000030001361474255700176610ustar00rootroot00000000000000mainmenu "Example Kconfig configuration" config MODULES bool "Enable loadable module support" option modules default y menu "Bool and tristate symbols" config BOOL bool "Bool symbol" default y config BOOL_DEP bool "Dependent bool symbol" depends on BOOL # Mix it up a bit with an 'if' instead of a 'depends on' if BOOL config TRI_DEP tristate "Dependent tristate symbol" select SELECTED_BY_TRI_DEP imply IMPLIED_BY_TRI_DEP endif config TWO_MENU_NODES bool "First prompt" depends on BOOL config TRI tristate "Tristate symbol" config TWO_MENU_NODES bool "Second prompt" comment "These are selected by TRI_DEP" config SELECTED_BY_TRI_DEP tristate "Tristate selected by TRI_DEP" config IMPLIED_BY_TRI_DEP tristate "Tristate implied by TRI_DEP" endmenu menu "String, int, and hex symbols" config STRING string "String symbol" default "foo" config INT int "Int symbol" default 747 config HEX hex "Hex symbol" default 0xABC endmenu menu "Various choices" choice BOOL_CHOICE bool "Bool choice" config BOOL_CHOICE_SYM_1 bool "Bool choice sym 1" config BOOL_CHOICE_SYM_2 bool "Bool choice sym 2" endchoice choice TRI_CHOICE tristate "Tristate choice" config TRI_CHOICE_SYM_1 tristate "Tristate choice sym 1" config TRI_CHOICE_SYM_2 tristate "Tristate choice sym 2" endchoice choice OPT_BOOL_CHOICE bool "Optional bool choice" optional config OPT_BOOL_CHOICE_SYM_1 bool "Optional bool choice sym 1" config OPT_BOOL_CHOICE_SYM_2 bool "Optional bool choice sym 2" endchoice endmenu Kconfiglib-14.1.0/examples/allnoconfig_walk.py000066400000000000000000000036701361474255700213710ustar00rootroot00000000000000# This is tree-walking version of allnoconfig.py, for demonstration purposes. # Verified by the test suite to generate identical output to 'make allnoconfig' # for all ARCHes. # # Note: A more practical version would use Kconfig.node_iter(). The manual tree # walking is for demonstration purposes. # # Usage for the Linux kernel: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/allnoconfig_walk.py import sys from kconfiglib import Kconfig, Symbol def do_allnoconfig(node): global changed # Walk the tree of menu nodes. You can imagine this as going down/into menu # entries in the menuconfig interface, setting each to n (or the lowest # assignable value). while node: if isinstance(node.item, Symbol): sym = node.item # Is the symbol a non-allnoconfig_y symbol that can be set to a # lower value than its current value? if (not sym.is_allnoconfig_y and sym.assignable and sym.assignable[0] < sym.tri_value): # Yup, lower it sym.set_value(sym.assignable[0]) changed = True # Recursively lower children if node.list: do_allnoconfig(node.list) node = node.next # Parse the Kconfig files kconf = Kconfig(sys.argv[1]) # Do an initial pass to set 'option allnoconfig_y' symbols to y for sym in kconf.unique_defined_syms: if sym.is_allnoconfig_y: sym.set_value(2) while True: # Changing later symbols in the configuration can sometimes allow earlier # symbols to be lowered, e.g. if a later symbol 'select's an earlier # symbol. To handle such situations, we do additional passes over the tree # until we're no longer able to change the value of any symbol in a pass. changed = False do_allnoconfig(kconf.top_node) # Did the pass change any symbols? if not changed: break print(kconf.write_config()) Kconfiglib-14.1.0/examples/defconfig_oldconfig.py000066400000000000000000000016061361474255700220250ustar00rootroot00000000000000# Produces exactly the same output as the following script: # # make defconfig # echo CONFIG_ETHERNET=n >> .config # make oldconfig # echo CONFIG_ETHERNET=y >> .config # yes n | make oldconfig # # This came up in https://github.com/ulfalizer/Kconfiglib/issues/15. # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/defconfig_oldconfig.py import sys import kconfiglib kconf = kconfiglib.Kconfig(sys.argv[1]) # Mirrors defconfig kconf.load_config("arch/x86/configs/x86_64_defconfig") kconf.write_config() # Mirrors the first oldconfig kconf.load_config() kconf.syms["ETHERNET"].set_value(0) kconf.write_config() # Mirrors the second oldconfig kconf.load_config() kconf.syms["ETHERNET"].set_value(2) for s in kconf.unique_defined_syms: if s.user_value is None and 0 in s.assignable: s.set_value(0) # Write the final configuration print(kconf.write_config()) Kconfiglib-14.1.0/examples/dumpvars.py000066400000000000000000000007171361474255700177200ustar00rootroot00000000000000# Prints all (set) environment variables referenced in the Kconfig files # together with their values, as a list of assignments. # # Note: This only works for environment variables referenced via the $(FOO) # preprocessor syntax. The older $FOO syntax is maintained for backwards # compatibility. import os import sys import kconfiglib print(" ".join("{}='{}'".format(var, os.environ[var]) for var in kconfiglib.Kconfig(sys.argv[1]).env_vars)) Kconfiglib-14.1.0/examples/eval_expr.py000066400000000000000000000012031361474255700200330ustar00rootroot00000000000000# Evaluates an expression (e.g. "X86_64 || (X86_32 && X86_LOCAL_APIC)") in the # context of a configuration. Note that this always yields a tristate value (n, # m, or y). # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/eval_expr.py SCRIPT_ARG= import sys import kconfiglib if len(sys.argv) < 3: sys.exit("Pass the expression to evaluate with SCRIPT_ARG=") kconf = kconfiglib.Kconfig(sys.argv[1]) expr = sys.argv[2] # Enable modules so that m doesn't get demoted to n kconf.modules.set_value(2) print("the expression '{}' evaluates to {}" .format(expr, kconf.eval_string(expr))) Kconfiglib-14.1.0/examples/find_symbol.py000066400000000000000000000065551361474255700203720ustar00rootroot00000000000000# Prints all menu nodes that reference a given symbol any of their properties # or property conditions, along with their parent menu nodes. # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/find_symbol.py SCRIPT_ARG= # # Example output for SCRIPT_ARG=X86: # # Found 470 locations that reference X86: # # ========== Location 1 (init/Kconfig:1108) ========== # # config SGETMASK_SYSCALL # bool # prompt "sgetmask/ssetmask syscalls support" if EXPERT # default PARISC || M68K || PPC || MIPS || X86 || SPARC || MICROBLAZE || SUPERH # help # sys_sgetmask and sys_ssetmask are obsolete system calls # no longer supported in libc but still enabled by default in some # architectures. # # If unsure, leave the default option here. # # ---------- Parent 1 (init/Kconfig:1077) ---------- # # menuconfig EXPERT # bool # prompt "Configure standard kernel features (expert users)" # select DEBUG_KERNEL # help # This option allows certain base kernel options and settings # to be disabled or tweaked. This is for specialized # environments which can tolerate a "non-standard" kernel. # Only use this if you really know what you are doing. # # ---------- Parent 2 (init/Kconfig:39) ---------- # # menu "General setup" # # ========== Location 2 (arch/Kconfig:29) ========== # # config OPROFILE_EVENT_MULTIPLEX # bool # prompt "OProfile multiplexing support (EXPERIMENTAL)" # default "n" # depends on OPROFILE && X86 # help # The number of hardware counters is limited. The multiplexing # feature enables OProfile to gather more events than counters # are provided by the hardware. This is realized by switching # between events at a user specified time interval. # # If unsure, say N. # # ---------- Parent 1 (arch/Kconfig:16) ---------- # # config OPROFILE # tristate # prompt "OProfile system profiling" # select RING_BUFFER # select RING_BUFFER_ALLOW_SWAP # depends on PROFILING && HAVE_OPROFILE # help # OProfile is a profiling system capable of profiling the # whole system, include the kernel, kernel modules, libraries, # and applications. # # If unsure, say N. # # ---------- Parent 2 (init/Kconfig:39) ---------- # # menu "General setup" # # ... (tons more) import sys import kconfiglib if len(sys.argv) < 3: sys.exit('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=') kconf = kconfiglib.Kconfig(sys.argv[1]) sym_name = sys.argv[2] if sym_name not in kconf.syms: print("No symbol {} exists in the configuration".format(sym_name)) sys.exit(0) referencing = [node for node in kconf.node_iter() if kconf.syms[sym_name] in node.referenced] if not referencing: print("No references to {} found".format(sym_name)) sys.exit(0) print("Found {} locations that reference {}:\n" .format(len(referencing), sym_name)) for i, node in enumerate(referencing, 1): print("========== Location {} ({}:{}) ==========\n\n{}" .format(i, node.filename, node.linenr, node)) # Print the parents of the menu node too node = node.parent parent_i = 1 while node is not kconf.top_node: print("---------- Parent {} ({}:{}) ----------\n\n{}" .format(parent_i, node.filename, node.linenr, node)) node = node.parent parent_i += 1 Kconfiglib-14.1.0/examples/help_grep.py000066400000000000000000000031471361474255700200240ustar00rootroot00000000000000# Does a case-insensitive search for a regular expression in the help texts of # symbols and choices and the prompts of menus and comments. Prints the # matching items together with their locations and the matching text. # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/help_grep.py SCRIPT_ARG= # # Shortened example output for SCRIPT_ARG=general: # # menu "General setup" # location: init/Kconfig:39 # # config SYSVIPC # bool # prompt "System V IPC" # help # ... # exchange information. It is generally considered to be a good thing, # ... # # location: init/Kconfig:233 # # config BSD_PROCESS_ACCT # bool # prompt "BSD Process Accounting" if MULTIUSER # help # ... # information. This is generally a good idea, so say Y. # # location: init/Kconfig:403 # # ... import re import sys from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT if len(sys.argv) < 3: sys.exit("Pass the regex with SCRIPT_ARG=") search = re.compile(sys.argv[2], re.IGNORECASE).search for node in Kconfig(sys.argv[1]).node_iter(): match = False if isinstance(node.item, (Symbol, Choice)) and \ node.help is not None and search(node.help): print(node.item) match = True elif node.item == MENU and search(node.prompt[0]): print('menu "{}"'.format(node.prompt[0])) match = True elif node.item == COMMENT and search(node.prompt[0]): print('comment "{}"'.format(node.prompt[0])) match = True if match: print("location: {}:{}\n".format(node.filename, node.linenr)) Kconfiglib-14.1.0/examples/kconfiglib.py000077700000000000000000000000001361474255700230502../kconfiglib.pyustar00rootroot00000000000000Kconfiglib-14.1.0/examples/list_undefined.py000066400000000000000000000116361361474255700210550ustar00rootroot00000000000000# Prints a list of symbols that are referenced in the Kconfig files of some # architecture but not defined by the Kconfig files of any architecture. # # A Kconfig file might be shared between many architectures and legitimately # reference undefined symbols for some of them, but if no architecture defines # the symbol, it usually indicates a problem or potential cleanup. # # This script could be sped up a lot if needed. See the comment near the # referencing_nodes() call. # # Run with the following command in the kernel root: # # $ python(3) Kconfiglib/examples/list_undefined.py # # Example output: # # Registering defined and undefined symbols for all arches # Processing mips # Processing ia64 # Processing metag # ... # # Finding references to each undefined symbol # Processing mips # Processing ia64 # Processing metag # ... # # The following globally undefined symbols were found, listed here # together with the locations of the items that reference them. # References might come from enclosing menus and ifs. # # ARM_ERRATA_753970: arch/arm/mach-mvebu/Kconfig:56, arch/arm/mach-mvebu/Kconfig:39 # SUNXI_CCU_MP: drivers/clk/sunxi-ng/Kconfig:14 # SUNXI_CCU_DIV: drivers/clk/sunxi-ng/Kconfig:14 # AC97: sound/ac97/Kconfig:6 # ... import os import subprocess from kconfiglib import Kconfig # Referenced inside the Kconfig files os.environ["KERNELVERSION"] = str( subprocess.check_output(("make", "kernelversion")).decode("utf-8").rstrip() ) def all_arch_srcarch_pairs(): """ Generates all valid (ARCH, SRCARCH) tuples for the kernel, corresponding to different architectures. SRCARCH holds the arch/ subdirectory. """ for srcarch in os.listdir("arch"): # Each subdirectory of arch/ containing a Kconfig file corresponds to # an architecture if os.path.exists(os.path.join("arch", srcarch, "Kconfig")): yield (srcarch, srcarch) # Some architectures define additional ARCH settings with ARCH != SRCARCH # (search for "Additional ARCH settings for" in the top-level Makefile) yield ("i386", "x86") yield ("x86_64", "x86") yield ("sparc32", "sparc") yield ("sparc64", "sparc") yield ("sh64", "sh") yield ("um", "um") def all_arch_srcarch_kconfigs(): """ Generates Kconfig instances for all the architectures in the kernel """ os.environ["srctree"] = "." os.environ["HOSTCC"] = "gcc" os.environ["HOSTCXX"] = "g++" os.environ["CC"] = "gcc" os.environ["LD"] = "ld" for arch, srcarch in all_arch_srcarch_pairs(): print(" Processing " + arch) os.environ["ARCH"] = arch os.environ["SRCARCH"] = srcarch # um (User Mode Linux) uses a different base Kconfig file yield Kconfig("Kconfig" if arch != "um" else "arch/x86/um/Kconfig", warn=False) print("Registering defined and undefined symbols for all arches") # Sets holding the names of all defined and undefined symbols, for all # architectures defined = set() undefined = set() for kconf in all_arch_srcarch_kconfigs(): for name, sym in kconf.syms.items(): if sym.nodes: # If the symbol has a menu node, it is defined defined.add(name) else: # Undefined symbol. We skip some of the uninteresting ones. # Due to how Kconfig works, integer literals show up as symbols # (from e.g. 'default 1'). Skip those. try: int(name, 0) continue except ValueError: # Interesting undefined symbol undefined.add(name) print("\nFinding references to each undefined symbol") def referencing_nodes(kconf, name): # Returns a list of all menu nodes that reference a symbol named 'name' in # any of their properties or property conditions res = [] for node in kconf.node_iter(): for ref in node.referenced: if ref.name == name: res.append(node) return res # Maps each globally undefined symbol to the menu nodes that reference it undef_sym_refs = [(name, set()) for name in undefined - defined] for kconf in all_arch_srcarch_kconfigs(): for name, refs in undef_sym_refs: # This means that we search the entire configuration tree for each # undefined symbol, which is terribly inefficient. We could speed # things up by tweaking referencing_nodes() to compare each symbol to # multiple symbols while walking the configuration tree. for node in referencing_nodes(kconf, name): refs.add("{}:{}".format(node.filename, node.linenr)) print("\nThe following globally undefined symbols were found, listed here\n" "together with the locations of the items that reference them.\n" "References might come from enclosing menus and ifs.\n") for name, refs in undef_sym_refs: print(" {}: {}".format(name, ", ".join(refs))) Kconfiglib-14.1.0/examples/menuconfig_example.py000077500000000000000000000274141361474255700217320ustar00rootroot00000000000000#!/usr/bin/env python3 # Implements a simple configuration interface on top of Kconfiglib to # demonstrate concepts for building a menuconfig-like. Emulates how the # standard menuconfig prints menu entries. # # Always displays the entire Kconfig tree to keep things as simple as possible # (all symbols, choices, menus, and comments). # # Usage: # # $ python(3) Kconfiglib/examples/menuconfig.py # # A sample Kconfig is available in Kconfiglib/examples/Kmenuconfig. # # Here's a notation guide. The notation matches the one used by menuconfig # (scripts/kconfig/mconf): # # [ ] prompt - Bool # < > prompt - Tristate # {M} prompt - Tristate selected to m. Can only be set to m or y. # -*- prompt - Bool/tristate selected to y, pinning it # -M- prompt - Tristate selected to m that also has m visibility, # pinning it to m # (foo) prompt - String/int/hex symbol with value "foo" # --> prompt - The selected symbol in a choice in y mode. This # syntax is unique to this example. # # When modules are disabled, the .type attribute of TRISTATE symbols and # choices automatically changes to BOOL. This trick is used by the C # implementation as well, and gives the expected behavior without having to do # anything extra here. The original type is available in .orig_type if needed. # # The Kconfiglib/examples/Kmenuconfig example uses named choices to be able to # refer to choices by name. Named choices are supported in the C tools too, but # I don't think I've ever seen them used in the wild. # # Sample session: # # $ python Kconfiglib/examples/menuconfig.py Kconfiglib/examples/Kmenuconfig # # ======== Example Kconfig configuration ======== # # [*] Enable loadable module support (MODULES) # Bool and tristate symbols # [*] Bool symbol (BOOL) # [ ] Dependent bool symbol (BOOL_DEP) # < > Dependent tristate symbol (TRI_DEP) # [ ] First prompt (TWO_MENU_NODES) # < > Tristate symbol (TRI) # [ ] Second prompt (TWO_MENU_NODES) # *** These are selected by TRI_DEP *** # < > Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) # < > Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) # String, int, and hex symbols # (foo) String symbol (STRING) # (747) Int symbol (INT) # (0xABC) Hex symbol (HEX) # Various choices # -*- Bool choice (BOOL_CHOICE) # --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) # Bool choice sym 2 (BOOL_CHOICE_SYM_2) # {M} Tristate choice (TRI_CHOICE) # < > Tristate choice sym 1 (TRI_CHOICE_SYM_1) # < > Tristate choice sym 2 (TRI_CHOICE_SYM_2) # [ ] Optional bool choice (OPT_BOOL_CHOICE) # # Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): BOOL # Value for BOOL (available: n, y): n # # ======== Example Kconfig configuration ======== # # [*] Enable loadable module support (MODULES) # Bool and tristate symbols # [ ] Bool symbol (BOOL) # < > Tristate symbol (TRI) # [ ] Second prompt (TWO_MENU_NODES) # *** These are selected by TRI_DEP *** # < > Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) # < > Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) # String, int, and hex symbols # (foo) String symbol (STRING) # (747) Int symbol (INT) # (0xABC) Hex symbol (HEX) # Various choices # -*- Bool choice (BOOL_CHOICE) # --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) # Bool choice sym 2 (BOOL_CHOICE_SYM_2) # {M} Tristate choice (TRI_CHOICE) # < > Tristate choice sym 1 (TRI_CHOICE_SYM_1) # < > Tristate choice sym 2 (TRI_CHOICE_SYM_2) # [ ] Optional bool choice (OPT_BOOL_CHOICE) # # Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): MODULES # Value for MODULES (available: n, y): n # # ======== Example Kconfig configuration ======== # # [ ] Enable loadable module support (MODULES) # Bool and tristate symbols # [ ] Bool symbol (BOOL) # [ ] Tristate symbol (TRI) # [ ] Second prompt (TWO_MENU_NODES) # *** These are selected by TRI_DEP *** # [ ] Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) # [ ] Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) # String, int, and hex symbols # (foo) String symbol (STRING) # (747) Int symbol (INT) # (0xABC) Hex symbol (HEX) # Various choices # -*- Bool choice (BOOL_CHOICE) # --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) # Bool choice sym 2 (BOOL_CHOICE_SYM_2) # -*- Tristate choice (TRI_CHOICE) # --> Tristate choice sym 1 (TRI_CHOICE_SYM_1) # Tristate choice sym 2 (TRI_CHOICE_SYM_2) # [ ] Optional bool choice (OPT_BOOL_CHOICE) # # Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): ^D from __future__ import print_function import readline import sys from kconfiglib import Kconfig, \ Symbol, MENU, COMMENT, \ BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN, \ expr_value, \ TRI_TO_STR # Python 2/3 compatibility hack if sys.version_info[0] < 3: input = raw_input def indent_print(s, indent): print(indent*" " + s) def value_str(sc): """ Returns the value part ("[*]", "", "(foo)" etc.) of a menu entry. sc: Symbol or Choice. """ if sc.type in (STRING, INT, HEX): return "({})".format(sc.str_value) # BOOL or TRISTATE # The choice mode is an upper bound on the visibility of choice symbols, so # we can check the choice symbols' own visibility to see if the choice is # in y mode if isinstance(sc, Symbol) and sc.choice and sc.visibility == 2: # For choices in y mode, print '-->' next to the selected symbol return "-->" if sc.choice.selection is sc else " " tri_val_str = (" ", "M", "*")[sc.tri_value] if len(sc.assignable) == 1: # Pinned to a single value return "-{}-".format(tri_val_str) if sc.type == BOOL: return "[{}]".format(tri_val_str) if sc.type == TRISTATE: if sc.assignable == (1, 2): # m and y available return "{" + tri_val_str + "}" # Gets a bit confusing with .format() return "<{}>".format(tri_val_str) def node_str(node): """ Returns the complete menu entry text for a menu node, or "" for invisible menu nodes. Invisible menu nodes are those that lack a prompt or that do not have a satisfied prompt condition. Example return value: "[*] Bool symbol (BOOL)" The symbol name is printed in parentheses to the right of the prompt. This is so that symbols can easily be referred to in the configuration interface. """ if not node.prompt: return "" # Even for menu nodes for symbols and choices, it's wrong to check # Symbol.visibility / Choice.visibility here. The reason is that a symbol # (and a choice, in theory) can be defined in multiple locations, giving it # multiple menu nodes, which do not necessarily all have the same prompt # visibility. Symbol.visibility / Choice.visibility is calculated as the OR # of the visibility of all the prompts. prompt, prompt_cond = node.prompt if not expr_value(prompt_cond): return "" if node.item == MENU: return " " + prompt if node.item == COMMENT: return " *** {} ***".format(prompt) # Symbol or Choice sc = node.item if sc.type == UNKNOWN: # Skip symbols defined without a type (these are obscure and generate # a warning) return "" # {:3} sets the field width to three. Gives nice alignment for empty string # values. res = "{:3} {}".format(value_str(sc), prompt) # Don't print the name for unnamed choices (the normal kind) if sc.name is not None: res += " ({})".format(sc.name) return res def print_menuconfig_nodes(node, indent): """ Prints a tree with all the menu entries rooted at 'node'. Child menu entries are indented. """ while node: string = node_str(node) if string: indent_print(string, indent) if node.list: print_menuconfig_nodes(node.list, indent + 8) node = node.next def print_menuconfig(kconf): """ Prints all menu entries for the configuration. """ # Print the expanded mainmenu text at the top. This is the same as # kconf.top_node.prompt[0], but with variable references expanded. print("\n======== {} ========\n".format(kconf.mainmenu_text)) print_menuconfig_nodes(kconf.top_node.list, 0) print("") def get_value_from_user(sc): """ Prompts the user for a value for the symbol or choice 'sc'. For bool/tristate symbols and choices, provides a list of all the assignable values. """ if not sc.visibility: print(sc.name + " is not currently visible") return False prompt = "Value for {}".format(sc.name) if sc.type in (BOOL, TRISTATE): prompt += " (available: {})" \ .format(", ".join(TRI_TO_STR[val] for val in sc.assignable)) prompt += ": " val = input(prompt) # Automatically add a "0x" prefix for hex symbols, like the menuconfig # interface does. This isn't done when loading .config files, hence why # set_value() doesn't do it automatically. if sc.type == HEX and not val.startswith(("0x", "0X")): val = "0x" + val # Let Kconfiglib itself print a warning here if the value is invalid. We # could also disable warnings temporarily with 'kconf.warn = False' and # print our own warning. return sc.set_value(val) if __name__ == "__main__": if len(sys.argv) != 2: sys.exit("usage: menuconfig.py ") # Load Kconfig configuration files kconf = Kconfig(sys.argv[1]) # Print the initial configuration tree print_menuconfig(kconf) while True: try: cmd = input('Enter a symbol/choice name, "load_config", or ' '"write_config" (or press CTRL+D to exit): ').strip() except EOFError: print("") break if cmd == "load_config": config_filename = input(".config file to load: ") try: # Returns a message telling which file got loaded print(kconf.load_config(config_filename)) except EnvironmentError as e: print(e, file=sys.stderr) print_menuconfig(kconf) continue if cmd == "write_config": config_filename = input("To this file: ") try: # Returns a message telling which file got saved print(kconf.write_config(config_filename)) except EnvironmentError as e: print(e, file=sys.stderr) continue # Assume 'cmd' is the name of a symbol or choice if it isn't one of the # commands above, prompt the user for a value for it, and print the new # configuration tree if cmd in kconf.syms: if get_value_from_user(kconf.syms[cmd]): print_menuconfig(kconf) continue if cmd in kconf.named_choices: if get_value_from_user(kconf.named_choices[cmd]): print_menuconfig(kconf) continue print("No symbol/choice named '{}' in the configuration".format(cmd), file=sys.stderr) Kconfiglib-14.1.0/examples/merge_config.py000077500000000000000000000070631361474255700205070ustar00rootroot00000000000000#!/usr/bin/env python3 # This script functions similarly to scripts/kconfig/merge_config.sh from the # kernel tree, merging multiple configurations fragments to produce a complete # .config, with unspecified values filled in as for alldefconfig. # # The generated .config respects symbol dependencies, and a warning is printed # if any symbol gets a different value from the assigned value. # # For a real-world merging example based on this script, see # https://github.com/zephyrproject-rtos/zephyr/blob/master/scripts/kconfig/kconfig.py. # # Here's a demo: # # Kconfig contents: # # config FOO # bool "FOO" # # config BAR # bool "BAR" # # config BAZ # string "BAZ" # # config QAZ # bool "QAZ" if n # # # conf1 contents: # # CONFIG_FOO=y # # # conf2 contents: # # CONFIG_BAR=y # # # conf3 contents: # # # Assigned twice (would generate warning if 'warn_assign_override' was # # True) # # CONFIG_FOO is not set # # # Ops... this symbol doesn't exist # CONFIG_OPS=y # # CONFIG_BAZ="baz string" # # # conf4 contents: # # CONFIG_QAZ=y # # # Running: # # $ python(3) merge_config.py Kconfig merged conf1 conf2 conf3 conf4 # Merged configuration 'conf1' # Merged configuration 'conf2' # conf3:5: warning: attempt to assign the value 'y' to the undefined symbol OPS # Merged configuration 'conf3' # Merged configuration 'conf4' # Configuration saved to 'merged' # warning: QAZ (defined at Kconfig:10) was assigned the value 'y' but got the value 'n' -- check dependencies # $ cat merged # Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) # # CONFIG_FOO is not set # CONFIG_BAR=y # CONFIG_BAZ="baz string" from __future__ import print_function import sys from kconfiglib import Kconfig, BOOL, TRISTATE, TRI_TO_STR if len(sys.argv) < 4: sys.exit("usage: merge_config.py Kconfig merged_config config1 [config2 ...]") kconf = Kconfig(sys.argv[1], suppress_traceback=True) # Enable warnings for assignments to undefined symbols kconf.warn_assign_undef = True # (This script uses alldefconfig as the base. Other starting states could be # set up here as well. The approach in examples/allnoconfig_simpler.py could # provide an allnoconfig starting state for example.) # Disable warnings generated for multiple assignments to the same symbol within # a (set of) configuration files. Assigning a symbol multiple times might be # done intentionally when merging configuration files. kconf.warn_assign_override = False kconf.warn_assign_redun = False # Create a merged configuration by loading the fragments with replace=False. # load_config() and write_config() returns a message to print. for config in sys.argv[3:]: print(kconf.load_config(config, replace=False)) # Write the merged configuration print(kconf.write_config(sys.argv[2])) # Print warnings for symbols whose actual value doesn't match the assigned # value for sym in kconf.defined_syms: # Was the symbol assigned to? if sym.user_value is not None: # Tristate values are represented as 0, 1, 2. Having them as # "n", "m", "y" is more convenient here, so convert. if sym.type in (BOOL, TRISTATE): user_value = TRI_TO_STR[sym.user_value] else: user_value = sym.user_value if user_value != sym.str_value: print("warning: {} was assigned the value '{}' but got the " "value '{}' -- check dependencies".format( sym.name_and_loc, user_value, sym.str_value), file=sys.stderr) Kconfiglib-14.1.0/examples/print_config_tree.py000066400000000000000000000157711361474255700215650ustar00rootroot00000000000000# Prints menu entries as a tree with its value in the .config file. This can be # handy e.g. for diffing between different .config files or versions of Kconfig files. # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=print_config_tree.py [SCRIPT_ARG=<.config>] # # If the variable WITH_HELP_DESC is modified to 'True', the help is added # to the symbols. # # Here's a notation guide. The notation matches the one used by menuconfig # (scripts/kconfig/mconf): # # [ ] prompt - Bool # < > prompt - Tristate # {M} prompt - Tristate selected to m. Can only be set to m or y. # -*- prompt - Bool/tristate selected to y, pinning it # -M- prompt - Tristate selected to m that also has m visibility, # pinning it to m # (foo) prompt - String/int/hex symbol with value "foo" # --> prompt - The selected symbol in a choice in y mode. This # syntax is unique to this example. # # When modules are disabled, the .type attribute of TRISTATE symbols and # choices automatically changes to BOOL. This trick is used by the C # implementation as well, and gives the expected behavior without having to do # anything extra here. The original type is available in .orig_type if needed. # # Example output: # # $ make scriptconfig SCRIPT=Kconfiglib/examples/print_config_tree.py [SCRIPT_ARG=<.config file>] # # ======== Linux/x86 4.9.82 Kernel Configuration ======== # # [*] 64-bit kernel (64BIT) # General setup # () Cross-compiler tool prefix (CROSS_COMPILE) # [ ] Compile also drivers which will not load (COMPILE_TEST) # () Local version - append to kernel release (LOCALVERSION) # [*] Automatically append version information to the version string (LOCALVERSION_AUTO) # -*- Kernel compression mode # ... # # With the variable WITH_HELP_DESC modified to 'True': # # ======== Linux/x86 4.9.82 Kernel Configuration ======== # # [*] 64-bit kernel - 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 (64BIT) # General setup # () Cross-compiler tool prefix - Same as running 'make CROSS_COMPILE=prefix-' but stored for default make runs in this kernel build directory. You don't need to set this unless you want the configured kernel build directory to select the cross-compiler automatically. (CROSS_COMPILE) # [ ] Compile also drivers which will not load - Some drivers can be compiled on a different platform than they are intended to be run on. Despite they cannot be loaded there (or even when they load they cannot be used due to missing HW support), developers still, opposing to distributors, might want to build such drivers to compile-test them. If you are a developer and want to build everything available, say Y here. If you are a user/distributor, say N here to exclude useless drivers to be distributed. (COMPILE_TEST) # ... import sys from kconfiglib import Kconfig, \ Symbol, MENU, COMMENT, \ BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN, \ expr_value # Add help description to output WITH_HELP_DESC = False def indent_print(s, indent): print(indent*" " + s) def value_str(sc): """ Returns the value part ("[*]", "", "(foo)" etc.) of a menu entry. sc: Symbol or Choice. """ if sc.type in (STRING, INT, HEX): return "({})".format(sc.str_value) # BOOL or TRISTATE # The choice mode is an upper bound on the visibility of choice symbols, so # we can check the choice symbols' own visibility to see if the choice is # in y mode if isinstance(sc, Symbol) and sc.choice and sc.visibility == 2: # For choices in y mode, print '-->' next to the selected symbol return "-->" if sc.choice.selection is sc else " " tri_val_str = (" ", "M", "*")[sc.tri_value] if len(sc.assignable) == 1: # Pinned to a single value return "-{}-".format(tri_val_str) if sc.type == BOOL: return "[{}]".format(tri_val_str) if sc.type == TRISTATE: if sc.assignable == (1, 2): # m and y available return "{" + tri_val_str + "}" # Gets a bit confusing with .format() return "<{}>".format(tri_val_str) def node_str(node): """ Returns the complete menu entry text for a menu node, or "" for invisible menu nodes. Invisible menu nodes are those that lack a prompt or that do not have a satisfied prompt condition. Example return value: "[*] Bool symbol (BOOL)" The symbol name is printed in parentheses to the right of the prompt. """ if not node.prompt: return "" # Even for menu nodes for symbols and choices, it's wrong to check # Symbol.visibility / Choice.visibility here. The reason is that a symbol # (and a choice, in theory) can be defined in multiple locations, giving it # multiple menu nodes, which do not necessarily all have the same prompt # visibility. Symbol.visibility / Choice.visibility is calculated as the OR # of the visibility of all the prompts. prompt, prompt_cond = node.prompt if not expr_value(prompt_cond): return "" if node.item == MENU: return " " + prompt if node.item == COMMENT: return " *** {} ***".format(prompt) # Symbol or Choice sc = node.item if sc.type == UNKNOWN: # Skip symbols defined without a type (these are obscure and generate # a warning) return "" # Add help text if WITH_HELP_DESC: prompt += ' - ' + str(node.help).replace('\n', ' ').replace('\r', '') # {:3} sets the field width to three. Gives nice alignment for empty string # values. res = "{:3} {}".format(value_str(sc), prompt) # Don't print the name for unnamed choices (the normal kind) if sc.name is not None: res += " ({})".format(sc.name) return res def print_menuconfig_nodes(node, indent): """ Prints a tree with all the menu entries rooted at 'node'. Child menu entries are indented. """ while node: string = node_str(node) if string: indent_print(string, indent) if node.list: print_menuconfig_nodes(node.list, indent + 8) node = node.next def print_menuconfig(kconf): """ Prints all menu entries for the configuration. """ # Print the expanded mainmenu text at the top. This is the same as # kconf.top_node.prompt[0], but with variable references expanded. print("\n======== {} ========\n".format(kconf.mainmenu_text)) print_menuconfig_nodes(kconf.top_node.list, 0) print("") if __name__ == "__main__": # Load Kconfig configuration files kconf = Kconfig(sys.argv[1]) # Set default .config file or load it from argv if len(sys.argv) == 2: config_filename = '.config' else: config_filename = sys.argv[2] kconf.load_config(config_filename) # Print the configuration tree print_menuconfig(kconf) Kconfiglib-14.1.0/examples/print_sym_info.py000066400000000000000000000032201361474255700211060ustar00rootroot00000000000000# Loads a Kconfig and a .config and prints a symbol. # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/print_sym_info.py SCRIPT_ARG= # # Example output for SCRIPT_ARG=MODULES: # # menuconfig MODULES # bool # prompt "Enable loadable module support" # option modules # help # Kernel modules are small pieces of compiled code which can # be inserted in the running kernel, rather than being # permanently built into the kernel. You use the "modprobe" # tool to add (and sometimes remove) them. If you say Y here, # many parts of the kernel can be built as modules (by # answering M instead of Y where indicated): this is most # useful for infrequently used options which are not required # for booting. For more information, see the man pages for # modprobe, lsmod, modinfo, insmod and rmmod. # # If you say Y here, you will need to run "make # modules_install" to put the modules under /lib/modules/ # where modprobe can find them (you may need to be root to do # this). # # If unsure, say Y. # # value = n # visibility = y # currently assignable values: n, y # defined at init/Kconfig:1674 import sys from kconfiglib import Kconfig, TRI_TO_STR if len(sys.argv) < 3: sys.exit('Pass symbol name (without "CONFIG_" prefix) with SCRIPT_ARG=') kconf = Kconfig(sys.argv[1]) sym = kconf.syms[sys.argv[2]] print(sym) print("value = " + sym.str_value) print("visibility = " + TRI_TO_STR[sym.visibility]) print("currently assignable values: " + ", ".join([TRI_TO_STR[v] for v in sym.assignable])) for node in sym.nodes: print("defined at {}:{}".format(node.filename, node.linenr)) Kconfiglib-14.1.0/examples/print_tree.py000066400000000000000000000034641361474255700202340ustar00rootroot00000000000000# Prints the menu tree of the configuration. Dependencies between symbols can # sometimes implicitly alter the menu structure (see kconfig-language.txt), and # that's implemented too. # # Note: See the Kconfig.node_iter() function as well, which provides a simpler # interface for walking the menu tree. # # Usage: # # $ make [ARCH=] scriptconfig SCRIPT=Kconfiglib/examples/print_tree.py # # Example output: # # ... # config HAVE_KERNEL_LZO # config HAVE_KERNEL_LZ4 # choice # config KERNEL_GZIP # config KERNEL_BZIP2 # config KERNEL_LZMA # config KERNEL_XZ # config KERNEL_LZO # config KERNEL_LZ4 # config DEFAULT_HOSTNAME # config SWAP # config SYSVIPC # config SYSVIPC_SYSCTL # config POSIX_MQUEUE # config POSIX_MQUEUE_SYSCTL # config CROSS_MEMORY_ATTACH # config FHANDLE # config USELIB # config AUDIT # config HAVE_ARCH_AUDITSYSCALL # config AUDITSYSCALL # config AUDIT_WATCH # config AUDIT_TREE # menu "IRQ subsystem" # config MAY_HAVE_SPARSE_IRQ # config GENERIC_IRQ_LEGACY # config GENERIC_IRQ_PROBE # ... import sys from kconfiglib import Kconfig, Symbol, Choice, MENU, COMMENT def indent_print(s, indent): print(indent*" " + s) def print_items(node, indent): while node: if isinstance(node.item, Symbol): indent_print("config " + node.item.name, indent) elif isinstance(node.item, Choice): indent_print("choice", indent) elif node.item == MENU: indent_print('menu "{}"'.format(node.prompt[0]), indent) elif node.item == COMMENT: indent_print('comment "{}"'.format(node.prompt[0]), indent) if node.list: print_items(node.list, indent + 2) node = node.next kconf = Kconfig(sys.argv[1]) print_items(kconf.top_node, 0) Kconfiglib-14.1.0/genconfig.py000077500000000000000000000123171361474255700162020ustar00rootroot00000000000000#!/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() Kconfiglib-14.1.0/guiconfig.py000077500000000000000000002176441361474255700162270ustar00rootroot00000000000000#!/usr/bin/env python3 # Copyright (c) 2019, Ulf Magnusson # SPDX-License-Identifier: ISC """ Overview ======== A Tkinter-based menuconfig implementation, based around a treeview control and a help display. The interface should feel familiar to people used to qconf ('make xconfig'). Compatible with both Python 2 and Python 3. The display can be toggled between showing the full tree and showing just a single menu (like menuconfig.py). Only single-menu mode distinguishes between symbols defined with 'config' and symbols defined with 'menuconfig'. A show-all mode is available that shows invisible items in red. Supports both mouse and keyboard controls. The following keyboard shortcuts are available: Ctrl-S : Save configuration Ctrl-O : Open configuration Ctrl-A : Toggle show-all mode Ctrl-N : Toggle show-name mode Ctrl-M : Toggle single-menu mode Ctrl-F, /: Open jump-to dialog ESC : Close Running ======= guiconfig.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. """ # Note: There's some code duplication with menuconfig.py below, especially for # the help text. Maybe some of it could be moved into kconfiglib.py or a shared # helper script, but OTOH it's pretty nice to have things standalone and # customizable. import errno import os import sys _PY2 = sys.version_info[0] < 3 if _PY2: # Python 2 from Tkinter import * import ttk import tkFont as font import tkFileDialog as filedialog import tkMessageBox as messagebox else: # Python 3 from tkinter import * import tkinter.ttk as ttk import tkinter.font as font from tkinter import filedialog, messagebox 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 # If True, use GIF image data embedded in this file instead of separate GIF # files. See _load_images(). _USE_EMBEDDED_IMAGES = True # Help text for the jump-to dialog _JUMP_TO_HELP = """\ Type one or more strings/regexes and press Enter to list items that match all of them. Python's regex flavor is used (see the 're' module). Double-clicking an item will jump to it. Item values can be toggled directly within the dialog.\ """ def _main(): menuconfig(standard_kconfig(__doc__)) # Global variables used below: # # _root: # The Toplevel instance for the main window # # _tree: # The Treeview in the main window # # _jump_to_tree: # The Treeview in the jump-to dialog. None if the jump-to dialog isn't # open. Doubles as a flag. # # _jump_to_matches: # List of Nodes shown in the jump-to dialog # # _menupath: # The Label that shows the menu path of the selected item # # _backbutton: # The button shown in single-menu mode for jumping to the parent menu # # _status_label: # Label with status text shown at the bottom of the main window # ("Modified", "Saved to ...", etc.) # # _id_to_node: # We can't use Node objects directly as Treeview item IDs, so we use their # id()s instead. This dictionary maps Node id()s back to Nodes. (The keys # are actually str(id(node)), just to simplify lookups.) # # _cur_menu: # The current menu. Ignored outside single-menu mode. # # _show_all_var/_show_name_var/_single_menu_var: # Tkinter Variable instances bound to the corresponding checkboxes # # _show_all/_single_menu: # Plain Python bools that track _show_all_var and _single_menu_var, to # speed up and simplify things a bit # # _conf_filename: # File to save the configuration to # # _minconf_filename: # File to save minimal configurations to # # _conf_changed: # True if the configuration has been changed. If False, we don't bother # showing the save-and-quit dialog. # # We reset this to False whenever the configuration is saved. # # _*_img: # PhotoImage instances for images def menuconfig(kconf): """ Launches the configuration interface, returning after the user exits. kconf: Kconfig instance to be configured """ global _kconf global _conf_filename global _minconf_filename global _jump_to_tree global _cur_menu _kconf = kconf _jump_to_tree = None _create_id_to_node() _create_ui() # Filename to save configuration to _conf_filename = standard_config_filename() # Load existing configuration and check if it's outdated _set_conf_changed(_load_config()) # Filename to save minimal configuration to _minconf_filename = "defconfig" # Current menu in single-menu mode _cur_menu = _kconf.top_node # Any visible items in the top menu? if not _shown_menu_nodes(kconf.top_node): # Nothing visible. Start in show-all mode and try again. _show_all_var.set(True) if not _shown_menu_nodes(kconf.top_node): # Give up and show an error. It's nice to be able to assume that # the tree is non-empty in the rest of the code. _root.wait_visibility() messagebox.showerror( "Error", "Empty configuration -- nothing to configure.\n\n" "Check that environment variables are set properly.") _root.destroy() return # Build the initial tree _update_tree() # Select the first item and focus the Treeview, so that keyboard controls # work immediately _select(_tree, _tree.get_children()[0]) _tree.focus_set() # Make geometry information available for centering the window. This # indirectly creates the window, so hide it so that it's never shown at the # old location. _root.withdraw() _root.update_idletasks() # Center the window _root.geometry("+{}+{}".format( (_root.winfo_screenwidth() - _root.winfo_reqwidth())//2, (_root.winfo_screenheight() - _root.winfo_reqheight())//2)) # Show it _root.deiconify() # Prevent the window from being automatically resized. Otherwise, it # changes size when scrollbars appear/disappear before the user has # manually resized it. _root.geometry(_root.geometry()) _root.mainloop() def _load_config(): # Loads any existing .config file. See the Kconfig.load_config() docstring. # # Returns True if .config is missing or outdated. We always prompt for # saving the configuration in that case. print(_kconf.load_config()) if not os.path.exists(_conf_filename): # No .config return True return _needs_save() def _needs_save(): # Returns True if a just-loaded .config file is outdated (would get # modified when saving) if _kconf.missing_syms: # Assignments to undefined symbols in the .config return True for sym in _kconf.unique_defined_syms: if sym.user_value is None: if sym.config_string: # Unwritten symbol return True elif sym.orig_type in (BOOL, TRISTATE): if sym.tri_value != sym.user_value: # Written bool/tristate symbol, new value return True elif sym.str_value != sym.user_value: # Written string/int/hex symbol, new value return True # No need to prompt for save return False def _create_id_to_node(): global _id_to_node _id_to_node = {str(id(node)): node for node in _kconf.node_iter()} def _create_ui(): # Creates the main window UI global _root global _tree # Create the root window. This initializes Tkinter and makes e.g. # PhotoImage available, so do it early. _root = Tk() _load_images() _init_misc_ui() _fix_treeview_issues() _create_top_widgets() # Create the pane with the Kconfig tree and description text panedwindow, _tree = _create_kconfig_tree_and_desc(_root) panedwindow.grid(column=0, row=1, sticky="nsew") _create_status_bar() _root.columnconfigure(0, weight=1) # Only the pane with the Kconfig tree and description grows vertically _root.rowconfigure(1, weight=1) # Start with show-name disabled _do_showname() _tree.bind("", _tree_left_key) _tree.bind("", _tree_right_key) # Note: Binding this for the jump-to tree as well would cause issues due to # the Tk bug mentioned in _tree_open() _tree.bind("<>", _tree_open) # add=True to avoid overriding the description text update _tree.bind("<>", _update_menu_path, add=True) _root.bind("", _save) _root.bind("", _open) _root.bind("", _toggle_showall) _root.bind("", _toggle_showname) _root.bind("", _toggle_tree_mode) _root.bind("", _jump_to_dialog) _root.bind("/", _jump_to_dialog) _root.bind("", _on_quit) def _load_images(): # Loads GIF images, creating the global _*_img PhotoImage variables. # Base64-encoded images embedded in this script are used if # _USE_EMBEDDED_IMAGES is True, and separate image files in the same # directory as the script otherwise. # # Using a global variable indirectly prevents the image from being # garbage-collected. Passing an image to a Tkinter function isn't enough to # keep it alive. def load_image(name, data): var_name = "_{}_img".format(name) if _USE_EMBEDDED_IMAGES: globals()[var_name] = PhotoImage(data=data, format="gif") else: globals()[var_name] = PhotoImage( file=os.path.join(os.path.dirname(__file__), name + ".gif"), format="gif") # Note: Base64 data can be put on the clipboard with # $ base64 -w0 foo.gif | xclip 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") load_image("m_my", "R0lGODlhEAAQAPEDAAAAAOQMuv///wAAACH5BAUKAAMALAAAAAAQABAAAAI5nIGpxiAPI2ghxFinq/ZygQhc94zgZopmOLYf67anGr+oZdp02emfV5n9MEHN5QhqICETxkABbQ4KADs=") load_image("y_my", "R0lGODlhEAAQAPH/AAAAAADQAAPRA////yH5BAUKAAQALAAAAAAQABAAAAM+SArcrhCMSSuIM9Q8rxxBWIXawIBkmWonupLd565Um9G1PIs59fKmzw8WnAlusBYR2SEIN6DmAmqBLBxYSAIAOw==") load_image("n_locked", "R0lGODlhEAAQAPABAAAAAP///yH5BAUKAAEALAAAAAAQABAAAAIgjB8AyKwN04pu0vMutpqqz4Hih4ydlnUpyl2r23pxUAAAOw==") load_image("m_locked", "R0lGODlhEAAQAPD/AAAAAOQMuiH5BAUKAAIALAAAAAAQABAAAAIylC8AyKwN04ohnGcqqlZmfXDWI26iInZoyiore05walolV39ftxsYHgL9QBBMBGFEFAAAOw==") 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=") def _fix_treeview_issues(): # Fixes some Treeview issues global _treeview_rowheight style = ttk.Style() # The treeview rowheight isn't adjusted automatically on high-DPI displays, # so do it ourselves. The font will probably always be TkDefaultFont, but # play it safe and look it up. _treeview_rowheight = font.Font(font=style.lookup("Treeview", "font")) \ .metrics("linespace") + 2 style.configure("Treeview", rowheight=_treeview_rowheight) # Work around regression in https://core.tcl.tk/tk/tktview?name=509cafafae, # which breaks tag background colors for option in "foreground", "background": # Filter out any styles starting with ("!disabled", "!selected", ...). # style.map() returns an empty list for missing options, so this should # be future-safe. style.map( "Treeview", **{option: [elm for elm in style.map("Treeview", query_opt=option) if elm[:2] != ("!disabled", "!selected")]}) def _init_misc_ui(): # Does misc. UI initialization, like setting the title, icon, and theme _root.title(_kconf.mainmenu_text) # iconphoto() isn't available in Python 2's Tkinter _root.tk.call("wm", "iconphoto", _root._w, "-default", _icon_img) # Reducing the width of the window to 1 pixel makes it move around, at # least on GNOME. Prevent weird stuff like that. _root.minsize(128, 128) _root.protocol("WM_DELETE_WINDOW", _on_quit) # Use the 'clam' theme on *nix if it's available. It looks nicer than the # 'default' theme. if _root.tk.call("tk", "windowingsystem") == "x11": style = ttk.Style() if "clam" in style.theme_names(): style.theme_use("clam") def _create_top_widgets(): # Creates the controls above the Kconfig tree in the main window global _show_all_var global _show_name_var global _single_menu_var global _menupath global _backbutton topframe = ttk.Frame(_root) topframe.grid(column=0, row=0, sticky="ew") ttk.Button(topframe, text="Save", command=_save) \ .grid(column=0, row=0, sticky="ew", padx=".05c", pady=".05c") ttk.Button(topframe, text="Save as...", command=_save_as) \ .grid(column=1, row=0, sticky="ew") ttk.Button(topframe, text="Save minimal (advanced)...", command=_save_minimal) \ .grid(column=2, row=0, sticky="ew", padx=".05c") ttk.Button(topframe, text="Open...", command=_open) \ .grid(column=3, row=0) ttk.Button(topframe, text="Jump to...", command=_jump_to_dialog) \ .grid(column=4, row=0, padx=".05c") _show_name_var = BooleanVar() ttk.Checkbutton(topframe, text="Show name", command=_do_showname, variable=_show_name_var) \ .grid(column=0, row=1, sticky="nsew", padx=".05c", pady="0 .05c", ipady=".2c") _show_all_var = BooleanVar() ttk.Checkbutton(topframe, text="Show all", command=_do_showall, variable=_show_all_var) \ .grid(column=1, row=1, sticky="nsew", pady="0 .05c") # Allow the show-all and single-menu status to be queried via plain global # Python variables, which is faster and simpler def show_all_updated(*_): global _show_all _show_all = _show_all_var.get() _trace_write(_show_all_var, show_all_updated) _show_all_var.set(False) _single_menu_var = BooleanVar() ttk.Checkbutton(topframe, text="Single-menu mode", command=_do_tree_mode, variable=_single_menu_var) \ .grid(column=2, row=1, sticky="nsew", padx=".05c", pady="0 .05c") _backbutton = ttk.Button(topframe, text="<--", command=_leave_menu, state="disabled") _backbutton.grid(column=0, row=4, sticky="nsew", padx=".05c", pady="0 .05c") def tree_mode_updated(*_): global _single_menu _single_menu = _single_menu_var.get() if _single_menu: _backbutton.grid() else: _backbutton.grid_remove() _trace_write(_single_menu_var, tree_mode_updated) _single_menu_var.set(False) # Column to the right of the buttons that the menu path extends into, so # that it can grow wider than the buttons topframe.columnconfigure(5, weight=1) _menupath = ttk.Label(topframe) _menupath.grid(column=0, row=3, columnspan=6, sticky="w", padx="0.05c", pady="0 .05c") def _create_kconfig_tree_and_desc(parent): # Creates a Panedwindow with a Treeview that shows Kconfig nodes and a Text # that shows a description of the selected node. Returns a tuple with the # Panedwindow and the Treeview. This code is shared between the main window # and the jump-to dialog. panedwindow = ttk.Panedwindow(parent, orient=VERTICAL) tree_frame, tree = _create_kconfig_tree(panedwindow) desc_frame, desc = _create_kconfig_desc(panedwindow) panedwindow.add(tree_frame, weight=1) panedwindow.add(desc_frame) def tree_select(_): # The Text widget does not allow editing the text in its disabled # state. We need to temporarily enable it. desc["state"] = "normal" sel = tree.selection() if not sel: desc.delete("1.0", "end") desc["state"] = "disabled" return # Text.replace() is not available in Python 2's Tkinter desc.delete("1.0", "end") desc.insert("end", _info_str(_id_to_node[sel[0]])) desc["state"] = "disabled" tree.bind("<>", tree_select) tree.bind("<1>", _tree_click) tree.bind("", _tree_double_click) tree.bind("", _tree_enter) tree.bind("", _tree_enter) tree.bind("", _tree_toggle) tree.bind("n", _tree_set_val(0)) tree.bind("m", _tree_set_val(1)) tree.bind("y", _tree_set_val(2)) return panedwindow, tree def _create_kconfig_tree(parent): # Creates a Treeview for showing Kconfig nodes frame = ttk.Frame(parent) tree = ttk.Treeview(frame, selectmode="browse", height=20, columns=("name",)) tree.heading("#0", text="Option", anchor="w") tree.heading("name", text="Name", anchor="w") tree.tag_configure("n-bool", image=_n_bool_img) tree.tag_configure("y-bool", image=_y_bool_img) tree.tag_configure("m-tri", image=_m_tri_img) tree.tag_configure("n-tri", image=_n_tri_img) tree.tag_configure("m-tri", image=_m_tri_img) tree.tag_configure("y-tri", image=_y_tri_img) tree.tag_configure("m-my", image=_m_my_img) tree.tag_configure("y-my", image=_y_my_img) tree.tag_configure("n-locked", image=_n_locked_img) tree.tag_configure("m-locked", image=_m_locked_img) 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("invisible", foreground="red") tree.grid(column=0, row=0, sticky="nsew") _add_vscrollbar(frame, tree) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) # Create items for all menu nodes. These can be detached/moved later. # Micro-optimize this a bit. insert = tree.insert id_ = id Symbol_ = Symbol for node in _kconf.node_iter(): item = node.item insert("", "end", iid=id_(node), values=item.name if item.__class__ is Symbol_ else "") return frame, tree def _create_kconfig_desc(parent): # Creates a Text for showing the description of the selected Kconfig node frame = ttk.Frame(parent) desc = Text(frame, height=12, wrap="none", borderwidth=0, state="disabled") desc.grid(column=0, row=0, sticky="nsew") # Work around not being to Ctrl-C/V text from a disabled Text widget, with a # tip found in https://stackoverflow.com/questions/3842155/is-there-a-way-to-make-the-tkinter-text-widget-read-only desc.bind("<1>", lambda _: desc.focus_set()) _add_vscrollbar(frame, desc) frame.columnconfigure(0, weight=1) frame.rowconfigure(0, weight=1) return frame, desc def _add_vscrollbar(parent, widget): # Adds a vertical scrollbar to 'widget' that's only shown as needed vscrollbar = ttk.Scrollbar(parent, orient="vertical", command=widget.yview) vscrollbar.grid(column=1, row=0, sticky="ns") def yscrollcommand(first, last): # Only show the scrollbar when needed. 'first' and 'last' are # strings. if float(first) <= 0.0 and float(last) >= 1.0: vscrollbar.grid_remove() else: vscrollbar.grid() vscrollbar.set(first, last) widget["yscrollcommand"] = yscrollcommand def _create_status_bar(): # Creates the status bar at the bottom of the main window global _status_label _status_label = ttk.Label(_root, anchor="e", padding="0 0 0.4c 0") _status_label.grid(column=0, row=3, sticky="ew") def _set_status(s): # Sets the text in the status bar to 's' _status_label["text"] = s def _set_conf_changed(changed): # Updates the status re. whether there are unsaved changes global _conf_changed _conf_changed = changed if changed: _set_status("Modified") def _update_tree(): # Updates the Kconfig tree in the main window by first detaching all nodes # and then updating and reattaching them. The tree structure might have # changed. # If a selected/focused item is detached and later reattached, it stays # selected/focused. That can give multiple selections even though # selectmode=browse. Save and later restore the selection and focus as a # workaround. old_selection = _tree.selection() old_focus = _tree.focus() # Detach all tree items before re-stringing them. This is relatively fast, # luckily. _tree.detach(*_id_to_node.keys()) if _single_menu: _build_menu_tree() else: _build_full_tree(_kconf.top_node) _tree.selection_set(old_selection) _tree.focus(old_focus) def _build_full_tree(menu): # Updates the tree starting from menu.list, in full-tree mode. To speed # things up, only open menus are updated. The menu-at-a-time logic here is # to deal with invisible items that can show up outside show-all mode (see # _shown_full_nodes()). for node in _shown_full_nodes(menu): _add_to_tree(node, _kconf.top_node) # _shown_full_nodes() includes nodes from menus rooted at symbols, so # we only need to check "real" menus/choices here if node.list and not isinstance(node.item, Symbol): if _tree.item(id(node), "open"): _build_full_tree(node) else: # We're just probing here, so _shown_menu_nodes() will work # fine, and might be a bit faster shown = _shown_menu_nodes(node) if shown: # Dummy element to make the open/closed toggle appear _tree.move(id(shown[0]), id(shown[0].parent), "end") def _shown_full_nodes(menu): # Returns the list of menu nodes shown in 'menu' (a menu node for a menu) # for full-tree mode. A tricky detail is that invisible items need to be # shown if they have visible children. def rec(node): res = [] while node: if _visible(node) or _show_all: res.append(node) if node.list and isinstance(node.item, Symbol): # Nodes from menu created from dependencies res += rec(node.list) elif node.list and isinstance(node.item, Symbol): # Show invisible symbols (defined with either 'config' and # 'menuconfig') if they have visible children. This can happen # for an m/y-valued symbol with an optional prompt # ('prompt "foo" is COND') that is currently disabled. shown_children = rec(node.list) if shown_children: res.append(node) res += shown_children node = node.next return res return rec(menu.list) def _build_menu_tree(): # Updates the tree in single-menu mode. See _build_full_tree() as well. for node in _shown_menu_nodes(_cur_menu): _add_to_tree(node, _cur_menu) def _shown_menu_nodes(menu): # Used for single-menu mode. Similar to _shown_full_nodes(), but doesn't # include children of symbols defined with 'menuconfig'. def rec(node): res = [] while node: if _visible(node) or _show_all: res.append(node) if node.list and not node.is_menuconfig: res += rec(node.list) elif node.list and isinstance(node.item, Symbol): shown_children = rec(node.list) if shown_children: # Invisible item with visible children res.append(node) if not node.is_menuconfig: res += shown_children node = node.next return res return rec(menu.list) def _visible(node): # Returns True if the node should appear in the menu (outside show-all # mode) return node.prompt and expr_value(node.prompt[1]) and not \ (node.item == MENU and not expr_value(node.visibility)) def _add_to_tree(node, top): # Adds 'node' to the tree, at the end of its menu. We rely on going through # the nodes linearly to get the correct order. 'top' holds the menu that # corresponds to the top-level menu, and can vary in single-menu mode. parent = node.parent _tree.move(id(node), "" if parent is top else id(parent), "end") _tree.item( id(node), text=_node_str(node), # The _show_all test avoids showing invisible items in red outside # show-all mode, which could look confusing/broken. Invisible symbols # are shown outside show-all mode if an invisible symbol has visible # children in an implicit menu. tags=_img_tag(node) if _visible(node) or not _show_all else _img_tag(node) + " invisible") def _node_str(node): # Returns the string shown to the right of the image (if any) for the node if node.prompt: if node.item == COMMENT: s = "*** {} ***".format(node.prompt[0]) else: s = node.prompt[0] if isinstance(node.item, Symbol): sym = node.item # Print "(NEW)" next to symbols without a user value (from e.g. a # .config), but skip it for choice symbols in choices in y mode, # and for symbols of UNKNOWN type (which generate a warning though) if sym.user_value is None and sym.type and not \ (sym.choice and sym.choice.tri_value == 2): s += " (NEW)" elif isinstance(node.item, Symbol): # Symbol without prompt (can show up in show-all) s = "<{}>".format(node.item.name) else: # Choice without prompt. Use standard_sc_expr_str() so that it shows up # as ''. s = standard_sc_expr_str(node.item) if isinstance(node.item, Symbol): sym = node.item if sym.orig_type == STRING: s += ": " + sym.str_value elif sym.orig_type in (INT, HEX): s = "({}) {}".format(sym.str_value, s) elif isinstance(node.item, Choice) and node.item.tri_value == 2: # Print the prompt of the selected symbol after the choice for # choices in y mode sym = node.item.selection if sym: for sym_node in sym.nodes: # Use the prompt used at this choice location, in case the # choice symbol is defined in multiple locations if sym_node.parent is node and sym_node.prompt: s += " ({})".format(sym_node.prompt[0]) break else: # If the symbol isn't defined at this choice location, then # just use whatever prompt we can find for it for sym_node in sym.nodes: if sym_node.prompt: s += " ({})".format(sym_node.prompt[0]) break # In single-menu mode, print "--->" next to nodes that have menus that can # potentially be entered. Print "----" if the menu is empty. We don't allow # those to be entered. if _single_menu and node.is_menuconfig: s += " --->" if _shown_menu_nodes(node) else " ----" return s def _img_tag(node): # Returns the tag for the image that should be shown next to 'node', or the # empty string if it shouldn't have an image item = node.item if item in (MENU, COMMENT) or not item.orig_type: return "" if item.orig_type in (STRING, INT, HEX): return "edit" # BOOL or TRISTATE if _is_y_mode_choice_sym(item): # Choice symbol in y-mode choice return "selected" if item.choice.selection is item else "not-selected" if len(item.assignable) <= 1: # Pinned to a single value return "" if isinstance(item, Choice) else item.str_value + "-locked" if item.type == BOOL: return item.str_value + "-bool" # item.type == TRISTATE if item.assignable == (1, 2): return item.str_value + "-my" return item.str_value + "-tri" def _is_y_mode_choice_sym(item): # The choice mode is an upper bound on the visibility of choice symbols, so # we can check the choice symbols' own visibility to see if the choice is # in y mode return isinstance(item, Symbol) and item.choice and item.visibility == 2 def _tree_click(event): # Click on the Kconfig Treeview tree = event.widget if tree.identify_element(event.x, event.y) == "image": item = tree.identify_row(event.y) # Select the item before possibly popping up a dialog for # string/int/hex items, so that its help is visible _select(tree, item) _change_node(_id_to_node[item], tree.winfo_toplevel()) return "break" def _tree_double_click(event): # Double-click on the Kconfig treeview # Do an extra check to avoid weirdness when double-clicking in the tree # heading area if not _in_heading(event): return _tree_enter(event) def _in_heading(event): # Returns True if 'event' took place in the tree heading tree = event.widget return hasattr(tree, "identify_region") and \ tree.identify_region(event.x, event.y) in ("heading", "separator") def _tree_enter(event): # Enter press or double-click within the Kconfig treeview. Prefer to # open/close/enter menus, but toggle the value if that's not possible. tree = event.widget sel = tree.focus() if sel: node = _id_to_node[sel] if tree.get_children(sel): _tree_toggle_open(sel) elif _single_menu_mode_menu(node, tree): _enter_menu_and_select_first(node) else: _change_node(node, tree.winfo_toplevel()) return "break" def _tree_toggle(event): # Space press within the Kconfig treeview. Prefer to toggle the value, but # open/close/enter the menu if that's not possible. tree = event.widget sel = tree.focus() if sel: node = _id_to_node[sel] if _changeable(node): _change_node(node, tree.winfo_toplevel()) elif _single_menu_mode_menu(node, tree): _enter_menu_and_select_first(node) elif tree.get_children(sel): _tree_toggle_open(sel) return "break" def _tree_left_key(_): # Left arrow key press within the Kconfig treeview if _single_menu: # Leave the current menu in single-menu mode _leave_menu() return "break" # Otherwise, default action def _tree_right_key(_): # Right arrow key press within the Kconfig treeview sel = _tree.focus() if sel: node = _id_to_node[sel] # If the node can be entered in single-menu mode, do it if _single_menu_mode_menu(node, _tree): _enter_menu_and_select_first(node) return "break" # Otherwise, default action def _single_menu_mode_menu(node, tree): # Returns True if single-menu mode is on and 'node' is an (interface) # menu that can be entered return _single_menu and tree is _tree and node.is_menuconfig and \ _shown_menu_nodes(node) def _changeable(node): # Returns True if 'node' is a Symbol/Choice whose value can be changed sc = node.item if not isinstance(sc, (Symbol, Choice)): return False # This will hit for invisible symbols, which appear in show-all mode and # when an invisible symbol has visible children (which can happen e.g. for # symbols with optional prompts) if not (node.prompt and expr_value(node.prompt[1])): return False return sc.orig_type in (STRING, INT, HEX) or len(sc.assignable) > 1 \ or _is_y_mode_choice_sym(sc) def _tree_toggle_open(item): # Opens/closes the Treeview item 'item' if _tree.item(item, "open"): _tree.item(item, open=False) else: node = _id_to_node[item] if not isinstance(node.item, Symbol): # Can only get here in full-tree mode _build_full_tree(node) _tree.item(item, open=True) def _tree_set_val(tri_val): def tree_set_val(event): # n/m/y press within the Kconfig treeview # Sets the value of the currently selected item to 'tri_val', if that # value can be assigned sel = event.widget.focus() if sel: sc = _id_to_node[sel].item if isinstance(sc, (Symbol, Choice)) and tri_val in sc.assignable: _set_val(sc, tri_val) return tree_set_val def _tree_open(_): # Lazily populates the Kconfig tree when menus are opened in full-tree mode if _single_menu: # Work around https://core.tcl.tk/tk/tktview?name=368fa4561e # ("ttk::treeview open/closed indicators can be toggled while hidden"). # Clicking on the hidden indicator will call _build_full_tree() in # single-menu mode otherwise. return node = _id_to_node[_tree.focus()] # _shown_full_nodes() includes nodes from menus rooted at symbols, so we # only need to check "real" menus and choices here if not isinstance(node.item, Symbol): _build_full_tree(node) def _update_menu_path(_): # Updates the displayed menu path when nodes are selected in the Kconfig # treeview sel = _tree.selection() _menupath["text"] = _menu_path_info(_id_to_node[sel[0]]) if sel else "" def _item_row(item): # Returns the row number 'item' appears on within the Kconfig treeview, # starting from the top of the tree. Used to preserve scrolling. # # ttkTreeview.c in the Tk sources defines a RowNumber() function that does # the same thing, but it's not exposed. row = 0 while True: prev = _tree.prev(item) if prev: item = prev row += _n_rows(item) else: item = _tree.parent(item) if not item: return row row += 1 def _n_rows(item): # _item_row() helper. Returns the number of rows occupied by 'item' and # # its children. rows = 1 if _tree.item(item, "open"): for child in _tree.get_children(item): rows += _n_rows(child) return rows def _attached(item): # Heuristic for checking if a Treeview item is attached. Doesn't seem to be # good APIs for this. Might fail for super-obscure cases with tiny trees, # but you'd just get a small scroll mess-up. return bool(_tree.next(item) or _tree.prev(item) or _tree.parent(item)) def _change_node(node, parent): # Toggles/changes the value of 'node'. 'parent' is the parent window # (either the main window or the jump-to dialog), in case we need to pop up # a dialog. if not _changeable(node): return # sc = symbol/choice sc = node.item if sc.type in (INT, HEX, STRING): s = _set_val_dialog(node, parent) # Tkinter can return 'unicode' strings on Python 2, which Kconfiglib # can't deal with. UTF-8-encode the string to work around it. if _PY2 and isinstance(s, unicode): s = s.encode("utf-8", "ignore") if s is not None: _set_val(sc, s) elif len(sc.assignable) == 1: # Handles choice symbols for choices in y mode, which are a special # case: .assignable can be (2,) while .tri_value is 0. _set_val(sc, sc.assignable[0]) else: # Set the symbol to the value after the current value in # sc.assignable, with wrapping val_index = sc.assignable.index(sc.tri_value) _set_val(sc, sc.assignable[(val_index + 1) % len(sc.assignable)]) def _set_val(sc, val): # Wrapper around Symbol/Choice.set_value() for updating the menu state and # _conf_changed # Use the string representation of tristate values. This makes the format # consistent for all symbol types. if val in TRI_TO_STR: val = TRI_TO_STR[val] if val != sc.str_value: sc.set_value(val) _set_conf_changed(True) # Update the tree and try to preserve the scroll. Do a cheaper variant # than in the show-all case, that might mess up the scroll slightly in # rare cases, but is fast and flicker-free. stayput = _loc_ref_item() # Item to preserve scroll for old_row = _item_row(stayput) _update_tree() # If the reference item disappeared (can happen if the change was done # from the jump-to dialog), then avoid messing with the scroll and hope # for the best if _attached(stayput): _tree.yview_scroll(_item_row(stayput) - old_row, "units") if _jump_to_tree: _update_jump_to_display() def _set_val_dialog(node, parent): # Pops up a dialog for setting the value of the string/int/hex # symbol at node 'node'. 'parent' is the parent window. def ok(_=None): # No 'nonlocal' in Python 2 global _entry_res s = entry.get() if sym.type == HEX and not s.startswith(("0x", "0X")): s = "0x" + s if _check_valid(dialog, entry, sym, s): _entry_res = s dialog.destroy() def cancel(_=None): global _entry_res _entry_res = None dialog.destroy() sym = node.item dialog = Toplevel(parent) dialog.title("Enter {} value".format(TYPE_TO_STR[sym.type])) dialog.resizable(False, False) dialog.transient(parent) dialog.protocol("WM_DELETE_WINDOW", cancel) ttk.Label(dialog, text=node.prompt[0] + ":") \ .grid(column=0, row=0, columnspan=2, sticky="w", padx=".3c", pady=".2c .05c") entry = ttk.Entry(dialog, width=30) # Start with the previous value in the editbox, selected entry.insert(0, sym.str_value) entry.selection_range(0, "end") entry.grid(column=0, row=1, columnspan=2, sticky="ew", padx=".3c") entry.focus_set() range_info = _range_info(sym) if range_info: ttk.Label(dialog, text=range_info) \ .grid(column=0, row=2, columnspan=2, sticky="w", padx=".3c", pady=".2c 0") ttk.Button(dialog, text="OK", command=ok) \ .grid(column=0, row=4 if range_info else 3, sticky="e", padx=".3c", pady=".4c") ttk.Button(dialog, text="Cancel", command=cancel) \ .grid(column=1, row=4 if range_info else 3, padx="0 .3c") # Give all horizontal space to the grid cell with the OK button, so that # Cancel moves to the right dialog.columnconfigure(0, weight=1) _center_on_root(dialog) # Hack to scroll the entry so that the end of the text is shown, from # https://stackoverflow.com/questions/29334544/why-does-tkinters-entry-xview-moveto-fail. # Related Tk ticket: https://core.tcl.tk/tk/info/2513186fff def scroll_entry(_): _root.update_idletasks() entry.unbind("") entry.xview_moveto(1) entry.bind("", scroll_entry) # The dialog must be visible before we can grab the input dialog.wait_visibility() dialog.grab_set() dialog.bind("", ok) dialog.bind("", ok) dialog.bind("", cancel) # Wait for the user to be done with the dialog parent.wait_window(dialog) # Regrab the input in the parent parent.grab_set() return _entry_res def _center_on_root(dialog): # Centers 'dialog' on the root window. It often ends up at some bad place # like the top-left corner of the screen otherwise. See the menuconfig() # function, which has similar logic. dialog.withdraw() _root.update_idletasks() dialog_width = dialog.winfo_reqwidth() dialog_height = dialog.winfo_reqheight() screen_width = _root.winfo_screenwidth() screen_height = _root.winfo_screenheight() x = _root.winfo_rootx() + (_root.winfo_width() - dialog_width)//2 y = _root.winfo_rooty() + (_root.winfo_height() - dialog_height)//2 # Clamp so that no part of the dialog is outside the screen if x + dialog_width > screen_width: x = screen_width - dialog_width elif x < 0: x = 0 if y + dialog_height > screen_height: y = screen_height - dialog_height elif y < 0: y = 0 dialog.geometry("+{}+{}".format(x, y)) dialog.deiconify() def _check_valid(dialog, entry, sym, s): # Returns True if the string 's' is a well-formed value for 'sym'. # Otherwise, pops up an error and returns False. if sym.type not in (INT, HEX): # Anything goes for non-int/hex symbols return True base = 10 if sym.type == INT else 16 try: int(s, base) except ValueError: messagebox.showerror( "Bad value", "'{}' is a malformed {} value".format( s, TYPE_TO_STR[sym.type]), parent=dialog) entry.focus_set() return False for low_sym, high_sym, cond in sym.ranges: if expr_value(cond): low_s = low_sym.str_value high_s = high_sym.str_value if not int(low_s, base) <= int(s, base) <= int(high_s, base): messagebox.showerror( "Value out of range", "{} is outside the range {}-{}".format(s, low_s, high_s), parent=dialog) entry.focus_set() return False break return True def _range_info(sym): # Returns a string with information about the valid range for the symbol # 'sym', or None if 'sym' doesn't have a range if sym.type in (INT, HEX): for low, high, cond in sym.ranges: if expr_value(cond): return "Range: {}-{}".format(low.str_value, high.str_value) return None def _save(_=None): # Tries to save the configuration if _try_save(_kconf.write_config, _conf_filename, "configuration"): _set_conf_changed(False) _tree.focus_set() def _save_as(): # Pops up a dialog for saving the configuration to a specific location global _conf_filename filename = _conf_filename while True: filename = filedialog.asksaveasfilename( title="Save configuration as", initialdir=os.path.dirname(filename), initialfile=os.path.basename(filename), parent=_root) if not filename: break if _try_save(_kconf.write_config, filename, "configuration"): _conf_filename = filename break _tree.focus_set() def _save_minimal(): # Pops up a dialog for saving a minimal configuration (defconfig) to a # specific location global _minconf_filename filename = _minconf_filename while True: filename = filedialog.asksaveasfilename( title="Save minimal configuration as", initialdir=os.path.dirname(filename), initialfile=os.path.basename(filename), parent=_root) if not filename: break if _try_save(_kconf.write_min_config, filename, "minimal configuration"): _minconf_filename = filename break _tree.focus_set() def _open(_=None): # Pops up a dialog for loading a configuration global _conf_filename if _conf_changed and \ not messagebox.askokcancel( "Unsaved changes", "You have unsaved changes. Load new configuration anyway?"): return filename = _conf_filename while True: filename = filedialog.askopenfilename( title="Open configuration", initialdir=os.path.dirname(filename), initialfile=os.path.basename(filename), parent=_root) if not filename: break if _try_load(filename): # Maybe something fancier could be done here later to try to # preserve the scroll _conf_filename = filename _set_conf_changed(_needs_save()) if _single_menu and not _shown_menu_nodes(_cur_menu): # Turn on show-all if we're in single-menu mode and would end # up with an empty menu _show_all_var.set(True) _update_tree() break _tree.focus_set() def _toggle_showname(_): # Toggles show-name mode on/off _show_name_var.set(not _show_name_var.get()) _do_showname() def _do_showname(): # Updates the UI for the current show-name setting # Columns do not automatically shrink/expand, so we have to update # column widths ourselves tree_width = _tree.winfo_width() if _show_name_var.get(): _tree["displaycolumns"] = ("name",) _tree["show"] = "tree headings" name_width = tree_width//3 _tree.column("#0", width=max(tree_width - name_width, 1)) _tree.column("name", width=name_width) else: _tree["displaycolumns"] = () _tree["show"] = "tree" _tree.column("#0", width=tree_width) _tree.focus_set() def _toggle_showall(_): # Toggles show-all mode on/off _show_all_var.set(not _show_all) _do_showall() def _do_showall(): # Updates the UI for the current show-all setting # 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 # Save scroll information. old_scroll can end up negative here, if the # reference item isn't shown (only invisible items on the screen, and # show-all being turned off). stayput = _vis_loc_ref_item() # Probe the middle of the first row, to play it safe. identify_row(0) seems # to return the row before the top row. old_scroll = _item_row(stayput) - \ _item_row(_tree.identify_row(_treeview_rowheight//2)) _update_tree() if _show_all: # Deep magic: Unless we call update_idletasks(), the scroll adjustment # below is restricted to the height of the old tree, instead of the # height of the new tree. Since the tree with show-all on is guaranteed # to be taller, and we want the maximum range, we only call it when # turning show-all on. # # Strictly speaking, something similar ought to be done when changing # symbol values, but it causes annoying flicker, and in 99% of cases # things work anyway there (with usually minor scroll mess-ups in the # 1% case). _root.update_idletasks() # Restore scroll _tree.yview(_item_row(stayput) - old_scroll) _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 _single_menu_var.set(not _single_menu) _do_tree_mode() def _do_tree_mode(): # Updates the UI for the current tree mode (full-tree or single-menu) loc_ref_node = _id_to_node[_loc_ref_item()] if not _single_menu: # _jump_to() -> _enter_menu() already updates the tree, but # _jump_to() -> load_parents() doesn't, because it isn't always needed. # We always need to update the tree here, e.g. to add/remove "--->". _update_tree() _jump_to(loc_ref_node) _tree.focus_set() def _enter_menu_and_select_first(menu): # Enters the menu 'menu' and selects the first item. Used in single-menu # mode. _enter_menu(menu) _select(_tree, _tree.get_children()[0]) def _enter_menu(menu): # Enters the menu 'menu'. Used in single-menu mode. global _cur_menu _cur_menu = menu _update_tree() _backbutton["state"] = "disabled" if menu is _kconf.top_node else "normal" def _leave_menu(): # Leaves the current menu. Used in single-menu mode. global _cur_menu if _cur_menu is not _kconf.top_node: old_menu = _cur_menu _cur_menu = _parent_menu(_cur_menu) _update_tree() _select(_tree, id(old_menu)) if _cur_menu is _kconf.top_node: _backbutton["state"] = "disabled" _tree.focus_set() def _select(tree, item): # Selects, focuses, and see()s 'item' in 'tree' tree.selection_set(item) tree.focus(item) tree.see(item) def _loc_ref_item(): # Returns a Treeview item that can serve as a reference for the current # scroll location. We try to make this item stay on the same row on the # screen when updating the tree. # If the selected item is visible, use that sel = _tree.selection() if sel and _tree.bbox(sel[0]): return sel[0] # Otherwise, use the middle item on the screen. If it doesn't exist, the # tree is probably really small, so use the first item in the entire tree. return _tree.identify_row(_tree.winfo_height()//2) or \ _tree.get_children()[0] def _vis_loc_ref_item(): # Like _loc_ref_item(), but finds a visible item around the reference item. # Used when changing show-all mode, where non-visible (red) items will # disappear. item = _loc_ref_item() vis_before = _vis_before(item) if vis_before and _tree.bbox(vis_before): return vis_before vis_after = _vis_after(item) if vis_after and _tree.bbox(vis_after): return vis_after return vis_before or vis_after def _vis_before(item): # _vis_loc_ref_item() helper. Returns the first visible (not red) item, # searching backwards from 'item'. while item: if not _tree.tag_has("invisible", item): return item prev = _tree.prev(item) item = prev if prev else _tree.parent(item) return None def _vis_after(item): # _vis_loc_ref_item() helper. Returns the first visible (not red) item, # searching forwards from 'item'. while item: if not _tree.tag_has("invisible", item): return item next = _tree.next(item) if next: item = next else: item = _tree.parent(item) if not item: break item = _tree.next(item) return None def _on_quit(_=None): # Called when the user wants to exit if not _conf_changed: _quit("No changes to save (for '{}')".format(_conf_filename)) return while True: ync = messagebox.askyesnocancel("Quit", "Save changes?") if ync is None: return if not ync: _quit("Configuration ({}) was not saved".format(_conf_filename)) return if _try_save(_kconf.write_config, _conf_filename, "configuration"): # _try_save() already prints the "Configuration saved to ..." # message _quit() return def _quit(msg=None): # Quits the application # Do not call sys.exit() here, in case we're being run from a script _root.destroy() if msg: print(msg) def _try_save(save_fn, filename, description): # Tries to save a configuration file. Pops up an error and returns False on # failure. # # save_fn: # Function to call with 'filename' to save the file # # description: # String describing the thing being saved try: # save_fn() returns a message to print msg = save_fn(filename) _set_status(msg) print(msg) return True except EnvironmentError as e: messagebox.showerror( "Error saving " + description, "Error saving {} to '{}': {} (errno: {})" .format(description, e.filename, e.strerror, errno.errorcode[e.errno])) return False def _try_load(filename): # Tries to load a configuration file. Pops up an error and returns False on # failure. # # filename: # Configuration file to load try: msg = _kconf.load_config(filename) _set_status(msg) print(msg) return True except EnvironmentError as e: messagebox.showerror( "Error loading configuration", "Error loading '{}': {} (errno: {})" .format(filename, e.strerror, errno.errorcode[e.errno])) return False def _jump_to_dialog(_=None): # Pops up a dialog for jumping directly to a particular node. Symbol values # can also be changed within the dialog. # # Note: There's nothing preventing this from doing an incremental search # like menuconfig.py does, but currently it's a bit jerky for large Kconfig # trees, at least when inputting the beginning of the search string. We'd # need to somehow only update the tree items that are shown in the Treeview # to fix it. global _jump_to_tree def search(_=None): _update_jump_to_matches(msglabel, entry.get()) def jump_to_selected(event=None): # Jumps to the selected node and closes the dialog # Ignore double clicks on the image and in the heading area if event and (tree.identify_element(event.x, event.y) == "image" or _in_heading(event)): return sel = tree.selection() if not sel: return node = _id_to_node[sel[0]] if node not in _shown_menu_nodes(_parent_menu(node)): _show_all_var.set(True) if not _single_menu: # See comment in _do_tree_mode() _update_tree() _jump_to(node) dialog.destroy() def tree_select(_): jumpto_button["state"] = "normal" if tree.selection() else "disabled" dialog = Toplevel(_root) dialog.geometry("+{}+{}".format( _root.winfo_rootx() + 50, _root.winfo_rooty() + 50)) dialog.title("Jump to symbol/choice/menu/comment") dialog.minsize(128, 128) # See _create_ui() dialog.transient(_root) ttk.Label(dialog, text=_JUMP_TO_HELP) \ .grid(column=0, row=0, columnspan=2, sticky="w", padx=".1c", pady=".1c") entry = ttk.Entry(dialog) entry.grid(column=0, row=1, sticky="ew", padx=".1c", pady=".1c") entry.focus_set() entry.bind("", search) entry.bind("", search) ttk.Button(dialog, text="Search", command=search) \ .grid(column=1, row=1, padx="0 .1c", pady="0 .1c") msglabel = ttk.Label(dialog) msglabel.grid(column=0, row=2, sticky="w", pady="0 .1c") panedwindow, tree = _create_kconfig_tree_and_desc(dialog) panedwindow.grid(column=0, row=3, columnspan=2, sticky="nsew") # Clear tree tree.set_children("") _jump_to_tree = tree jumpto_button = ttk.Button(dialog, text="Jump to selected item", state="disabled", command=jump_to_selected) jumpto_button.grid(column=0, row=4, columnspan=2, sticky="ns", pady=".1c") dialog.columnconfigure(0, weight=1) # Only the pane with the Kconfig tree and description grows vertically dialog.rowconfigure(3, weight=1) # See the menuconfig() function _root.update_idletasks() dialog.geometry(dialog.geometry()) # The dialog must be visible before we can grab the input dialog.wait_visibility() dialog.grab_set() tree.bind("", jump_to_selected) tree.bind("", jump_to_selected) tree.bind("", jump_to_selected) # add=True to avoid overriding the description text update tree.bind("<>", tree_select, add=True) dialog.bind("", lambda _: dialog.destroy()) # Wait for the user to be done with the dialog _root.wait_window(dialog) _jump_to_tree = None _tree.focus_set() def _update_jump_to_matches(msglabel, search_string): # Searches for nodes matching the search string and updates # _jump_to_matches. Puts a message in 'msglabel' if there are no matches, # or regex errors. global _jump_to_matches _jump_to_tree.selection_set(()) try: # We could use re.IGNORECASE here instead of lower(), but this is # faster for regexes like '.*debug$' (though the '.*' is redundant # there). Those probably have bad interactions with re.search(), which # matches anywhere in the string. regex_searches = [re.compile(regex).search for regex in search_string.lower().split()] except re.error as e: msg = "Bad regular expression" # re.error.msg was added in Python 3.5 if hasattr(e, "msg"): msg += ": " + e.msg msglabel["text"] = msg # Clear tree _jump_to_tree.set_children("") return _jump_to_matches = [] add_match = _jump_to_matches.append for node in _sorted_sc_nodes(): # Symbol/choice sc = node.item for search in regex_searches: # Both the name and the prompt might be missing, since # we're searching both symbols and choices # Does the regex match either the symbol name or the # prompt (if any)? if not (sc.name and search(sc.name.lower()) or node.prompt and search(node.prompt[0].lower())): # Give up on the first regex that doesn't match, to # speed things up a bit when multiple regexes are # entered break else: add_match(node) # Search menus and comments for node in _sorted_menu_comment_nodes(): for search in regex_searches: if not search(node.prompt[0].lower()): break else: add_match(node) msglabel["text"] = "" if _jump_to_matches else "No matches" _update_jump_to_display() if _jump_to_matches: item = id(_jump_to_matches[0]) _jump_to_tree.selection_set(item) _jump_to_tree.focus(item) def _update_jump_to_display(): # Updates the images and text for the items in _jump_to_matches, and sets # them as the items of _jump_to_tree # Micro-optimize a bit item = _jump_to_tree.item id_ = id node_str = _node_str img_tag = _img_tag visible = _visible for node in _jump_to_matches: item(id_(node), text=node_str(node), tags=img_tag(node) if visible(node) else img_tag(node) + " invisible") _jump_to_tree.set_children("", *map(id, _jump_to_matches)) def _jump_to(node): # Jumps directly to 'node' and selects it if _single_menu: _enter_menu(_parent_menu(node)) else: _load_parents(node) _select(_tree, id(node)) # Obscure Python: We never pass a value for cached_nodes, and it keeps pointing # to the same list. This avoids a global. def _sorted_sc_nodes(cached_nodes=[]): # Returns a sorted list of symbol and choice nodes to search. The symbol # nodes appear first, sorted by name, and then the choice nodes, sorted by # prompt and (secondarily) name. if not cached_nodes: # Add symbol nodes for sym in sorted(_kconf.unique_defined_syms, key=lambda sym: sym.name): # += is in-place for lists cached_nodes += sym.nodes # Add choice nodes choices = sorted(_kconf.unique_choices, key=lambda choice: choice.name or "") cached_nodes += sorted( [node for choice in choices for node in choice.nodes], key=lambda node: node.prompt[0] if node.prompt else "") return cached_nodes def _sorted_menu_comment_nodes(cached_nodes=[]): # Returns a list of menu and comment nodes to search, sorted by prompt, # with the menus first if not cached_nodes: def prompt_text(mc): return mc.prompt[0] cached_nodes += sorted(_kconf.menus, key=prompt_text) cached_nodes += sorted(_kconf.comments, key=prompt_text) return cached_nodes def _load_parents(node): # Menus are lazily populated as they're opened in full-tree mode, but # jumping to an item needs its parent menus to be populated. This function # populates 'node's parents. # Get all parents leading up to 'node', sorted with the root first parents = [] cur = node.parent while cur is not _kconf.top_node: parents.append(cur) cur = cur.parent parents.reverse() for i, parent in enumerate(parents): if not _tree.item(id(parent), "open"): # Found a closed menu. Populate it and all the remaining menus # leading up to 'node'. for parent in parents[i:]: # We only need to populate "real" menus/choices. Implicit menus # are populated when their parents menus are entered. if not isinstance(parent.item, Symbol): _build_full_tree(parent) return def _parent_menu(node): # Returns the menu node of the menu that contains 'node'. In addition to # proper 'menu's, this might also be a 'menuconfig' symbol or a 'choice'. # "Menu" here means a menu in the interface. menu = node.parent while not menu.is_menuconfig: menu = menu.parent return menu def _trace_write(var, fn): # Makes fn() be called whenever the Tkinter Variable 'var' changes value # trace_variable() is deprecated according to the docstring, # which recommends trace_add() if hasattr(var, "trace_add"): var.trace_add("write", fn) else: var.trace_variable("w", fn) def _info_str(node): # Returns information about the menu node 'node' as a string. # # The helper functions are responsible for adding newlines. This allows # them to return "" if they don't want to add any output. if isinstance(node.item, Symbol): sym = node.item return ( _name_info(sym) + _help_info(sym) + _direct_dep_info(sym) + _defaults_info(sym) + _select_imply_info(sym) + _kconfig_def_info(sym) ) if isinstance(node.item, Choice): choice = node.item return ( _name_info(choice) + _help_info(choice) + 'Mode: {}\n\n'.format(choice.str_value) + _choice_syms_info(choice) + _direct_dep_info(choice) + _defaults_info(choice) + _kconfig_def_info(choice) ) # node.item in (MENU, COMMENT) return _kconfig_def_info(node) def _name_info(sc): # Returns a string with the name of the symbol/choice. Choices are shown as # . return (sc.name if sc.name else standard_sc_expr_str(sc)) + "\n\n" def _value_info(sym): # Returns a string showing 'sym's value # Only put quotes around the value for string symbols return "Value: {}\n".format( '"{}"'.format(sym.str_value) if sym.orig_type == STRING else sym.str_value) def _choice_syms_info(choice): # Returns a string listing the choice symbols in 'choice'. Adds # "(selected)" next to the selected one. s = "Choice symbols:\n" for sym in choice.syms: s += " - " + sym.name if sym is choice.selection: s += " (selected)" s += "\n" return s + "\n" def _help_info(sc): # Returns a string with the help text(s) of 'sc' (Symbol or Choice). # Symbols and choices defined in multiple locations can have multiple help # texts. s = "" for node in sc.nodes: if node.help is not None: s += node.help + "\n\n" return s def _direct_dep_info(sc): # Returns a string describing the direct dependencies of 'sc' (Symbol or # Choice). The direct dependencies are the OR of the dependencies from each # definition location. The dependencies at each definition location come # from 'depends on' and dependencies inherited from parent items. return "" if sc.direct_dep is _kconf.y else \ 'Direct dependencies (={}):\n{}\n' \ .format(TRI_TO_STR[expr_value(sc.direct_dep)], _split_expr_info(sc.direct_dep, 2)) def _defaults_info(sc): # Returns a string describing the defaults of 'sc' (Symbol or Choice) if not sc.defaults: return "" s = "Default" if len(sc.defaults) > 1: s += "s" s += ":\n" for val, cond in sc.orig_defaults: s += " - " if isinstance(sc, Symbol): s += _expr_str(val) # Skip the tristate value hint if the expression is just a single # symbol. _expr_str() already shows its value as a string. # # This also avoids showing the tristate value for string/int/hex # defaults, which wouldn't make any sense. if isinstance(val, tuple): s += ' (={})'.format(TRI_TO_STR[expr_value(val)]) else: # Don't print the value next to the symbol name for choice # defaults, as it looks a bit confusing s += val.name s += "\n" if cond is not _kconf.y: s += " Condition (={}):\n{}" \ .format(TRI_TO_STR[expr_value(cond)], _split_expr_info(cond, 4)) return s + "\n" def _split_expr_info(expr, indent): # Returns a string with 'expr' split into its top-level && or || operands, # with one operand per line, together with the operand's value. This is # usually enough to get something readable for long expressions. A fancier # recursive thingy would be possible too. # # indent: # Number of leading spaces to add before the split expression. if len(split_expr(expr, AND)) > 1: split_op = AND op_str = "&&" else: split_op = OR op_str = "||" s = "" for i, term in enumerate(split_expr(expr, split_op)): s += "{}{} {}".format(indent*" ", " " if i == 0 else op_str, _expr_str(term)) # Don't bother showing the value hint if the expression is just a # single symbol. _expr_str() already shows its value. if isinstance(term, tuple): s += " (={})".format(TRI_TO_STR[expr_value(term)]) s += "\n" return s def _select_imply_info(sym): # Returns a string with information about which symbols 'select' or 'imply' # 'sym'. The selecting/implying symbols are grouped according to which # value they select/imply 'sym' to (n/m/y). def sis(expr, val, title): # sis = selects/implies sis = [si for si in split_expr(expr, OR) if expr_value(si) == val] if not sis: return "" res = title for si in sis: res += " - {}\n".format(split_expr(si, AND)[0].name) return res + "\n" s = "" if sym.rev_dep is not _kconf.n: s += sis(sym.rev_dep, 2, "Symbols currently y-selecting this symbol:\n") s += sis(sym.rev_dep, 1, "Symbols currently m-selecting this symbol:\n") s += sis(sym.rev_dep, 0, "Symbols currently n-selecting this symbol (no effect):\n") if sym.weak_rev_dep is not _kconf.n: s += sis(sym.weak_rev_dep, 2, "Symbols currently y-implying this symbol:\n") s += sis(sym.weak_rev_dep, 1, "Symbols currently m-implying this symbol:\n") s += sis(sym.weak_rev_dep, 0, "Symbols currently n-implying this symbol (no effect):\n") return s def _kconfig_def_info(item): # Returns a string with the definition of 'item' in Kconfig syntax, # together with the definition location(s) and their include and menu paths nodes = [item] if isinstance(item, MenuNode) else item.nodes s = "Kconfig definition{}, with parent deps. propagated to 'depends on'\n" \ .format("s" if len(nodes) > 1 else "") s += (len(s) - 1)*"=" for node in nodes: s += "\n\n" \ "At {}:{}\n" \ "{}" \ "Menu path: {}\n\n" \ "{}" \ .format(node.filename, node.linenr, _include_path_info(node), _menu_path_info(node), node.custom_str(_name_and_val_str)) return s def _include_path_info(node): if not node.include_path: # In the top-level Kconfig file return "" return "Included via {}\n".format( " -> ".join("{}:{}".format(filename, linenr) for filename, linenr in node.include_path)) def _menu_path_info(node): # Returns a string describing the menu path leading up to 'node' path = "" while node.parent is not _kconf.top_node: node = node.parent # Promptless choices might appear among the parents. Use # standard_sc_expr_str() for them, so that they show up as # ''. path = " -> " + (node.prompt[0] if node.prompt else standard_sc_expr_str(node.item)) + path return "(Top)" + path def _name_and_val_str(sc): # Custom symbol/choice printer that shows symbol values after symbols # Show the values of non-constant (non-quoted) symbols that don't look like # numbers. Things like 123 are actually symbol references, and only work as # expected due to undefined symbols getting their name as their value. # Showing the symbol value for those isn't helpful though. if isinstance(sc, Symbol) and not sc.is_constant and not _is_num(sc.name): if not sc.nodes: # Undefined symbol reference return "{}(undefined/n)".format(sc.name) return '{}(={})'.format(sc.name, sc.str_value) # For other items, use the standard format return standard_sc_expr_str(sc) def _expr_str(expr): # Custom expression printer that shows symbol values return expr_str(expr, _name_and_val_str) def _is_num(name): # Heuristic to see if a symbol name looks like a number, for nicer output # when printing expressions. Things like 16 are actually symbol names, only # they get their name as their value when the symbol is undefined. try: int(name) except ValueError: if not name.startswith(("0x", "0X")): return False try: int(name, 16) except ValueError: return False return True if __name__ == "__main__": _main() Kconfiglib-14.1.0/kconfiglib.py000066400000000000000000007732441361474255700163640ustar00rootroot00000000000000# Copyright (c) 2011-2019, Ulf Magnusson # SPDX-License-Identifier: ISC """ Overview ======== Kconfiglib is a Python 2/3 library for scripting and extracting information from Kconfig (https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt) configuration systems. See the homepage at https://github.com/ulfalizer/Kconfiglib for a longer overview. Since Kconfiglib 12.0.0, the library version is available in kconfiglib.VERSION, which is a (, , ) tuple, e.g. (12, 0, 0). Using Kconfiglib on the Linux kernel with the Makefile targets ============================================================== For the Linux kernel, a handy interface is provided by the scripts/kconfig/Makefile patch, which can be applied with either 'git am' or the 'patch' utility: $ wget -qO- https://raw.githubusercontent.com/ulfalizer/Kconfiglib/master/makefile.patch | git am $ wget -qO- https://raw.githubusercontent.com/ulfalizer/Kconfiglib/master/makefile.patch | patch -p1 Warning: Not passing -p1 to patch will cause the wrong file to be patched. Please tell me if the patch does not apply. It should be trivial to apply manually, as it's just a block of text that needs to be inserted near the other *conf: targets in scripts/kconfig/Makefile. Look further down for a motivation for the Makefile patch and for instructions on how you can use Kconfiglib without it. If you do not wish to install Kconfiglib via pip, the Makefile patch is set up so that you can also just clone Kconfiglib into the kernel root: $ git clone git://github.com/ulfalizer/Kconfiglib.git $ git am Kconfiglib/makefile.patch (or 'patch -p1 < Kconfiglib/makefile.patch') Warning: The directory name Kconfiglib/ is significant in this case, because it's added to PYTHONPATH by the new targets in makefile.patch. The targets added by the Makefile patch are described in the following sections. make kmenuconfig ---------------- This target runs the curses menuconfig interface with Python 3. As of Kconfiglib 12.2.0, both Python 2 and Python 3 are supported (previously, only Python 3 was supported, so this was a backport). make guiconfig -------------- This target runs the Tkinter menuconfig interface. Both Python 2 and Python 3 are supported. To change the Python interpreter used, pass PYTHONCMD= to 'make'. The default is 'python'. make [ARCH=] iscriptconfig -------------------------------- This target gives an interactive Python prompt where a Kconfig instance has been preloaded and is available in 'kconf'. To change the Python interpreter used, pass PYTHONCMD= to 'make'. The default is 'python'. To get a feel for the API, try evaluating and printing the symbols in kconf.defined_syms, and explore the MenuNode menu tree starting at kconf.top_node by following 'next' and 'list' pointers. The item contained in a menu node is found in MenuNode.item (note that this can be one of the constants kconfiglib.MENU and kconfiglib.COMMENT), and all symbols and choices have a 'nodes' attribute containing their menu nodes (usually only one). Printing a menu node will print its item, in Kconfig format. If you want to look up a symbol by name, use the kconf.syms dictionary. make scriptconfig SCRIPT=