kconfiglib/menuconfig/kconfig.py: Various improvements
Update Kconfiglib and menuconfig to upstream revision d3866958c7685, to
add various improvements:
- Support HOME and END in the jump-to dialog in the menuconfig. END can
be handy as choices, menus, and comments appear at the end.
- Add more fine-grained warning controls for multiple assignments to a
symbol in configuration files. Use it to simplify kconfig.py a bit.
Clean up kconfig.py a bit in other ways too, e.g. by removing unused
imports.
- Improve Kconfig parsing performance slightly
Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
diff --git a/scripts/kconfig/kconfig.py b/scripts/kconfig/kconfig.py
index 9e9b6d7..0f8ca3d 100755
--- a/scripts/kconfig/kconfig.py
+++ b/scripts/kconfig/kconfig.py
@@ -5,7 +5,8 @@
import sys
import textwrap
-from kconfiglib import Kconfig, Symbol, BOOL, STRING, TRISTATE, TRI_TO_STR
+from kconfiglib import Kconfig, BOOL, TRISTATE, TRI_TO_STR
+
# Warnings that won't be turned into errors (but that will still be printed),
# identified by a substring of the warning. The warning texts from Kconfiglib
@@ -16,34 +17,25 @@
"y-selected",
)
+
def fatal(warning):
# Returns True if 'warning' is not whitelisted and should be turned into an
# error
- for wl_warning in WARNING_WHITELIST:
- if wl_warning in warning:
- return False
-
- # Only allow enabled (printed) warnings to be fatal
- return enabled(warning)
-
-
-def enabled(warning):
- # Returns True if 'warning' should be printed
-
- # Some prj.conf files seem to deliberately override settings from the board
- # configuration (e.g. samples/bluetooth/hci_usb/prj.conf, with GPIO=y).
- # Disable the warning about a symbol being assigned more than once.
- return "set more than once" not in warning
+ return not any(wl_warning in warning for wl_warning in WARNING_WHITELIST)
def main():
- parse_args()
+ args = parse_args()
- print("Parsing Kconfig tree in {}".format(args.kconfig_root))
+ print("Parsing Kconfig tree in " + args.kconfig_root)
kconf = Kconfig(args.kconfig_root, warn_to_stderr=False)
- # Enable warnings for assignments to undefined symbols
+ # prj.conf may override settings from the board configuration, so disable
+ # warnings about symbols being assigned more than once
+ kconf.disable_override_warnings()
+ kconf.disable_redun_warnings()
+ # Warn for assignments to undefined symbols
kconf.enable_undef_warnings()
for i, config in enumerate(args.conf_fragments):
@@ -73,11 +65,11 @@
# fast.
kconf.write_config(os.devnull)
- # We could roll this into the loop below, but it's nice to always print all
- # warnings, even if one of them turns out to be fatal
+ # Print warnings ourselves so that we can put a blank line between them for
+ # readability. We could roll this into the loop below, but it's nice to
+ # always print all warnings, even if one of them turns out to be fatal.
for warning in kconf.warnings:
- if enabled(warning):
- print("\n" + warning, file=sys.stderr)
+ print("\n" + warning, file=sys.stderr)
# Turn all warnings except for explicity whitelisted ones into errors. In
# particular, this will turn assignments to undefined Kconfig variables
@@ -191,8 +183,6 @@
def parse_args():
- global args
-
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter
@@ -203,7 +193,7 @@
parser.add_argument("autoconf")
parser.add_argument("conf_fragments", metavar='conf', type=str, nargs='+')
- args = parser.parse_args()
+ return parser.parse_args()
if __name__ == "__main__":
diff --git a/scripts/kconfig/kconfiglib.py b/scripts/kconfig/kconfiglib.py
index c7510b2..e8bf99e 100644
--- a/scripts/kconfig/kconfiglib.py
+++ b/scripts/kconfig/kconfiglib.py
@@ -152,7 +152,7 @@
(1)
menu "menu"
- depends on A
+ depends on A
if B
@@ -168,7 +168,7 @@
(2)
menu "menu"
- depends on A
+ depends on A
config FOO
tristate "foo" if A && B && C && D
@@ -731,6 +731,7 @@
"_set_match",
"_unset_match",
"_warn_for_no_prompt",
+ "_warn_for_override",
"_warn_for_redun_assign",
"_warn_for_undef_assign",
"_warn_to_stderr",
@@ -852,7 +853,7 @@
self._warn_to_stderr = warn_to_stderr
self._warn_for_undef_assign = \
os.environ.get("KCONFIG_WARN_UNDEF_ASSIGN") == "y"
- self._warn_for_redun_assign = True
+ self._warn_for_redun_assign = self._warn_for_override = True
self._encoding = encoding
@@ -1179,14 +1180,14 @@
else:
display_user_val = sym.user_value
- warn_msg = '{} set more than once. Old value: "{}", new value: "{}".'.format(
+ msg = '{} set more than once. Old value: "{}", new value: "{}".'.format(
_name_and_loc(sym), display_user_val, val
)
if display_user_val == val:
- self._warn_redun_assign(warn_msg, filename, linenr)
+ self._warn_redun_assign(msg, filename, linenr)
else:
- self._warn( warn_msg, filename, linenr)
+ self._warn_override(msg, filename, linenr)
sym.set_value(val)
@@ -1391,7 +1392,8 @@
function when adding symbol prerequisites to source files.
In case you need a different scheme for your project, the sync_deps()
- implementation can be used as a template."""
+ implementation can be used as a template.
+ """
if not os.path.exists(path):
os.mkdir(path, 0o755)
@@ -1590,7 +1592,7 @@
self._tokens = self._tokenize("if " + s)[1:]
self._tokens_i = -1
- return expr_value(self._expect_expr_and_eol()) # transform_m
+ return expr_value(self._expect_expr_and_eol())
def unset_values(self):
"""
@@ -1648,6 +1650,23 @@
"""
self._warn_for_undef_assign = False
+ def enable_override_warnings(self):
+ """
+ Enables warnings for duplicated assignments in .config files that set
+ different values (e.g. CONFIG_FOO=m followed by CONFIG_FOO=y, where
+ the last value set is used).
+
+ These warnings are enabled by default. Disabling them might be helpful
+ in certain cases when merging configurations.
+ """
+ self._warn_for_override = True
+
+ def disable_override_warnings(self):
+ """
+ See enable_override_warnings().
+ """
+ self._warn_for_override = False
+
def enable_redun_warnings(self):
"""
Enables warnings for duplicated assignments in .config files that all
@@ -1974,28 +1993,37 @@
c = s[i]
if c in "\"'":
- s, end_i = self._expand_str(s, i)
+ if "$" not in s and "\\" not in s:
+ # Fast path for lines without $ and \. Find the
+ # matching quote.
+ end_i = s.find(c, i + 1) + 1
+ if not end_i:
+ self._parse_error("unterminated string")
+ val = s[i + 1:end_i - 1]
+ i = end_i
+ else:
+ # Slow path
+ s, end_i = self._expand_str(s, i)
- # os.path.expandvars() and the $UNAME_RELEASE replace() is
- # a backwards compatibility hack, which should be
- # reasonably safe as expandvars() leaves references to
- # undefined env. vars. as is.
- #
- # The preprocessor functionality changed how environment
- # variables are referenced, to $(FOO).
- val = os.path.expandvars(
- s[i + 1:end_i - 1].replace("$UNAME_RELEASE",
- platform.uname()[2]))
+ # os.path.expandvars() and the $UNAME_RELEASE replace()
+ # is a backwards compatibility hack, which should be
+ # reasonably safe as expandvars() leaves references to
+ # undefined env. vars. as is.
+ #
+ # The preprocessor functionality changed how
+ # environment variables are referenced, to $(FOO).
+ val = os.path.expandvars(
+ s[i + 1:end_i - 1].replace("$UNAME_RELEASE",
+ platform.uname()[2]))
- i = end_i
+ i = end_i
# This is the only place where we don't survive with a
# single token of lookback: 'option env="FOO"' does not
# refer to a constant symbol named "FOO".
- token = val \
- if token in _STRING_LEX or \
- tokens[0] is _T_OPTION else \
- self._lookup_const_sym(val)
+ token = \
+ val if token in _STRING_LEX or tokens[0] is _T_OPTION \
+ else self._lookup_const_sym(val)
elif s.startswith("&&", i):
token = _T_AND
@@ -2830,6 +2858,7 @@
# 'prompt' properties override each other within a single definition of
# a symbol, but additional prompts can be added by defining the symbol
# multiple times
+
if node.prompt:
self._warn(_name_and_loc(node.item) +
" defined with multiple prompts in single location")
@@ -2903,7 +2932,7 @@
self._linenr += len(help_lines)
- node.help = "\n".join(help_lines).rstrip() + "\n"
+ node.help = "\n".join(help_lines).rstrip()
self._line_after_help(line)
def _parse_expr(self, transform_m):
@@ -3510,7 +3539,7 @@
if self._warn_to_stderr:
sys.stderr.write(msg + "\n")
- def _warn_undef_assign(self, msg, filename=None, linenr=None):
+ def _warn_undef_assign(self, msg, filename, linenr):
# See the class documentation
if self._warn_for_undef_assign:
@@ -3523,7 +3552,13 @@
'attempt to assign the value "{}" to the undefined symbol {}'
.format(val, name), filename, linenr)
- def _warn_redun_assign(self, msg, filename=None, linenr=None):
+ def _warn_override(self, msg, filename, linenr):
+ # See the class documentation
+
+ if self._warn_for_override:
+ self._warn(msg, filename, linenr)
+
+ def _warn_redun_assign(self, msg, filename, linenr):
# See the class documentation
if self._warn_for_redun_assign:
@@ -3855,7 +3890,7 @@
has_active_range = False
# Defaults are used if the symbol is invisible, lacks a user value,
- # or has an out-of-range user value.
+ # or has an out-of-range user value
use_defaults = True
if vis and self.user_value:
@@ -4250,7 +4285,8 @@
defined in multiple locations will return a string with all
definitions.
- An empty string is returned for undefined and constant symbols.
+ The returned string does not end in a newline. An empty string is
+ returned for undefined and constant symbols.
"""
return self.custom_str(standard_sc_expr_str)
@@ -4259,8 +4295,8 @@
Works like Symbol.__str__(), but allows a custom format to be used for
all symbol/choice references. See expr_str().
"""
- return "\n".join(node.custom_str(sc_expr_str_fn)
- for node in self.nodes)
+ return "\n\n".join(node.custom_str(sc_expr_str_fn)
+ for node in self.nodes)
#
# Private methods
@@ -4825,6 +4861,8 @@
matching the Kconfig format (though without the contained choice
symbols).
+ The returned string does not end in a newline.
+
See Symbol.__str__() as well.
"""
return self.custom_str(standard_sc_expr_str)
@@ -4834,8 +4872,8 @@
Works like Choice.__str__(), but allows a custom format to be used for
all symbol/choice references. See expr_str().
"""
- return "\n".join(node.custom_str(sc_expr_str_fn)
- for node in self.nodes)
+ return "\n\n".join(node.custom_str(sc_expr_str_fn)
+ for node in self.nodes)
#
# Private methods
@@ -5005,6 +5043,10 @@
It is possible to have a separate help text at each location if a symbol
is defined in multiple locations.
+ Trailing whitespace (including a final newline) is stripped from the help
+ text. This was not the case before Kconfiglib 10.21.0, where the format
+ was undocumented.
+
dep:
The 'depends on' dependencies for the menu node, or self.kconfig.y if
there are no dependencies. Parent dependencies are propagated to this
@@ -5194,6 +5236,8 @@
locations), properties that aren't associated with a particular menu
node are shown on all menu nodes ('option env=...', 'optional' for
choices, etc.).
+
+ The returned string does not end in a newline.
"""
return self.custom_str(standard_sc_expr_str)
@@ -5207,14 +5251,14 @@
self._sym_choice_node_str(sc_expr_str_fn)
def _menu_comment_node_str(self, sc_expr_str_fn):
- s = '{} "{}"\n'.format("menu" if self.item is MENU else "comment",
- self.prompt[0])
+ s = '{} "{}"'.format("menu" if self.item is MENU else "comment",
+ self.prompt[0])
if self.dep is not self.kconfig.y:
- s += "\tdepends on {}\n".format(expr_str(self.dep, sc_expr_str_fn))
+ s += "\n\tdepends on {}".format(expr_str(self.dep, sc_expr_str_fn))
if self.item is MENU and self.visibility is not self.kconfig.y:
- s += "\tvisible if {}\n".format(expr_str(self.visibility,
+ s += "\n\tvisible if {}".format(expr_str(self.visibility,
sc_expr_str_fn))
return s
@@ -5288,7 +5332,7 @@
for line in self.help.splitlines():
indent_add(" " + line)
- return "\n".join(lines) + "\n"
+ return "\n".join(lines)
class Variable(object):
"""
@@ -5600,6 +5644,61 @@
"""
return os.environ.get("KCONFIG_CONFIG", ".config")
+def load_allconfig(kconf, filename):
+ """
+ Helper for all*config. Loads (merges) the configuration file specified by
+ KCONFIG_ALLCONFIG, if any. See Documentation/kbuild/kconfig.txt in the
+ Linux kernel.
+
+ Disables warnings for duplicated assignments within configuration files for
+ the duration of the call (disable_override_warnings() +
+ disable_redun_warnings()), and enables them at the end. The
+ KCONFIG_ALLCONFIG configuration file is expected to override symbols.
+
+ Exits with sys.exit() (which raises a SystemExit exception) and prints an
+ error to stderr if KCONFIG_ALLCONFIG is set but the configuration file
+ can't be opened.
+
+ kconf:
+ Kconfig instance to load the configuration in.
+
+ filename:
+ Command-specific configuration filename - "allyes.config",
+ "allno.config", etc.
+ """
+ def std_msg(e):
+ # "Upcasts" a _KconfigIOError to an IOError, removing the custom
+ # __str__() message. The standard message is better here.
+ return IOError(e.errno, e.strerror, e.filename)
+
+ kconf.disable_override_warnings()
+ kconf.disable_redun_warnings()
+
+ allconfig = os.environ.get("KCONFIG_ALLCONFIG")
+ if allconfig is not None:
+ if allconfig in ("", "1"):
+ try:
+ kconf.load_config(filename, False)
+ except IOError as e1:
+ try:
+ kconf.load_config("all.config", False)
+ except IOError as e2:
+ sys.exit("error: KCONFIG_ALLCONFIG is set, but neither {} "
+ "nor all.config could be opened: {}, {}"
+ .format(filename, std_msg(e1), std_msg(e2)))
+ else:
+ try:
+ kconf.load_config(allconfig, False)
+ except IOError as e:
+ sys.exit("error: KCONFIG_ALLCONFIG is set to '{}', which "
+ "could not be opened: {}"
+ .format(allconfig, std_msg(e)))
+
+ # API wart: It would be nice if there was a way to query and/or push/pop
+ # warning settings
+ kconf.enable_override_warnings()
+ kconf.enable_redun_warnings()
+
#
# Internal functions
#
@@ -5987,7 +6086,7 @@
if isinstance(item, Symbol) and item.choice:
msg += "the choice symbol "
- msg += "{}, with definition...\n\n{}\n" \
+ msg += "{}, with definition...\n\n{}\n\n" \
.format(_name_and_loc(item), item)
# Small wart: Since we reuse the already calculated
diff --git a/scripts/kconfig/menuconfig.py b/scripts/kconfig/menuconfig.py
index 271cc1e..d34f7d8 100755
--- a/scripts/kconfig/menuconfig.py
+++ b/scripts/kconfig/menuconfig.py
@@ -1131,9 +1131,10 @@
# (as determined by _SCROLL_OFFSET), increase the scroll by one. This
# gives nice and non-jumpy behavior even when
# _SCROLL_OFFSET >= _menu_win_height().
- if _sel_node_i >= _menu_scroll + _menu_win_height() - _SCROLL_OFFSET:
- _menu_scroll = min(_menu_scroll + 1,
- _max_scroll(_shown, _menu_win))
+ if _sel_node_i >= _menu_scroll + _menu_win_height() - _SCROLL_OFFSET \
+ and _menu_scroll < _max_scroll(_shown, _menu_win):
+
+ _menu_scroll += 1
def _select_prev_menu_entry():
# Selects the menu entry before the current one, adjusting the scroll if
@@ -1385,13 +1386,12 @@
rec(node.list) if node.list and not node.is_menuconfig else []
# Always show the node if it is the root of an implicit submenu
- # with visible items, even when the node itself is invisible. This
+ # with visible items, even if the node itself is invisible. This
# can happen e.g. if the symbol has an optional prompt
# ('prompt "foo" if COND') that is currently invisible.
if shown(node) or shown_children:
res.append(node)
-
- res.extend(shown_children)
+ res += shown_children
node = node.next
@@ -1869,8 +1869,10 @@
if sel_node_i < len(matches) - 1:
sel_node_i += 1
- if sel_node_i >= scroll + matches_win.getmaxyx()[0] - _SCROLL_OFFSET:
- scroll = min(scroll + 1, _max_scroll(matches, matches_win))
+ if sel_node_i >= scroll + matches_win.getmaxyx()[0] - _SCROLL_OFFSET \
+ and scroll < _max_scroll(matches, matches_win):
+
+ scroll += 1
def select_prev_match():
nonlocal sel_node_i
@@ -1991,16 +1993,24 @@
elif c == curses.KEY_UP:
select_prev_match()
- elif c == curses.KEY_NPAGE: # Page Down
+ elif c in (curses.KEY_NPAGE, "\x04"): # Page Down/Ctrl-D
# Keep it simple. This way we get sane behavior for small windows,
# etc., for free.
for _ in range(_PG_JUMP):
select_next_match()
- elif c == curses.KEY_PPAGE: # Page Up
+ # Page Up (no Ctrl-U, as it's already used by the edit box)
+ elif c == curses.KEY_PPAGE:
for _ in range(_PG_JUMP):
select_prev_match()
+ elif c == curses.KEY_END:
+ sel_node_i = len(matches) - 1
+ scroll = _max_scroll(matches, matches_win)
+
+ elif c == curses.KEY_HOME:
+ sel_node_i = scroll = 0
+
else:
s, s_i, hscroll = _edit_text(c, s, s_i, hscroll,
edit_box.getmaxyx()[1] - 2)
@@ -2040,8 +2050,8 @@
def prompt_text(mc):
return mc.prompt[0]
- cached_nodes += sorted(_kconf.menus, key=prompt_text) + \
- sorted(_kconf.comments, key=prompt_text)
+ cached_nodes += sorted(_kconf.menus, key=prompt_text)
+ cached_nodes += sorted(_kconf.comments, key=prompt_text)
return cached_nodes
@@ -2155,7 +2165,7 @@
edit_box.erase()
- _draw_frame(edit_box, "Jump to symbol")
+ _draw_frame(edit_box, "Jump to symbol/choice/menu/comment")
# Draw arrows pointing up if the symbol list is scrolled down
if scroll > 0: