kconfiglib: Update to hide tracebacks for expected errors

Update Kconfiglib to upstream revision 9c0b562c94 to get this commit in:

    Add Kconfig.__init__() helper flag for suppressing tracebacks

    Tools that don't use standard_kconfig() currently generate spammy
    tracebacks for e.g. syntax errors.

    Add a suppress_traceback flag to Kconfig.__init__() for catching
    "expected" exceptions and printing them to stderr and exiting with
    status 1. Use it to make all tools consistently hide tracebacks.

Use the new flag to hide tracebacks for expected exceptions in
kconfig.py, lint.py, and genrest.py.

Some menuconfig robustness tweaks for wonky terminals are included as
well, and a new feature for customizing .config and autoconf.h header
comments via environment variables.

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
This commit is contained in:
Ulf Magnusson 2019-12-17 08:39:07 +01:00 committed by Anas Nashif
commit e181e1b773
5 changed files with 222 additions and 142 deletions

View file

@ -105,7 +105,7 @@ def init():
args = parse_args() args = parse_args()
kconf = kconfiglib.Kconfig(args.kconfig) kconf = kconfiglib.Kconfig(args.kconfig, suppress_traceback=True)
out_dir = args.out_dir out_dir = args.out_dir
index_desc = args.index_desc index_desc = args.index_desc
separate_all_index = args.separate_all_index separate_all_index = args.separate_all_index

View file

@ -29,7 +29,8 @@ def main():
args = parse_args() args = parse_args()
print("Parsing Kconfig tree in " + args.kconfig_root) print("Parsing Kconfig tree in " + args.kconfig_root)
kconf = Kconfig(args.kconfig_root, warn_to_stderr=False) kconf = Kconfig(args.kconfig_root, warn_to_stderr=False,
suppress_traceback=True)
# Warn for assignments to undefined symbols # Warn for assignments to undefined symbols
kconf.warn_assign_undef = True kconf.warn_assign_undef = True

View file

