mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/doc_kconfig_default_and_range' into 'master'
tools: show defaults and ranges in generated kconfig documentation Closes IDF-1816 and IDF-1679 See merge request espressif/esp-idf!10875
This commit is contained in:
commit
1f6d2b0257
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
child_list = []
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
80
tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py
Executable file
80
tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py
Executable file
@ -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()
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user