From cc14c40a2dd88d48eff1ec82a4dd12688604bfde Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Mon, 3 Jun 2019 19:57:27 +0200 Subject: [PATCH] kconfiglib: Unclutter symbol strings, avoid redundant writes, misc. Update kconfiglib, menuconfig, and guiconfig to upstream revision 5c904f4549 to get various improvements and fixes in: - Marc Herbert found an issue involving symlinks, absolute paths, and rsource that could lead to files not being found. The root cause was relpath() assuming that symlink/../bar is the same as bar/, which isn't guaranteed. Fix it by handling paths in a simpler, more textual way. - Propagated dependencies from 'depends on' are now stripped from properties when symbols are printed (e.g. in information dialogs and generated documentation). The printed representation now also uses shorthands. Before: config A bool prompt "foo" if C && D default A if B && C && D depends on C && D After: config A bool "foo" default A if B depends on C && D - Before writing a configuration file or header, Kconfiglib now compares the previous contents of the file against the new contents, and skips the write if there's no change. This avoids updating the modification time, and can save work. A message like "No change to '.config'" is shown when there's no change. - .config now has '# end of ' comments to make it easier to see where a menu ends. This was taken from a change to the C tools. - load_config() and write_(min_)config() now return a message that can be printed with print(kconf.load_config()). This allows messages to be reused in e.g. the configuration interfaces (nice now that there's also a "No change to..." string). Signed-off-by: Ulf Magnusson --- scripts/kconfig/guiconfig.py | 28 +- scripts/kconfig/kconfiglib.py | 977 ++++++++++++++++++++++------------ scripts/kconfig/menuconfig.py | 37 +- 3 files changed, 675 insertions(+), 367 deletions(-) diff --git a/scripts/kconfig/guiconfig.py b/scripts/kconfig/guiconfig.py index 5e71f31b2c7..612677d2910 100755 --- a/scripts/kconfig/guiconfig.py +++ b/scripts/kconfig/guiconfig.py @@ -175,12 +175,12 @@ def menuconfig(kconf): _create_ui() - # Load existing configuration and check if it's outdated - _set_conf_changed(_load_config()) - # Filename to save configuration to _conf_filename = standard_config_filename() + # Load existing configuration and check if it's outdated + _set_conf_changed(_load_config()) + # Filename to save minimal configuration to _minconf_filename = "defconfig" @@ -238,7 +238,8 @@ def _load_config(): # Returns True if .config is missing or outdated. We always prompt for # saving the configuration in that case. - if not _kconf.load_config(): + print(_kconf.load_config()) + if not os.path.exists(_conf_filename): # No .config return True @@ -639,7 +640,8 @@ def _set_conf_changed(changed): global _conf_changed _conf_changed = changed - _set_status("Modified" if changed else "") + if changed: + _set_status("Modified") def _update_tree(): @@ -1341,7 +1343,6 @@ def _save(_=None): if _try_save(_kconf.write_config, _conf_filename, "configuration"): _set_conf_changed(False) - _set_status("Configuration saved to " + _conf_filename) _tree.focus_set() @@ -1363,7 +1364,6 @@ def _save_as(): break if _try_save(_kconf.write_config, filename, "configuration"): - _set_status("Configuration saved to " + filename) _conf_filename = filename break @@ -1433,7 +1433,6 @@ def _open(_=None): _update_tree() - _set_status("Configuration loaded from " + filename) break _tree.focus_set() @@ -1694,8 +1693,10 @@ def _try_save(save_fn, filename, description): # String describing the thing being saved try: - save_fn(filename) - print("{} saved to '{}'".format(description, filename)) + # save_fn() returns a message to print + msg = save_fn(filename) + _set_status(msg) + print(msg) return True except (OSError, IOError) as e: messagebox.showerror( @@ -1714,8 +1715,9 @@ def _try_load(filename): # Configuration file to load try: - _kconf.load_config(filename) - print("configuration loaded from " + filename) + msg = _kconf.load_config(filename) + _set_status(msg) + print(msg) return True except (OSError, IOError) as e: messagebox.showerror( @@ -2222,7 +2224,7 @@ def _kconfig_def_info(item): nodes = [item] if isinstance(item, MenuNode) else item.nodes - s = "Kconfig definition{}, with propagated dependencies\n" \ + s = "Kconfig definition{}, with parent deps. propagated to 'depends on'\n" \ .format("s" if len(nodes) > 1 else "") s += (len(s) - 1)*"=" diff --git a/scripts/kconfig/kconfiglib.py b/scripts/kconfig/kconfiglib.py index 4083110a7f1..5b6880d87c8 100644 --- a/scripts/kconfig/kconfiglib.py +++ b/scripts/kconfig/kconfiglib.py @@ -12,6 +12,11 @@ configuration systems. See the homepage at https://github.com/ulfalizer/Kconfiglib for a longer overview. +Since Kconfiglib 12.0.0, the library version is available in +kconfiglib.VERSION, which is a (, , ) tuple, e.g. +(12, 0, 0). + + Using Kconfiglib on the Linux kernel with the Makefile targets ============================================================== @@ -390,7 +395,7 @@ Globbing 'source' ----------------- 'source' and 'rsource' accept glob patterns, sourcing all matching Kconfig -files. They require at least one matching file, throwing a KconfigError +files. They require at least one matching file, raising a KconfigError otherwise. For example, the following statement might source sub1/foofoofoo and @@ -445,8 +450,8 @@ Some optional warnings can be controlled via environment variables: all assignments to undefined symbols within .config files. By default, no such warnings are generated. - This warning can also be enabled/disabled via - Kconfig.enable/disable_undef_warnings(). + This warning can also be enabled/disabled via the Kconfig.warn_assign_undef + variable. Preprocessor user functions defined in Python @@ -537,7 +542,10 @@ import sys # Get rid of some attribute lookups. These are obvious in context. from glob import iglob -from os.path import dirname, exists, expandvars, isabs, islink, join, relpath +from os.path import dirname, exists, expandvars, islink, join, realpath + + +VERSION = (12, 0, 0) # File layout: @@ -626,9 +634,8 @@ class Kconfig(object): top-level Kconfig file. If a file is source'd multiple times, it will appear multiple times. Use set() to get unique filenames. - Note: Using this for incremental builds is redundant. Kconfig.sync_deps() - already indirectly catches any file modifications that change the - configuration output. + Note that Kconfig.sync_deps() already indirectly catches any file + modifications that change configuration output. env_vars: A set() with the names of all environment variables referenced in the @@ -698,18 +705,55 @@ class Kconfig(object): A dictionary with all preprocessor variables, indexed by name. See the Variable class. + warn: + Set this variable to True/False to enable/disable warnings. See + Kconfig.__init__(). + + When 'warn' is False, the values of the other warning-related variables + are ignored. + + This variable as well as the other warn* variables can be read to check + the current warning settings. + + warn_to_stderr: + Set this variable to True/False to enable/disable warnings on stderr. See + Kconfig.__init__(). + + warn_assign_undef: + Set this variable to True to generate warnings for assignments to + undefined symbols in configuration files. + + This variable is False by default unless the KCONFIG_WARN_UNDEF_ASSIGN + environment variable was set to 'y' when the Kconfig instance was + created. + + warn_assign_override: + Set this variable to True to generate warnings for multiple assignments + to the same symbol in configuration files, where the assignments set + different values (e.g. CONFIG_FOO=m followed by CONFIG_FOO=y, where the + last value would get used). + + This variable is True by default. Disabling it might be useful when + merging configurations. + + warn_assign_redun: + Like warn_assign_override, but for multiple assignments setting a symbol + to the same value. + + This variable is True by default. Disabling it might be useful when + merging configurations. + warnings: - A list of strings containing all warnings that have been generated. This - allows flexibility in how warnings are printed and processed. + A list of strings containing all warnings that have been generated, for + cases where more flexibility is needed. See the 'warn_to_stderr' parameter to Kconfig.__init__() and the - Kconfig.enable/disable_stderr_warnings() functions as well. Note that - warnings still get added to Kconfig.warnings when 'warn_to_stderr' is - True. + Kconfig.warn_to_stderr variable as well. Note that warnings still get + added to Kconfig.warnings when 'warn_to_stderr' is True. - Just as for warnings printed to stderr, only optional warnings that are - enabled will get added to Kconfig.warnings. See the various - Kconfig.enable/disable_*_warnings() functions. + Just as for warnings printed to stderr, only warnings that are enabled + will get added to Kconfig.warnings. See the various Kconfig.warn* + variables. missing_syms: A list with (name, value) tuples for all assignments to undefined symbols @@ -748,13 +792,9 @@ class Kconfig(object): "_encoding", "_functions", "_set_match", + "_srctree_prefix", "_unset_match", - "_warn_for_no_prompt", - "_warn_for_override", - "_warn_for_redun_assign", - "_warn_for_undef_assign", - "_warn_to_stderr", - "_warnings_enabled", + "_warn_no_prompt", "choices", "comments", "config_prefix", @@ -776,6 +816,11 @@ class Kconfig(object): "unique_choices", "unique_defined_syms", "variables", + "warn", + "warn_assign_override", + "warn_assign_redun", + "warn_assign_undef", + "warn_to_stderr", "warnings", "y", @@ -807,8 +852,8 @@ class Kconfig(object): default warning settings (KCONFIG_WARN_UNDEF and KCONFIG_WARN_UNDEF_ASSIGN). - Raises KconfigError on syntax errors, and (possibly a subclass of) - IOError on IO errors ('errno', 'strerror', and 'filename' are + Raises KconfigError on syntax/semantic errors, and (possibly a subclass + of) IOError on IO errors ('errno', 'strerror', and 'filename' are available). Note that IOError can be caught as OSError on Python 3. filename (default: "Kconfig"): @@ -827,12 +872,12 @@ class Kconfig(object): warn (default: True): True if warnings related to this configuration should be generated. - This can be changed later with Kconfig.enable/disable_warnings(). It + This can be changed later by setting Kconfig.warn to True/False. It is provided as a constructor argument since warnings might be generated during parsing. - See the other Kconfig.enable_*_warnings() functions as well, which - enable or suppress certain warnings when warnings are enabled. + See the other Kconfig.warn_* variables as well, which enable or + suppress certain warnings when warnings are enabled. All generated warnings are added to the Kconfig.warnings list. See the class documentation. @@ -841,8 +886,8 @@ class Kconfig(object): True if warnings should be printed to stderr in addition to being added to Kconfig.warnings. - This can be changed later with - Kconfig.enable/disable_stderr_warnings(). + This can be changed later by setting Kconfig.warn_to_stderr to + True/False. encoding (default: "utf-8"): The encoding to use when reading and writing files. If None, the @@ -859,6 +904,11 @@ class Kconfig(object): Related PEP: https://www.python.org/dev/peps/pep-0538/ """ self.srctree = os.environ.get("srctree", "") + # A prefix we can reliably strip from glob() results to get a filename + # relative to $srctree. relpath() can cause issues for symlinks, + # because it assumes symlink/../foo is the same as foo/. + self._srctree_prefix = realpath(self.srctree) + os.sep + self.config_prefix = os.environ.get("CONFIG_", "CONFIG_") # Regular expressions for parsing .config files @@ -869,11 +919,11 @@ class Kconfig(object): self.warnings = [] - self._warnings_enabled = warn - self._warn_to_stderr = warn_to_stderr - self._warn_for_undef_assign = \ + self.warn = warn + self.warn_to_stderr = warn_to_stderr + self.warn_assign_undef = \ os.environ.get("KCONFIG_WARN_UNDEF_ASSIGN") == "y" - self._warn_for_redun_assign = self._warn_for_override = True + self.warn_assign_override = self.warn_assign_redun = True self._encoding = encoding @@ -1025,7 +1075,7 @@ class Kconfig(object): self._add_choice_deps() - self._warn_for_no_prompt = True + self._warn_no_prompt = True self.mainmenu_text = self.top_node.prompt[0] @@ -1045,7 +1095,7 @@ class Kconfig(object): return None - def load_config(self, filename=None, replace=True, verbose=True): + def load_config(self, filename=None, replace=True, verbose=None): """ Loads symbol values from a file in the .config format. Equivalent to calling Symbol.set_value() to set each of the values. @@ -1093,50 +1143,54 @@ class Kconfig(object): If True, all existing user values will be cleared before loading the .config. Pass False to merge configurations. - verbose (default: True): - If True and filename is None (automatically infer configuration - file), a message will be printed to stdout telling which file got - loaded (or that no file got loaded). This is meant to reduce - boilerplate in tools. + verbose (default: None): + Limited backwards compatibility to prevent crashes. A warning is + printed if anything but None is passed. - Returns True if an existing configuration was loaded (that didn't come - from the 'option defconfig_list' symbol), and False otherwise. This is - mostly useful in conjunction with filename=None, as True will always be - returned otherwise. + Prior to Kconfiglib 12.0.0, this option enabled printing of messages + to stdout when 'filename' was None. A message is (always) returned + now instead, which is more flexible. + + Will probably be removed in some future version. + + Returns a string with a message saying which file got loaded (or + possibly that no file got loaded, when 'filename' is None). This is + meant to reduce boilerplate in tools, which can do e.g. + print(kconf.load_config()). """ - loaded_existing = True + if verbose is not None: + _warn_verbose_deprecated("load_config") + + msg = None if filename is None: filename = standard_config_filename() - if exists(filename): - if verbose: - print("Using existing configuration '{}' as base" - .format(filename)) - else: - filename = self.defconfig_filename - if filename is None: - if verbose: - print("Using default symbol values as base") - return False + if not exists(filename) and \ + not exists(join(self.srctree, filename)): + defconfig = self.defconfig_filename + if defconfig is None: + return "Using default symbol values (no '{}')" \ + .format(filename) - if verbose: - print("Using default configuration found in '{}' as " - "base".format(filename)) + msg = " default configuration '{}' (no '{}')" \ + .format(defconfig, filename) + filename = defconfig - loaded_existing = False + if not msg: + msg = " configuration '{}'".format(filename) # Disable the warning about assigning to symbols without prompts. This # is normal and expected within a .config file. - self._warn_for_no_prompt = False + self._warn_no_prompt = False - # This stub only exists to make sure _warn_for_no_prompt gets reenabled + # This stub only exists to make sure _warn_no_prompt gets reenabled try: self._load_config(filename, replace) except UnicodeDecodeError as e: _decoding_error(e, filename) finally: - self._warn_for_no_prompt = True + self._warn_no_prompt = True - return loaded_existing + return ("Loaded" if replace else "Merged") + msg def _load_config(self, filename, replace): with self._open_config(filename) as f: @@ -1257,14 +1311,15 @@ class Kconfig(object): else: display_user_val = sym.user_value - msg = '{} set more than once. Old value: "{}", new value: "{}".'.format( + msg = '{} set more than once. Old value "{}", new value "{}".'.format( _name_and_loc(sym), display_user_val, val ) if display_user_val == val: - self._warn_redun_assign(msg, filename, linenr) - else: - self._warn_override(msg, filename, linenr) + if self.warn_assign_redun: + self._warn(msg, filename, linenr) + elif self.warn_assign_override: + self._warn(msg, filename, linenr) sym.set_value(val) @@ -1284,8 +1339,7 @@ class Kconfig(object): # Called for assignments to undefined symbols during .config loading self.missing_syms.append((name, val)) - - if self._warn_for_undef_assign: + if self.warn_assign_undef: self._warn( "attempt to assign the value '{}' to the undefined symbol {}" .format(val, name), filename, linenr) @@ -1300,6 +1354,11 @@ class Kconfig(object): write_config(). The order in the C implementation depends on the hash table implementation as of writing, and so won't match. + If 'filename' exists and its contents is identical to what would get + written out, it is left untouched. This avoids updating file metadata + like the modification time and possibly triggering redundant work in + build tools. + filename: Self-explanatory. @@ -1308,37 +1367,48 @@ class Kconfig(object): would usually want it enclosed in '/* */' to make it a C comment, and include a final terminating newline. """ - with self._open(filename, "w") as f: - f.write(header) + self._write_if_changed(filename, self._autoconf_contents(header)) - for sym in self.unique_defined_syms: - # Note: _write_to_conf is determined when the value is - # calculated. This is a hidden function call due to - # property magic. - val = sym.str_value - if sym._write_to_conf: - if sym.orig_type in _BOOL_TRISTATE: - if val != "n": - f.write("#define {}{}{} 1\n" - .format(self.config_prefix, sym.name, - "_MODULE" if val == "m" else "")) + def _autoconf_contents(self, header): + # write_autoconf() helper. Returns the contents to write as a string, + # with 'header' at the beginning. - elif sym.orig_type is STRING: - f.write('#define {}{} "{}"\n' - .format(self.config_prefix, sym.name, - escape(val))) + # "".join()ed later + chunks = [header] + add = chunks.append - else: # sym.orig_type in _INT_HEX: - if sym.orig_type is HEX and \ - not val.startswith(("0x", "0X")): - val = "0x" + val + for sym in self.unique_defined_syms: + # _write_to_conf is determined when the value is calculated. This + # is a hidden function call due to property magic. + val = sym.str_value + if not sym._write_to_conf: + continue - f.write("#define {}{} {}\n" - .format(self.config_prefix, sym.name, val)) + if sym.orig_type in _BOOL_TRISTATE: + if val == "y": + add("#define {}{} 1\n" + .format(self.config_prefix, sym.name)) + elif val == "m": + add("#define {}{}_MODULE 1\n" + .format(self.config_prefix, sym.name)) + + elif sym.orig_type is STRING: + add('#define {}{} "{}"\n' + .format(self.config_prefix, sym.name, escape(val))) + + else: # sym.orig_type in _INT_HEX: + if sym.orig_type is HEX and \ + not val.startswith(("0x", "0X")): + val = "0x" + val + + add("#define {}{} {}\n" + .format(self.config_prefix, sym.name, val)) + + return "".join(chunks) def write_config(self, filename=None, header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n", - save_old=True, verbose=True): + save_old=True, verbose=None): r""" Writes out symbol values in the .config format. The format matches the C implementation, including ordering. @@ -1351,10 +1421,15 @@ class Kconfig(object): See the 'Intro to symbol values' section in the module docstring to understand which symbols get written out. + If 'filename' exists and its contents is identical to what would get + written out, it is left untouched. This avoids updating file metadata + like the modification time and possibly triggering redundant work in + build tools. + filename (default: None): Filename to save configuration to (a string). - If None (the default), the filename in the the environment variable + If None (the default), the filename in the environment variable KCONFIG_CONFIG is used if set, and ".config" otherwise. See standard_config_filename(). @@ -1369,38 +1444,113 @@ class Kconfig(object): written. Errors are silently ignored if .old cannot be written (e.g. - due to being a directory). + due to being a directory, or being something like + /dev/null). - verbose (default: True): - If True and filename is None (automatically infer configuration - file), a message will be printed to stdout telling which file got - written. This is meant to reduce boilerplate in tools. + verbose (default: None): + Limited backwards compatibility to prevent crashes. A warning is + printed if anything but None is passed. + + Prior to Kconfiglib 12.0.0, this option enabled printing of messages + to stdout when 'filename' was None. A message is (always) returned + now instead, which is more flexible. + + Will probably be removed in some future version. + + Returns a string with a message saying which file got saved. This is + meant to reduce boilerplate in tools, which can do e.g. + print(kconf.write_config()). """ + if verbose is not None: + _warn_verbose_deprecated("write_config") + if filename is None: filename = standard_config_filename() - else: - verbose = False + + contents = self._config_contents(header) + if self._contents_eq(filename, contents): + return "No change to '{}'".format(filename) if save_old: _save_old(filename) with self._open(filename, "w") as f: - f.write(header) + f.write(contents) - for node in self.node_iter(unique_syms=True): - item = node.item + return "Configuration saved to '{}'".format(filename) - if item.__class__ is Symbol: - f.write(item.config_string) + def _config_contents(self, header): + # write_config() helper. Returns the contents to write as a string, + # with '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. - elif expr_value(node.dep) and \ - ((item is MENU and expr_value(node.visibility)) or - item is COMMENT): + # node_iter() was used here before commit 3aea9f7 ("Add '# end of + # ' after menus in .config"). Those comments get tricky to + # implement with it. - f.write("\n#\n# {}\n#\n".format(node.prompt[0])) + for sym in self.unique_defined_syms: + sym._visited = False - if verbose: - print("Configuration written to '{}'".format(filename)) + # 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 + if node.list: + node = node.list + elif node.next: + node = node.next + else: + while node.parent: + node = node.parent + + # Add a comment when leaving visible menus + if node.item is MENU and expr_value(node.dep) and \ + expr_value(node.visibility) and \ + node is not self.top_node: + add("# end of {}\n".format(node.prompt[0])) + after_end_comment = True + + if node.next: + node = node.next + break + else: + # No more nodes + return "".join(chunks) + + # Generate configuration output for the node + + item = node.item + + if item.__class__ is Symbol: + if item._visited: + continue + item._visited = True + + conf_string = item.config_string + if not conf_string: + continue + + if after_end_comment: + # Add a blank line before the first symbol printed after an + # '# end of ...' comment + after_end_comment = False + add("\n") + add(conf_string) + + elif expr_value(node.dep) and \ + ((item is MENU and expr_value(node.visibility)) or + item is COMMENT): + + 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"): @@ -1422,34 +1572,53 @@ class Kconfig(object): 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. + + Returns a string with a message saying which file got saved. 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(header) + f.write(contents) - for sym in self.unique_defined_syms: - # Skip symbols that cannot be changed. Only check - # non-choice symbols, as selects don't affect choice - # symbols. - if not sym.choice and \ - sym.visibility <= expr_value(sym.rev_dep): - continue + return "Minimal configuration saved to '{}'".format(filename) - # Skip symbols whose value matches their default - if sym.str_value == sym._str_default(): - continue + def _min_config_contents(self, header): + # write_min_config() helper. Returns the contents to write as a string, + # with 'header' at the beginning. - # Skip symbols that would be selected by default in a - # choice, unless the choice is optional or the symbol type - # isn't bool (it might be possible to set the choice mode - # to n or the symbol to m in those cases). - if sym.choice and \ - not sym.choice.is_optional and \ - sym.choice._get_selection_from_defaults() is sym and \ - sym.orig_type is BOOL and \ - sym.tri_value == 2: - continue + chunks = [header] + add = chunks.append - f.write(sym.config_string) + for sym in self.unique_defined_syms: + # Skip symbols that cannot be changed. Only check + # non-choice symbols, as selects don't affect choice + # symbols. + if not sym.choice and \ + sym.visibility <= expr_value(sym.rev_dep): + continue + + # Skip symbols whose value matches their default + if sym.str_value == sym._str_default(): + continue + + # Skip symbols that would be selected by default in a + # choice, unless the choice is optional or the symbol type + # isn't bool (it might be possible to set the choice mode + # to n or the symbol to m in those cases). + if sym.choice and \ + not sym.choice.is_optional and \ + sym.choice._get_selection_from_defaults() is sym and \ + sym.orig_type is BOOL and \ + sym.tri_value == 2: + continue + + add(sym.config_string) + + return "".join(chunks) def sync_deps(self, path): """ @@ -1490,6 +1659,11 @@ class Kconfig(object): 3. A new auto.conf with the current symbol values is written, to keep track of them for the next build. + If auto.conf exists and its contents is identical to what would + get written out, it is left untouched. This avoids updating file + metadata like the modification time and possibly triggering + redundant work in build tools. + The last piece of the puzzle is knowing what symbols each source file depends on. Knowing that, dependencies can be added from source files @@ -1512,13 +1686,12 @@ class Kconfig(object): self._load_old_vals(path) for sym in self.unique_defined_syms: - # Note: _write_to_conf is determined when the value is - # calculated. This is a hidden function call due to - # property magic. + # _write_to_conf is determined when the value is calculated. This + # is a hidden function call due to property magic. val = sym.str_value - # Note: n tristate values do not get written to auto.conf and - # autoconf.h, making a missing symbol logically equivalent to n + # n tristate values do not get written to auto.conf and autoconf.h, + # making a missing symbol logically equivalent to n if sym._write_to_conf: if sym._old_val is None and \ @@ -1602,10 +1775,18 @@ class Kconfig(object): # A separate helper function is neater than complicating write_config() # by passing a flag to it, plus we only need to look at symbols here. - with self._open(join(path, "auto.conf"), "w") as f: - for sym in self.unique_defined_syms: - if not (sym.orig_type in _BOOL_TRISTATE and not sym.tri_value): - f.write(sym.config_string) + self._write_if_changed( + os.path.join(path, "auto.conf"), + self._old_vals_contents()) + + def _old_vals_contents(self): + # _write_old_vals() helper. Returns the contents to write as a string. + + # Temporary list instead of generator makes this a bit faster + return "".join([ + sym.config_string for sym in self.unique_defined_syms + if not (sym.orig_type in _BOOL_TRISTATE and not sym.tri_value) + ]) def node_iter(self, unique_syms=False): """ @@ -1696,7 +1877,7 @@ class Kconfig(object): Removes any user values from all symbols, as if Kconfig.load_config() or Symbol.set_value() had never been called. """ - self._warn_for_no_prompt = False + self._warn_no_prompt = False try: # set_value() already rejects undefined symbols, and they don't # need to be invalidated (because their value never changes), so we @@ -1707,98 +1888,100 @@ class Kconfig(object): for choice in self.unique_choices: choice.unset_value() finally: - self._warn_for_no_prompt = True + self._warn_no_prompt = True def enable_warnings(self): """ - See Kconfig.__init__(). + Do 'Kconfig.warn = True' instead. Maintained for backwards + compatibility. """ - self._warnings_enabled = True + self.warn = True def disable_warnings(self): """ - See Kconfig.__init__(). + Do 'Kconfig.warn = False' instead. Maintained for backwards + compatibility. """ - self._warnings_enabled = False + self.warn = False def enable_stderr_warnings(self): """ - See Kconfig.__init__(). + Do 'Kconfig.warn_to_stderr = True' instead. Maintained for backwards + compatibility. """ - self._warn_to_stderr = True + self.warn_to_stderr = True def disable_stderr_warnings(self): """ - See Kconfig.__init__(). + Do 'Kconfig.warn_to_stderr = False' instead. Maintained for backwards + compatibility. """ - self._warn_to_stderr = False + self.warn_to_stderr = False def enable_undef_warnings(self): """ - Enables warnings for assignments to undefined symbols. Disabled by - default unless the KCONFIG_WARN_UNDEF_ASSIGN environment variable was - set to 'y' when the Kconfig instance was created. + Do 'Kconfig.warn_assign_undef = True' instead. Maintained for backwards + compatibility. """ - self._warn_for_undef_assign = True + self.warn_assign_undef = True def disable_undef_warnings(self): """ - See enable_undef_assign(). + Do 'Kconfig.warn_assign_undef = False' instead. Maintained for + backwards compatibility. """ - self._warn_for_undef_assign = False + self.warn_assign_undef = False def enable_override_warnings(self): """ - Enables warnings for duplicated assignments in .config files that set - different values (e.g. CONFIG_FOO=m followed by CONFIG_FOO=y, where - the last value set is used). - - These warnings are enabled by default. Disabling them might be helpful - in certain cases when merging configurations. + Do 'Kconfig.warn_assign_override = True' instead. Maintained for + backwards compatibility. """ - self._warn_for_override = True + self.warn_assign_override = True def disable_override_warnings(self): """ - See enable_override_warnings(). + Do 'Kconfig.warn_assign_override = False' instead. Maintained for + backwards compatibility. """ - self._warn_for_override = False + self.warn_assign_override = False def enable_redun_warnings(self): """ - Enables warnings for duplicated assignments in .config files that all - set the same value. - - These warnings are enabled by default. Disabling them might be helpful - in certain cases when merging configurations. + Do 'Kconfig.warn_assign_redun = True' instead. Maintained for backwards + compatibility. """ - self._warn_for_redun_assign = True + self.warn_assign_redun = True def disable_redun_warnings(self): """ - See enable_redun_warnings(). + Do 'Kconfig.warn_assign_redun = False' instead. Maintained for + backwards compatibility. """ - self._warn_for_redun_assign = False + self.warn_assign_redun = False def __repr__(self): """ Returns a string with information about the Kconfig object when it is evaluated on e.g. the interactive Python prompt. """ + def status(flag): + return "enabled" if flag else "disabled" + return "<{}>".format(", ".join(( "configuration with {} symbols".format(len(self.syms)), 'main menu prompt "{}"'.format(self.mainmenu_text), "srctree is current directory" if not self.srctree else 'srctree "{}"'.format(self.srctree), 'config symbol prefix "{}"'.format(self.config_prefix), - "warnings " + - ("enabled" if self._warnings_enabled else "disabled"), - "printing of warnings to stderr " + - ("enabled" if self._warn_to_stderr else "disabled"), + "warnings " + status(self.warn), + "printing of warnings to stderr " + status(self.warn_to_stderr), "undef. symbol assignment warnings " + - ("enabled" if self._warn_for_undef_assign else "disabled"), + status(self.warn_assign_undef), + "overriding symbol assignment warnings " + + status(self.warn_assign_override), "redundant symbol assignment warnings " + - ("enabled" if self._warn_for_redun_assign else "disabled") + status(self.warn_assign_redun) ))) # @@ -1836,17 +2019,23 @@ class Kconfig(object): "set to '{}'".format(self.srctree) if self.srctree else "unset or blank")) - def _enter_file(self, full_filename, rel_filename): + def _enter_file(self, filename): # Jumps to the beginning of a sourced Kconfig file, saving the previous # position and file object. # - # full_filename: - # Actual path to the file. - # - # rel_filename: - # File path with $srctree prefix stripped, stored in e.g. - # self._filename (which makes it indirectly show up in - # MenuNode.filename). Equals full_filename for absolute paths. + # filename: + # Absolute path to file + + # Path relative to $srctree, stored in e.g. self._filename + # (which makes it indirectly show up in MenuNode.filename). Equals + # 'filename' for absolute paths passed to 'source'. + if filename.startswith(self._srctree_prefix): + # Relative path (or a redundant absolute path to within $srctree, + # but it's probably fine to reduce those too) + rel_filename = filename[len(self._srctree_prefix):] + else: + # Absolute path + rel_filename = filename self.kconfig_filenames.append(rel_filename) @@ -1881,14 +2070,13 @@ class Kconfig(object): "\n".join("{}:{}".format(name, linenr) for name, linenr in self._include_path))) - # Note: We already know that the file exists - try: - self._readline = self._open(full_filename, "r").readline + self._readline = self._open(filename, "r").readline except IOError as e: + # We already know that the file exists raise _KconfigIOError( e, "{}:{}: Could not open '{}' ({}: {})" - .format(self._filename, self._linenr, full_filename, + .format(self._filename, self._linenr, filename, errno.errorcode[e.errno], e.strerror)) self._filename = rel_filename @@ -1918,8 +2106,8 @@ class Kconfig(object): # a help text) return True - # Note: readline() returns '' over and over at EOF, which we rely on - # for help texts at the end of files (see _line_after_help()) + # readline() returns '' over and over at EOF, which we rely on for help + # texts at the end of files (see _line_after_help()) line = self._readline() if not line: return False @@ -1956,6 +2144,33 @@ class Kconfig(object): self._tokens = self._tokenize(line) self._reuse_tokens = True + def _write_if_changed(self, filename, contents): + # Writes 'contents' into 'filename', but only if it differs from the + # current contents of the file. + # + # Another variant would be write a temporary file on the same + # filesystem, compare the files, and rename() the temporary file if it + # 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. + + if not self._contents_eq(filename, contents): + with self._open(filename, "w") as f: + f.write(contents) + + def _contents_eq(self, filename, contents): + # Returns True if the contents of 'filename' is 'contents' (a string), + # and False otherwise (including if 'filename' can't be opened/read) + + try: + with self._open(filename, "r") as f: + # Robust re. things like encoding and line endings (mmap() + # trickery isn't) + return f.read(len(contents) + 1) == contents + except IOError: + # If the error here would prevent writing the file as well, we'll + # notice it later + return False # # Tokenization @@ -2007,9 +2222,9 @@ class Kconfig(object): # regexes and string operations where possible. This is the biggest # hotspot during parsing. # - # Note: It might be possible to rewrite this to 'yield' tokens instead, - # working across multiple lines. The 'option env' lookback thing below - # complicates things though. + # It might be possible to rewrite this to 'yield' tokens instead, + # working across multiple lines. Lookback and compatibility with old + # janky versions of the C tools complicate things though. # Initial token on the line match = _command_match(s) @@ -2258,7 +2473,6 @@ class Kconfig(object): return True return False - # # Preprocessor logic # @@ -2516,7 +2730,6 @@ class Kconfig(object): return "" - # # Parsing # @@ -2620,19 +2833,19 @@ class Kconfig(object): elif t0 in _SOURCE_TOKENS: pattern = self._expect_str_and_eol() - # Check if the pattern is absolute and avoid stripping srctree - # from it below in that case. We must do the check before - # join()'ing, as srctree might be an absolute path. - pattern_is_abs = isabs(pattern) - if t0 in _REL_SOURCE_TOKENS: # Relative source pattern = join(dirname(self._filename), pattern) - # Sort the glob results to ensure a consistent ordering of - # Kconfig symbols, which indirectly ensures a consistent - # ordering in e.g. .config files - filenames = sorted(iglob(join(self.srctree, pattern))) + # - glob() doesn't support globbing relative to a directory, so + # we need to prepend $srctree to 'pattern'. Use join() + # instead of '+' so that an absolute path in 'pattern' is + # preserved. + # + # - Sort the glob results to ensure a consistent ordering of + # Kconfig symbols, which indirectly ensures a consistent + # ordering in e.g. .config files + filenames = sorted(iglob(join(self._srctree_prefix, pattern))) if not filenames and t0 in _OBL_SOURCE_TOKENS: raise KconfigError( @@ -2646,18 +2859,8 @@ class Kconfig(object): if self.srctree else "unset or blank")) for filename in filenames: - self._enter_file( - filename, - # Unless an absolute path is passed to *source, strip - # the $srctree prefix from the filename. That way it - # appears without a $srctree prefix in - # MenuNode.filename, which is nice e.g. when generating - # documentation. - filename if pattern_is_abs else - relpath(filename, self.srctree)) - + self._enter_file(filename) prev = self._parse_block(None, parent, prev) - self._leave_file() elif t0 is end_token: @@ -2949,7 +3152,7 @@ class Kconfig(object): return def _set_type(self, node, new_type): - # Note: UNKNOWN == 0, which is falsy + # 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), @@ -3223,7 +3426,6 @@ class Kconfig(object): for choice in self.unique_choices: choice._invalidate() - # # Post-parsing menu tree processing, including dependency propagation and # implicit submenu creation @@ -3321,18 +3523,15 @@ class Kconfig(object): cur = node.list while cur: - cur.dep = dep = self._make_and(cur.dep, basedep) - - # Propagate dependencies to prompt - if cur.prompt: - cur.prompt = (cur.prompt[0], - self._make_and(cur.prompt[1], dep)) + dep = cur.dep = self._make_and(cur.dep, basedep) if cur.item.__class__ in _SYMBOL_CHOICE: - # Propagate 'visible if' dependencies to the prompt + # Propagate 'visible if' and dependencies to the prompt if cur.prompt: cur.prompt = (cur.prompt[0], - self._make_and(cur.prompt[1], visible_if)) + self._make_and( + cur.prompt[1], + self._make_and(visible_if, dep))) # Propagate dependencies to defaults if cur.defaults: @@ -3354,6 +3553,11 @@ class Kconfig(object): cur.implies = [(target, self._make_and(cond, dep)) for target, cond in cur.implies] + elif cur.prompt: # Not a symbol/choice + # Propagate dependencies to the prompt. 'visible if' is only + # propagated to symbols/choices. + cur.prompt = (cur.prompt[0], + self._make_and(cur.prompt[1], dep)) cur = cur.next @@ -3390,7 +3594,6 @@ class Kconfig(object): target.weak_rev_dep, self._make_and(sym, cond)) - # # Misc. # @@ -3641,26 +3844,16 @@ class Kconfig(object): def _warn(self, msg, filename=None, linenr=None): # For printing general warnings - if self._warnings_enabled: - msg = "warning: " + msg - if filename is not None: - msg = "{}:{}: {}".format(filename, linenr, msg) + if not self.warn: + return - self.warnings.append(msg) - if self._warn_to_stderr: - sys.stderr.write(msg + "\n") + msg = "warning: " + msg + if filename is not None: + msg = "{}:{}: {}".format(filename, linenr, msg) - def _warn_override(self, msg, filename, linenr): - # See the class documentation - - if self._warn_for_override: - self._warn(msg, filename, linenr) - - def _warn_redun_assign(self, msg, filename, linenr): - # See the class documentation - - if self._warn_for_redun_assign: - self._warn(msg, filename, linenr) + self.warnings.append(msg) + if self.warn_to_stderr: + sys.stderr.write(msg + "\n") class Symbol(object): @@ -4196,8 +4389,8 @@ class Symbol(object): """ See the class documentation. """ - # Note: _write_to_conf is determined when the value is calculated. This - # is a hidden function call due to property magic. + # _write_to_conf is determined when the value is calculated. This is a + # hidden function call due to property magic. val = self.str_value if not self._write_to_conf: return "" @@ -4376,8 +4569,9 @@ class Symbol(object): def __str__(self): """ - Returns a string representation of the symbol when it is printed, - matching the Kconfig format, with parent dependencies propagated. + Returns a string representation of the symbol when it is printed. + Matches the Kconfig format, with any parent dependencies propagated to + the 'depends on' condition. The string is constructed by joining the strings returned by MenuNode.__str__() for each of the symbol's menu nodes, so symbols @@ -4544,7 +4738,7 @@ class Symbol(object): self._rec_invalidate() return - if self.kconfig._warn_for_no_prompt: + if self.kconfig._warn_no_prompt: self.kconfig._warn(_name_and_loc(self) + " has no prompt, meaning " "user values have no effect on it") @@ -4952,9 +5146,10 @@ class Choice(object): def __str__(self): """ - Returns a string representation of the choice when it is printed, - matching the Kconfig format (though without the contained choice - symbols). + Returns a string representation of the choice when it is printed. + Matches the Kconfig format (though without the contained choice + symbols), with any parent dependencies propagated to the 'depends on' + condition. The returned string does not end in a newline. @@ -5133,6 +5328,18 @@ class MenuNode(object): ranges: Like MenuNode.defaults, for ranges. + orig_prompt: + orig_defaults: + orig_selects: + orig_implies: + orig_ranges: + These work the like the corresponding attributes without orig_*, but omit + any dependencies propagated from 'depends on' and surrounding 'if's (the + direct dependencies, stored in MenuNode.dep). + + One use for this is generating less cluttered documentation, by only + showing the direct dependencies in one place. + help: The help text for the menu node for Symbols and Choices. None if there is no help text. Always stored in the node rather than the Symbol or Choice. @@ -5231,6 +5438,47 @@ class MenuNode(object): self.implies = [] self.ranges = [] + @property + def orig_prompt(self): + """ + See the class documentation. + """ + if not self.prompt: + return None + return (self.prompt[0], self._strip_dep(self.prompt[1])) + + @property + def orig_defaults(self): + """ + See the class documentation. + """ + return [(default, self._strip_dep(cond)) + for default, cond in self.defaults] + + @property + def orig_selects(self): + """ + See the class documentation. + """ + return [(select, self._strip_dep(cond)) + for select, cond in self.selects] + + @property + def orig_implies(self): + """ + See the class documentation. + """ + return [(imply, self._strip_dep(cond)) + for imply, cond in self.implies] + + @property + def orig_ranges(self): + """ + See the class documentation. + """ + return [(low, high, self._strip_dep(cond)) + for low, high, cond in self.ranges] + @property def referenced(self): """ @@ -5316,8 +5564,9 @@ class MenuNode(object): def __str__(self): """ - Returns a string representation of the menu node, matching the Kconfig - format. + Returns a string representation of the menu node. Matches the Kconfig + format, with any parent dependencies propagated to the 'depends on' + condition. The output could (almost) be fed back into a Kconfig parser to redefine the object associated with the menu node. See the module documentation @@ -5371,13 +5620,20 @@ class MenuNode(object): else: lines = ["choice " + sc.name if sc.name else "choice"] - if sc.orig_type: # != UNKNOWN + if sc.orig_type and not self.prompt: # sc.orig_type != UNKNOWN + # If there's a prompt, we'll use the ' "prompt"' shorthand + # instead indent_add(TYPE_TO_STR[sc.orig_type]) if self.prompt: - indent_add_cond( - 'prompt "{}"'.format(escape(self.prompt[0])), - self.prompt[1]) + if sc.orig_type: + prefix = TYPE_TO_STR[sc.orig_type] + else: + # Symbol defined without a type (which generates a warning) + prefix = "prompt" + + indent_add_cond(prefix + ' "{}"'.format(escape(self.prompt[0])), + self.orig_prompt[1]) if sc.__class__ is Symbol: if sc.is_allnoconfig_y: @@ -5392,13 +5648,13 @@ class MenuNode(object): if sc is sc.kconfig.modules: indent_add("option modules") - for low, high, cond in self.ranges: + for low, high, cond in self.orig_ranges: indent_add_cond( "range {} {}".format(sc_expr_str_fn(low), sc_expr_str_fn(high)), cond) - for default, cond in self.defaults: + for default, cond in self.orig_defaults: indent_add_cond("default " + expr_str(default, sc_expr_str_fn), cond) @@ -5406,10 +5662,10 @@ class MenuNode(object): indent_add("optional") if sc.__class__ is Symbol: - for select, cond in self.selects: + for select, cond in self.orig_selects: indent_add_cond("select " + sc_expr_str_fn(select), cond) - for imply, cond in self.implies: + for imply, cond in self.orig_implies: indent_add_cond("imply " + sc_expr_str_fn(imply), cond) if self.dep is not sc.kconfig.y: @@ -5422,6 +5678,21 @@ class MenuNode(object): return "\n".join(lines) + def _strip_dep(self, expr): + # Helper function for removing MenuNode.dep from 'expr'. Uses two + # pieces of internal knowledge: (1) Expressions are reused rather than + # copied, and (2) the direct dependencies always appear at the end. + + # ... if dep -> ... if y + if self.dep is expr: + return self.kconfig.y + + # (AND, X, dep) -> X + if expr.__class__ is tuple and expr[0] is AND and expr[2] is self.dep: + return expr[1] + + return expr + class Variable(object): """ @@ -5440,9 +5711,9 @@ class Variable(object): with :=), this will equal 'value'. Accessing this property will raise a KconfigError if the expansion seems to be stuck in a loop. - Note: Accessing this field is the same as calling expanded_value_w_args() - with no arguments. I hadn't considered function arguments when adding it. - It is retained for backwards compatibility though. + Accessing this field is the same as calling expanded_value_w_args() with + no arguments. I hadn't considered function arguments when adding it. It + is retained for backwards compatibility though. is_recursive: True if the variable is recursive (defined with =). @@ -5479,7 +5750,12 @@ class Variable(object): class KconfigError(Exception): - "Exception raised for Kconfig-related errors" + """ + Exception raised for Kconfig-related errors. + + KconfigError and KconfigSyntaxError are the same class. The + KconfigSyntaxError alias is only maintained for backwards compatibility. + """ KconfigSyntaxError = KconfigError # Backwards compatibility @@ -5574,7 +5850,9 @@ def standard_sc_expr_str(sc): See expr_str(). """ if sc.__class__ is Symbol: - return '"{}"'.format(escape(sc.name)) if sc.is_constant else sc.name + if sc.is_constant and sc.name not in ("n", "m", "y"): + return '"{}"'.format(escape(sc.name)) + return sc.name # Choice return "".format(sc.name) if sc.name else "" @@ -5742,8 +6020,8 @@ def standard_config_filename(): Helper for tools. Returns the value of KCONFIG_CONFIG (which specifies the .config file to load/save) if it is set, and ".config" otherwise. - Note: Calling load_config() with filename=None might give the behavior you - want, without having to use this function. + Calling load_config() with filename=None might give the behavior you want, + without having to use this function. """ return os.environ.get("KCONFIG_CONFIG", ".config") @@ -5755,8 +6033,8 @@ def load_allconfig(kconf, filename): Linux kernel. Disables warnings for duplicated assignments within configuration files for - the duration of the call (disable_override_warnings() + - disable_redun_warnings()), and enables them at the end. The + 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 @@ -5770,38 +6048,39 @@ def load_allconfig(kconf, filename): Command-specific configuration filename - "allyes.config", "allno.config", etc. """ + allconfig = os.environ.get("KCONFIG_ALLCONFIG") + if allconfig is None: + return + def std_msg(e): # "Upcasts" a _KconfigIOError to an IOError, removing the custom # __str__() message. The standard message is better here. return IOError(e.errno, e.strerror, e.filename) - kconf.disable_override_warnings() - kconf.disable_redun_warnings() + old_warn_assign_override = kconf.warn_assign_override + old_warn_assign_redun = kconf.warn_assign_redun + kconf.warn_assign_override = kconf.warn_assign_redun = False - allconfig = os.environ.get("KCONFIG_ALLCONFIG") - if allconfig is not None: - if allconfig in ("", "1"): + if allconfig in ("", "1"): + try: + print(kconf.load_config(filename, False)) + except IOError as e1: try: - kconf.load_config(filename, False) - except IOError as e1: - try: - kconf.load_config("all.config", False) - except IOError as e2: - sys.exit("error: KCONFIG_ALLCONFIG is set, but neither {} " - "nor all.config could be opened: {}, {}" - .format(filename, std_msg(e1), std_msg(e2))) - else: - try: - kconf.load_config(allconfig, False) - except IOError as e: - sys.exit("error: KCONFIG_ALLCONFIG is set to '{}', which " - "could not be opened: {}" - .format(allconfig, std_msg(e))) + print(kconf.load_config("all.config", False)) + except IOError as e2: + sys.exit("error: KCONFIG_ALLCONFIG is set, but neither {} " + "nor all.config could be opened: {}, {}" + .format(filename, std_msg(e1), std_msg(e2))) + else: + try: + print(kconf.load_config(allconfig, False)) + except IOError as e: + sys.exit("error: KCONFIG_ALLCONFIG is set to '{}', which " + "could not be opened: {}" + .format(allconfig, std_msg(e))) - # API wart: It would be nice if there was a way to query and/or push/pop - # warning settings - kconf.enable_override_warnings() - kconf.enable_redun_warnings() + kconf.warn_assign_override = old_warn_assign_override + kconf.warn_assign_redun = old_warn_assign_redun # @@ -5916,49 +6195,37 @@ def _touch_dep_file(path, sym_name): def _save_old(path): - # See write_config(). os.replace() would be nice here, but it's Python 3 - # (3.3+) only. + # See write_config() + + def copy(src, dst): + # Import as needed, to save some startup time + import shutil + shutil.copyfile(src, dst) + + if islink(path): + # Preserve symlinks + copy_fn = copy + elif hasattr(os, "replace"): + # Python 3 (3.3+) only. Best choice when available, because it + # removes .old on both *nix and Windows. + copy_fn = os.replace + elif os.name == "posix": + # Removes .old on POSIX systems + copy_fn = os.rename + else: + # Fall back on copying + copy_fn = copy try: - if os.name == "posix" and not islink(path): - # Will remove .old if it already exists on POSIX - # systems - os.rename(path, path + ".old") - else: - # Use copyfile() if 'path' is a symlink. The intention is probably - # to overwrite the target in that case. Only import shutil as - # needed, to save some startup time. - import shutil - shutil.copyfile(path, path + ".old") - except: - # Ignore errors from 'filename' missing as well as other errors. The + copy_fn(path, path + ".old") + except Exception: + # Ignore errors from 'path' missing as well as other errors. # .old file is usually more of a nice-to-have, and not worth - # erroring out over e.g. if .old happens to be a directory. + # erroring out over e.g. if .old happens to be a directory or + # is something like /dev/null. pass -def _decoding_error(e, filename, macro_linenr=None): - # Gives the filename and context for UnicodeDecodeError's, which are a pain - # to debug otherwise. 'e' is the UnicodeDecodeError object. - # - # If the decoding error is for the output of a $(shell,...) command, - # macro_linenr holds the line number where it was run (the exact line - # number isn't available for decoding errors in files). - - raise KconfigError( - "\n" - "Malformed {} in {}\n" - "Context: {}\n" - "Problematic data: {}\n" - "Reason: {}".format( - e.encoding, - "'{}'".format(filename) if macro_linenr is None else - "output from macro at {}:{}".format(filename, macro_linenr), - e.object[max(e.start - 40, 0):e.end + 40], - e.object[e.start:e.end], - e.reason)) - - def _name_and_loc(sc): # Helper for giving the symbol/choice name and location(s) in e.g. warnings @@ -6256,6 +6523,38 @@ def _found_dep_loop(loop, cur): raise KconfigError(msg) +def _decoding_error(e, filename, macro_linenr=None): + # Gives the filename and context for UnicodeDecodeError's, which are a pain + # to debug otherwise. 'e' is the UnicodeDecodeError object. + # + # If the decoding error is for the output of a $(shell,...) command, + # macro_linenr holds the line number where it was run (the exact line + # number isn't available for decoding errors in files). + + raise KconfigError( + "\n" + "Malformed {} in {}\n" + "Context: {}\n" + "Problematic data: {}\n" + "Reason: {}".format( + e.encoding, + "'{}'".format(filename) if macro_linenr is None else + "output from macro at {}:{}".format(filename, macro_linenr), + e.object[max(e.start - 40, 0):e.end + 40], + e.object[e.start:e.end], + e.reason)) + + +def _warn_verbose_deprecated(fn_name): + sys.stderr.write( + "Deprecation warning: {0}()'s 'verbose' argument has no effect. Since " + "Kconfiglib 12.0.0, the message is returned from {0}() instead, " + "and is always generated. Do e.g. print(kconf.{0}()) if you want to " + "want to show a message like \"Loaded configuration '.config'\" on " + "stdout. The old API required ugly hacks to reuse messages in " + "configuration interfaces.\n".format(fn_name)) + + # Predefined preprocessor functions @@ -6348,8 +6647,8 @@ except AttributeError: import platform _UNAME_RELEASE = platform.uname()[2] -# Note: The token and type constants below are safe to test with 'is', which is -# a bit faster (~30% faster on my machine, and a few % faster for total parsing +# The token and type constants below are safe to test with 'is', which is a bit +# faster (~30% faster on my machine, and a few % faster for total parsing # time), even without assuming Python's small integer optimization (which # caches small integer objects). The constants end up pointing to unique # integer objects, and since we consistently refer to them via the names below, diff --git a/scripts/kconfig/menuconfig.py b/scripts/kconfig/menuconfig.py index 8ecf6115f96..a5345b2906d 100755 --- a/scripts/kconfig/menuconfig.py +++ b/scripts/kconfig/menuconfig.py @@ -650,12 +650,12 @@ def menuconfig(kconf): _kconf = kconf - # Load existing configuration and set _conf_changed True if it is outdated - _conf_changed = _load_config() - # Filename to save configuration to _conf_filename = standard_config_filename() + # Load existing configuration and set _conf_changed True if it is outdated + _conf_changed = _load_config() + # Filename to save minimal configuration to _minconf_filename = "defconfig" @@ -673,7 +673,7 @@ def menuconfig(kconf): # Disable warnings. They get mangled in curses mode, and we deal with # errors ourselves. - kconf.disable_warnings() + kconf.warn = False # Make curses use the locale settings specified in the environment locale.setlocale(locale.LC_ALL, "") @@ -710,7 +710,8 @@ def _load_config(): # Returns True if .config is missing or outdated. We always prompt for # saving the configuration in that case. - if not _kconf.load_config(): + print(_kconf.load_config()) + if not os.path.exists(_conf_filename): # No .config return True @@ -922,8 +923,10 @@ def _quit_dialog(): return None if c == "y": - if _try_save(_kconf.write_config, _conf_filename, "configuration"): - return "Configuration saved to '{}'".format(_conf_filename) + # Returns a message to print + msg = _try_save(_kconf.write_config, _conf_filename, "configuration") + if msg: + return msg elif c == "n": return "Configuration ({}) was not saved".format(_conf_filename) @@ -1858,29 +1861,33 @@ def _save_dialog(save_fn, default_filename, description): filename = os.path.expanduser(filename) - if _try_save(save_fn, filename, description): - _msg("Success", "{} saved to {}".format(description, filename)) + msg = _try_save(save_fn, filename, description) + if msg: + _msg("Success", msg) return filename def _try_save(save_fn, filename, description): - # Tries to save a configuration file. Pops up an error and returns False on - # failure. + # Tries to save a configuration file. Returns a message to print on + # success. # # save_fn: # Function to call with 'filename' to save the file # # description: # String describing the thing being saved + # + # Return value: + # A message to print on success, and None on failure try: - save_fn(filename) - return True + # save_fn() returns a message to print + return save_fn(filename) except OSError as e: _error("Error saving {} to '{}'\n\n{} (errno: {})" .format(description, e.filename, e.strerror, errno.errorcode[e.errno])) - return False + return None def _key_dialog(title, text, keys): @@ -2739,7 +2746,7 @@ def _kconfig_def_info(item): nodes = [item] if isinstance(item, MenuNode) else item.nodes - s = "Kconfig definition{}, with propagated dependencies\n" \ + s = "Kconfig definition{}, with parent deps. propagated to 'depends on'\n" \ .format("s" if len(nodes) > 1 else "") s += (len(s) - 1)*"="