menuconfig: Add jump-to for choices, menus, and comments

Update menuconfig (and Kconfiglib, just to sync) to upstream revision
bf1701b36634b, to add this commit:

    menuconfig: Add jump-to for choices, menus, and comments

    For choices, search the name and the prompt. This is the same as for
    symbols, except names are optional (and rare) for choices.

    For menus and comments, search the prompt (title text).

    When jumping to a non-empty choice or menu, jump into it instead of
    jumping to its menu node. If show-all mode is off and there are
    visible items in the choice/menu, then jump to the first visible
    node. Otherwise, enable show-all and jump to the first node.

Previously, only symbols could be jumped to.

Various other small fixes/improvements are included too:

 - The "no range constraints" text was dropped from the input dialog
   when settings int/hex symbols without an active 'range'. It might be
   more confusing than helpful.

 - A crash when pressing Ctrl-F (the view-help shortcut) with no matches
   in the jump-to dialog was fixed

 - Some gnome-terminal shoddiness was worked around to remove minor
   jumpiness when reducing the height of the terminal

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
This commit is contained in:
Ulf Magnusson 2018-10-15 02:05:45 +02:00 committed by Anas Nashif
commit 85f8c0d4eb
2 changed files with 529 additions and 403 deletions

View file

@ -415,18 +415,30 @@ def_tristate, allowing int, hex, and string symbols to be given a type and a
default at the same time.
Warnings for undefined symbols
------------------------------
Extra optional warnings
-----------------------
Setting the environment variable KCONFIG_STRICT to "y" will cause warnings to
be printed for all references to undefined Kconfig symbols within Kconfig
files. The only gotcha is that all hex literals must be prefixed by "0x" or
"0X", to make it possible to distuinguish them from symbol references.
Some optional warnings can be controlled via environment variables:
Some projects (e.g. the Linux kernel) use multiple Kconfig trees with many
shared Kconfig files, leading to some safe undefined symbol references.
KCONFIG_STRICT is useful in projects that only have a single Kconfig tree
though.
- KCONFIG_WARN_UNDEF: If set to 'y', warnings will be generated for all
references to undefined symbols within Kconfig files. The only gotcha is
that all hex literals must be prefixed with "0x" or "0X", to make it
possible to distinguish them from symbol references.
Some projects (e.g. the Linux kernel) use multiple Kconfig trees with many
shared Kconfig files, leading to some safe undefined symbol references.
KCONFIG_WARN_UNDEF is useful in projects that only have a single Kconfig
tree though.
KCONFIG_STRICT is an older alias for this environment variable, supported
for backwards compatibility.
- KCONFIG_WARN_UNDEF_ASSIGN: If set to 'y', warnings will be generated for
all assignments to undefined symbols within .config files. By default, no
such warnings are generated.
This warning can also be enabled/disabled via
Kconfig.enable/disable_undef_warnings().
Preprocessor user functions defined in Python
@ -770,19 +782,9 @@ class Kconfig(object):
KconfigError on syntax errors. Note that Kconfig files are not the same
as .config files (which store configuration symbol values).
If the environment variable KCONFIG_STRICT is set to "y", warnings will
be generated for all references to undefined symbols within Kconfig
files. The reason this isn't the default is that some projects (e.g.
the Linux kernel) use multiple Kconfig trees (one per architecture)
with many shared Kconfig files, leading to some safe references to
undefined symbols.
KCONFIG_STRICT relies on literal hex values being prefixed with 0x/0X.
They are indistinguishable from references to undefined symbols
otherwise.
KCONFIG_STRICT might enable other warnings that depend on there being
just a single Kconfig tree in the future.
See the module docstring for some environment variables that influence
default warning settings (KCONFIG_WARN_UNDEF and
KCONFIG_WARN_UNDEF_ASSIGN).
filename (default: "Kconfig"):
The Kconfig file to load. For the Linux kernel, you'll want "Kconfig"
@ -844,7 +846,8 @@ class Kconfig(object):
self._warnings_enabled = warn
self._warn_to_stderr = warn_to_stderr
self._warn_for_undef_assign = False
self._warn_for_undef_assign = \
os.environ.get("KCONFIG_WARN_UNDEF_ASSIGN") == "y"
self._warn_for_redun_assign = True
@ -974,16 +977,15 @@ class Kconfig(object):
self._finalize_tree(self.top_node, self.y)
# Do sanity checks. Some of these depend on everything being
# finalized.
# Do sanity checks. Some of these depend on everything being finalized.
self._check_sym_sanity()
self._check_choice_sanity()
for sym in self.unique_defined_syms:
_check_sym_sanity(sym)
# KCONFIG_STRICT is an older alias for KCONFIG_WARN_UNDEF, supported
# for backwards compatibility
if os.environ.get("KCONFIG_WARN_UNDEF") == "y" or \
os.environ.get("KCONFIG_STRICT") == "y":
for choice in self.unique_choices:
_check_choice_sanity(choice)
if os.environ.get("KCONFIG_STRICT") == "y":
self._check_undef_syms()
@ -1626,8 +1628,8 @@ class Kconfig(object):
def enable_undef_warnings(self):
"""
Enables warnings for assignments to undefined symbols. Disabled by
default since they tend to be spammy for Kernel configurations (and
mostly suggests cleanups).
default unless the KCONFIG_WARN_UNDEF_ASSIGN environment variable was
set to 'y' when the Kconfig instance was created.
"""
self._warn_for_undef_assign = True
@ -2450,8 +2452,6 @@ class Kconfig(object):
while self._next_line():
t0 = self._next_token()
if t0 is None:
continue
if t0 in (_T_CONFIG, _T_MENUCONFIG):
# The tokenizer allocates Symbol objects for us
@ -2479,6 +2479,10 @@ class Kconfig(object):
# Tricky Python semantics: This assigns prev.next before prev
prev.next = prev = node
elif t0 is None:
# Blank line
continue
elif t0 in (_T_SOURCE, _T_RSOURCE, _T_OSOURCE, _T_ORSOURCE):
pattern = self._expect_str_and_eol()
@ -2579,8 +2583,6 @@ class Kconfig(object):
if self._peek_token() is None:
choice = Choice()
choice.direct_dep = self.n
self.choices.append(choice)
else:
# Named choice
name = self._expect_str_and_eol()
@ -2589,9 +2591,9 @@ class Kconfig(object):
choice = Choice()
choice.name = name
choice.direct_dep = self.n
self.named_choices[name] = choice
self.choices.append(choice)
self.named_choices[name] = choice
choice.kconfig = self
@ -2645,7 +2647,8 @@ class Kconfig(object):
def _parse_properties(self, node):
# Parses and adds properties to the MenuNode 'node' (type, 'prompt',
# 'default's, etc.) Properties are later copied up to symbols and
# choices in a separate pass after parsing, in _add_props_to_sc().
# choices in a separate pass after parsing, in e.g.
# _add_props_to_sym().
#
# An older version of this code added properties directly to symbols
# and choices instead of to their menu nodes (and handled dependency
@ -2664,8 +2667,6 @@ class Kconfig(object):
while self._next_line():
t0 = self._next_token()
if t0 is None:
continue
if t0 in _TYPE_TOKENS:
self._set_type(node, _TOKEN_TO_TYPE[t0])
@ -2689,12 +2690,9 @@ class Kconfig(object):
node.selects.append((self._expect_nonconst_sym(),
self._parse_cond()))
elif t0 is _T_IMPLY:
if not isinstance(node.item, Symbol):
self._parse_error("only symbols can imply")
node.implies.append((self._expect_nonconst_sym(),
self._parse_cond()))
elif t0 is None:
# Blank line
continue
elif t0 is _T_DEFAULT:
node.defaults.append((self._parse_expr(False),
@ -2714,6 +2712,21 @@ class Kconfig(object):
self._expect_sym(),
self._parse_cond()))
elif t0 is _T_IMPLY:
if not isinstance(node.item, Symbol):
self._parse_error("only symbols can imply")
node.implies.append((self._expect_nonconst_sym(),
self._parse_cond()))
elif t0 is _T_VISIBLE:
if not self._check_token(_T_IF):
self._parse_error('expected "if" after "visible"')
node.visibility = self._make_and(node.visibility,
self._expect_expr_and_eol())
elif t0 is _T_OPTION:
if self._check_token(_T_ENV):
if not self._check_token(_T_EQUAL):
@ -2779,13 +2792,6 @@ class Kconfig(object):
else:
self._parse_error("unrecognized option")
elif t0 is _T_VISIBLE:
if not self._check_token(_T_IF):
self._parse_error('expected "if" after "visible"')
node.visibility = self._make_and(node.visibility,
self._expect_expr_and_eol())
elif t0 is _T_OPTIONAL:
if not isinstance(node.item, Choice):
self._parse_error('"optional" is only valid for choices')
@ -2983,6 +2989,9 @@ class Kconfig(object):
# The calculated sets might be larger than necessary as we don't do any
# complex analysis of the expressions.
# Optimization
make_depend_on = _make_depend_on
# Only calculate _dependents for defined symbols. Constant and
# undefined symbols could theoretically be selected/implied, but it
# wouldn't change their value, so it's not a true dependency.
@ -2992,29 +3001,29 @@ class Kconfig(object):
# The prompt conditions
for node in sym.nodes:
if node.prompt:
_make_depend_on(sym, node.prompt[1])
make_depend_on(sym, node.prompt[1])
# The default values and their conditions
for value, cond in sym.defaults:
_make_depend_on(sym, value)
_make_depend_on(sym, cond)
make_depend_on(sym, value)
make_depend_on(sym, cond)
# The reverse and weak reverse dependencies
_make_depend_on(sym, sym.rev_dep)
_make_depend_on(sym, sym.weak_rev_dep)
make_depend_on(sym, sym.rev_dep)
make_depend_on(sym, sym.weak_rev_dep)
# The ranges along with their conditions
for low, high, cond in sym.ranges:
_make_depend_on(sym, low)
_make_depend_on(sym, high)
_make_depend_on(sym, cond)
make_depend_on(sym, low)
make_depend_on(sym, high)
make_depend_on(sym, cond)
# The direct dependencies. This is usually redundant, as the direct
# dependencies get propagated to properties, but it's needed to get
# invalidation solid for 'imply', which only checks the direct
# dependencies (even if there are no properties to propagate it
# to).
_make_depend_on(sym, sym.direct_dep)
make_depend_on(sym, sym.direct_dep)
# In addition to the above, choice symbols depend on the choice
# they're in, but that's handled automatically since the Choice is
@ -3027,11 +3036,11 @@ class Kconfig(object):
# The prompt conditions
for node in choice.nodes:
if node.prompt:
_make_depend_on(choice, node.prompt[1])
make_depend_on(choice, node.prompt[1])
# The default symbol conditions
for _, cond in choice.defaults:
_make_depend_on(choice, cond)
make_depend_on(choice, cond)
def _add_choice_deps(self):
# Choices also depend on the choice symbols themselves, because the
@ -3105,7 +3114,7 @@ class Kconfig(object):
elif isinstance(node.item, Symbol):
# Add the node's non-node-specific properties (defaults, ranges,
# etc.) to the Symbol
self._add_props_to_sc(node)
self._add_props_to_sym(node)
# See if we can create an implicit menu rooted at the Symbol and
# finalize each child menu node in that menu if so, like for the
@ -3135,8 +3144,12 @@ class Kconfig(object):
# Empty choices (node.list None) are possible, so this needs to go
# outside
if isinstance(node.item, Choice):
# Add the node's non-node-specific properties to the choice
self._add_props_to_sc(node)
# Add the node's non-node-specific properties to the choice, like
# _add_props_to_sym() does
choice = node.item
choice.direct_dep = self._make_or(choice.direct_dep, node.dep)
choice.defaults += node.defaults
_finalize_choice(node)
def _propagate_deps(self, node, visible_if):
@ -3189,52 +3202,197 @@ class Kconfig(object):
cur = cur.next
def _add_props_to_sc(self, node):
def _add_props_to_sym(self, node):
# Copies properties from the menu node 'node' up to its contained
# symbol or choice.
# symbol, and adds (weak) reverse dependencies to selected/implied
# symbols.
#
# This can't be rolled into _propagate_deps(), because that function
# traverses the menu tree roughly breadth-first order, meaning
# properties on symbols and choices defined in multiple locations could
# end up in the wrong order.
# traverses the menu tree roughly breadth-first, meaning properties on
# symbols defined in multiple locations could end up in the wrong
# order.
# Symbol or choice
sc = node.item
sym = node.item
# See the Symbol class docstring
sc.direct_dep = self._make_or(sc.direct_dep, node.dep)
sym.direct_dep = self._make_or(sym.direct_dep, node.dep)
sc.defaults += node.defaults
# The properties below aren't available on choices
if node.ranges:
sc.ranges += node.ranges
if node.selects:
sc.selects += node.selects
sym.defaults += node.defaults
sym.ranges += node.ranges
sym.selects += node.selects
sym.implies += node.implies
# Modify the reverse dependencies of the selected symbol
for target, cond in node.selects:
target.rev_dep = self._make_or(
target.rev_dep,
self._make_and(sc, cond))
if node.implies:
sc.implies += node.implies
self._make_and(sym, cond))
# Modify the weak reverse dependencies of the implied
# symbol
for target, cond in node.implies:
target.weak_rev_dep = self._make_or(
target.weak_rev_dep,
self._make_and(sc, cond))
self._make_and(sym, cond))
#
# Misc.
#
def _check_sym_sanity(self):
# Checks various symbol properties that are handiest to check after
# parsing. Only generates errors and warnings.
def num_ok(sym, type_):
# Returns True if the (possibly constant) symbol 'sym' is valid as a value
# for a symbol of type type_ (INT or HEX)
# 'not sym.nodes' implies a constant or undefined symbol, e.g. a plain
# "123"
if not sym.nodes:
return _is_base_n(sym.name, _TYPE_TO_BASE[type_])
return sym.orig_type is type_
for sym in self.unique_defined_syms:
if sym.orig_type in (BOOL, TRISTATE):
# A helper function could be factored out here, but keep it
# speedy/straightforward
for target_sym, _ in sym.selects:
if target_sym.orig_type not in (BOOL, TRISTATE, UNKNOWN):
self._warn("{} selects the {} symbol {}, which is not "
"bool or tristate"
.format(_name_and_loc(sym),
TYPE_TO_STR[target_sym.orig_type],
_name_and_loc(target_sym)))
for target_sym, _ in sym.implies:
if target_sym.orig_type not in (BOOL, TRISTATE, UNKNOWN):
self._warn("{} implies the {} symbol {}, which is not "
"bool or tristate"
.format(_name_and_loc(sym),
TYPE_TO_STR[target_sym.orig_type],
_name_and_loc(target_sym)))
elif sym.orig_type in (STRING, INT, HEX):
for default, _ in sym.defaults:
if not isinstance(default, Symbol):
raise KconfigError(
"the {} symbol {} has a malformed default {} -- expected "
"a single symbol"
.format(TYPE_TO_STR[sym.orig_type], _name_and_loc(sym),
expr_str(default)))
if sym.orig_type is STRING:
if not default.is_constant and not default.nodes and \
not default.name.isupper():
# 'default foo' on a string symbol could be either a symbol
# reference or someone leaving out the quotes. Guess that
# the quotes were left out if 'foo' isn't all-uppercase
# (and no symbol named 'foo' exists).
self._warn("style: quotes recommended around "
"default value for string symbol "
+ _name_and_loc(sym))
elif sym.orig_type in (INT, HEX) and \
not num_ok(default, sym.orig_type):
self._warn("the {0} symbol {1} has a non-{0} default {2}"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym),
_name_and_loc(default)))
if sym.selects or sym.implies:
self._warn("the {} symbol {} has selects or implies"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym)))
else: # UNKNOWN
self._warn("{} defined without a type"
.format(_name_and_loc(sym)))
if sym.ranges:
if sym.orig_type not in (INT, HEX):
self._warn(
"the {} symbol {} has ranges, but is not int or hex"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym)))
else:
for low, high, _ in sym.ranges:
if not num_ok(low, sym.orig_type) or \
not num_ok(high, sym.orig_type):
self._warn("the {0} symbol {1} has a non-{0} "
"range [{2}, {3}]"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym),
_name_and_loc(low),
_name_and_loc(high)))
def _check_choice_sanity(self):
# Checks various choice properties that are handiest to check after
# parsing. Only generates errors and warnings.
def warn_select_imply(sym, expr, expr_type):
msg = "the choice symbol {} is {} by the following symbols, which " \
"has no effect: ".format(_name_and_loc(sym), expr_type)
# si = select/imply
for si in split_expr(expr, OR):
msg += "\n - " + _name_and_loc(split_expr(si, AND)[0])
self._warn(msg)
for choice in self.unique_choices:
if choice.orig_type not in (BOOL, TRISTATE):
self._warn("{} defined with type {}"
.format(_name_and_loc(choice),
TYPE_TO_STR[choice.orig_type]))
for node in choice.nodes:
if node.prompt:
break
else:
self._warn(_name_and_loc(choice) + " defined without a prompt")
for default, _ in choice.defaults:
if not isinstance(default, Symbol):
raise KconfigError(
"{} has a malformed default {}"
.format(_name_and_loc(choice), expr_str(default)))
if default.choice is not choice:
self._warn("the default selection {} of {} is not "
"contained in the choice"
.format(_name_and_loc(default),
_name_and_loc(choice)))
for sym in choice.syms:
if sym.defaults:
self._warn("default on the choice symbol {} will have "
"no effect, as defaults do not affect choice "
"symbols".format(_name_and_loc(sym)))
if sym.rev_dep is not sym.kconfig.n:
warn_select_imply(sym, sym.rev_dep, "selected")
if sym.weak_rev_dep is not sym.kconfig.n:
warn_select_imply(sym, sym.weak_rev_dep, "implied")
for node in sym.nodes:
if node.parent.item is choice:
if not node.prompt:
self._warn("the choice symbol {} has no prompt"
.format(_name_and_loc(sym)))
elif node.prompt:
self._warn("the choice symbol {} is defined with a "
"prompt outside the choice"
.format(_name_and_loc(sym)))
def _parse_error(self, msg):
if self._filename is None:
loc = ""
@ -3285,6 +3443,29 @@ class Kconfig(object):
# Prints warnings for all references to undefined symbols within the
# Kconfig files
def is_num(s):
# Returns True if the string 's' looks like a number.
#
# Internally, all operands in Kconfig are symbols, only undefined symbols
# (which numbers usually are) get their name as their value.
#
# Only hex numbers that start with 0x/0X are classified as numbers.
# Otherwise, symbols whose names happen to contain only the letters A-F
# would trigger false positives.
try:
int(s)
except ValueError:
if not s.startswith(("0x", "0X")):
return False
try:
int(s, 16)
except ValueError:
return False
return True
for sym in (self.syms.viewvalues if _IS_PY2 else self.syms.values)():
# - sym.nodes empty means the symbol is undefined (has no
# definition locations)
@ -3293,7 +3474,7 @@ class Kconfig(object):
# symbols, but shouldn't be flagged
#
# - The MODULES symbol always exists
if not sym.nodes and not _is_num(sym.name) and \
if not sym.nodes and not is_num(sym.name) and \
sym.name != "MODULES":
msg = "undefined symbol {}:".format(sym.name)
@ -3450,8 +3631,29 @@ class Symbol(object):
config_string:
The .config assignment string that would get written out for the symbol
by Kconfig.write_config(). Returns the empty string if no .config
assignment would get written out. In general, visible symbols, symbols
with (active) defaults, and selected symbols get written out.
assignment would get written out.
In general, visible symbols, symbols with (active) defaults, and selected
symbols get written out. This includes all non-n-valued bool/tristate
symbols, and all visible string/int/hex symbols.
Symbols with the (no longer needed) 'option env=...' option generate no
configuration output, and neither does the special
'option defconfig_list' symbol.
Tip: This field is useful when generating custom configuration output,
even for non-.config-like formats. To write just the symbols that would
get written out to .config files, do this:
if sym.config_string:
*Write symbol, e.g. by looking sym.str_value*
This is a superset of the symbols written out by write_autoconf().
That function skips all n-valued symbols.
There usually won't be any great harm in just writing all symbols either,
though you might get some special symbols and possibly some "redundant"
n-valued symbol entries in there.
nodes:
A list of MenuNodes for this symbol. Will contain a single MenuNode for
@ -5091,7 +5293,11 @@ class Variable(object):
expanded_value:
The expanded value of the variable. For simple variables (those defined
with :=), this will equal 'value'. Accessing this property will raise a
KconfigError if any variable in the expansion expands to itself.
KconfigError if the expansion seems to be stuck in a loop.
Note: Accessing this field is the same as calling expanded_value_w_args()
with no arguments. I hadn't considered function arguments when adding it.
It is retained for backwards compatibility though.
is_recursive:
True if the variable is recursive (defined with =).
@ -5109,7 +5315,22 @@ class Variable(object):
"""
See the class documentation.
"""
return self.kconfig._expand_whole(self.value, ())
return self.expanded_value_w_args()
def expanded_value_w_args(self, *args):
"""
Returns the expanded value of the variable/function. Any arguments
passed will be substituted for $(1), $(2), etc.
Raises a KconfigError if the expansion seems to be stuck in a loop.
"""
return self.kconfig._fn_val((self.name,) + args)
def __repr__(self):
return "<variable {}, {}, value '{}'>" \
.format(self.name,
"recursive" if self.is_recursive else "immediate",
self.value)
class KconfigError(Exception):
"""
@ -5437,29 +5658,6 @@ def _strcmp(s1, s2):
return (s1 > s2) - (s1 < s2)
def _is_num(s):
# Returns True if the string 's' looks like a number.
#
# Internally, all operands in Kconfig are symbols, only undefined symbols
# (which numbers usually are) get their name as their value.
#
# Only hex numbers that start with 0x/0X are classified as numbers.
# Otherwise, symbols whose names happen to contain only the letters A-F
# would trigger false positives.
try:
int(s)
except ValueError:
if not s.startswith(("0x", "0X")):
return False
try:
int(s, 16)
except ValueError:
return False
return True
def _sym_to_num(sym):
# expr_value() helper for converting a symbol to a number. Raises
# ValueError for symbols that can't be converted.
@ -5504,7 +5702,9 @@ def _decoding_error(e, filename, macro_linenr=None):
def _name_and_loc(sc):
# Helper for giving the symbol/choice name and location(s) in e.g. warnings
name = sc.name or "<choice>"
# Reuse the expression format. That way choices show up as
# '<choice (name, if any)>'
name = standard_sc_expr_str(sc)
if not sc.nodes:
return name + " (undefined)"
@ -5781,158 +5981,6 @@ def _found_dep_loop(loop, cur):
raise KconfigError(msg)
def _check_sym_sanity(sym):
# Checks various symbol properties that are handiest to check after
# parsing. Only generates errors and warnings.
if sym.orig_type in (BOOL, TRISTATE):
# A helper function could be factored out here, but keep it
# speedy/straightforward for now. bool/tristate symbols are by far the
# most common, and most lack selects and implies.
for target_sym, _ in sym.selects:
if target_sym.orig_type not in (BOOL, TRISTATE, UNKNOWN):
sym.kconfig._warn("{} selects the {} symbol {}, which is not "
"bool or tristate"
.format(_name_and_loc(sym),
TYPE_TO_STR[target_sym.orig_type],
_name_and_loc(target_sym)))
for target_sym, _ in sym.implies:
if target_sym.orig_type not in (BOOL, TRISTATE, UNKNOWN):
sym.kconfig._warn("{} implies the {} symbol {}, which is not "
"bool or tristate"
.format(_name_and_loc(sym),
TYPE_TO_STR[target_sym.orig_type],
_name_and_loc(target_sym)))
elif sym.orig_type in (STRING, INT, HEX):
for default, _ in sym.defaults:
if not isinstance(default, Symbol):
raise KconfigError(
"the {} symbol {} has a malformed default {} -- expected "
"a single symbol"
.format(TYPE_TO_STR[sym.orig_type], _name_and_loc(sym),
expr_str(default)))
if sym.orig_type is STRING:
if not default.is_constant and not default.nodes and \
not default.name.isupper():
# 'default foo' on a string symbol could be either a symbol
# reference or someone leaving out the quotes. Guess that
# the quotes were left out if 'foo' isn't all-uppercase
# (and no symbol named 'foo' exists).
sym.kconfig._warn("style: quotes recommended around "
"default value for string symbol "
+ _name_and_loc(sym))
elif sym.orig_type in (INT, HEX) and \
not _int_hex_ok(default, sym.orig_type):
sym.kconfig._warn("the {0} symbol {1} has a non-{0} default {2}"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym),
_name_and_loc(default)))
if sym.selects or sym.implies:
sym.kconfig._warn("the {} symbol {} has selects or implies"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym)))
else: # UNKNOWN
sym.kconfig._warn("{} defined without a type"
.format(_name_and_loc(sym)))
if sym.ranges:
if sym.orig_type not in (INT, HEX):
sym.kconfig._warn(
"the {} symbol {} has ranges, but is not int or hex"
.format(TYPE_TO_STR[sym.orig_type], _name_and_loc(sym)))
else:
for low, high, _ in sym.ranges:
if not _int_hex_ok(low, sym.orig_type) or \
not _int_hex_ok(high, sym.orig_type):
sym.kconfig._warn("the {0} symbol {1} has a non-{0} range "
"[{2}, {3}]"
.format(TYPE_TO_STR[sym.orig_type],
_name_and_loc(sym),
_name_and_loc(low),
_name_and_loc(high)))
def _int_hex_ok(sym, type_):
# Returns True if the (possibly constant) symbol 'sym' is valid as a value
# for a symbol of type type_ (INT or HEX)
# 'not sym.nodes' implies a constant or undefined symbol, e.g. a plain
# "123"
if not sym.nodes:
return _is_base_n(sym.name, _TYPE_TO_BASE[type_])
return sym.orig_type is type_
def _check_choice_sanity(choice):
# Checks various choice properties that are handiest to check after
# parsing. Only generates errors and warnings.
if choice.orig_type not in (BOOL, TRISTATE):
choice.kconfig._warn("{} defined with type {}"
.format(_name_and_loc(choice),
TYPE_TO_STR[choice.orig_type]))
for node in choice.nodes:
if node.prompt:
break
else:
choice.kconfig._warn(_name_and_loc(choice) +
" defined without a prompt")
for default, _ in choice.defaults:
if not isinstance(default, Symbol):
raise KconfigError(
"{} has a malformed default {}"
.format(_name_and_loc(choice), expr_str(default)))
if default.choice is not choice:
choice.kconfig._warn("the default selection {} of {} is not "
"contained in the choice"
.format(_name_and_loc(default),
_name_and_loc(choice)))
for sym in choice.syms:
if sym.defaults:
sym.kconfig._warn("default on the choice symbol {} will have "
"no effect".format(_name_and_loc(sym)))
if sym.rev_dep is not sym.kconfig.n:
_warn_choice_select_imply(sym, sym.rev_dep, "selected")
if sym.weak_rev_dep is not sym.kconfig.n:
_warn_choice_select_imply(sym, sym.weak_rev_dep, "implied")
for node in sym.nodes:
if node.parent.item is choice:
if not node.prompt:
sym.kconfig._warn("the choice symbol {} has no prompt"
.format(_name_and_loc(sym)))
elif node.prompt:
sym.kconfig._warn("the choice symbol {} is defined with a "
"prompt outside the choice"
.format(_name_and_loc(sym)))
def _warn_choice_select_imply(sym, expr, expr_type):
msg = "the choice symbol {} is {} by the following symbols, which has " \
"no effect: ".format(_name_and_loc(sym), expr_type)
# si = select/imply
for si in split_expr(expr, OR):
msg += "\n - " + _name_and_loc(split_expr(si, AND)[0])
sym.kconfig._warn(msg)
# Predefined preprocessor functions

View file

@ -22,8 +22,8 @@ inspired by Vi:
The mconf feature where pressing a key jumps to a menu entry with that
character in it in the current menu isn't supported. A jump-to feature for
jumping directly to any symbol (including invisible symbols) is available
instead.
jumping directly to any symbol (including invisible symbols), choice, menu or
comment (as in a Kconfig 'comment "Foo"') is available instead.
Space and Enter are "smart" and try to do what you'd expect for the given
menu entry.
@ -226,7 +226,7 @@ _MAIN_HELP_LINES = """
# Lines of help text shown at the bottom of the information dialog
_INFO_HELP_LINES = """
[ESC/q] Return to menu
[ESC/q] Return to menu [/] Jump to symbol
"""[1:-1].split("\n")
# Lines of help text shown at the bottom of the search dialog
@ -658,7 +658,21 @@ def menuconfig(kconf):
if _CONVERT_C_LC_CTYPE_TO_UTF8:
_convert_c_lc_ctype_to_utf8()
# Get rid of the delay between pressing ESC and jumping to the parent menu
# Get rid of the delay between pressing ESC and jumping to the parent menu,
# unless the user has set ESCDELAY (see ncurses(3)). This makes the UI much
# smoother to work with.
#
# Note: This is strictly pretty iffy, since escape codes for e.g. cursor
# keys start with ESC, but I've never seen it cause problems in practice
# (probably because it's unlikely that the escape code for a key would get
# split up across read()s, at least with a terminal emulator). Please
# report if you run into issues. Some suitable small default value could be
# used here instead in that case. Maybe it's silly to not put in the
# smallest imperceptible delay here already, though I don't like guessing.
#
# (From a quick glance at the ncurses source code, ESCDELAY might only be
# relevant for mouse events there, so maybe escapes are assumed to arrive
# in one piece already...)
os.environ.setdefault("ESCDELAY", "0")
# Enter curses mode. _menuconfig() returns a string to print on exit, after
@ -1019,18 +1033,33 @@ def _jump_to(node):
# parent menus before.
_parent_screen_rows = []
old_show_all = _show_all
jump_into = (isinstance(node.item, Choice) or node.item == MENU) and \
node.list
# If we're jumping to a non-empty choice or menu, jump to the first entry
# in it instead of jumping to its menu node
if jump_into:
_cur_menu = node
node = node.list
else:
_cur_menu = _parent_menu(node)
_shown = _shown_nodes(_cur_menu)
if node not in _shown:
# Turn on show-all mode if the node wouldn't be shown. Checking whether
# the node is visible instead would needlessly turn on show-all mode in
# an obscure case: when jumping to an invisible symbol with visible
# children from an implicit submenu.
# The node wouldn't be shown. Turn on show-all to show it.
_show_all = True
_shown = _shown_nodes(_cur_menu)
_sel_node_i = _shown.index(node)
if jump_into and not old_show_all and _show_all:
# If we're jumping into a choice or menu and were forced to turn on
# show-all because the first entry wasn't visible, try turning it off.
# That will land us at the first visible node if there are visible
# nodes, and is a no-op otherwise.
_toggle_show_all()
_center_vertically()
def _leave_menu():
@ -1115,7 +1144,8 @@ def _select_first_menu_entry():
_sel_node_i = _menu_scroll = 0
def _toggle_show_all():
# Toggles show-all mode on/off
# Toggles show-all mode on/off. If turning it off would give no visible
# items in the current menu, it is left on.
global _show_all
global _shown
@ -1178,46 +1208,6 @@ def _draw_main():
term_width = _stdscr.getmaxyx()[1]
#
# Update the top row with the menu path
#
_path_win.erase()
# Draw the menu path ("(top menu) -> menu -> submenu -> ...")
menu_prompts = []
menu = _cur_menu
while menu is not _kconf.top_node:
# Promptless choices can be entered in show-all mode. Use
# standard_sc_expr_str() for them, so they show up as
# '<choice (name if any)>'.
menu_prompts.append(menu.prompt[0] if menu.prompt else
standard_sc_expr_str(menu.item))
menu = _parent_menu(menu)
menu_prompts.append("(top menu)")
menu_prompts.reverse()
# Hack: We can't put ACS_RARROW directly in the string. Temporarily
# represent it with NULL. Maybe using a Unicode character would be better.
menu_path_str = " \0 ".join(menu_prompts)
# Scroll the menu path to the right if needed to make the current menu's
# title visible
if len(menu_path_str) > term_width:
menu_path_str = menu_path_str[len(menu_path_str) - term_width:]
# Print the path with the arrows reinserted
split_path = menu_path_str.split("\0")
_safe_addstr(_path_win, split_path[0])
for s in split_path[1:]:
_safe_addch(_path_win, curses.ACS_RARROW)
_safe_addstr(_path_win, s)
_path_win.noutrefresh()
#
# Update the separator row below the menu path
#
@ -1236,6 +1226,7 @@ def _draw_main():
_top_sep_win.noutrefresh()
# Note: The menu path at the top is deliberately updated last. See below.
#
# Update the symbol window
@ -1298,6 +1289,51 @@ def _draw_main():
_help_win.noutrefresh()
#
# Update the top row with the menu path.
#
# Doing this last leaves the cursor on the top row, which avoids some minor
# annoying jumpiness in gnome-terminal when reducing the height of the
# terminal. It seems to happen whenever the row with the cursor on it
# disappears.
#
_path_win.erase()
# Draw the menu path ("(top menu) -> menu -> submenu -> ...")
menu_prompts = []
menu = _cur_menu
while menu is not _kconf.top_node:
# Promptless choices can be entered in show-all mode. Use
# standard_sc_expr_str() for them, so they show up as
# '<choice (name if any)>'.
menu_prompts.append(menu.prompt[0] if menu.prompt else
standard_sc_expr_str(menu.item))
menu = _parent_menu(menu)
menu_prompts.append("(top menu)")
menu_prompts.reverse()
# Hack: We can't put ACS_RARROW directly in the string. Temporarily
# represent it with NULL. Maybe using a Unicode character would be better.
menu_path_str = " \0 ".join(menu_prompts)
# Scroll the menu path to the right if needed to make the current menu's
# title visible
if len(menu_path_str) > term_width:
menu_path_str = menu_path_str[len(menu_path_str) - term_width:]
# Print the path with the arrows reinserted
split_path = menu_path_str.split("\0")
_safe_addstr(_path_win, split_path[0])
for s in split_path[1:]:
_safe_addch(_path_win, curses.ACS_RARROW)
_safe_addstr(_path_win, s)
_path_win.noutrefresh()
def _parent_menu(node):
# Returns the menu node of the menu that contains 'node'. In addition to
# proper 'menu's, this might also be a 'menuconfig' symbol or a 'choice'.
@ -1854,13 +1890,20 @@ def _jump_to_dialog():
# List of matching nodes
matches = []
for node in _searched_nodes():
# Search symbols and choices
for node in _sorted_sc_nodes():
# Symbol/choice
sc = node.item
for search in regex_searches:
# Both the name and the prompt might be missing, since
# we're searching both symbols and choices
# Does the regex match either the symbol name or the
# prompt (if any)?
if not (search(node.item.name.lower()) or
(node.prompt and
search(node.prompt[0].lower()))):
if not (sc.name and search(sc.name.lower()) or
node.prompt and search(node.prompt[0].lower())):
# Give up on the first regex that doesn't match, to
# speed things up a bit when multiple regexes are
@ -1870,6 +1913,15 @@ def _jump_to_dialog():
else:
matches.append(node)
# Search menus and comments
for node in _sorted_menu_comment_nodes():
for search in regex_searches:
if not search(node.prompt[0].lower()):
break
else:
matches.append(node)
except re.error as e:
# Bad regex. Remember the error message so we can show it.
bad_re = "Bad regular expression"
@ -1909,9 +1961,10 @@ def _jump_to_dialog():
sel_node_i, scroll)
elif c == "\x06": # \x06 = Ctrl-F
if matches:
_safe_curs_set(0)
_info_dialog(matches[sel_node_i], True)
_safe_curs_set(1)
_safe_curs_set(2)
scroll = _resize_jump_to_dialog(
edit_box, matches_win, bot_sep_win, help_win,
@ -1937,20 +1990,45 @@ def _jump_to_dialog():
s, s_i, hscroll = _edit_text(c, s, s_i, hscroll,
edit_box.getmaxyx()[1] - 2)
# Obscure Python: We never pass a value for cached_search_nodes, and it keeps
# pointing to the same list. This avoids a global.
def _searched_nodes(cached_search_nodes=[]):
# Returns a list of menu nodes to search, sorted by symbol name
# Obscure Python: We never pass a value for cached_nodes, and it keeps pointing
# to the same list. This avoids a global.
def _sorted_sc_nodes(cached_nodes=[]):
# Returns a sorted list of symbol and choice nodes to search. The symbol
# nodes appear first, sorted by name, and then the choice nodes, sorted by
# prompt and (secondarily) name.
if not cached_search_nodes:
# Sort symbols by name, then add all nodes for each symbol
if not cached_nodes:
# Add symbol nodes
for sym in sorted(_kconf.unique_defined_syms,
key=lambda sym: sym.name):
# += is in-place for lists
cached_search_nodes += sym.nodes
cached_nodes += sym.nodes
return cached_search_nodes
# Add choice nodes
choices = sorted(_kconf.unique_choices,
key=lambda choice: choice.name or "")
cached_nodes += sorted(
[node
for choice in choices
for node in choice.nodes],
key=lambda node: node.prompt[0] if node.prompt else "")
return cached_nodes
def _sorted_menu_comment_nodes(cached_nodes=[]):
# Returns a list of menu and comment nodes to search, sorted by prompt,
# with the menus first
if not cached_nodes:
def prompt_text(mc):
return mc.prompt[0]
cached_nodes += sorted(_kconf.menus, key=prompt_text) + \
sorted(_kconf.comments, key=prompt_text)
return cached_nodes
def _resize_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win,
sel_node_i, scroll):
@ -2009,13 +2087,18 @@ def _draw_jump_to_dialog(edit_box, matches_win, bot_sep_win, help_win,
for i in range(scroll,
min(scroll + matches_win.getmaxyx()[0], len(matches))):
sym = matches[i].item
node = matches[i]
sym_str = _name_and_val_str(sym)
if matches[i].prompt:
sym_str += ' "{}"'.format(matches[i].prompt[0])
if isinstance(node.item, (Symbol, Choice)):
node_str = _name_and_val_str(node.item)
if node.prompt:
node_str += ' "{}"'.format(node.prompt[0])
elif node.item == MENU:
node_str = 'menu "{}"'.format(node.prompt[0])
else: # node.item == COMMENT
node_str = 'comment "{}"'.format(node.prompt[0])
_safe_addstr(matches_win, i - scroll, 0, sym_str,
_safe_addstr(matches_win, i - scroll, 0, node_str,
_style["selection" if i == sel_node_i else "list"])
else:
@ -2189,27 +2272,7 @@ def _draw_info_dialog(node, lines, scroll, top_line_win, text_win,
text_win_height, text_win_width = text_win.getmaxyx()
#
# Update top row
#
top_line_win.erase()
# Draw arrows pointing up if the information window is scrolled down. Draw
# them before drawing the title, so the title ends up on top for small
# windows.
if scroll > 0:
_safe_hline(top_line_win, 0, 4, curses.ACS_UARROW, _N_SCROLL_ARROWS)
title = ("Symbol" if isinstance(node.item, Symbol) else
"Choice" if isinstance(node.item, Choice) else
"Menu" if node.item == MENU else
"Comment") + " information"
_safe_addstr(top_line_win, 0, max((text_win_width - len(title))//2, 0),
title)
top_line_win.noutrefresh()
# Note: The top row is deliberately updated last. See _draw_main().
#
# Update text display
@ -2247,6 +2310,28 @@ def _draw_info_dialog(node, lines, scroll, top_line_win, text_win,
help_win.noutrefresh()
#
# Update top row
#
top_line_win.erase()
# Draw arrows pointing up if the information window is scrolled down. Draw
# them before drawing the title, so the title ends up on top for small
# windows.
if scroll > 0:
_safe_hline(top_line_win, 0, 4, curses.ACS_UARROW, _N_SCROLL_ARROWS)
title = ("Symbol" if isinstance(node.item, Symbol) else
"Choice" if isinstance(node.item, Choice) else
"Menu" if node.item == MENU else
"Comment") + " information"
_safe_addstr(top_line_win, 0, max((text_win_width - len(title))//2, 0),
title)
top_line_win.noutrefresh()
def _info_str(node):
# Returns information about the menu node 'node' as a string.
#
@ -2503,24 +2588,20 @@ def _menu_path_info(node):
return "(top menu)" + path
def _name_and_val_str(sc):
# Custom symbol printer that shows the symbol value after the symbol, used
# for the information display
# Custom symbol/choice printer that shows symbol values after symbols
# Show the values of non-constant (non-quoted) symbols that don't look like
# numbers. Things like 123 are actually symbol references, and only work as
# expected due to undefined symbols getting their name as their value.
# Showing the symbol value for those isn't helpful though.
if isinstance(sc, Symbol) and \
not sc.is_constant and \
not _is_num(sc.name):
if isinstance(sc, Symbol) and not sc.is_constant and not _is_num(sc.name):
if not sc.nodes:
# Undefined symbol reference
return "{}(undefined/n)".format(sc.name)
return '{}(={})'.format(sc.name, sc.str_value)
# For other symbols, use the standard format
# For other items, use the standard format
return standard_sc_expr_str(sc)
def _expr_str(expr):
@ -2662,11 +2743,10 @@ def _node_str(node):
s += " " + standard_sc_expr_str(node.item)
if node.prompt:
s += " "
if node.item == COMMENT:
s += "*** {} ***".format(node.prompt[0])
s += " *** {} ***".format(node.prompt[0])
else:
s += node.prompt[0]
s += " " + node.prompt[0]
if isinstance(node.item, Symbol):
sym = node.item
@ -2779,16 +2859,14 @@ def _check_validity(sym, s):
def _range_info(sym):
# Returns a string with information about the valid range for the symbol
# 'sym', or None if 'sym' isn't an int/hex symbol
if sym.type not in (INT, HEX):
return None
# 'sym', or None if 'sym' doesn't have a range
if sym.type in (INT, HEX):
for low, high, cond in sym.ranges:
if expr_value(cond):
return "Range: {}-{}".format(low.str_value, high.str_value)
return "No range constraints."
return None
def _is_num(name):
# Heuristic to see if a symbol name looks like a number, for nicer output