From 81b116b1b99cbb0f73a58e88c28db3bfe8ce45e5 Mon Sep 17 00:00:00 2001 From: Roland Dobai Date: Mon, 25 Nov 2019 13:32:23 +0100 Subject: [PATCH] Update kconfiglib from v12.14.0 to v13.3.2 Thank you @ulfalizer for fixing the aquatic color theme in upstream kconfiglib. Now "xterm" or "screen" values of TERM environment variable is sufficient to use our default blue menuconfig. "xterm-256color" or "screen-256color" was necessary before this fix. See https://github.com/espressif/esp-idf/issues/4387#issuecomment-557823262 --- tools/kconfig_new/kconfiglib.py | 422 ++++++++++++++++++++------------ tools/kconfig_new/menuconfig.py | 110 +++++---- 2 files changed, 324 insertions(+), 208 deletions(-) diff --git a/tools/kconfig_new/kconfiglib.py b/tools/kconfig_new/kconfiglib.py index 3908985c7..700ce7a63 100644 --- a/tools/kconfig_new/kconfiglib.py +++ b/tools/kconfig_new/kconfiglib.py @@ -554,7 +554,7 @@ from glob import iglob from os.path import dirname, exists, expandvars, islink, join, realpath -VERSION = (12, 14, 0) +VERSION = (13, 3, 2) # File layout: @@ -773,8 +773,8 @@ class Kconfig(object): See Kconfig.load_config() as well. srctree: - The value of the $srctree environment variable when the configuration was - loaded, or the empty string if $srctree wasn't set. This gives nice + The value the $srctree environment variable had when the Kconfig instance + 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, without adding "./". @@ -789,13 +789,22 @@ class Kconfig(object): if multiple configurations are loaded with different values for $srctree. config_prefix: - The value of the $CONFIG_ environment variable when the configuration was - loaded. This is the prefix used (and expected) on symbol names in .config - files and C headers. Defaults to "CONFIG_". Used in the same way in the C - tools. + The value the CONFIG_ environment variable had when the Kconfig instance + was created, or "CONFIG_" if CONFIG_ wasn't set. This is the prefix used + (and expected) on symbol names in .config files and C headers. Used in + the same way in the C tools. - Like for srctree, only the value of $CONFIG_ when the configuration is - loaded matters. + config_header: + 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: The current parsing location, for use in Python preprocessor functions. @@ -810,11 +819,13 @@ class Kconfig(object): "_warn_assign_no_prompt", "choices", "comments", + "config_header", "config_prefix", "const_syms", "defconfig_list", "defined_syms", "env_vars", + "header_header", "kconfig_filenames", "m", "menus", @@ -943,6 +954,9 @@ class Kconfig(object): self._unset_match = _re_match(r"# {}([^ ]+) is not set".format( self.config_prefix)) + self.config_header = os.getenv("KCONFIG_CONFIG_HEADER", "") + self.header_header = os.getenv("KCONFIG_AUTOHEADER_HEADER", "") + self.syms = {} self.const_syms = {} self.defined_syms = [] @@ -1245,7 +1259,7 @@ class Kconfig(object): self._warn("'{}' is not a valid value for the {} " "symbol {}. Assignment ignored." .format(val, TYPE_TO_STR[sym.orig_type], - _name_and_loc(sym)), + sym.name_and_loc), filename, linenr) continue @@ -1272,7 +1286,7 @@ class Kconfig(object): if not match: self._warn("malformed string literal in " "assignment to {}. Assignment ignored." - .format(_name_and_loc(sym)), + .format(sym.name_and_loc), filename, linenr) continue @@ -1341,7 +1355,7 @@ class Kconfig(object): user_val = sym.user_value msg = '{} set more than once. Old value "{}", new value "{}".'.format( - _name_and_loc(sym), user_val, new_val) + sym.name_and_loc, user_val, new_val) if user_val == new_val: if self.warn_assign_redun: @@ -1349,8 +1363,7 @@ class Kconfig(object): elif self.warn_assign_override: self._warn(msg, filename, linenr) - def write_autoconf(self, filename, - header="/* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */\n"): + def write_autoconf(self, filename=None, header=None): r""" Writes out symbol values as a C header file, matching the format used by include/generated/autoconf.h in the kernel. @@ -1364,22 +1377,37 @@ class Kconfig(object): like the modification time and possibly triggering redundant work in build tools. - filename: - Self-explanatory. + filename (default: None): + Path to write header to. - header (default: "/* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */\n"): - Text that will be inserted verbatim at the beginning of the file. You - would usually want it enclosed in '/* */' to make it a C comment, - and include a final terminating newline. + If None (the default), the path in the environment variable + KCONFIG_AUTOHEADER is used if set, and "include/generated/autoconf.h" + otherwise. This is compatible with the C tools. + + 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)) def _autoconf_contents(self, header): # 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 - chunks = [header] + if header is None: + header = self.header_header + + chunks = [header] # "".join()ed later add = chunks.append for sym in self.unique_defined_syms: @@ -1415,9 +1443,8 @@ class Kconfig(object): return "".join(chunks) - def write_config(self, filename=None, - header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n", - save_old=True, verbose=None): + def write_config(self, filename=None, header=None, save_old=True, + verbose=None): r""" Writes out symbol values in the .config format. The format matches the C implementation, including ordering. @@ -1439,16 +1466,21 @@ class Kconfig(object): (OSError/IOError). KconfigError is never raised here. 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 standard_config_filename(). - header (default: "# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"): - Text that will be inserted verbatim at the beginning of the file. You - would usually want each line to start with '#' to make it a comment, - and include a final terminating newline. + header (default: None): + Text inserted verbatim at the beginning of the file. You would + usually want each line to start with '#' to make it a comment, and + 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): If True and already exists, a copy of it will be saved to @@ -1493,7 +1525,7 @@ class Kconfig(object): def _config_contents(self, header): # 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 # "".join(_config_contents()), but it was a bit slower on my system. @@ -1505,13 +1537,15 @@ class Kconfig(object): for sym in self.unique_defined_syms: 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? after_end_comment = False - # "".join()ed later - chunks = [header] - add = chunks.append - node = self.top_node while 1: # Jump to the next node with an iterative tree walk @@ -1564,8 +1598,7 @@ class Kconfig(object): add("\n#\n# {}\n#\n".format(node.prompt[0])) after_end_comment = False - def write_min_config(self, filename, - header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"): + def write_min_config(self, filename, header=None): """ Writes out a "minimal" configuration file, omitting symbols whose value matches their default value. The format matches the one produced by @@ -1581,12 +1614,17 @@ class Kconfig(object): (OSError/IOError). KconfigError is never raised here. filename: - Self-explanatory. + Path to write minimal configuration to. - header (default: "# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"): - Text that will be inserted verbatim at the beginning of the file. You - would usually want each line to start with '#' to make it a comment, - and include a final terminating newline. + header (default: None): + Text inserted verbatim at the beginning of the file. You would + usually want each line to start with '#' to make it a comment, and + 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 meant to reduce boilerplate in tools, which can do e.g. @@ -1603,9 +1641,12 @@ class Kconfig(object): def _min_config_contents(self, header): # 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 for sym in self.unique_defined_syms: @@ -2603,10 +2644,9 @@ class Kconfig(object): while 1: match = _name_special_search(s, i) - if match.group() == "$(": - s, i = self._expand_macro(s, match.start(), ()) - else: + if match.group() != "$(": return (s, match.start()) + s, i = self._expand_macro(s, match.start(), ()) def _expand_str(self, s, i): # Expands a quoted string starting at index 'i' in 's'. Handles both @@ -2649,14 +2689,12 @@ class Kconfig(object): # Returns the expanded 's' (including the part before the macro) and # the index of the first character after the expanded macro in 's'. - start = i + res = s[:i] i += 2 # Skip over "$(" - # Start of current macro argument - arg_start = i - - # Arguments of this macro call - new_args = [] + arg_start = i # Start of current macro argument + new_args = [] # Arguments of this macro call + nesting = 0 # Current parentheses nesting level while 1: match = _macro_special_search(s, i) @@ -2664,32 +2702,42 @@ class Kconfig(object): self._parse_error("missing end parenthesis in macro expansion") - if match.group() == ")": + if match.group() == "(": + nesting += 1 + i = match.end() + + elif match.group() == ")": + if nesting: + nesting -= 1 + i = match.end() + continue + # Found the end of the macro new_args.append(s[arg_start:match.start()]) - prefix = s[:start] - # $(1) is replaced by the first argument to the function, etc., # provided at least that many arguments were passed try: # Does the macro look like an integer, with a corresponding # argument? If so, expand it to the value of the argument. - prefix += args[int(new_args[0])] + res += args[int(new_args[0])] except (ValueError, IndexError): # Regular variables are just functions without arguments, # and also go through the function value path - prefix += self._fn_val(new_args) + res += self._fn_val(new_args) - return (prefix + s[match.end():], - len(prefix)) + return (res + s[match.end():], len(res)) elif match.group() == ",": + i = match.end() + if nesting: + continue + # Found the end of a macro argument new_args.append(s[arg_start:match.start()]) - arg_start = i = match.end() + arg_start = i else: # match.group() == "$(" # A nested macro call within the macro @@ -2836,7 +2884,7 @@ class Kconfig(object): if node.is_menuconfig and not node.prompt: self._warn("the menuconfig symbol {} has no prompt" - .format(_name_and_loc(sym))) + .format(sym.name_and_loc)) # Equivalent to # @@ -3173,8 +3221,7 @@ class Kconfig(object): # UNKNOWN is falsy if node.item.orig_type and node.item.orig_type is not new_type: self._warn("{} defined with multiple types, {} will be used" - .format(_name_and_loc(node.item), - TYPE_TO_STR[new_type])) + .format(node.item.name_and_loc, TYPE_TO_STR[new_type])) node.item.orig_type = new_type @@ -3184,7 +3231,7 @@ class Kconfig(object): # multiple times if node.prompt: - self._warn(_name_and_loc(node.item) + + self._warn(node.item.name_and_loc + " defined with multiple prompts in single location") prompt = self._tokens[1] @@ -3194,7 +3241,7 @@ class Kconfig(object): self._parse_error("expected prompt string") if prompt != prompt.strip(): - self._warn(_name_and_loc(node.item) + + self._warn(node.item.name_and_loc + " has leading or trailing whitespace in its prompt") # This avoid issues for e.g. reStructuredText documentation, where @@ -3205,7 +3252,7 @@ class Kconfig(object): def _parse_help(self, node): if node.help is not None: - self._warn(_name_and_loc(node.item) + " defined with more than " + self._warn(node.item.name_and_loc + " defined with more than " "one help text -- only the last one will be used") # Micro-optimization. This code is pretty hot. @@ -3261,7 +3308,7 @@ class Kconfig(object): self._line_after_help(line) def _empty_help(self, node, line): - self._warn(_name_and_loc(node.item) + + self._warn(node.item.name_and_loc + " has 'help' but empty help text") node.help = "" if line: @@ -3641,26 +3688,26 @@ class Kconfig(object): 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), + .format(sym.name_and_loc, TYPE_TO_STR[target_sym.orig_type], - _name_and_loc(target_sym))) + target_sym.name_and_loc)) 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), + .format(sym.name_and_loc, TYPE_TO_STR[target_sym.orig_type], - _name_and_loc(target_sym))) + target_sym.name_and_loc)) elif sym.orig_type: # STRING/INT/HEX for default, _ in sym.defaults: if default.__class__ is not 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))) + "the {} symbol {} has a malformed default {} -- " + "expected a single symbol" + .format(TYPE_TO_STR[sym.orig_type], + sym.name_and_loc, expr_str(default))) if sym.orig_type is STRING: if not default.is_constant and not default.nodes and \ @@ -3671,22 +3718,22 @@ class Kconfig(object): # (and no symbol named 'foo' exists). self._warn("style: quotes recommended around " "default value for string symbol " - + _name_and_loc(sym)) + + sym.name_and_loc) elif not num_ok(default, sym.orig_type): # INT/HEX 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))) + sym.name_and_loc, + default.name_and_loc)) 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))) + sym.name_and_loc)) else: # UNKNOWN self._warn("{} defined without a type" - .format(_name_and_loc(sym))) + .format(sym.name_and_loc)) if sym.ranges: @@ -3694,7 +3741,7 @@ class Kconfig(object): self._warn( "the {} symbol {} has ranges, but is not int or hex" .format(TYPE_TO_STR[sym.orig_type], - _name_and_loc(sym))) + sym.name_and_loc)) else: for low, high, _ in sym.ranges: if not num_ok(low, sym.orig_type) or \ @@ -3703,9 +3750,9 @@ class Kconfig(object): 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))) + sym.name_and_loc, + low.name_and_loc, + high.name_and_loc)) def _check_choice_sanity(self): # Checks various choice properties that are handiest to check after @@ -3714,43 +3761,43 @@ class Kconfig(object): def warn_select_imply(sym, expr, expr_type): msg = "the choice symbol {} is {} by the following symbols, but " \ "select/imply has no effect on choice symbols" \ - .format(_name_and_loc(sym), expr_type) + .format(sym.name_and_loc, expr_type) # si = select/imply for si in split_expr(expr, OR): - msg += "\n - " + _name_and_loc(split_expr(si, AND)[0]) + msg += "\n - " + split_expr(si, AND)[0].name_and_loc 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), + .format(choice.name_and_loc, 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") + self._warn(choice.name_and_loc + " defined without a prompt") for default, _ in choice.defaults: if default.__class__ is not Symbol: raise KconfigError( "{} has a malformed default {}" - .format(_name_and_loc(choice), expr_str(default))) + .format(choice.name_and_loc, 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))) + .format(default.name_and_loc, + choice.name_and_loc)) 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))) + "symbols".format(sym.name_and_loc)) if sym.rev_dep is not sym.kconfig.n: warn_select_imply(sym, sym.rev_dep, "selected") @@ -3762,12 +3809,12 @@ class Kconfig(object): if node.parent.item is choice: if not node.prompt: self._warn("the choice symbol {} has no prompt" - .format(_name_and_loc(sym))) + .format(sym.name_and_loc)) elif node.prompt: self._warn("the choice symbol {} is defined with a " "prompt outside the choice" - .format(_name_and_loc(sym))) + .format(sym.name_and_loc)) def _parse_error(self, msg): raise KconfigError("{}couldn't parse '{}': {}".format( @@ -3907,6 +3954,13 @@ class Symbol(object): The type as given in the Kconfig file, without any magic applied. Used when printing the symbol. + tri_value: + The tristate value of the symbol as an integer. One of 0, 1, 2, + representing n, m, y. Always 0 (n) for non-bool/tristate symbols. + + This is the symbol value that's used outside of relation expressions + (A, !A, A && B, A || B). + str_value: The value of the symbol as a string. Gives the value for string/int/hex symbols. For bool/tristate symbols, gives "n", "m", or "y". @@ -3914,17 +3968,20 @@ class Symbol(object): This is the symbol value that's used in relational expressions (A = B, A != B, etc.) - Gotcha: For int/hex symbols, the exact format of the value must often be - preserved (e.g., when writing a .config file), hence why you can't get it + Gotcha: For int/hex symbols, the exact format of the value is often + preserved (e.g. when writing a .config file), hence why you can't get it directly as an int. Do int(int_sym.str_value) or int(hex_sym.str_value, 16) to get the integer value. - tri_value: - The tristate value of the symbol as an integer. One of 0, 1, 2, - representing n, m, y. Always 0 (n) for non-bool/tristate symbols. + user_value: + The user value of the symbol. None if no user value has been assigned + (via Kconfig.load_config() or Symbol.set_value()). - This is the symbol value that's used outside of relation expressions - (A, !A, A && B, A || B). + Holds 0, 1, or 2 for bool/tristate symbols, and a string for the other + symbol types. + + WARNING: Do not assign directly to this. It will break things. Use + Symbol.set_value(). assignable: A tuple containing the tristate user values that can currently be @@ -3965,16 +4022,6 @@ class Symbol(object): The visibility of the symbol. One of 0, 1, 2, representing n, m, y. See the module documentation for an overview of symbol values and visibility. - user_value: - The user value of the symbol. None if no user value has been assigned - (via Kconfig.load_config() or Symbol.set_value()). - - Holds 0, 1, or 2 for bool/tristate symbols, and a string for the other - symbol types. - - WARNING: Do not assign directly to this. It will break things. Use - Symbol.set_value(). - 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 @@ -4002,6 +4049,15 @@ class Symbol(object): though you might get some special symbols and possibly some "redundant" n-valued symbol entries in there. + name_and_loc: + Holds a string like + + "MY_SYMBOL (defined at foo/Kconfig:12, bar/Kconfig:14)" + + , giving the name of the symbol and its definition location(s). + + If the symbol is undefined, the location is given as "(undefined)". + nodes: A list of MenuNodes for this symbol. Will contain a single MenuNode for most symbols. Undefined and constant symbols have an empty nodes list. @@ -4232,7 +4288,7 @@ class Symbol(object): "being outside the active range ([{}, {}]) -- falling " "back on defaults" .format(num2str(user_val), TYPE_TO_STR[self.orig_type], - _name_and_loc(self), + self.name_and_loc, num2str(low), num2str(high))) else: # If the user value is well-formed and satisfies range @@ -4282,7 +4338,7 @@ class Symbol(object): self.kconfig._warn( "default value {} on {} clamped to {} due to " "being outside the active range ([{}, {}])" - .format(val_num, _name_and_loc(self), + .format(val_num, self.name_and_loc, num2str(clamp), num2str(low), num2str(high))) @@ -4323,7 +4379,7 @@ class Symbol(object): self.kconfig._warn( "The {} symbol {} is being evaluated in a logical context " "somewhere. It will always evaluate to n." - .format(TYPE_TO_STR[self.orig_type], _name_and_loc(self))) + .format(TYPE_TO_STR[self.orig_type], self.name_and_loc)) self._cached_tri_val = 0 return 0 @@ -4433,6 +4489,13 @@ class Symbol(object): return '{}{}="{}"\n' \ .format(self.kconfig.config_prefix, self.name, escape(val)) + @property + def name_and_loc(self): + """ + See the class documentation. + """ + return self.name + " " + _locs(self) + def set_value(self, value): """ Sets the user value of the symbol. @@ -4454,8 +4517,8 @@ class Symbol(object): value: The user value to give to the symbol. For bool and tristate symbols, n/m/y can be specified either as 0/1/2 (the usual format for tristate - values in Kconfiglib) or as one of the strings "n"/"m"/"y". For other - symbol types, pass a string. + values in Kconfiglib) or as one of the strings "n", "m", or "y". For + other symbol types, pass a string. Note that the value for an int/hex symbol is passed as a string, e.g. "123" or "0x0123". The format of this string is preserved in the @@ -4502,7 +4565,7 @@ class Symbol(object): "assignment ignored" .format(TRI_TO_STR[value] if value in TRI_TO_STR else "'{}'".format(value), - _name_and_loc(self), TYPE_TO_STR[self.orig_type])) + self.name_and_loc, TYPE_TO_STR[self.orig_type])) return False @@ -4790,7 +4853,7 @@ class Symbol(object): return if self.kconfig._warn_assign_no_prompt: - self.kconfig._warn(_name_and_loc(self) + " has no prompt, meaning " + self.kconfig._warn(self.name_and_loc + " has no prompt, meaning " "user values have no effect on it") def _str_default(self): @@ -4836,7 +4899,7 @@ class Symbol(object): msg = "{} has direct dependencies {} with value {}, but is " \ "currently being {}-selected by the following symbols:" \ - .format(_name_and_loc(self), expr_str(self.direct_dep), + .format(self.name_and_loc, expr_str(self.direct_dep), TRI_TO_STR[expr_value(self.direct_dep)], TRI_TO_STR[expr_value(self.rev_dep)]) @@ -4854,7 +4917,7 @@ class Symbol(object): msg += "\n - {}, with value {}, direct dependencies {} " \ "(value: {})" \ - .format(_name_and_loc(selecting_sym), + .format(selecting_sym.name_and_loc, selecting_sym.str_value, expr_str(selecting_sym.direct_dep), TRI_TO_STR[expr_value(selecting_sym.direct_dep)]) @@ -4938,19 +5001,9 @@ class Choice(object): Corresponding attributes have the same name in the Symbol and Choice classes, for consistency and compatibility. - assignable: - See the symbol class documentation. Gives the assignable values (modes). - - visibility: - See the Symbol class documentation. Acts on the value (mode). - - selection: - The Symbol instance of the currently selected symbol. None if the Choice - is not in y mode or has no selected symbol (due to unsatisfied - dependencies on choice symbols). - - WARNING: Do not assign directly to this. It will break things. Call - sym.set_value(2) on the choice symbol you want to select instead. + str_value: + Like choice.tri_value, but gives the value as one of the strings + "n", "m", or "y" user_value: The value (mode) selected by the user through Choice.set_value(). Either @@ -4960,6 +5013,17 @@ class Choice(object): WARNING: Do not assign directly to this. It will break things. Use Choice.set_value() instead. + assignable: + See the symbol class documentation. Gives the assignable values (modes). + + selection: + The Symbol instance of the currently selected symbol. None if the Choice + is not in y mode or has no selected symbol (due to unsatisfied + dependencies on choice symbols). + + WARNING: Do not assign directly to this. It will break things. Call + sym.set_value(2) on the choice symbol you want to select instead. + user_selection: The symbol selected by the user (by setting it to y). Ignored if the choice is not in y mode, but still remembered so that the choice "snaps @@ -4969,6 +5033,19 @@ class Choice(object): WARNING: Do not assign directly to this. It will break things. Call sym.set_value(2) on the choice symbol to be selected instead. + visibility: + See the Symbol class documentation. Acts on the value (mode). + + name_and_loc: + Holds a string like + + " (defined at foo/Kconfig:12)" + + , giving the name of the choice and its definition location(s). If the + choice has no name (isn't defined with 'choice MY_CHOICE'), then it will + be shown as "" before the list of locations (always a single one + in that case). + syms: List of symbols contained in the choice. @@ -5088,6 +5165,14 @@ class Choice(object): self._cached_vis = _visibility(self) return self._cached_vis + @property + def name_and_loc(self): + """ + See the class documentation. + """ + # Reuse the expression format, which is ''. + return standard_sc_expr_str(self) + " " + _locs(self) + @property def selection(self): """ @@ -5128,7 +5213,7 @@ class Choice(object): "assignment ignored" .format(TRI_TO_STR[value] if value in TRI_TO_STR else "'{}'".format(value), - _name_and_loc(self), TYPE_TO_STR[self.orig_type])) + self.name_and_loc, TYPE_TO_STR[self.orig_type])) return False @@ -6050,21 +6135,36 @@ def unescape(s): _unescape_sub = re.compile(r"\\(.)").sub -def standard_kconfig(): +def standard_kconfig(description=None): """ - Helper for tools. Loads the top-level Kconfig specified as the first - command-line argument, or "Kconfig" if there are no command-line arguments. - Returns the Kconfig instance. + Argument parsing helper for tools that take a single optional Kconfig file + argument (default: Kconfig). Returns the Kconfig instance for the parsed + configuration. Uses argparse internally. - Exits with sys.exit() (which raises a SystemExit exception) and prints a - usage note to stderr if more than one command-line argument is passed. + Exits with sys.exit() (which raises SystemExit) on errors. + + description (default: None): + The 'description' passed to argparse.ArgumentParser(). + argparse.RawDescriptionHelpFormatter is used, so formatting is preserved. """ - if len(sys.argv) > 2: - sys.exit("usage: {} [Kconfig]".format(sys.argv[0])) + import argparse - # Only show backtraces for unexpected exceptions + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=description) + + parser.add_argument( + "kconfig", + metavar="KCONFIG", + default="Kconfig", + nargs="?", + help="Kconfig file (default: Kconfig)") + + args = parser.parse_args() + + # Suppress backtraces for expected exceptions try: - return Kconfig("Kconfig" if len(sys.argv) < 2 else sys.argv[1]) + 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. @@ -6286,20 +6386,16 @@ def _save_old(path): pass -def _name_and_loc(sc): - # Helper for giving the symbol/choice name and location(s) in e.g. warnings +def _locs(sc): + # Symbol/Choice.name_and_loc helper. Returns the "(defined at ...)" part of + # the string. 'sc' is a Symbol or Choice. - # Reuse the expression format. That way choices show up as - # '' - name = standard_sc_expr_str(sc) + if sc.nodes: + return "(defined at {})".format( + ", ".join("{0.filename}:{0.linenr}".format(node) + for node in sc.nodes)) - if not sc.nodes: - return name + " (undefined)" - - return "{} (defined at {})".format( - name, - ", ".join("{}:{}".format(node.filename, node.linenr) - for node in sc.nodes)) + return "(undefined)" # Menu manipulation @@ -6554,7 +6650,7 @@ def _found_dep_loop(loop, cur): msg += "the choice symbol " msg += "{}, with definition...\n\n{}\n\n" \ - .format(_name_and_loc(item), item) + .format(item.name_and_loc, item) # Small wart: Since we reuse the already calculated # Symbol/Choice._dependents sets for recursive dependency detection, we @@ -6578,7 +6674,7 @@ def _found_dep_loop(loop, cur): msg += "(imply-related dependencies: {})\n\n" \ .format(expr_str(item.rev_dep)) - msg += "...depends again on {}".format(_name_and_loc(loop[0])) + msg += "...depends again on " + loop[0].name_and_loc raise KconfigError(msg) @@ -7015,8 +7111,8 @@ _assignment_lhs_fragment_match = _re_match("[A-Za-z0-9_-]*") # variable assignment _assignment_rhs_match = _re_match(r"\s*(=|:=|\+=)\s*(.*)") -# Special characters/strings while expanding a macro (')', ',', and '$(') -_macro_special_search = _re_search(r"\)|,|\$\(") +# Special characters/strings while expanding a macro ('(', ')', ',', and '$(') +_macro_special_search = _re_search(r"\(|\)|,|\$\(") # Special characters/strings while expanding a string (quotes, '\', and '$(') _string_special_search = _re_search(r'"|\'|\\|\$\(') diff --git a/tools/kconfig_new/menuconfig.py b/tools/kconfig_new/menuconfig.py index 09d2df857..b26e2aa14 100644 --- a/tools/kconfig_new/menuconfig.py +++ b/tools/kconfig_new/menuconfig.py @@ -76,7 +76,7 @@ This is the current list of built-in styles: - default classic Kconfiglib theme with a yellow accent - monochrome colorless theme (uses only bold and standout) attributes, this style is used if the terminal doesn't support colors - - aquatic blue tinted style loosely resembling the lxdialog theme + - aquatic blue-tinted style loosely resembling the lxdialog theme It is possible to customize the current style by changing colors of UI elements on the screen. This is the list of elements that can be stylized: @@ -140,13 +140,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 stderr. -The 'default' theme is always implicitly parsed first (or the 'monochrome' -theme if the terminal lacks colors), so the following two settings have the -same effect: +The 'default' theme is always implicitly parsed first, so the following two +settings have the same effect: MENUCONFIG_STYLE="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 ============== @@ -177,27 +180,41 @@ Other features Limitations =========== -Doesn't work out of the box on Windows, but can be made to work with 'pip -install windows-curses'. See the -https://github.com/zephyrproject-rtos/windows-curses repository. +Doesn't work out of the box on Windows, but can be made to work with -'pip install kconfiglib' on Windows automatically installs windows-curses -to make the menuconfig usable. + pip install windows-curses + +See the https://github.com/zephyrproject-rtos/windows-curses repository. """ from __future__ import print_function +import os import sys + +_IS_WINDOWS = os.name == "nt" # Are we running on Windows? + try: import curses -except ImportError: - print('"windows-curses" package is required in Windows command line. Please install it by running ' - '"{} -m pip install --user windows-curses" (or without the "--user" option)' - ''.format(sys.executable)) - exit(1) +except ImportError as e: + if not _IS_WINDOWS: + raise + sys.exit("""\ +menuconfig failed to import the standard Python 'curses' library. Try +installing a package like windows-curses +(https://github.com/zephyrproject-rtos/windows-curses) by running this command +in cmd.exe: + + pip install windows-curses + +Starting with Kconfiglib 13.0.0, windows-curses is no longer automatically +installed when installing Kconfiglib via pip on Windows (because it breaks +installation on MSYS2). + +Exception: +{}: {}""".format(type(e).__name__, e)) import errno import locale -import os import re import textwrap @@ -309,19 +326,18 @@ _STYLES = { text= """, - # Blue tinted style loosely resembling lxdialog + # Blue-tinted style loosely resembling lxdialog "aquatic": """ path=fg:cyan,bg:blue,bold separator=fg:white,bg:cyan,bold help=path frame=fg:white,bg:cyan,bold - body=fg:brightwhite,bg:blue + body=fg:white,bg:blue edit=fg:black,bg:white """ } -# Standard colors definition -_STYLE_STD_COLORS = { +_NAMED_COLORS = { # Basic colors "black": curses.COLOR_BLACK, "red": curses.COLOR_RED, @@ -547,9 +563,6 @@ def _style_to_curses(style_def): def parse_color(color_def): 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 if re.match("#[A-Fa-f0-9]{6}", color_def): return _color_from_rgb(( @@ -557,19 +570,20 @@ def _style_to_curses(style_def): int(color_def[3:5], 16), int(color_def[5:7], 16))) - try: - color_num = _color_from_num(int(color_def, 0)) - except ValueError: - _warn("Ignoring color ", color_def, "that's neither predefined " - "nor a number") - - return -1 + if color_def in _NAMED_COLORS: + color_num = _color_from_num(_NAMED_COLORS[color_def]) + else: + try: + color_num = _color_from_num(int(color_def, 0)) + except ValueError: + _warn("Ignoring color", color_def, "that's neither " + "predefined nor a number") + return -1 if not -1 <= color_num < curses.COLORS: _warn("Ignoring color {}, which is outside the range " "-1..curses.COLORS-1 (-1..{})" .format(color_def, curses.COLORS - 1)) - return -1 return color_num @@ -602,15 +616,26 @@ def _style_to_curses(style_def): def _init_styles(): 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 - # color - _parse_style("default" if curses.has_colors() else "monochrome", True) - - # Add any user-defined style from the environment - if "MENUCONFIG_STYLE" in os.environ: - _parse_style(os.environ["MENUCONFIG_STYLE"], False) + # Use the 'default' theme as the base, and add any user-defined style + # settings from the environment + _parse_style("default", True) + if "MENUCONFIG_STYLE" in os.environ: + _parse_style(os.environ["MENUCONFIG_STYLE"], False) + else: + # 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 @@ -644,7 +669,7 @@ def _style_attr(fg_color, bg_color, attribs, color_attribs={}): def _main(): - menuconfig(standard_kconfig()) + menuconfig(standard_kconfig(__doc__)) def menuconfig(kconf): @@ -2210,9 +2235,7 @@ def _sorted_sc_nodes(cached_nodes=[]): key=lambda choice: choice.name or "") cached_nodes += sorted( - [node - for choice in choices - for node in choice.nodes], + [node for choice in choices for node in choice.nodes], key=lambda node: node.prompt[0] if node.prompt else "") return cached_nodes @@ -3273,8 +3296,5 @@ def _change_c_lc_ctype_to_utf8(): return -# Are we running on Windows? -_IS_WINDOWS = os.name == "nt" - if __name__ == "__main__": _main()