Docs: Omit kconfig configurations not available for the target

This commit is contained in:
Roland Dobai 2019-12-12 16:02:36 +01:00
parent 86034ad049
commit 5454c268f7
17 changed files with 439 additions and 33 deletions

View file

@ -1,4 +1,5 @@
menu "Bluetooth"
visible if IDF_TARGET_ESP32
config BT_ENABLED
bool "Bluetooth"
@ -432,6 +433,7 @@ endmenu
menuconfig BLE_MESH
bool "ESP BLE Mesh Support"
depends on BT_ENABLED
help
This option enables ESP BLE Mesh support. The specific features that are
available may depend on other features that have been enabled in the

View file

@ -83,6 +83,7 @@ menu "Driver configurations"
endmenu # UART Configuration
menu "RTCIO configuration"
visible if IDF_TARGET_ESP32
config RTCIO_SUPPORT_RTC_GPIO_DESC
bool "Support array `rtc_gpio_desc` for ESP32"

View file

@ -95,11 +95,6 @@ kconfigs = find_component_files("../../components", "Kconfig")
kconfig_projbuilds = find_component_files("../../components", "Kconfig.projbuild")
sdkconfig_renames = find_component_files("../../components", "sdkconfig.rename")
# trim the esp32s2 component, until we have proper multi-target support
kconfigs = [k for k in kconfigs if "esp32s2" not in k]
kconfig_projbuilds = [k for k in kconfig_projbuilds if "esp32s2" not in k]
sdkconfig_renames = [r for r in sdkconfig_renames if "esp32s2" not in r]
kconfigs_source_path = '{}/inc/kconfigs_source.in'.format(builddir)
kconfig_projbuilds_source_path = '{}/inc/kconfig_projbuilds_source.in'.format(builddir)
@ -123,6 +118,7 @@ confgen_args = [sys.executable,
"--env", "COMPONENT_KCONFIGS_SOURCE_FILE={}".format(kconfigs_source_path),
"--env", "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE={}".format(kconfig_projbuilds_source_path),
"--env", "IDF_PATH={}".format(idf_path),
"--env", "IDF_TARGET={}".format(os.environ.get('IDF_TARGET', 'esp32')),
"--output", "docs", kconfig_inc_path + '.in'
]
subprocess.check_call(confgen_args)

View file

@ -160,9 +160,15 @@ test_multi_heap_on_host:
test_confserver:
extends: .host_test_template
script:
- cd tools/kconfig_new/test
- cd tools/kconfig_new/test/confserver
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_confserver.py
test_gen_kconfig_doc:
extends: .host_test_template
script:
- cd tools/kconfig_new/test/gen_kconfig_doc/
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_target_visibility.py
test_idf_monitor:
extends: .host_test_template
artifacts:

View file

@ -75,7 +75,8 @@ tools/kconfig/merge_config.sh
tools/kconfig/streamline_config.pl
tools/kconfig_new/confgen.py
tools/kconfig_new/confserver.py
tools/kconfig_new/test/test_confserver.py
tools/kconfig_new/test/confserver/test_confserver.py
tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py
tools/ldgen/ldgen.py
tools/ldgen/test/test_fragments.py
tools/ldgen/test/test_generation.py

View file

@ -7,7 +7,7 @@
# Used internally by the ESP-IDF build system. But designed to be
# non-IDF-specific.
#
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
# Copyright 2018-2020 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.
@ -112,10 +112,10 @@ class DeprecatedOptions(object):
print('{}:{} {} was replaced with {}'.format(sdkconfig_in, line_num, depr_opt, new_opt))
f_out.write(line)
def append_doc(self, config, path_output):
def append_doc(self, config, visibility, path_output):
def option_was_written(opt):
return any(gen_kconfig_doc.node_should_write(node) for node in config.syms[opt].nodes)
return any(visibility.visible(node) for node in config.syms[opt].nodes)
if len(self.r_dic) > 0:
with open(path_output, 'a') as f_o:
@ -572,8 +572,15 @@ def write_json_menus(deprecated_options, config, filename):
def write_docs(deprecated_options, config, filename):
gen_kconfig_doc.write_docs(config, filename)
deprecated_options.append_doc(config, filename)
try:
target = os.environ['IDF_TARGET']
except KeyError:
print('IDF_TARGET environment variable must be defined!')
sys.exit(1)
visibility = gen_kconfig_doc.ConfigTargetVisibility(config, target)
gen_kconfig_doc.write_docs(config, visibility, filename)
deprecated_options.append_doc(config, visibility, filename)
def update_if_changed(source, destination):

View file

@ -7,7 +7,7 @@
# generated, allowing options to be referenced in other documents
# (using :ref:`CONFIG_FOO`)
#
# Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
# Copyright 2017-2020 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.
@ -42,13 +42,126 @@ INITIAL_HEADING_LEVEL = 3
MAX_HEADING_LEVEL = len(HEADING_SYMBOLS) - 1
def write_docs(config, filename):
class ConfigTargetVisibility(object):
"""
Determine the visibility of Kconfig options based on IDF targets. Note that other environment variables should not
imply invisibility and neither dependencies on visible options with default disabled state. This difference makes
it necessary to implement our own visibility and cannot use the visibility defined inside Kconfiglib.
"""
def __init__(self, config, target):
# target actually is not necessary here because kconfiglib.expr_value() will evaluate it internally
self.config = config
self.visibility = dict() # node name to (x, y) mapping where x is the visibility (True/False) and y is the
# name of the config which implies the visibility
self.target_env_var = 'IDF_TARGET'
self.direct_eval_set = frozenset([kconfiglib.EQUAL, kconfiglib.UNEQUAL, kconfiglib.LESS, kconfiglib.LESS_EQUAL,
kconfiglib.GREATER, kconfiglib.GREATER_EQUAL])
def _implies_invisibility(self, item):
if isinstance(item, tuple):
if item[0] == kconfiglib.NOT:
(invisibility, source) = self._implies_invisibility(item[1])
if source is not None and source.startswith(self.target_env_var):
return (not invisibility, source)
else:
# we want to be visible all configs which are not dependent on target variables,
# e.g. "depends on XY" and "depends on !XY" as well
return (False, None)
elif item[0] == kconfiglib.AND:
(invisibility, source) = self._implies_invisibility(item[1])
if invisibility:
return (True, source)
(invisibility, source) = self._implies_invisibility(item[2])
if invisibility:
return (True, source)
return (False, None)
elif item[0] == kconfiglib.OR:
implication_list = [self._implies_invisibility(item[1]), self._implies_invisibility(item[2])]
if all([implies for (implies, _) in implication_list]):
source_list = [s for (_, s) in implication_list if s.startswith(self.target_env_var)]
if len(set(source_list)) != 1: # set removes the duplicates
print('[WARNING] list contains targets: {}'.format(source_list))
return (True, source_list[0])
return (False, None)
elif item[0] in self.direct_eval_set:
def node_is_invisible(item):
return all([node.prompt is None for node in item.nodes])
if node_is_invisible(item[1]) or node_is_invisible(item[1]):
# it makes no sense to call self._implies_invisibility() here because it won't generate any useful
# "source"
return (not kconfiglib.expr_value(item), None)
else:
# expressions with visible configs can be changed to make the item visible
return (False, None)
else:
raise RuntimeError('Unimplemented operation in {}'.format(item))
else: # Symbol or Choice
vis_list = [self._visible(node) for node in item.nodes]
if len(vis_list) > 0 and all([not visible for (visible, _) in vis_list]):
source_list = [s for (_, s) in vis_list if s is not None and s.startswith(self.target_env_var)]
if len(set(source_list)) != 1: # set removes the duplicates
print('[WARNING] list contains targets: {}'.format(source_list))
return (True, source_list[0])
if item.name.startswith(self.target_env_var):
return (not kconfiglib.expr_value(item), item.name)
if len(vis_list) == 1:
(visible, source) = vis_list[0]
if visible:
return (False, item.name) # item.name is important here in case the result will be inverted: if
# the dependency is on another config then it can be still visible
return (False, None)
def _visible(self, node):
if isinstance(node.item, kconfiglib.Symbol) or isinstance(node.item, kconfiglib.Choice):
dependencies = node.item.direct_dep # "depends on" for configs
name_id = node.item.name
simple_def = len(node.item.nodes) <= 1 # defined only in one source file
# Probably it is not necessary to check the default statements.
else:
dependencies = node.visibility # "visible if" for menu
name_id = node.prompt[0]
simple_def = False # menus can be defined with the same name at multiple locations and they don't know
# about each other like configs through node.item.nodes. Therefore, they cannot be stored and have to be
# re-evaluated always.
try:
(visib, source) = self.visibility[name_id]
except KeyError:
def invert_first_arg(_tuple):
return (not _tuple[0], _tuple[1])
(visib, source) = self._visible(node.parent) if node.parent else (True, None)
if visib:
(visib, source) = invert_first_arg(self._implies_invisibility(dependencies))
if simple_def:
# Configs defined at multiple places are not stored because they could have different visibility based
# on different targets. kconfiglib.expr_value() will handle the visibility.
self.visibility[name_id] = (visib, source)
return (visib, source) # not used in "finally" block because failure messages from _implies_invisibility are
# this way more understandable
def visible(self, node):
if not node.prompt:
# don't store this in self.visibility because don't want to stop at invisible nodes when recursively
# searching for invisible targets
return False
return self._visible(node)[0]
def write_docs(config, visibility, 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:
for node in config.node_iter():
write_menu_item(f, node)
write_menu_item(f, node, visibility)
def node_is_menu(node):
@ -108,18 +221,12 @@ def format_rest_text(text, indent):
return text
def node_should_write(node):
if not node.prompt:
return False # Don't do anything for invisible menu items
def write_menu_item(f, node, visibility):
def is_choice(node):
""" Skip choice nodes, they are handled as part of the parent (see below) """
return isinstance(node.parent.item, kconfiglib.Choice)
if isinstance(node.parent.item, kconfiglib.Choice):
return False # Skip choice nodes, they are handled as part of the parent (see below)
return True
def write_menu_item(f, node):
if not node_should_write(node):
if is_choice(node) or not visibility.visible(node):
return
try:
@ -175,7 +282,7 @@ def write_menu_item(f, node):
child = node.list
while child:
try:
if node_should_write(child):
if not is_choice(child) and child.prompt and visibility.visible(child):
if first:
f.write("Contains:\n\n")
first = False

View file

@ -56,12 +56,12 @@ def main():
with tempfile.NamedTemporaryFile(delete=False) as f:
temp_kconfig_projbuilds_source_file = os.path.join(tempfile.gettempdir(), f.name)
cmdline = '''../confserver.py --env "COMPONENT_KCONFIGS_SOURCE_FILE=%s" \
--env "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=%s" \
--env "COMPONENT_KCONFIGS=" \
--env "COMPONENT_KCONFIGS_PROJBUILD=" \
--kconfig Kconfig \
--config %s \
cmdline = '''../../confserver.py --env "COMPONENT_KCONFIGS_SOURCE_FILE=%s" \
--env "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=%s" \
--env "COMPONENT_KCONFIGS=" \
--env "COMPONENT_KCONFIGS_PROJBUILD=" \
--kconfig Kconfig \
--config %s \
''' % (temp_kconfigs_source_file, temp_kconfig_projbuilds_source_file, temp_sdkconfig_path)
cmdline = re.sub(r' +', ' ', cmdline)

View file

@ -0,0 +1,137 @@
config IDF_TARGET
string
default "$IDF_TARGET"
config IDF_TARGET_CHIPA
bool
default "y" if IDF_TARGET="chipa"
config IDF_TARGET_CHIPB
bool
default "y" if IDF_TARGET="chipb"
config ALWAYS_VISIBLE
bool "Always visible option"
choice ALWAYS_VISIBLE_CHOICE
prompt "Always visible choice"
default ALWAYS_VISIBLE_CHOICE_OP1
config ALWAYS_VISIBLE_CHOICE_OP1
bool "op1"
config ALWAYS_VISIBLE_CHOICE_OP2
bool "op2"
endchoice
config CONFIG_FOR_CHIPA
bool "Config for chip A"
depends on IDF_TARGET_CHIPA
default n
config CONFIG_FOR_CHIPB
bool "Config for chip B"
depends on IDF_TARGET_CHIPB
choice CHOICE_FOR_CHIPA
prompt "Always visible choice"
default CHOICE_FOR_CHIPA_OP1
depends on IDF_TARGET_CHIPA
config CHOICE_FOR_CHIPA_OP1
bool "op1"
config CHOICE_FOR_CHIPA_OP2
bool "op2"
endchoice
choice CHOICE_FOR_CHIPB
prompt "Always visible choice"
default CHOICE_FOR_CHIPB_OP1
depends on IDF_TARGET_CHIPB
config CHOICE_FOR_CHIPB_OP1
bool "op1"
config CHOICE_FOR_CHIPB_OP2
bool "op2"
endchoice
source "Kconfig.chipa"
source "Kconfig.chipb"
config DEEP_DEPENDENT_CONFIG
bool "Config depends on another config with default no value"
depends on CONFIG_FOR_CHIPA
config DEEP_DEPENDENT_CONFIG_INV
bool "Config depends on the inverted value of another config"
depends on !CONFIG_FOR_CHIPA
choice DEEP_DEPENDENT_CHOICE
prompt "depends on target-specific config"
default DEEP_DEPENDENT_CHOICE_OP1
depends on DEEP_DEPENDENT_CONFIG
config DEEP_DEPENDENT_CHOICE_OP1
bool "op1"
config DEEP_DEPENDENT_CHOICE_OP2
bool "op2"
endchoice
config INVISIBLE1
bool "depends on cannot be satified at the same time"
depends on CONFIG_FOR_CHIPA && IDF_TARGET_CHIPB
config VISIBLE1
bool "makes no sense, just for testing OR dependencies"
depends on CONFIG_FOR_CHIPA || IDF_TARGET_CHIPB
config CONFIG_FOR_CHIPA_DEPENDS_VAR1
bool "redundant AND in depends on"
depends on CONFIG_FOR_CHIPA && IDF_TARGET_CHIPA
config CONFIG_FOR_CHIPA_DEPENDS_VAR2
bool "test AND + NOT"
depends on CONFIG_FOR_CHIPA && !IDF_TARGET_CHIPB
config CONFIG_FOR_CHIPA_DEPENDS_VAR3
bool "test NOT"
depends on !IDF_TARGET_CHIPB
config CONFIG_DEPENDS_ENV_VAR1
bool "test other environment variable (should be visible because only IDF_TARGET should make something invisible)"
depends on IDF_XYZ
config CONFIG_DEPENDS_ENV_VAR2
bool "test other environment variable (should be visible because only IDF_TARGET should make something invisible)"
depends on !IDF_XYZ
choice CHIPA_VERSION
prompt "CHIPA version"
default CHIPA_VERSION2
depends on CONFIG_FOR_CHIPA
config CHIPA_VERSION1
bool "Version 1"
config CHIPA_VERSION2
bool "Version 2"
config CHIPA_VERSION3
bool "Version 3"
endchoice
config CHIPA_REV_MIN
int
default 1 if CHIPA_VERSION1
default 2 if CHIPA_VERSION2
default 3 if CHIPA_VERSION3
config CHIPA_FEATURE_FROM_V1
depends on CONFIG_FOR_CHIPA && (CHIPA_REV_MIN <= 1)
bool "Feature available from version 1"
config CHIPA_FEATURE_FROM_V3
depends on CONFIG_FOR_CHIPA && (CHIPA_REV_MIN <= 3)
bool "Feature available from version 3"

View file

@ -0,0 +1,19 @@
menu "Menu for CHIPA"
visible if IDF_TARGET_CHIPA
config EXT_CONFIG1_FOR_CHIPA_MENU
bool "Config for chip A"
depends on IDF_TARGET_CHIPA
config EXT_CONFIG2_FOR_CHIPA_MENU
bool "Config for chip A (depend on the visibility of the menu)"
endmenu
config EXT_CONFIG3_FOR_CHIPA
bool "Config for chip A"
depends on IDF_TARGET_CHIPA
config EXT_CONFIG4
bool "Config for every chip (note that the config is defined at multiple places)"
depends on IDF_TARGET_CHIPA

View file

@ -0,0 +1,19 @@
menu "Menu for CHIPB"
visible if IDF_TARGET_CHIPB
config EXT_CONFIG1_FOR_CHIPB_MENU
bool "Config for chip B"
depends on IDF_TARGET_CHIPB
config EXT_CONFIG2_FOR_CHIPB_MENU
bool "Config for chip B (depend on the visibility of the menu)"
endmenu
config EXT_CONFIG3_FOR_CHIPB
bool "Config for chip B"
depends on IDF_TARGET_CHIPB
config EXT_CONFIG4
bool "Config for every chip (note that the config is defined at multiple places)"
depends on IDF_TARGET_CHIPB

View file

@ -0,0 +1,111 @@
#!/usr/bin/env python
import os
import sys
import unittest
try:
from . import kconfiglib
except Exception:
sys.path.insert(0, os.path.normpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../..')))
import kconfiglib
import gen_kconfig_doc
class ConfigTargetVisibilityTestCase(unittest.TestCase):
def setUp(self):
self.target = os.environ['IDF_TARGET']
self.config = kconfiglib.Kconfig('Kconfig')
self.v = gen_kconfig_doc.ConfigTargetVisibility(self.config, self.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 visible(self, config_name):
self.assertTrue(self.v.visible(self._get_config(config_name)))
def invisible(self, config_name):
self.assertFalse(self.v.visible(self._get_config(config_name)))
class ConfigTargetVisibilityChipA(ConfigTargetVisibilityTestCase):
@classmethod
def setUpClass(cls):
os.environ['IDF_TARGET'] = 'chipa'
def test_config_visibility(self):
self.invisible('IDF_TARGET')
self.invisible('IDF_TARGET_CHIPA')
self.visible('ALWAYS_VISIBLE')
self.visible('ALWAYS_VISIBLE_CHOICE')
self.visible('CONFIG_FOR_CHIPA')
self.invisible('CONFIG_FOR_CHIPB')
self.visible('CHOICE_FOR_CHIPA')
self.invisible('CHOICE_FOR_CHIPB')
self.visible('EXT_CONFIG1_FOR_CHIPA_MENU')
self.visible('EXT_CONFIG2_FOR_CHIPA_MENU')
self.visible('EXT_CONFIG3_FOR_CHIPA')
self.invisible('EXT_CONFIG1_FOR_CHIPB_MENU')
self.invisible('EXT_CONFIG2_FOR_CHIPB_MENU')
self.invisible('EXT_CONFIG3_FOR_CHIPB')
self.visible('EXT_CONFIG4')
self.visible('DEEP_DEPENDENT_CONFIG')
self.visible('DEEP_DEPENDENT_CONFIG_INV')
self.visible('DEEP_DEPENDENT_CHOICE')
self.invisible('INVISIBLE1')
self.visible('VISIBLE1')
self.visible('CONFIG_FOR_CHIPA_DEPENDS_VAR1')
self.visible('CONFIG_FOR_CHIPA_DEPENDS_VAR2')
self.visible('CONFIG_FOR_CHIPA_DEPENDS_VAR3')
self.visible('CONFIG_DEPENDS_ENV_VAR1')
self.visible('CONFIG_DEPENDS_ENV_VAR2')
self.visible('CHIPA_VERSION')
self.invisible('CHIPA_REV_MIN')
self.visible('CHIPA_FEATURE_FROM_V1')
self.visible('CHIPA_FEATURE_FROM_V3')
class ConfigTargetVisibilityChipB(ConfigTargetVisibilityTestCase):
@classmethod
def setUpClass(cls):
os.environ['IDF_TARGET'] = 'chipb'
def test_config_visibility(self):
self.invisible('IDF_TARGET')
self.invisible('IDF_TARGET_CHIPA')
self.visible('ALWAYS_VISIBLE')
self.visible('ALWAYS_VISIBLE_CHOICE')
self.invisible('CONFIG_FOR_CHIPA')
self.visible('CONFIG_FOR_CHIPB')
self.invisible('CHOICE_FOR_CHIPA')
self.visible('CHOICE_FOR_CHIPB')
self.invisible('EXT_CONFIG1_FOR_CHIPA_MENU')
self.invisible('EXT_CONFIG2_FOR_CHIPA_MENU')
self.invisible('EXT_CONFIG3_FOR_CHIPA')
self.visible('EXT_CONFIG1_FOR_CHIPB_MENU')
self.visible('EXT_CONFIG2_FOR_CHIPB_MENU')
self.visible('EXT_CONFIG3_FOR_CHIPB')
self.visible('EXT_CONFIG4')
self.invisible('DEEP_DEPENDENT_CONFIG')
self.visible('DEEP_DEPENDENT_CONFIG_INV')
self.invisible('DEEP_DEPENDENT_CHOICE')
self.invisible('INVISIBLE1')
self.visible('VISIBLE1')
self.invisible('CONFIG_FOR_CHIPA_DEPENDS_VAR1')
self.invisible('CONFIG_FOR_CHIPA_DEPENDS_VAR2')
self.invisible('CONFIG_FOR_CHIPA_DEPENDS_VAR3')
self.visible('CONFIG_DEPENDS_ENV_VAR1')
self.visible('CONFIG_DEPENDS_ENV_VAR2')
self.invisible('CHIPA_VERSION')
self.invisible('CHIPA_REV_MIN')
self.invisible('CHIPA_FEATURE_FROM_V1')
self.invisible('CHIPA_FEATURE_FROM_V3')
if __name__ == "__main__":
unittest.main()