Skip to content

Commit 1f6d2b0

Browse files
committed
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
2 parents dc73031 + c31dab5 commit 1f6d2b0

File tree

7 files changed

+229
-1
lines changed

7 files changed

+229
-1
lines changed

tools/ci/config/host-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ test_gen_kconfig_doc:
142142
script:
143143
- cd tools/kconfig_new/test/gen_kconfig_doc/
144144
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_target_visibility.py
145+
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_kconfig_out.py
145146

146147
test_confgen:
147148
extends: .host_test_template

tools/ci/executable-list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ tools/kconfig_new/confgen.py
8585
tools/kconfig_new/confserver.py
8686
tools/kconfig_new/test/confgen/test_confgen.py
8787
tools/kconfig_new/test/confserver/test_confserver.py
88+
tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py
8889
tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py
8990
tools/ldgen/ldgen.py
9091
tools/ldgen/test/test_fragments.py

tools/kconfig_new/gen_kconfig_doc.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,67 @@ def format_rest_text(text, indent):
214214
return text
215215

216216

217+
def _minimize_expr(expr, visibility):
218+
def expr_nodes_invisible(e):
219+
return hasattr(e, 'nodes') and len(e.nodes) > 0 and all(not visibility.visible(i) for i in e.nodes)
220+
221+
if isinstance(expr, tuple):
222+
if expr[0] == kconfiglib.NOT:
223+
new_expr = _minimize_expr(expr[1], visibility)
224+
return kconfiglib.Kconfig.y if new_expr == kconfiglib.Kconfig.n else new_expr
225+
else:
226+
new_expr1 = _minimize_expr(expr[1], visibility)
227+
new_expr2 = _minimize_expr(expr[2], visibility)
228+
if expr[0] == kconfiglib.AND:
229+
if new_expr1 == kconfiglib.Kconfig.n or new_expr2 == kconfiglib.Kconfig.n:
230+
return kconfiglib.Kconfig.n
231+
if new_expr1 == kconfiglib.Kconfig.y:
232+
return new_expr2
233+
if new_expr2 == kconfiglib.Kconfig.y:
234+
return new_expr1
235+
elif expr[0] == kconfiglib.OR:
236+
if new_expr1 == kconfiglib.Kconfig.y or new_expr2 == kconfiglib.Kconfig.y:
237+
return kconfiglib.Kconfig.y
238+
if new_expr1 == kconfiglib.Kconfig.n:
239+
return new_expr2
240+
if new_expr2 == kconfiglib.Kconfig.n:
241+
return new_expr1
242+
elif expr[0] == kconfiglib.EQUAL:
243+
if not isinstance(new_expr1, type(new_expr2)):
244+
return kconfiglib.Kconfig.n
245+
if new_expr1 == new_expr2:
246+
return kconfiglib.Kconfig.y
247+
elif expr[0] == kconfiglib.UNEQUAL:
248+
if not isinstance(new_expr1, type(new_expr2)):
249+
return kconfiglib.Kconfig.y
250+
if new_expr1 != new_expr2:
251+
return kconfiglib.Kconfig.n
252+
else: # <, <=, >, >=
253+
if not isinstance(new_expr1, type(new_expr2)):
254+
return kconfiglib.Kconfig.n # e.g "True < 2"
255+
256+
if expr_nodes_invisible(new_expr1) or expr_nodes_invisible(new_expr2):
257+
return kconfiglib.Kconfig.y if kconfiglib.expr_value(expr) else kconfiglib.Kconfig.n
258+
259+
return (expr[0], new_expr1, new_expr2)
260+
261+
if (not kconfiglib.expr_value(expr) and len(expr.config_string) == 0 and expr_nodes_invisible(expr)):
262+
# nodes which are invisible
263+
# len(expr.nodes) > 0 avoids constant symbols without actual node definitions, e.g. integer constants
264+
# len(expr.config_string) == 0 avoids hidden configs which reflects the values of choices
265+
return kconfiglib.Kconfig.n
266+
267+
if (kconfiglib.expr_value(expr) and len(expr.config_string) > 0 and expr_nodes_invisible(expr)):
268+
# hidden config dependencies which will be written to sdkconfig as enabled ones.
269+
return kconfiglib.Kconfig.y
270+
271+
if any(node.item.name.startswith(visibility.target_env_var) for node in expr.nodes):
272+
# We know the actual values for IDF_TARGETs
273+
return kconfiglib.Kconfig.y if kconfiglib.expr_value(expr) else kconfiglib.Kconfig.n
274+
275+
return expr
276+
277+
217278
def write_menu_item(f, node, visibility):
218279
def is_choice(node):
219280
""" Skip choice nodes, they are handled as part of the parent (see below) """
@@ -269,6 +330,56 @@ def is_choice(node):
269330

