From c31dab51a03b90a93d29577da6a5e9b1155414c0 Mon Sep 17 00:00:00 2001 From: Roland Dobai Date: Wed, 2 Sep 2020 16:17:34 +0200 Subject: [PATCH] Show defaults and ranges in generated kconfig documentation --- tools/ci/config/host-test.yml | 1 + tools/ci/executable-list.txt | 1 + tools/kconfig_new/gen_kconfig_doc.py | 111 ++++++++++++++++++ .../kconfig_new/test/gen_kconfig_doc/Kconfig | 32 +++++ .../test/gen_kconfig_doc/Kconfig.chipa | 3 + .../test/gen_kconfig_doc/test_kconfig_out.py | 80 +++++++++++++ .../gen_kconfig_doc/test_target_visibility.py | 2 +- 7 files changed, 229 insertions(+), 1 deletion(-) create mode 100755 tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py diff --git a/tools/ci/config/host-test.yml b/tools/ci/config/host-test.yml index d5f3b6b219..e6d03dccb1 100644 --- a/tools/ci/config/host-test.yml +++ b/tools/ci/config/host-test.yml @@ -142,6 +142,7 @@ test_gen_kconfig_doc: script: - cd tools/kconfig_new/test/gen_kconfig_doc/ - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_target_visibility.py + - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_kconfig_out.py test_confgen: extends: .host_test_template diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt index 456a9d2958..5cea2cbb28 100644 --- a/tools/ci/executable-list.txt +++ b/tools/ci/executable-list.txt @@ -85,6 +85,7 @@ tools/kconfig_new/confgen.py tools/kconfig_new/confserver.py tools/kconfig_new/test/confgen/test_confgen.py tools/kconfig_new/test/confserver/test_confserver.py +tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py tools/ldgen/ldgen.py tools/ldgen/test/test_fragments.py diff --git a/tools/kconfig_new/gen_kconfig_doc.py b/tools/kconfig_new/gen_kconfig_doc.py index df1920e022..63340866ad 100644 --- a/tools/kconfig_new/gen_kconfig_doc.py +++ b/tools/kconfig_new/gen_kconfig_doc.py @@ -214,6 +214,67 @@ def format_rest_text(text, indent): return text +def _minimize_expr(expr, visibility): + def expr_nodes_invisible(e): + return hasattr(e, 'nodes') and len(e.nodes) > 0 and all(not visibility.visible(i) for i in e.nodes) + + if isinstance(expr, tuple): + if expr[0] == kconfiglib.NOT: + new_expr = _minimize_expr(expr[1], visibility) + return kconfiglib.Kconfig.y if new_expr == kconfiglib.Kconfig.n else new_expr + else: + new_expr1 = _minimize_expr(expr[1], visibility) + new_expr2 = _minimize_expr(expr[2], visibility) + if expr[0] == kconfiglib.AND: + if new_expr1 == kconfiglib.Kconfig.n or new_expr2 == kconfiglib.Kconfig.n: + return kconfiglib.Kconfig.n + if new_expr1 == kconfiglib.Kconfig.y: + return new_expr2 + if new_expr2 == kconfiglib.Kconfig.y: + return new_expr1 + elif expr[0] == kconfiglib.OR: + if new_expr1 == kconfiglib.Kconfig.y or new_expr2 == kconfiglib.Kconfig.y: + return kconfiglib.Kconfig.y + if new_expr1 == kconfiglib.Kconfig.n: + return new_expr2 + if new_expr2 == kconfiglib.Kconfig.n: + return new_expr1 + elif expr[0] == kconfiglib.EQUAL: + if not isinstance(new_expr1, type(new_expr2)): + return kconfiglib.Kconfig.n + if new_expr1 == new_expr2: + return kconfiglib.Kconfig.y + elif expr[0] == kconfiglib.UNEQUAL: + if not isinstance(new_expr1, type(new_expr2)): + return kconfiglib.Kconfig.y + if new_expr1 != new_expr2: + return kconfiglib.Kconfig.n + else: # <, <=, >, >= + if not isinstance(new_expr1, type(new_expr2)): + return kconfiglib.Kconfig.n # e.g "True < 2" + + if expr_nodes_invisible(new_expr1) or expr_nodes_invisible(new_expr2): + return kconfiglib.Kconfig.y if kconfiglib.expr_value(expr) else kconfiglib.Kconfig.n + + return (expr[0], new_expr1, new_expr2) + + if (not kconfiglib.expr_value(expr) and len(expr.config_string) == 0 and expr_nodes_invisible(expr)): + # nodes which are invisible + # len(expr.nodes) > 0 avoids constant symbols without actual node definitions, e.g. integer constants + # len(expr.config_string) == 0 avoids hidden configs which reflects the values of choices + return kconfiglib.Kconfig.n + + if (kconfiglib.expr_value(expr) and len(expr.config_string) > 0 and expr_nodes_invisible(expr)): + # hidden config dependencies which will be written to sdkconfig as enabled ones. + return kconfiglib.Kconfig.y + + if any(node.item.name.startswith(visibility.target_env_var) for node in expr.nodes): + # We know the actual values for IDF_TARGETs + return kconfiglib.Kconfig.y if kconfiglib.expr_value(expr) else kconfiglib.Kconfig.n + + return expr + + def write_menu_item(f, node, visibility): def is_choice(node): """ Skip choice nodes, they are handled as part of the parent (see below) """ @@ -269,6 +330,56 @@ def write_menu_item(f, node, visibility): f.write('\n\n') + if isinstance(node.item, kconfiglib.Symbol): + def _expr_str(sc): + if sc.is_constant or not sc.nodes or sc.choice: + return '{}'.format(sc.name) + return ':ref:`%s%s`' % (sc.kconfig.config_prefix, sc.name) + + range_strs = [] + for low, high, cond in node.item.ranges: + cond = _minimize_expr(cond, visibility) + if cond == kconfiglib.Kconfig.n: + continue + if not isinstance(cond, tuple) and cond != kconfiglib.Kconfig.y: + if len(cond.nodes) > 0 and all(not visibility.visible(i) for i in cond.nodes): + if not kconfiglib.expr_value(cond): + continue + range_str = '%s- from %s to %s' % (INDENT * 2, low.str_value, high.str_value) + if cond != kconfiglib.Kconfig.y and not kconfiglib.expr_value(cond): + range_str += ' if %s' % kconfiglib.expr_str(cond, _expr_str) + range_strs.append(range_str) + if len(range_strs) > 0: + f.write('%sRange:\n' % INDENT) + f.write('\n'.join(range_strs)) + f.write('\n\n') + + default_strs = [] + for default, cond in node.item.defaults: + cond = _minimize_expr(cond, visibility) + if cond == kconfiglib.Kconfig.n: + continue + if not isinstance(cond, tuple) and cond != kconfiglib.Kconfig.y: + if len(cond.nodes) > 0 and all(not visibility.visible(i) for i in cond.nodes): + if not kconfiglib.expr_value(cond): + continue + # default.type is mostly UNKNOWN so it cannot be used reliably for detecting the type + d = default.str_value + if d in ['y', 'Y']: + d = 'Yes (enabled)' + elif d in ['n', 'N']: + d = 'No (disabled)' + elif re.search(r'[^0-9a-fA-F]', d): # simple string detection: if it not a valid number + d = '"%s"' % d + default_str = '%s- %s' % (INDENT * 2, d) + if cond != kconfiglib.Kconfig.y and not kconfiglib.expr_value(cond): + default_str += ' if %s' % kconfiglib.expr_str(cond, _expr_str) + default_strs.append(default_str) + if len(default_strs) > 0: + f.write('%sDefault value:\n' % INDENT) + f.write('\n'.join(default_strs)) + f.write('\n\n') + if is_menu: # enumerate links to child items first = True diff --git a/tools/kconfig_new/test/gen_kconfig_doc/Kconfig b/tools/kconfig_new/test/gen_kconfig_doc/Kconfig index 06c1caa094..f4d63ae65a 100644 --- a/tools/kconfig_new/test/gen_kconfig_doc/Kconfig +++ b/tools/kconfig_new/test/gen_kconfig_doc/Kconfig @@ -134,3 +134,35 @@ config CHIPA_FEATURE_FROM_V1 config CHIPA_FEATURE_FROM_V3 depends on CONFIG_FOR_CHIPA && (CHIPA_REV_MIN <= 3) bool "Feature available from version 3" + +config CHIPA_OPTION + int "option with default value depending on the chip version" + depends on IDF_TARGET_CHIPA + default 5 if CHIPA_REV_MIN < 2 + default 4 if CHIPA_VERSION = 2 + default 9 if CHIPA_REV_MIN = 3 + +config COMPILER + string "compiler prefix" + default "ca" if IDF_TARGET_CHIPA + default "cb" if IDF_TARGET_CHIPB + +config BOOL_OPTION + bool "bool option" + default y + +config BOOL_OPTION2 + bool "bool option 2" + default BOOL_OPTION + +config HEX_OPTION + hex "bool option" + default 0xce if IDF_TARGET_CHIPA + default 0xff if IDF_TARGET_CHIPB + range 0xf 0xce if IDF_TARGET_CHIPA + range 0xfe 0xff if IDF_TARGET_CHIPB + +config INT_OPTION + int "int option" + range 1 10 if IDF_TARGET_CHIPA + range 100 200 if IDF_TARGET_CHIPB diff --git a/tools/kconfig_new/test/gen_kconfig_doc/Kconfig.chipa b/tools/kconfig_new/test/gen_kconfig_doc/Kconfig.chipa index 1f6309c0fb..245308abea 100644 --- a/tools/kconfig_new/test/gen_kconfig_doc/Kconfig.chipa +++ b/tools/kconfig_new/test/gen_kconfig_doc/Kconfig.chipa @@ -8,6 +8,9 @@ menu "Menu for CHIPA" config EXT_CONFIG2_FOR_CHIPA_MENU bool "Config for chip A (depend on the visibility of the menu)" + config EXT_CONFIG3_FOR_CHIPA_MENU + int "integer" + default 5 endmenu config EXT_CONFIG3_FOR_CHIPA diff --git a/tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py b/tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py new file mode 100755 index 0000000000..3c3dbc6b6e --- /dev/null +++ b/tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +from __future__ import unicode_literals +import io +import os +import sys +import unittest + +import kconfiglib + +try: + import gen_kconfig_doc +except ImportError: + sys.path.insert(0, os.path.normpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../..'))) + import gen_kconfig_doc + + +class TestDocOutput(unittest.TestCase): + @classmethod + def setUpClass(cls): + os.environ['IDF_TARGET'] = 'chipa' + cls.target = os.environ['IDF_TARGET'] + cls.config = kconfiglib.Kconfig('Kconfig') + cls.visibility = gen_kconfig_doc.ConfigTargetVisibility(cls.config, cls.target) + + def get_config(self, name): + sym = self.config.syms.get(name) + if sym: + return sym.nodes[0] + choice = self.config.named_choices.get(name) + if choice: + return choice.nodes[0] + raise RuntimeError('Unimplemented {}'.format(name)) + + def get_doc_out(self, config_name): + with io.StringIO() if sys.version_info.major == 3 else io.BytesIO() as output: + gen_kconfig_doc.write_menu_item(output, self.get_config(config_name), self.visibility) + output.seek(0) + return output.read() + + def test_simple_default(self): + s = self.get_doc_out('EXT_CONFIG3_FOR_CHIPA_MENU') + self.assertIn('- 5', s) + + def test_multiple_defaults(self): + s = self.get_doc_out('CHIPA_OPTION') + self.assertNotIn('- 5', s) + self.assertIn('- 4 if CHIPA_VERSION = 2', s) + self.assertNotIn('- 9', s) + + def test_string_default(self): + s = self.get_doc_out('COMPILER') + self.assertIn('- ca', s) + self.assertNotIn('- cb', s) + + def test_bool_default(self): + s = self.get_doc_out('BOOL_OPTION') + self.assertIn('- Yes', s) + + def test_bool_default_dependency(self): + s = self.get_doc_out('BOOL_OPTION2') + self.assertIn('- Yes', s) + + def test_hex_default(self): + s = self.get_doc_out('HEX_OPTION') + self.assertIn('- "0xce"', s) + self.assertNotIn('- "0xff"', s) + + def test_hex_range(self): + s = self.get_doc_out('HEX_OPTION') + self.assertIn('- from 0xf to 0xce', s) + self.assertNotIn('- from 0xfe', s) + + def test_int_range(self): + s = self.get_doc_out('INT_OPTION') + self.assertIn('- from 1 to 10', s) + self.assertNotIn('- from 100', s) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py b/tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py index c57e4d4516..6425c17ced 100755 --- a/tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py +++ b/tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py @@ -19,7 +19,7 @@ class ConfigTargetVisibilityTestCase(unittest.TestCase): def _get_config(self, name): sym = self.config.syms.get(name) - if sym: + if sym and len(sym.nodes) > 0: return sym.nodes[0] choice = self.config.named_choices.get(name) if choice: