From patchwork Thu May 14 12:30:00 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Rini X-Patchwork-Id: 245813 List-Id: U-Boot discussion From: trini at konsulko.com (Tom Rini) Date: Thu, 14 May 2020 08:30:00 -0400 Subject: [PATCH 01/10] kconfiglib: Update to the 14.1.0 release Message-ID: <20200514123009.5721-1-trini@konsulko.com> A large number of changes have happened upstream since our last sync in commit 65e05ddc1ae2 ("kconfiglib: Update to the 12.14.0 release"). The big motivation for this sync is support for user defined macros within Kconfig. Cc: Masahiro Yamada Signed-off-by: Tom Rini --- tools/buildman/kconfiglib.py | 614 +++++++++++++++++++++-------------- 1 file changed, 372 insertions(+), 242 deletions(-) diff --git a/tools/buildman/kconfiglib.py b/tools/buildman/kconfiglib.py index 3908985c7b29..c67895ced6b3 100644 --- a/tools/buildman/kconfiglib.py +++ b/tools/buildman/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 = (14, 1, 0) # 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. - - Like for srctree, only the value of $CONFIG_ when the configuration is - loaded matters. + 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. + + 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", @@ -854,7 +865,7 @@ class Kconfig(object): # 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. Note that Kconfig files are not the same as .config files (which store @@ -919,7 +930,35 @@ class Kconfig(object): anyway. 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 missing + 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.srctree = os.getenv("srctree", "") @@ -943,6 +982,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 = [] @@ -1038,8 +1080,9 @@ class Kconfig(object): self._readline = self._open(join(self.srctree, filename), "r").readline try: - # Parse the Kconfig files - self._parse_block(None, self.top_node, self.top_node) + # Parse the Kconfig files. Returns the last node, which we + # terminate with '.next = None'. + self._parse_block(None, self.top_node, self.top_node).next = None self.top_node.list = self.top_node.next self.top_node.next = None except UnicodeDecodeError as e: @@ -1245,7 +1288,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 +1315,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 +1384,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 +1392,29 @@ 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 load_allconfig(self, filename): + """ + Helper for all*config. Loads (merges) the configuration file specified + by KCONFIG_ALLCONFIG, if any. See Documentation/kbuild/kconfig.txt in + the Linux kernel. + + Disables warnings for duplicated assignments within configuration files + for the duration of the call + (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""" Writes out symbol values as a C header file, matching the format used by include/generated/autoconf.h in the kernel. @@ -1364,22 +1428,43 @@ 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. + + Returns a string with a message saying that the header got saved, or + that there were no changes to it. This is meant to reduce boilerplate + in tools, which can do e.g. print(kconf.write_autoconf()). """ - self._write_if_changed(filename, self._autoconf_contents(header)) + if filename is None: + filename = os.getenv("KCONFIG_AUTOHEADER", + "include/generated/autoconf.h") + + if self._write_if_changed(filename, self._autoconf_contents(header)): + return "Kconfig header saved to '{}'".format(filename) + return "No change to Kconfig header in '{}'".format(filename) 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 +1500,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 +1523,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 @@ -1481,7 +1570,7 @@ class Kconfig(object): contents = self._config_contents(header) if self._contents_eq(filename, contents): - return "No change to '{}'".format(filename) + return "No change to configuration in '{}'".format(filename) if save_old: _save_old(filename) @@ -1493,7 +1582,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 +1594,15 @@ class Kconfig(object): for sym in self.unique_defined_syms: sym._visited = False - # Did we just print an '# end of ...' comment? - after_end_comment = False + if header is None: + header = self.config_header - # "".join()ed later - chunks = [header] + chunks = [header] # "".join()ed later add = chunks.append + # Did we just print an '# end of ...' comment? + after_end_comment = False + node = self.top_node while 1: # Jump to the next node with an iterative tree walk @@ -1564,8 +1655,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,31 +1671,35 @@ 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. - Returns a string with a message saying which file got saved. This is - meant to reduce boilerplate in tools, which can do e.g. + 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 the minimal configuration got + saved, or that there were no changes to it. This is meant to reduce + boilerplate in tools, which can do e.g. print(kconf.write_min_config()). """ - contents = self._min_config_contents(header) - if self._contents_eq(filename, contents): - return "No change to '{}'".format(filename) - - with self._open(filename, "w") as f: - f.write(contents) - - return "Minimal configuration saved to '{}'".format(filename) + if self._write_if_changed(filename, self._min_config_contents(header)): + return "Minimal configuration saved to '{}'".format(filename) + return "No change to minimal configuration in '{}'".format(filename) 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: @@ -2122,9 +2216,9 @@ class Kconfig(object): # it's part of a different construct if self._reuse_tokens: self._reuse_tokens = False - # self._tokens_i is known to be 1 here, because _parse_properties() - # leaves it like that when it can't recognize a line (or parses - # a help text) + # 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 a + # help text) return True # readline() returns '' over and over at EOF, which we rely on for help @@ -2141,7 +2235,7 @@ class Kconfig(object): self._tokens = self._tokenize(line) # 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 return True @@ -2172,10 +2266,15 @@ class Kconfig(object): # differs, but it breaks stuff like write_config("/dev/null"), which is # used out there to force evaluation-related warnings to be generated. # This simple version is pretty failsafe and portable. + # + # Returns True if the file has changed and is updated, and False + # otherwise. - if not self._contents_eq(filename, contents): - with self._open(filename, "w") as f: - f.write(contents) + if self._contents_eq(filename, contents): + return False + with self._open(filename, "w") as f: + f.write(contents) + return True def _contents_eq(self, filename, contents): # Returns True if the contents of 'filename' is 'contents' (a string), @@ -2603,10 +2702,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 +2747,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 +2760,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 @@ -2796,7 +2902,7 @@ class Kconfig(object): # # prev: # The previous menu node. New nodes will be added after this one (by - # modifying their 'next' pointer). + # modifying 'next' pointers). # # 'prev' is reused to parse a list of child menu nodes (for a menu or # Choice): After parsing the children, the 'next' pointer is assigned @@ -2832,11 +2938,11 @@ class Kconfig(object): sym.nodes.append(node) - self._parse_properties(node) + self._parse_props(node) 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 # @@ -2918,7 +3024,7 @@ class Kconfig(object): self.menus.append(node) - self._parse_properties(node) + self._parse_props(node) self._parse_block(_T_ENDMENU, node, node) node.list = node.next @@ -2938,7 +3044,7 @@ class Kconfig(object): self.comments.append(node) - self._parse_properties(node) + self._parse_props(node) prev.next = prev = node @@ -2970,7 +3076,7 @@ class Kconfig(object): choice.nodes.append(node) - self._parse_properties(node) + self._parse_props(node) self._parse_block(_T_ENDCHOICE, node, node) node.list = node.next @@ -2988,17 +3094,16 @@ class Kconfig(object): "no corresponding 'menu'" if t0 is _T_ENDMENU else "unrecognized construct") - # End of file reached. Terminate the final node and return it. + # End of file reached. Return the last node. if end_token: raise KconfigError( - "expected '{}' at end of '{}'" + "error: expected '{}' at end of '{}'" .format("endchoice" if end_token is _T_ENDCHOICE else "endif" if end_token is _T_ENDIF else "endmenu", self.filename)) - prev.next = None return prev def _parse_cond(self): @@ -3012,7 +3117,7 @@ class Kconfig(object): return expr - def _parse_properties(self, node): + def _parse_props(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 e.g. @@ -3038,7 +3143,7 @@ class Kconfig(object): if t0 in _TYPE_TOKENS: # 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: self._parse_prompt(node) @@ -3068,7 +3173,7 @@ class Kconfig(object): self._parse_cond())) 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), self._parse_cond())) @@ -3169,14 +3274,15 @@ class Kconfig(object): self._reuse_tokens = True return - def _set_type(self, node, new_type): + def _set_type(self, sc, new_type): + # Sets the type of 'sc' (symbol or choice) to 'new_type' + # UNKNOWN is falsy - if node.item.orig_type and node.item.orig_type is not new_type: + if sc.orig_type and sc.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(sc.name_and_loc, TYPE_TO_STR[new_type])) - node.item.orig_type = new_type + sc.orig_type = new_type def _parse_prompt(self, node): # 'prompt' properties override each other within a single definition of @@ -3184,7 +3290,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 +3300,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 +3311,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 +3367,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: @@ -3366,7 +3472,7 @@ class Kconfig(object): # The calculated sets might be larger than necessary as we don't do any # 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 # undefined symbols could theoretically be selected/implied, but it @@ -3377,29 +3483,29 @@ class Kconfig(object): # The prompt conditions for node in sym.nodes: if node.prompt: - make_depend_on(sym, node.prompt[1]) + 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) + depend_on(sym, value) + 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) + depend_on(sym, sym.rev_dep) + 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) + depend_on(sym, low) + depend_on(sym, high) + 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) + 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 @@ -3412,11 +3518,11 @@ class Kconfig(object): # The prompt conditions for node in choice.nodes: if node.prompt: - make_depend_on(choice, node.prompt[1]) + depend_on(choice, node.prompt[1]) # The default symbol conditions for _, cond in choice.defaults: - make_depend_on(choice, cond) + depend_on(choice, cond) def _add_choice_deps(self): # Choices also depend on the choice symbols themselves, because the @@ -3641,26 +3747,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 +3777,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 +3800,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 +3809,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 +3820,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,15 +3868,15 @@ 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( + raise KconfigError("{}error: couldn't parse '{}': {}".format( "" if self.filename is None else "{}:{}: ".format(self.filename, self.linenr), self._line.strip(), msg)) @@ -3907,6 +4013,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 +4027,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 +4081,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 +4108,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 +4347,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 +4397,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 +4438,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 +4548,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 +4576,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 +4624,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 +4912,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 +4958,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 +4976,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,12 +5060,21 @@ class Choice(object): Corresponding attributes have the same name in the Symbol and Choice classes, for consistency and compatibility. + 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 + 0, 1, or 2, or None if the user hasn't selected a mode. See + Symbol.user_value. + + 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). - 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 @@ -4952,14 +5083,6 @@ class Choice(object): 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_value: - The value (mode) selected by the user through Choice.set_value(). Either - 0, 1, or 2, or None if the user hasn't selected a mode. See - Symbol.user_value. - - WARNING: Do not assign directly to this. It will break things. Use - Choice.set_value() 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 +5092,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 +5224,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 +5272,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 @@ -5251,8 +5395,8 @@ class Choice(object): self._cached_selection = _NO_CACHED_SELECTION - # is_constant is checked by _make_depend_on(). Just set it to avoid - # having to special-case choices. + # is_constant is checked by _depend_on(). Just set it to avoid having + # to special-case choices. self.is_constant = self.is_optional = False # See Kconfig._build_dep() @@ -6050,25 +6194,32 @@ 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 SystemExit) on errors. - 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. + 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 - try: - return Kconfig("Kconfig" if len(sys.argv) < 2 else sys.argv[1]) - 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()) + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=description) + + parser.add_argument( + "kconfig", + metavar="KCONFIG", + default="Kconfig", + nargs="?", + help="Top-level Kconfig file (default: Kconfig)") + + return Kconfig(parser.parse_args().kconfig, suppress_traceback=True) def standard_config_filename(): @@ -6084,25 +6235,9 @@ def standard_config_filename(): def load_allconfig(kconf, filename): """ - Helper for all*config. Loads (merges) the configuration file specified by - KCONFIG_ALLCONFIG, if any. See Documentation/kbuild/kconfig.txt in the - Linux kernel. - - Disables warnings for duplicated assignments within configuration files for - the duration of the call (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. + Use Kconfig.load_allconfig() instead, which was added in Kconfiglib 13.4.0. + Supported for backwards compatibility. Might be removed at some point after + a long period of deprecation warnings. """ allconfig = os.getenv("KCONFIG_ALLCONFIG") if allconfig is None: @@ -6178,7 +6313,7 @@ def _visibility(sc): 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'. # Constant symbols in 'expr' are skipped as they can never change value # anyway. @@ -6186,11 +6321,11 @@ def _make_depend_on(sc, expr): if expr.__class__ is tuple: # AND, OR, NOT, or relation - _make_depend_on(sc, expr[1]) + _depend_on(sc, expr[1]) # NOTs only have a single operand if expr[0] is not NOT: - _make_depend_on(sc, expr[2]) + _depend_on(sc, expr[2]) elif not expr.is_constant: # Non-constant symbol, or choice @@ -6286,20 +6421,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 +6685,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 +6709,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) @@ -6648,8 +6779,7 @@ def _error_if_fn(kconf, _, cond, msg): def _shell_fn(kconf, _, command): - # Only import as needed, to save some startup time - import subprocess + import subprocess # Only import as needed, to save some startup time stdout, stderr = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE @@ -7015,8 +7145,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'"|\'|\\|\$\(')