270331
f.write('\n\n')
271332

333+
if isinstance(node.item, kconfiglib.Symbol):
334+
def _expr_str(sc):
335+
if sc.is_constant or not sc.nodes or sc.choice:
336+
return '{}'.format(sc.name)
337+
return ':ref:`%s%s`' % (sc.kconfig.config_prefix, sc.name)
338+
339+
range_strs = []
340+
for low, high, cond in node.item.ranges:
341+
cond = _minimize_expr(cond, visibility)
342+
if cond == kconfiglib.Kconfig.n:
343+
continue
344+
if not isinstance(cond, tuple) and cond != kconfiglib.Kconfig.y:
345+
if len(cond.nodes) > 0 and all(not visibility.visible(i) for i in cond.nodes):
346+
if not kconfiglib.expr_value(cond):
347+
continue
348+
range_str = '%s- from %s to %s' % (INDENT * 2, low.str_value, high.str_value)
349+
if cond != kconfiglib.Kconfig.y and not kconfiglib.expr_value(cond):
350+
range_str += ' if %s' % kconfiglib.expr_str(cond, _expr_str)
351+
range_strs.append(range_str)
352+
if len(range_strs) > 0:
353+
f.write('%sRange:\n' % INDENT)
354+
f.write('\n'.join(range_strs))
355+
f.write('\n\n')
356+
357+
default_strs = []
358+
for default, cond in node.item.defaults:
359+
cond = _minimize_expr(cond, visibility)
360+
if cond == kconfiglib.Kconfig.n:
361+
continue
362+
if not isinstance(cond, tuple) and cond != kconfiglib.Kconfig.y:
363+
if len(cond.nodes) > 0 and all(not visibility.visible(i) for i in cond.nodes):
364+
if not kconfiglib.expr_value(cond):
365+
continue
366+
# default.type is mostly UNKNOWN so it cannot be used reliably for detecting the type
367+
d = default.str_value
368+
if d in ['y', 'Y']:
369+
d = 'Yes (enabled)'
370+
elif d in ['n', 'N']:
371+
d = 'No (disabled)'
372+
elif re.search(r'[^0-9a-fA-F]', d): # simple string detection: if it not a valid number
373+
d = '"%s"' % d
374+
default_str = '%s- %s' % (INDENT * 2, d)
375+
if cond != kconfiglib.Kconfig.y and not kconfiglib.expr_value(cond):
376+
default_str += ' if %s' % kconfiglib.expr_str(cond, _expr_str)
377+
default_strs.append(default_str)
378+
if len(default_strs) > 0:
379+
f.write('%sDefault value:\n' % INDENT)
380+
f.write('\n'.join(default_strs))
381+
f.write('\n\n')
382+
272383
if is_menu:
273384
# enumerate links to child items
274385
child_list = []

tools/kconfig_new/test/gen_kconfig_doc/Kconfig

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,35 @@ config CHIPA_FEATURE_FROM_V1
134134
config CHIPA_FEATURE_FROM_V3
135135
depends on CONFIG_FOR_CHIPA && (CHIPA_REV_MIN <= 3)
136136
bool "Feature available from version 3"
137+
138+
config CHIPA_OPTION
139+
int "option with default value depending on the chip version"
140+
depends on IDF_TARGET_CHIPA
141+
default 5 if CHIPA_REV_MIN < 2
142+
default 4 if CHIPA_VERSION = 2
143+
default 9 if CHIPA_REV_MIN = 3
144+
145+
config COMPILER
146+
string "compiler prefix"
147+
default "ca" if IDF_TARGET_CHIPA
148+
default "cb" if IDF_TARGET_CHIPB
149+
150+
config BOOL_OPTION
151+
bool "bool option"
152+
default y
153+
154+
config BOOL_OPTION2
155+
bool "bool option 2"
156+
default BOOL_OPTION
157+
158+
config HEX_OPTION
159+
hex "bool option"
160+
default 0xce if IDF_TARGET_CHIPA
161+
default 0xff if IDF_TARGET_CHIPB
162+
range 0xf 0xce if IDF_TARGET_CHIPA
163+
range 0xfe 0xff if IDF_TARGET_CHIPB
164+
165+
config INT_OPTION
166+
int "int option"
167+
range 1 10 if IDF_TARGET_CHIPA
168+
range 100 200 if IDF_TARGET_CHIPB

tools/kconfig_new/test/gen_kconfig_doc/Kconfig.chipa

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ menu "Menu for CHIPA"
88
config EXT_CONFIG2_FOR_CHIPA_MENU
99
bool "Config for chip A (depend on the visibility of the menu)"
1010

11+
config EXT_CONFIG3_FOR_CHIPA_MENU
12+
int "integer"
13+
default 5
1114
endmenu
1215

1316
config EXT_CONFIG3_FOR_CHIPA
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env python
2+
from __future__ import unicode_literals
3+
import io
4+
import os
5+
import sys
6+
import unittest
7+
8+
import kconfiglib
9+
10+
try:
11+
import gen_kconfig_doc
12+
except ImportError:
13+
sys.path.insert(0, os.path.normpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../..')))
14+
import gen_kconfig_doc
15+
16+
17+
class TestDocOutput(unittest.TestCase):
18+
@classmethod
19+
def setUpClass(cls):
20+
os.environ['IDF_TARGET'] = 'chipa'
21+
cls.target = os.environ['IDF_TARGET']
22+
cls.config = kconfiglib.Kconfig('Kconfig')
23+
cls.visibility = gen_kconfig_doc.ConfigTargetVisibility(cls.config, cls.target)
24+
25+
def get_config(self, name):
26+
sym = self.config.syms.get(name)
27+
if sym:
28+
return sym.nodes[0]
29+
choice = self.config.named_choices.get(name)
30+
if choice:
31+
return choice.nodes[0]
32+
raise RuntimeError('Unimplemented {}'.format(name))
33+
34+
def get_doc_out(self, config_name):
35+
with io.StringIO() if sys.version_info.major == 3 else io.BytesIO() as output:
36+
gen_kconfig_doc.write_menu_item(output, self.get_config(config_name), self.visibility)
37+
output.seek(0)
38+
return output.read()
39+
40+
def test_simple_default(self):
41+
s = self.get_doc_out('EXT_CONFIG3_FOR_CHIPA_MENU')
42+
self.assertIn('- 5', s)
43+
44+
def test_multiple_defaults(self):
45+
s = self.get_doc_out('CHIPA_OPTION')
46+
self.assertNotIn('- 5', s)
47+
self.assertIn('- 4 if CHIPA_VERSION = 2', s)
48+
self.assertNotIn('- 9', s)
49+
50+
def test_string_default(self):
51+
s = self.get_doc_out('COMPILER')
52+
self.assertIn('- ca', s)
53+
self.assertNotIn('- cb', s)
54+
55+
def test_bool_default(self):
56+
s = self.get_doc_out('BOOL_OPTION')
57+
self.assertIn('- Yes', s)
58+
59+
def test_bool_default_dependency(self):
60+
s = self.get_doc_out('BOOL_OPTION2')
61+
self.assertIn('- Yes', s)
62+
63+
def test_hex_default(self):
64+
s = self.get_doc_out('HEX_OPTION')
65+
self.assertIn('- "0xce"', s)
66+
self.assertNotIn('- "0xff"', s)
67+
68+
def test_hex_range(self):
69+
s = self.get_doc_out('HEX_OPTION')
70+
self.assertIn('- from 0xf to 0xce', s)
71+
self.assertNotIn('- from 0xfe', s)
72+
73+
def test_int_range(self):
74+
s = self.get_doc_out('INT_OPTION')
75+
self.assertIn('- from 1 to 10', s)
76+
self.assertNotIn('- from 100', s)
77+
78+
79+
if __name__ == "__main__":
80+
unittest.main()

tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def setUp(self):
1919

2020
def _get_config(self, name):
2121
sym = self.config.syms.get(name)
22-
if sym:
22+
if sym and len(sym.nodes) > 0:
2323
return sym.nodes[0]
2424
choice = self.config.named_choices.get(name)
2525
if choice:

0 commit comments

Comments
 (0)