@ -554,7 +554,7 @@ from glob import iglob
from os.path import dirname, exists, expandvars, islink, join, realpath from os.path import dirname, exists, expandvars, islink, join, realpath
VERSION = (13, 2, 0) VERSION = (13, 5, 0)
# File layout: # File layout:
@ -773,8 +773,8 @@ class Kconfig(object):
See Kconfig.load_config() as well. See Kconfig.load_config() as well.
srctree: srctree:
The value of the $srctree environment variable when the configuration was The value the $srctree environment variable had when the Kconfig instance
loaded, or the empty string if $srctree wasn't set. This gives nice was created, or the empty string if $srctree wasn't set. This gives nice
behavior with os.path.join(), which treats "" as the current directory, behavior with os.path.join(), which treats "" as the current directory,
without adding "./". without adding "./".
@ -789,13 +789,22 @@ class Kconfig(object):
if multiple configurations are loaded with different values for $srctree. if multiple configurations are loaded with different values for $srctree.
config_prefix: config_prefix:
The value of the $CONFIG_ environment variable when the configuration was The value the CONFIG_ environment variable had when the Kconfig instance
loaded. This is the prefix used (and expected) on symbol names in .config was created, or "CONFIG_" if CONFIG_ wasn't set. This is the prefix used
files and C headers. Defaults to "CONFIG_". Used in the same way in the C (and expected) on symbol names in .config files and C headers. Used in
tools. the same way in the C tools.
Like for srctree, only the value of $CONFIG_ when the configuration is config_header:
loaded matters. The value the KCONFIG_CONFIG_HEADER environment variable had when the
Kconfig instance was created, or the empty string if
KCONFIG_CONFIG_HEADER wasn't set. This string is inserted verbatim at the
beginning of configuration files. See write_config().
header_header:
The value the KCONFIG_AUTOHEADER_HEADER environment variable had when the
Kconfig instance was created, or the empty string if
KCONFIG_AUTOHEADER_HEADER wasn't set. This string is inserted verbatim at
the beginning of header files. See write_autoconf().
filename/linenr: filename/linenr:
The current parsing location, for use in Python preprocessor functions. The current parsing location, for use in Python preprocessor functions.
@ -810,11 +819,13 @@ class Kconfig(object):
"_warn_assign_no_prompt", "_warn_assign_no_prompt",
"choices", "choices",
"comments", "comments",
"config_header",
"config_prefix", "config_prefix",
"const_syms", "const_syms",
"defconfig_list", "defconfig_list",
"defined_syms", "defined_syms",
"env_vars", "env_vars",
"header_header",
"kconfig_filenames", "kconfig_filenames",
"m", "m",
"menus", "menus",
@ -854,7 +865,7 @@ class Kconfig(object):
# #
def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True, def __init__(self, filename="Kconfig", warn=True, warn_to_stderr=True,
encoding="utf-8"): encoding="utf-8", suppress_traceback=False):
""" """
Creates a new Kconfig object by parsing Kconfig files. Creates a new Kconfig object by parsing Kconfig files.
Note that Kconfig files are not the same as .config files (which store Note that Kconfig files are not the same as .config files (which store
@ -919,7 +930,35 @@ class Kconfig(object):
anyway. anyway.
Related PEP: https://www.python.org/dev/peps/pep-0538/ Related PEP: https://www.python.org/dev/peps/pep-0538/
suppress_traceback (default: False):
Helper for tools. When True, any EnvironmentError or KconfigError
generated during parsing is caught, the exception message is printed
to stderr together with the command name, and sys.exit(1) is called
(which generates SystemExit).
This hides the Python traceback for "expected" errors like syntax
errors in Kconfig files.
Other exceptions besides EnvironmentError and KconfigError are still
propagated when suppress_traceback is True.
""" """
try:
self._init(filename, warn, warn_to_stderr, encoding)
except (EnvironmentError, KconfigError) as e:
if suppress_traceback:
cmd = sys.argv[0] # Empty string if missisng
if cmd:
cmd += ": "
# Some long exception messages have extra newlines for better
# formatting when reported as an unhandled exception. Strip
# them here.
sys.exit(cmd + str(e).strip())
raise
def _init(self, filename, warn, warn_to_stderr, encoding):
# See __init__()
self._encoding = encoding self._encoding = encoding
self.srctree = os.getenv("srctree", "") self.srctree = os.getenv("srctree", "")
@ -943,6 +982,9 @@ class Kconfig(object):
self._unset_match = _re_match(r"# {}([^ ]+) is not set".format( self._unset_match = _re_match(r"# {}([^ ]+) is not set".format(
self.config_prefix)) self.config_prefix))
self.config_header = os.getenv("KCONFIG_CONFIG_HEADER", "")
self.header_header = os.getenv("KCONFIG_AUTOHEADER_HEADER", "")
self.syms = {} self.syms = {}
self.const_syms = {} self.const_syms = {}
self.defined_syms = [] self.defined_syms = []
@ -1349,8 +1391,29 @@ class Kconfig(object):
elif self.warn_assign_override: elif self.warn_assign_override:
self._warn(msg, filename, linenr) self._warn(msg, filename, linenr)
def write_autoconf(self, filename, def load_allconfig(self, filename):
header="/* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */\n"): """
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
(kconf.warn_assign_override/warn_assign_redun = False), and restores
the previous warning settings 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.
filename:
Command-specific configuration filename - "allyes.config",
"allno.config", etc.
"""
load_allconfig(self, filename)
def write_autoconf(self, filename=None, header=None):
r""" r"""
Writes out symbol values as a C header file, matching the format used Writes out symbol values as a C header file, matching the format used
by include/generated/autoconf.h in the kernel. by include/generated/autoconf.h in the kernel.
@ -1364,22 +1427,37 @@ class Kconfig(object):
like the modification time and possibly triggering redundant work in like the modification time and possibly triggering redundant work in
build tools. build tools.
filename: filename (default: None):
Self-explanatory. Path to write header to.
header (default: "/* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */\n"): If None (the default), the path in the environment variable
Text that will be inserted verbatim at the beginning of the file. You KCONFIG_AUTOHEADER is used if set, and "include/generated/autoconf.h"
would usually want it enclosed in '/* */' to make it a C comment, otherwise. This is compatible with the C tools.
and include a final terminating newline.
header (default: None):
Text inserted verbatim at the beginning of the file. You would
usually want it enclosed in '/* */' to make it a C comment, and
include a trailing newline.
If None (the default), the value of the environment variable
KCONFIG_AUTOHEADER_HEADER had when the Kconfig instance was created
will be used if it was set, and no header otherwise. See the
Kconfig.header_header attribute.
""" """
if filename is None:
filename = os.getenv("KCONFIG_AUTOHEADER",
"include/generated/autoconf.h")
self._write_if_changed(filename, self._autoconf_contents(header)) self._write_if_changed(filename, self._autoconf_contents(header))
def _autoconf_contents(self, header): def _autoconf_contents(self, header):
# write_autoconf() helper. Returns the contents to write as a string, # write_autoconf() helper. Returns the contents to write as a string,
# with 'header' at the beginning. # with 'header' or KCONFIG_AUTOHEADER_HEADER at the beginning.
# "".join()ed later if header is None:
chunks = [header] header = self.header_header
chunks = [header] # "".join()ed later
add = chunks.append add = chunks.append
for sym in self.unique_defined_syms: for sym in self.unique_defined_syms:
@ -1415,9 +1493,8 @@ class Kconfig(object):
return "".join(chunks) return "".join(chunks)
def write_config(self, filename=None, def write_config(self, filename=None, header=None, save_old=True,
header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n", verbose=None):
save_old=True, verbose=None):
r""" r"""
Writes out symbol values in the .config format. The format matches the Writes out symbol values in the .config format. The format matches the
C implementation, including ordering. C implementation, including ordering.
@ -1439,16 +1516,21 @@ class Kconfig(object):
(OSError/IOError). KconfigError is never raised here. (OSError/IOError). KconfigError is never raised here.
filename (default: None): filename (default: None):
Filename to save configuration to (a string). Path to write configuration to (a string).
If None (the default), the filename in the environment variable If None (the default), the path in the environment variable
KCONFIG_CONFIG is used if set, and ".config" otherwise. See KCONFIG_CONFIG is used if set, and ".config" otherwise. See
standard_config_filename(). standard_config_filename().
header (default: "# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"): header (default: None):
Text that will be inserted verbatim at the beginning of the file. You Text inserted verbatim at the beginning of the file. You would
would usually want each line to start with '#' to make it a comment, usually want each line to start with '#' to make it a comment, and
and include a final terminating newline. include a trailing newline.
if None (the default), the value of the environment variable
KCONFIG_CONFIG_HEADER had when the Kconfig instance was created will
be used if it was set, and no header otherwise. See the
Kconfig.config_header attribute.
save_old (default: True): save_old (default: True):
If True and <filename> already exists, a copy of it will be saved to If True and <filename> already exists, a copy of it will be saved to
@ -1493,7 +1575,7 @@ class Kconfig(object):
def _config_contents(self, header): def _config_contents(self, header):
# write_config() helper. Returns the contents to write as a string, # write_config() helper. Returns the contents to write as a string,
# with 'header' at the beginning. # with 'header' or KCONFIG_CONFIG_HEADER at the beginning.
# #
# More memory friendly would be to 'yield' the strings and # More memory friendly would be to 'yield' the strings and
# "".join(_config_contents()), but it was a bit slower on my system. # "".join(_config_contents()), but it was a bit slower on my system.
@ -1505,13 +1587,15 @@ class Kconfig(object):
for sym in self.unique_defined_syms: for sym in self.unique_defined_syms:
sym._visited = False sym._visited = False
if header is None:
header = self.config_header
chunks = [header] # "".join()ed later
add = chunks.append
# Did we just print an '# end of ...' comment? # Did we just print an '# end of ...' comment?
after_end_comment = False after_end_comment = False
# "".join()ed later
chunks = [header]
add = chunks.append
node = self.top_node node = self.top_node
while 1: while 1:
# Jump to the next node with an iterative tree walk # Jump to the next node with an iterative tree walk
@ -1564,8 +1648,7 @@ class Kconfig(object):
add("\n#\n# {}\n#\n".format(node.prompt[0])) add("\n#\n# {}\n#\n".format(node.prompt[0]))
after_end_comment = False after_end_comment = False
def write_min_config(self, filename, def write_min_config(self, filename, header=None):
header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"):
""" """
Writes out a "minimal" configuration file, omitting symbols whose value Writes out a "minimal" configuration file, omitting symbols whose value
matches their default value. The format matches the one produced by matches their default value. The format matches the one produced by
@ -1581,12 +1664,17 @@ class Kconfig(object):
(OSError/IOError). KconfigError is never raised here. (OSError/IOError). KconfigError is never raised here.
filename: filename:
Self-explanatory. Path to write minimal configuration to.
header (default: "# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"): header (default: None):
Text that will be inserted verbatim at the beginning of the file. You Text inserted verbatim at the beginning of the file. You would
would usually want each line to start with '#' to make it a comment, usually want each line to start with '#' to make it a comment, and
and include a final terminating newline. include a final terminating newline.
if None (the default), the value of the environment variable
KCONFIG_CONFIG_HEADER had when the Kconfig instance was created will
be used if it was set, and no header otherwise. See the
Kconfig.config_header attribute.
Returns a string with a message saying which file got saved. This is Returns a string with a message saying which file got saved. This is
meant to reduce boilerplate in tools, which can do e.g. meant to reduce boilerplate in tools, which can do e.g.
@ -1603,9 +1691,12 @@ class Kconfig(object):
def _min_config_contents(self, header): def _min_config_contents(self, header):
# write_min_config() helper. Returns the contents to write as a string, # write_min_config() helper. Returns the contents to write as a string,
# with 'header' at the beginning. # with 'header' or KCONFIG_CONFIG_HEADER at the beginning.
chunks = [header] if header is None:
header = self.config_header
chunks = [header] # "".join()ed later
add = chunks.append add = chunks.append
for sym in self.unique_defined_syms: for sym in self.unique_defined_syms:
@ -2122,9 +2213,9 @@ class Kconfig(object):
# it's part of a different construct # it's part of a different construct
if self._reuse_tokens: if self._reuse_tokens:
self._reuse_tokens = False self._reuse_tokens = False
# self._tokens_i is known to be 1 here, because _parse_properties() # self._tokens_i is known to be 1 here, because _parse_props()
# leaves it like that when it can't recognize a line (or parses # leaves it like that when it can't recognize a line (or parses a
# a help text) # help text)
return True return True
# readline() returns '' over and over at EOF, which we rely on for help # readline() returns '' over and over at EOF, which we rely on for help
@ -2141,7 +2232,7 @@ class Kconfig(object):
self._tokens = self._tokenize(line) self._tokens = self._tokenize(line)
# Initialize to 1 instead of 0 to factor out code from _parse_block() # Initialize to 1 instead of 0 to factor out code from _parse_block()
# and _parse_properties(). They immediately fetch self._tokens[0]. # and _parse_props(). They immediately fetch self._tokens[0].
self._tokens_i = 1 self._tokens_i = 1
return True return True
@ -2839,7 +2930,7 @@ class Kconfig(object):
sym.nodes.append(node) sym.nodes.append(node)
self._parse_properties(node) self._parse_props(node)
if node.is_menuconfig and not node.prompt: if node.is_menuconfig and not node.prompt:
self._warn("the menuconfig symbol {} has no prompt" self._warn("the menuconfig symbol {} has no prompt"
@ -2925,7 +3016,7 @@ class Kconfig(object):
self.menus.append(node) self.menus.append(node)
self._parse_properties(node) self._parse_props(node)
self._parse_block(_T_ENDMENU, node, node) self._parse_block(_T_ENDMENU, node, node)
node.list = node.next node.list = node.next
@ -2945,7 +3036,7 @@ class Kconfig(object):
self.comments.append(node) self.comments.append(node)
self._parse_properties(node) self._parse_props(node)
prev.next = prev = node prev.next = prev = node
@ -2977,7 +3068,7 @@ class Kconfig(object):
choice.nodes.append(node) choice.nodes.append(node)
self._parse_properties(node) self._parse_props(node)
self._parse_block(_T_ENDCHOICE, node, node) self._parse_block(_T_ENDCHOICE, node, node)
node.list = node.next node.list = node.next
@ -3019,7 +3110,7 @@ class Kconfig(object):
return expr return expr
def _parse_properties(self, node): def _parse_props(self, node):
# Parses and adds properties to the MenuNode 'node' (type, 'prompt', # Parses and adds properties to the MenuNode 'node' (type, 'prompt',
# 'default's, etc.) Properties are later copied up to symbols and # 'default's, etc.) Properties are later copied up to symbols and
# choices in a separate pass after parsing, in e.g. # choices in a separate pass after parsing, in e.g.
@ -3045,7 +3136,7 @@ class Kconfig(object):
if t0 in _TYPE_TOKENS: if t0 in _TYPE_TOKENS:
# Relies on '_T_BOOL is BOOL', etc., to save a conversion # Relies on '_T_BOOL is BOOL', etc., to save a conversion
self._set_type(node, t0) self._set_type(node.item, t0)
if self._tokens[1] is not None: if self._tokens[1] is not None:
self._parse_prompt(node) self._parse_prompt(node)
@ -3075,7 +3166,7 @@ class Kconfig(object):
self._parse_cond())) self._parse_cond()))
elif t0 in _DEF_TOKEN_TO_TYPE: elif t0 in _DEF_TOKEN_TO_TYPE:
self._set_type(node, _DEF_TOKEN_TO_TYPE[t0]) self._set_type(node.item, _DEF_TOKEN_TO_TYPE[t0])
node.defaults.append((self._parse_expr(False), node.defaults.append((self._parse_expr(False),
self._parse_cond())) self._parse_cond()))
@ -3176,13 +3267,15 @@ class Kconfig(object):
self._reuse_tokens = True self._reuse_tokens = True
return return
def _set_type(self, node, new_type): def _set_type(self, sc, new_type):
# UNKNOWN is falsy # Sets the type of 'sc' (symbol or choice) to 'new_type'
if node.item.orig_type and node.item.orig_type is not new_type:
self._warn("{} defined with multiple types, {} will be used"
.format(node.item.name_and_loc, TYPE_TO_STR[new_type]))
node.item.orig_type = new_type # UNKNOWN is falsy
if sc.orig_type and sc.orig_type is not new_type:
self._warn("{} defined with multiple types, {} will be used"
.format(sc.name_and_loc, TYPE_TO_STR[new_type]))
sc.orig_type = new_type
def _parse_prompt(self, node): def _parse_prompt(self, node):
# 'prompt' properties override each other within a single definition of # 'prompt' properties override each other within a single definition of
@ -3372,7 +3465,7 @@ class Kconfig(object):
# The calculated sets might be larger than necessary as we don't do any # The calculated sets might be larger than necessary as we don't do any
# complex analysis of the expressions. # complex analysis of the expressions.
make_depend_on = _make_depend_on # Micro-optimization depend_on = _depend_on # Micro-optimization
# Only calculate _dependents for defined symbols. Constant and # Only calculate _dependents for defined symbols. Constant and
# undefined symbols could theoretically be selected/implied, but it # undefined symbols could theoretically be selected/implied, but it
@ -3383,29 +3476,29 @@ class Kconfig(object):
# The prompt conditions # The prompt conditions
for node in sym.nodes: for node in sym.nodes:
if node.prompt: if node.prompt:
make_depend_on(sym, node.prompt[1]) depend_on(sym, node.prompt[1])
# The default values and their conditions # The default values and their conditions
for value, cond in sym.defaults: for value, cond in sym.defaults:
make_depend_on(sym, value) depend_on(sym, value)
make_depend_on(sym, cond) depend_on(sym, cond)
# The reverse and weak reverse dependencies # The reverse and weak reverse dependencies
make_depend_on(sym, sym.rev_dep) depend_on(sym, sym.rev_dep)
make_depend_on(sym, sym.weak_rev_dep) depend_on(sym, sym.weak_rev_dep)
# The ranges along with their conditions # The ranges along with their conditions
for low, high, cond in sym.ranges: for low, high, cond in sym.ranges:
make_depend_on(sym, low) depend_on(sym, low)
make_depend_on(sym, high) depend_on(sym, high)
make_depend_on(sym, cond) depend_on(sym, cond)
# The direct dependencies. This is usually redundant, as the direct # The direct dependencies. This is usually redundant, as the direct
# dependencies get propagated to properties, but it's needed to get # dependencies get propagated to properties, but it's needed to get
# invalidation solid for 'imply', which only checks the direct # invalidation solid for 'imply', which only checks the direct
# dependencies (even if there are no properties to propagate it # dependencies (even if there are no properties to propagate it
# to). # to).
make_depend_on(sym, sym.direct_dep) depend_on(sym, sym.direct_dep)
# In addition to the above, choice symbols depend on the choice # In addition to the above, choice symbols depend on the choice
# they're in, but that's handled automatically since the Choice is # they're in, but that's handled automatically since the Choice is
@ -3418,11 +3511,11 @@ class Kconfig(object):
# The prompt conditions # The prompt conditions
for node in choice.nodes: for node in choice.nodes:
if node.prompt: if node.prompt:
make_depend_on(choice, node.prompt[1]) depend_on(choice, node.prompt[1])
# The default symbol conditions # The default symbol conditions
for _, cond in choice.defaults: for _, cond in choice.defaults:
make_depend_on(choice, cond) depend_on(choice, cond)
def _add_choice_deps(self): def _add_choice_deps(self):
# Choices also depend on the choice symbols themselves, because the # Choices also depend on the choice symbols themselves, because the
@ -3776,7 +3869,7 @@ class Kconfig(object):
.format(sym.name_and_loc)) .format(sym.name_and_loc))
def _parse_error(self, msg): def _parse_error(self, msg):
raise KconfigError("{}couldn't parse '{}': {}".format( raise KconfigError("{}error: couldn't parse '{}': {}".format(
"" if self.filename is None else "" if self.filename is None else
"{}:{}: ".format(self.filename, self.linenr), "{}:{}: ".format(self.filename, self.linenr),
self._line.strip(), msg)) self._line.strip(), msg))
@ -5295,8 +5388,8 @@ class Choice(object):
self._cached_selection = _NO_CACHED_SELECTION self._cached_selection = _NO_CACHED_SELECTION
# is_constant is checked by _make_depend_on(). Just set it to avoid # is_constant is checked by _depend_on(). Just set it to avoid having
# having to special-case choices. # to special-case choices.
self.is_constant = self.is_optional = False self.is_constant = self.is_optional = False
# See Kconfig._build_dep() # See Kconfig._build_dep()
@ -6117,17 +6210,9 @@ def standard_kconfig(description=None):
metavar="KCONFIG", metavar="KCONFIG",
default="Kconfig", default="Kconfig",
nargs="?", nargs="?",
help="Kconfig file (default: Kconfig)") help="Top-level Kconfig file (default: Kconfig)")
args = parser.parse_args() return Kconfig(parser.parse_args().kconfig, suppress_traceback=True)
# Suppress backtraces for expected exceptions
try:
return Kconfig(args.kconfig)
except (EnvironmentError, KconfigError) as e:
# Some long exception messages have extra newlines for better
# formatting when reported as an unhandled exception. Strip them here.
sys.exit(str(e).strip())
def standard_config_filename(): def standard_config_filename():
@ -6143,25 +6228,9 @@ def standard_config_filename():
def load_allconfig(kconf, filename): def load_allconfig(kconf, filename):
""" """
Helper for all*config. Loads (merges) the configuration file specified by Use Kconfig.load_allconfig() instead, which was added in Kconfiglib 13.4.0.
KCONFIG_ALLCONFIG, if any. See Documentation/kbuild/kconfig.txt in the Supported for backwards compatibility. Might be removed at some point after
Linux kernel. a long period of deprecation warnings.
Disables warnings for duplicated assignments within configuration files for
the duration of the call (kconf.warn_assign_override/warn_assign_redun = False),
and restores the previous warning settings 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.
""" """
allconfig = os.getenv("KCONFIG_ALLCONFIG") allconfig = os.getenv("KCONFIG_ALLCONFIG")
if allconfig is None: if allconfig is None:
@ -6237,7 +6306,7 @@ def _visibility(sc):
return vis return vis
def _make_depend_on(sc, expr): def _depend_on(sc, expr):
# Adds 'sc' (symbol or choice) as a "dependee" to all symbols in 'expr'. # Adds 'sc' (symbol or choice) as a "dependee" to all symbols in 'expr'.
# Constant symbols in 'expr' are skipped as they can never change value # Constant symbols in 'expr' are skipped as they can never change value
# anyway. # anyway.
@ -6245,11 +6314,11 @@ def _make_depend_on(sc, expr):
if expr.__class__ is tuple: if expr.__class__ is tuple:
# AND, OR, NOT, or relation # AND, OR, NOT, or relation
_make_depend_on(sc, expr[1]) _depend_on(sc, expr[1])
# NOTs only have a single operand # NOTs only have a single operand
if expr[0] is not NOT: if expr[0] is not NOT:
_make_depend_on(sc, expr[2]) _depend_on(sc, expr[2])
elif not expr.is_constant: elif not expr.is_constant:
# Non-constant symbol, or choice # Non-constant symbol, or choice

