From 80d8ced2d8cc67ee11186a8bc24eb768ae559387 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Thu, 3 May 2018 16:23:20 +0800 Subject: [PATCH 1/5] chore(kconfig): Update kconfig tools --- tools/kconfig/Makefile | 50 ++++++++++++++++++++++++++++++++++-------- tools/kconfig/zconf.l | 2 +- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/tools/kconfig/Makefile b/tools/kconfig/Makefile index 6675294b..c8a56abd 100644 --- a/tools/kconfig/Makefile +++ b/tools/kconfig/Makefile @@ -2,6 +2,10 @@ # Kernel configuration targets # These targets are used from top-level makefile +# SRCDIR is kconfig source dir, allows for out-of-tree builds +# if building in tree, SRCDIR==build dir +SRCDIR := $(abspath $(dir $(firstword $(MAKEFILE_LIST)))) + PHONY += xconfig gconfig menuconfig config silentoldconfig \ localmodconfig localyesconfig clean @@ -23,6 +27,22 @@ CFLAGS := CPPFLAGS := LDFLAGS := +# Workaround for a bug on Windows if the mingw32 host compilers +# are installed in addition to the MSYS ones. The kconfig tools +# need to be compiled using the MSYS compiler. +# +# See https://github.com/espressif/esp-idf/issues/1296 +ifdef MSYSTEM +ifeq ("$(MSYSTEM)", "MINGW32") +ifeq ("$(CC)", "cc") +CC := /usr/bin/gcc +endif +ifeq ("$(LD)", "ld") +LD := /usr/bin/ld +endif +endif # MING32 +endif # MSYSTEM + default: mconf conf xconfig: qconf @@ -140,13 +160,22 @@ help: @echo ' tinyconfig - Configure the tiniest possible kernel' # lxdialog stuff -check-lxdialog := lxdialog/check-lxdialog.sh +check-lxdialog := $(SRCDIR)/lxdialog/check-lxdialog.sh # Use recursively expanded variables so we do not call gcc unless # we really need to do so. (Do not call gcc as part of make mrproper) CFLAGS += $(shell $(CONFIG_SHELL) $(check-lxdialog) -ccflags) \ -DLOCALE -MD +%.o: $(SRCDIR)/%.c + $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ + +lxdialog/%.o: $(SRCDIR)/lxdialog/%.c + $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ + +%.o: %.c + $(CC) -I $(SRCDIR) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ + # =========================================================================== # Shared Makefile for the various kconfig executables: # conf: Used for defconfig, oldconfig and related targets @@ -183,9 +212,12 @@ clean-files += $(all-objs) $(all-deps) conf mconf # Check that we have the required ncurses stuff installed for lxdialog (menuconfig) PHONY += dochecklxdialog $(addprefix ,$(lxdialog)): dochecklxdialog -dochecklxdialog: +dochecklxdialog: lxdialog $(CONFIG_SHELL) $(check-lxdialog) -check $(CC) $(CFLAGS) $(LOADLIBES_mconf) +lxdialog: + mkdir -p lxdialog + always := dochecklxdialog # Add environment specific flags @@ -292,7 +324,7 @@ gconf.glade.h: gconf.glade gconf.glade -mconf: $(mconf-objs) +mconf: lxdialog $(mconf-objs) $(CC) -o $@ $(mconf-objs) $(LOADLIBES_mconf) conf: $(conf-objs) @@ -300,15 +332,15 @@ conf: $(conf-objs) zconf.tab.c: zconf.lex.c -zconf.lex.c: zconf.l - flex -L -P zconf -o zconf.lex.c zconf.l +zconf.lex.c: $(SRCDIR)/zconf.l + flex -L -P zconf -o zconf.lex.c $< -zconf.hash.c: zconf.gperf +zconf.hash.c: $(SRCDIR)/zconf.gperf # strip CRs on Windows systems where gperf will otherwise barf on them - sed -E "s/\\x0D$$//" zconf.gperf | gperf -t --output-file zconf.hash.c -a -C -E -g -k '1,3,$$' -p -t + sed -E "s/\\x0D$$//" $< | gperf -t --output-file zconf.hash.c -a -C -E -g -k '1,3,$$' -p -t -zconf.tab.c: zconf.y - bison -t -l -p zconf -o zconf.tab.c zconf.y +zconf.tab.c: $(SRCDIR)/zconf.y + bison -t -l -p zconf -o zconf.tab.c $< clean: rm -f $(clean-files) diff --git a/tools/kconfig/zconf.l b/tools/kconfig/zconf.l index f0b65608..8fd75491 100644 --- a/tools/kconfig/zconf.l +++ b/tools/kconfig/zconf.l @@ -1,5 +1,5 @@ %option nostdinit noyywrap never-interactive full ecs -%option 8bit nodefault perf-report perf-report +%option 8bit perf-report perf-report %option noinput %x COMMAND HELP STRING PARAM %{ From 9e3b31f644e15a8d6f193639e2415f1b1fbbb20c Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Thu, 3 May 2018 16:24:05 +0800 Subject: [PATCH 2/5] chore(kconfig_new): Add kconfig_new tools --- tools/kconfig_new/confgen.py | 206 ++ tools/kconfig_new/gen_kconfig_doc.py | 137 + tools/kconfig_new/kconfiglib.py | 4317 ++++++++++++++++++++++++++ 3 files changed, 4660 insertions(+) create mode 100755 tools/kconfig_new/confgen.py create mode 100644 tools/kconfig_new/gen_kconfig_doc.py create mode 100644 tools/kconfig_new/kconfiglib.py diff --git a/tools/kconfig_new/confgen.py b/tools/kconfig_new/confgen.py new file mode 100755 index 00000000..13c74150 --- /dev/null +++ b/tools/kconfig_new/confgen.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python +# +# Command line tool to take in ESP-IDF sdkconfig files with project +# settings and output data in multiple formats (update config, generate +# header file, generate .cmake include file, documentation, etc). +# +# Used internally by the ESP-IDF build system. But designed to be +# non-IDF-specific. +# +# Copyright 2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import argparse +import sys +import os +import os.path +import tempfile +import json + +import gen_kconfig_doc +import kconfiglib + +__version__ = "0.1" + +def main(): + parser = argparse.ArgumentParser(description='confgen.py v%s - Config Generation Tool' % __version__, prog=os.path.basename(sys.argv[0])) + + parser.add_argument('--config', + help='Project configuration settings', + nargs='?', + default=None) + + parser.add_argument('--defaults', + help='Optional project defaults file, used if --config file doesn\'t exist', + nargs='?', + default=None) + + parser.add_argument('--create-config-if-missing', + help='If set, a new config file will be saved if the old one is not found', + action='store_true') + + parser.add_argument('--kconfig', + help='KConfig file with config item definitions', + required=True) + + parser.add_argument('--output', nargs=2, action='append', + help='Write output file (format and output filename)', + metavar=('FORMAT', 'FILENAME'), + default=[]) + + parser.add_argument('--env', action='append', default=[], + help='Environment to set when evaluating the config file', metavar='NAME=VAL') + + args = parser.parse_args() + + for fmt, filename in args.output: + if not fmt in OUTPUT_FORMATS.keys(): + print("Format '%s' not recognised. Known formats: %s" % (fmt, OUTPUT_FORMATS)) + sys.exit(1) + + try: + args.env = [ (name,value) for (name,value) in ( e.split("=",1) for e in args.env) ] + except ValueError: + print("--env arguments must each contain =. To unset an environment variable, use 'ENV='") + sys.exit(1) + + for name, value in args.env: + os.environ[name] = value + + config = kconfiglib.Kconfig(args.kconfig) + + if args.defaults is not None: + # always load defaults first, so any items which are not defined in that config + # will have the default defined in the defaults file + if not os.path.exists(args.defaults): + raise RuntimeError("Defaults file not found: %s" % args.defaults) + config.load_config(args.defaults) + + if args.config is not None: + if os.path.exists(args.config): + config.load_config(args.config) + elif args.create_config_if_missing: + print("Creating config file %s..." % args.config) + config.write_config(args.config) + elif args.default is None: + raise RuntimeError("Config file not found: %s" % args.config) + + for output_type, filename in args.output: + temp_file = tempfile.mktemp(prefix="confgen_tmp") + try: + output_function = OUTPUT_FORMATS[output_type] + output_function(config, temp_file) + update_if_changed(temp_file, filename) + finally: + try: + os.remove(temp_file) + except OSError: + pass + + +def write_config(config, filename): + CONFIG_HEADING = """# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) Project Configuration +# +""" + config.write_config(filename, header=CONFIG_HEADING) + +def write_header(config, filename): + CONFIG_HEADING = """/* + * Automatically generated file. DO NOT EDIT. + * Espressif IoT Development Framework (ESP-IDF) Configuration Header + */ +#pragma once +""" + config.write_autoconf(filename, header=CONFIG_HEADING) + +def write_cmake(config, filename): + with open(filename, "w") as f: + write = f.write + prefix = config.config_prefix + + write("""# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) Configuration cmake include file +# +""") + def write_node(node): + sym = node.item + if not isinstance(sym, kconfiglib.Symbol): + return + + # Note: str_value calculates _write_to_conf, due to + # internal magic in kconfiglib... + val = sym.str_value + if sym._write_to_conf: + if sym.orig_type in (kconfiglib.BOOL, kconfiglib.TRISTATE) and val == "n": + val = "" # write unset values as empty variables + write("set({}{} \"{}\")\n".format( + prefix, sym.name, val)) + config.walk_menu(write_node) + +def write_json(config, filename): + config_dict = {} + + def write_node(node): + sym = node.item + if not isinstance(sym, kconfiglib.Symbol): + return + + val = sym.str_value # this calculates _write_to_conf, due to kconfiglib magic + if sym._write_to_conf: + if sym.type in [ kconfiglib.BOOL, kconfiglib.TRISTATE ]: + val = (val != "n") + elif sym.type == kconfiglib.HEX: + val = int(val, 16) + elif sym.type == kconfiglib.INT: + val = int(val) + config_dict[sym.name] = val + config.walk_menu(write_node) + with open(filename, "w") as f: + json.dump(config_dict, f, indent=4, sort_keys=True) + +def update_if_changed(source, destination): + with open(source, "r") as f: + source_contents = f.read() + if os.path.exists(destination): + with open(destination, "r") as f: + dest_contents = f.read() + if source_contents == dest_contents: + return # nothing to update + + with open(destination, "w") as f: + f.write(source_contents) + + +OUTPUT_FORMATS = { + "config" : write_config, + "header" : write_header, + "cmake" : write_cmake, + "docs" : gen_kconfig_doc.write_docs, + "json" : write_json, + } + +class FatalError(RuntimeError): + """ + Class for runtime errors (not caused by bugs but by user input). + """ + pass + +if __name__ == '__main__': + try: + main() + except FatalError as e: + print("A fatal error occurred: %s" % e) + sys.exit(2) diff --git a/tools/kconfig_new/gen_kconfig_doc.py b/tools/kconfig_new/gen_kconfig_doc.py new file mode 100644 index 00000000..e5e6968f --- /dev/null +++ b/tools/kconfig_new/gen_kconfig_doc.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# gen_kconfig_doc - confgen.py support for generating ReST markup documentation +# +# For each option in the loaded Kconfig (e.g. 'FOO'), CONFIG_FOO link target is +# generated, allowing options to be referenced in other documents +# (using :ref:`CONFIG_FOO`) +# +# Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import kconfiglib + +# Indentation to be used in the generated file +INDENT = ' ' + +# Characters used when underlining section heading +HEADING_SYMBOLS = '#*=-^"+' + +# Keep the heading level in sync with api-reference/kconfig.rst +INITIAL_HEADING_LEVEL = 3 +MAX_HEADING_LEVEL = len(HEADING_SYMBOLS)-1 + +def write_docs(config, filename): + """ Note: writing .rst documentation ignores the current value + of any items. ie the --config option can be ignored. + (However at time of writing it still needs to be set to something...) """ + with open(filename, "w") as f: + config.walk_menu(lambda node: write_menu_item(f, node)) + + +def get_breadcrumbs(node): + # this is a bit wasteful as it recalculates each time, but still... + result = [] + node = node.parent + while node.parent: + if node.prompt: + result = [ node.prompt[0] ] + result + node = node.parent + return " > ".join(result) + +def get_heading_level(node): + # bit wasteful also + result = INITIAL_HEADING_LEVEL + node = node.parent + while node.parent: + result += 1 + if result == MAX_HEADING_LEVEL: + return MAX_HEADING_LEVEL + node = node.parent + return result + +def format_rest_text(text, indent): + # Format an indented text block for use with ReST + text = indent + text.replace('\n', '\n' + indent) + # Escape some characters which are inline formatting in ReST + text = text.replace("*", "\\*") + text = text.replace("_", "\\_") + text += '\n' + return text + +def write_menu_item(f, node): + if not node.prompt: + return # Don't do anything for invisible menu items + + if isinstance(node.parent.item, kconfiglib.Choice): + return # Skip choice nodes, they are handled as part of the parent (see below) + + try: + name = node.item.name + except AttributeError: + name = None + + try: + is_menu = node.item == kconfiglib.MENU or node.is_menuconfig + except AttributeError: + is_menu = False # not all MenuNodes have is_menuconfig for some reason + + ## Heading + if name: + title = name + # add link target so we can use :ref:`CONFIG_FOO` + f.write('.. _CONFIG_%s:\n\n' % name) + else: + title = node.prompt[0] + + # if no symbol name, use the prompt as the heading + if True or is_menu: + f.write('%s\n' % title) + f.write(HEADING_SYMBOLS[get_heading_level(node)] * len(title)) + f.write('\n\n') + else: + f.write('**%s**\n\n\n' % title) + + if name: + f.write('%s%s\n\n' % (INDENT, node.prompt[0])) + f.write('%s:emphasis:`Found in: %s`\n\n' % (INDENT, get_breadcrumbs(node))) + + try: + if node.help: + # Help text normally contains newlines, but spaces at the beginning of + # each line are stripped by kconfiglib. We need to re-indent the text + # to produce valid ReST. + f.write(format_rest_text(node.help, INDENT)) + except AttributeError: + pass # No help + + if isinstance(node.item, kconfiglib.Choice): + f.write('%sAvailable options:\n' % INDENT) + choice_node = node.list + while choice_node: + # Format available options as a list + f.write('%s- %-20s (%s)\n' % (INDENT * 2, choice_node.prompt[0], choice_node.item.name)) + if choice_node.help: + HELP_INDENT = INDENT * 2 + fmt_help = format_rest_text(choice_node.help, ' ' + HELP_INDENT) + f.write('%s \n%s\n' % (HELP_INDENT, fmt_help)) + choice_node = choice_node.next + + f.write('\n\n') + + +if __name__ == '__main__': + print("Run this via 'confgen.py --output doc FILENAME'") + diff --git a/tools/kconfig_new/kconfiglib.py b/tools/kconfig_new/kconfiglib.py new file mode 100644 index 00000000..527c5a55 --- /dev/null +++ b/tools/kconfig_new/kconfiglib.py @@ -0,0 +1,4317 @@ +# Copyright (c) 2011-2017, Ulf Magnusson +# Modifications (c) 2018 Espressif Systems +# SPDX-License-Identifier: ISC +# +# ******* IMPORTANT ********** +# +# This is kconfiglib 2.1.0 with some modifications to match the behaviour +# of the ESP-IDF kconfig: +# +# - 'source' nows uses wordexp(3) behaviour to allow source-ing multiple +# files at once, and to expand environment variables directly in the source +# command (without them having to be set as properties in the Kconfig file) +# +# - Added walk_menu() function and refactored to use this internally. +# +# - BOOL & TRISTATE items are allowed to have blank values in .config +# (equivalent to n, this is backwards compatibility with old IDF conf.c) +# +""" +Overview +======== + +Kconfiglib is a Python 2/3 library for scripting and extracting information +from Kconfig configuration systems. It can be used for the following, among +other things: + + - Programmatically get and set symbol values + + allnoconfig.py and allyesconfig.py examples are provided, automatically + verified to produce identical output to the standard 'make allnoconfig' and + 'make allyesconfig'. + + - Read and write .config files + + The generated .config 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. + + - Inspect symbols + + Printing a symbol gives output which could be fed back into a Kconfig parser + to redefine it***. The printing function (__str__()) is implemented with + public APIs, meaning you can fetch just whatever information you need as + well. + + A helpful __repr__() is implemented on all objects too, also implemented + with public APIs. + + ***Choice symbols get their parent choice as a dependency, which shows up as + e.g. 'prompt "choice symbol" if ' when printing the symbol. This + could easily be worked around if 100% reparsable output is needed. + + - 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 the menuconfig.py example. + + +Here are some other features: + + - Single-file implementation + + The entire library is contained in this file. + + - Runs unmodified under both Python 2 and Python 3 + + The code mostly uses basic Python features and has no third-party + dependencies. The most advanced things used are probably @property and + __slots__. + + - Robust and highly compatible with the standard Kconfig C 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. + + - Not horribly slow despite being a pure Python implementation + + The allyesconfig.py example currently runs in about 1.6 seconds on a Core i7 + 2600K (with a warm file cache), where half a second is overhead from 'make + scriptconfig' (see below). + + 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. + + - Internals that (mostly) mirror the C implementation + + While being simpler to understand. + + +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. Apply it 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. + +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. + +Look further down for a motivation for the Makefile patch and for instructions +on how you can use Kconfiglib without it. + +The Makefile patch adds the following targets: + + +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 MENU and 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=