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)*"="