View file

@ -151,7 +151,7 @@ def init_kconfig():
BOARD_DIR="boards/*/*", BOARD_DIR="boards/*/*",
ARCH="*") ARCH="*")
kconf = kconfiglib.Kconfig() kconf = kconfiglib.Kconfig(suppress_traceback=True)
def modules_file_dir(): def modules_file_dir():

View file

@ -137,13 +137,16 @@ If there's an error in the style definition or if a missing style is assigned
to, the assignment will be ignored, along with a warning being printed on to, the assignment will be ignored, along with a warning being printed on
stderr. stderr.
The 'default' theme is always implicitly parsed first (or the 'monochrome' The 'default' theme is always implicitly parsed first, so the following two
theme if the terminal lacks colors), so the following two settings have the settings have the same effect:
same effect:
MENUCONFIG_STYLE="selection=fg:white,bg:red" MENUCONFIG_STYLE="selection=fg:white,bg:red"
MENUCONFIG_STYLE="default selection=fg:white,bg:red" MENUCONFIG_STYLE="default selection=fg:white,bg:red"
If the terminal doesn't support colors, the 'monochrome' theme is used, and
MENUCONFIG_STYLE is ignored. The assumption is that the environment is broken
somehow, and that the important thing is to get something usable.
Other features Other features
============== ==============
@ -182,12 +185,15 @@ See the https://github.com/zephyrproject-rtos/windows-curses repository.
""" """
from __future__ import print_function from __future__ import print_function
import os
import sys import sys
_IS_WINDOWS = os.name == "nt" # Are we running on Windows?
try: try:
import curses import curses
except ImportError as e: except ImportError as e:
if sys.platform != "win32": if not _IS_WINDOWS:
raise raise
sys.exit("""\ sys.exit("""\
menuconfig failed to import the standard Python 'curses' library. Try menuconfig failed to import the standard Python 'curses' library. Try
@ -206,7 +212,6 @@ Exception:
import errno import errno
import locale import locale
import os
import re import re
import textwrap import textwrap
@ -312,19 +317,18 @@ _STYLES = {
text= text=
""", """,
# Blue tinted style loosely resembling lxdialog # Blue-tinted style loosely resembling lxdialog
"aquatic": """ "aquatic": """
path=fg:cyan,bg:blue,bold path=fg:cyan,bg:blue,bold
separator=fg:white,bg:cyan,bold separator=fg:white,bg:cyan,bold
help=path help=path
frame=fg:white,bg:cyan,bold frame=fg:white,bg:cyan,bold
body=fg:brightwhite,bg:blue body=fg:white,bg:blue
edit=fg:black,bg:white edit=fg:black,bg:white
""" """
} }
# Standard colors definition _NAMED_COLORS = {
_STYLE_STD_COLORS = {
# Basic colors # Basic colors
"black": curses.COLOR_BLACK, "black": curses.COLOR_BLACK,
"red": curses.COLOR_RED, "red": curses.COLOR_RED,
@ -550,9 +554,6 @@ def _style_to_curses(style_def):
def parse_color(color_def): def parse_color(color_def):
color_def = color_def.split(":", 1)[1] color_def = color_def.split(":", 1)[1]
if color_def in _STYLE_STD_COLORS:
return _color_from_num(_STYLE_STD_COLORS[color_def])
# HTML format, #RRGGBB # HTML format, #RRGGBB
if re.match("#[A-Fa-f0-9]{6}", color_def): if re.match("#[A-Fa-f0-9]{6}", color_def):
return _color_from_rgb(( return _color_from_rgb((
@ -560,19 +561,20 @@ def _style_to_curses(style_def):
int(color_def[3:5], 16), int(color_def[3:5], 16),
int(color_def[5:7], 16))) int(color_def[5:7], 16)))
try: if color_def in _NAMED_COLORS:
color_num = _color_from_num(int(color_def, 0)) color_num = _color_from_num(_NAMED_COLORS[color_def])
except ValueError: else:
_warn("Ignoring color ", color_def, "that's neither predefined " try:
"nor a number") color_num = _color_from_num(int(color_def, 0))
except ValueError:
return -1 _warn("Ignoring color", color_def, "that's neither "
"predefined nor a number")
return -1
if not -1 <= color_num < curses.COLORS: if not -1 <= color_num < curses.COLORS:
_warn("Ignoring color {}, which is outside the range " _warn("Ignoring color {}, which is outside the range "
"-1..curses.COLORS-1 (-1..{})" "-1..curses.COLORS-1 (-1..{})"
.format(color_def, curses.COLORS - 1)) .format(color_def, curses.COLORS - 1))
return -1 return -1
return color_num return color_num
@ -605,15 +607,26 @@ def _style_to_curses(style_def):
def _init_styles(): def _init_styles():
if curses.has_colors(): if curses.has_colors():
curses.use_default_colors() try:
curses.use_default_colors()
except curses.error:
# Ignore errors on funky terminals that support colors but not
# using default colors. Worst it can do is break transparency and
# the like. Ran across this with the MSYS2/winpty setup in
# https://github.com/msys2/MINGW-packages/issues/5823, though there
# seems to be a lot of general brokenness there.
pass
# Use the 'monochrome' style template as the base on terminals without # Use the 'default' theme as the base, and add any user-defined style
# color # settings from the environment
_parse_style("default" if curses.has_colors() else "monochrome", True) _parse_style("default", True)
if "MENUCONFIG_STYLE" in os.environ:
# Add any user-defined style from the environment _parse_style(os.environ["MENUCONFIG_STYLE"], False)
if "MENUCONFIG_STYLE" in os.environ: else:
_parse_style(os.environ["MENUCONFIG_STYLE"], False) # Force the 'monochrome' theme if the terminal doesn't support colors.
# MENUCONFIG_STYLE is likely to mess things up here (though any colors
# would be ignored), so ignore it.
_parse_style("monochrome", True)
# color_attribs holds the color pairs we've already created, indexed by a # color_attribs holds the color pairs we've already created, indexed by a
@ -3274,8 +3287,5 @@ def _change_c_lc_ctype_to_utf8():
return return
# Are we running on Windows?
_IS_WINDOWS = os.name == "nt"
if __name__ == "__main__": if __name__ == "__main__":
_main() _main()