Author: pierre
Date: Sat Jun 15 10:25:10 2019
New Revision: 4108
Log:
Update to Kconfiglib version 12.4.0. This removes the need to use
".configuration.old"
Modified:
jhalfs/trunk/Makefile
jhalfs/trunk/jhalfs
jhalfs/trunk/menu/kconfiglib.py
jhalfs/trunk/menu/menuconfig.py
Modified: jhalfs/trunk/Makefile
==============================================================================
--- jhalfs/trunk/Makefile Sat Jun 15 08:26:23 2019 (r4107)
+++ jhalfs/trunk/Makefile Sat Jun 15 10:25:10 2019 (r4108)
@@ -12,12 +12,12 @@
@$$(grep RUN_ME configuration 2>/dev/null | sed -e 's@RUN_ME=\"@@' -e
's@\"@@')
menuconfig:
- @cp -a configuration .configuration.old 2>/dev/null || true
+ @cp -a configuration configuration.old 2>/dev/null || true
@CONFIG_="" KCONFIG_CONFIG=configuration $(CONFIG)/menuconfig.py
$(CONFIG_CONFIG_IN)
# Clean up
clean:
- rm -f configuration .configuration.old error
+ rm -f configuration configuration.old error
.PHONY: all menuconfig clean
Modified: jhalfs/trunk/jhalfs
==============================================================================
--- jhalfs/trunk/jhalfs Sat Jun 15 08:26:23 2019 (r4107)
+++ jhalfs/trunk/jhalfs Sat Jun 15 10:25:10 2019 (r4108)
@@ -131,7 +131,7 @@
# If the user has not saved his configuration file, let's ask
# if he or she really wants to run this stuff
time_current=$(stat -c '%Y' configuration 2>/dev/null || date +%s)
-time_old=$(stat -c '%Y' .configuration.old 2>/dev/null || printf '%s'
"$time_current")
+time_old=$(stat -c '%Y' configuration.old 2>/dev/null || printf '%s'
"$time_current")
if [ "$(printf '%d' "$time_old")" -ge "$(printf '%d' "$time_current")" ] ; then
printf 'Do you want to run jhalfs? yes/no (yes): '
read -r ANSWER
Modified: jhalfs/trunk/menu/kconfiglib.py
==============================================================================
--- jhalfs/trunk/menu/kconfiglib.py Sat Jun 15 08:26:23 2019 (r4107)
+++ jhalfs/trunk/menu/kconfiglib.py Sat Jun 15 10:25:10 2019 (r4108)
@@ -12,6 +12,11 @@
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 (<major>, <minor>, <patch>) tuple, e.g.
+(12, 0, 0).
+
+
Using Kconfiglib on the Linux kernel with the Makefile targets
==============================================================
@@ -47,8 +52,17 @@
make kmenuconfig
----------------
-This target runs the curses menuconfig interface with Python 3 (Python 2 is
-currently not supported for the menuconfig).
+This target runs the curses menuconfig interface with Python 3. As of
+Kconfiglib 12.2.0, both Python 2 and Python 3 are supported (previously, only
+Python 3 was supported, so this was a backport).
+
+
+make guiconfig
+--------------
+
+This target runs the Tkinter menuconfig interface. Both Python 2 and Python 3
+are supported. To change the Python interpreter used, pass
+PYTHONCMD=<executable> to 'make'. The default is 'python'.
make [ARCH=<arch>] iscriptconfig
@@ -56,7 +70,7 @@
This target gives an interactive Python prompt where a Kconfig instance has
been preloaded and is available in 'kconf'. To change the Python interpreter
-used, pass PYTHONCMD=<executable> to make. The default is "python".
+used, pass PYTHONCMD=<executable> to 'make'. The default is 'python'.
To get a feel for the API, try evaluating and printing the symbols in
kconf.defined_syms, and explore the MenuNode menu tree starting at
@@ -382,7 +396,7 @@
-----------------
'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
@@ -437,8 +451,8 @@
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
@@ -529,8 +543,10 @@
# 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, split
+from os.path import dirname, exists, expandvars, islink, join, realpath
+
+
+VERSION = (12, 4, 0)
# File layout:
@@ -619,9 +635,8 @@
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
@@ -691,18 +706,55 @@
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.
-
- 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.
+ 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 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
@@ -741,13 +793,9 @@
"_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",
@@ -769,6 +817,11 @@
"unique_choices",
"unique_defined_syms",
"variables",
+ "warn",
+ "warn_assign_override",
+ "warn_assign_redun",
+ "warn_assign_undef",
+ "warn_to_stderr",
"warnings",
"y",
@@ -800,8 +853,8 @@
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"):
@@ -820,12 +873,12 @@
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.
@@ -834,8 +887,8 @@
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
@@ -852,6 +905,11 @@
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
@@ -862,11 +920,11 @@
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
@@ -1018,7 +1076,7 @@
self._add_choice_deps()
- self._warn_for_no_prompt = True
+ self._warn_no_prompt = True
self.mainmenu_text = self.top_node.prompt[0]
@@ -1038,7 +1096,7 @@
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.
@@ -1086,50 +1144,55 @@
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.
-
- 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.
+ 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 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()). The returned message distinguishes between
+ loading (replace == True) and merging (replace == False).
"""
- 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)
+
+ msg = " default configuration '{}' (no '{}')" \
+ .format(defconfig, filename)
+ filename = defconfig
- if verbose:
- print("Using default configuration found in '{}' as "
- "base".format(filename))
-
- 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:
@@ -1250,14 +1313,15 @@
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)
@@ -1277,8 +1341,7 @@
# 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)
@@ -1293,6 +1356,11 @@
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.
@@ -1301,37 +1369,48 @@
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.
+
+ # "".join()ed later
+ chunks = [header]
+ add = chunks.append
- elif sym.orig_type is STRING:
- f.write('#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
+ 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
+
+ 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))
- f.write("#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.
@@ -1344,10 +1423,15 @@
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().
@@ -1358,43 +1442,117 @@
save_old (default: True):
If True and <filename> already exists, a copy of it will be saved to
- .<filename>.old in the same directory before the new configuration is
- written. The leading dot is added only if the filename doesn't
- already start with a dot.
-
- Errors are silently ignored if .<filename>.old cannot be written
- (e.g. due to being a directory).
-
- 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.
+ <filename>.old in the same directory before the new configuration is
+ written.
+
+ Errors are silently ignored if <filename>.old cannot be written (e.g.
+ due to being a directory, or <filename> being something like
+ /dev/null).
+
+ 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)
+
+ return "Configuration saved to '{}'".format(filename)
+
+ 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.
+
+ # node_iter() was used here before commit 3aea9f7 ("Add '# end of
+ # <menu>' after menus in .config"). Those comments get tricky to
+ # implement with it.
+
+ for sym in self.unique_defined_syms:
+ sym._visited = False
+
+ # 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)
- for node in self.node_iter(unique_syms=True):
- item = node.item
+ # Generate configuration output for the node
- if item.__class__ is Symbol:
- f.write(item.config_string)
+ item = node.item
+
+ if item.__class__ is Symbol:
+ if item._visited:
+ continue
+ item._visited = True
- elif expr_value(node.dep) and \
- ((item is MENU and expr_value(node.visibility)) or
- item is COMMENT):
+ conf_string = item.config_string
+ if not conf_string:
+ continue
- f.write("\n#\n# {}\n#\n".format(node.prompt[0]))
+ 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):
- if verbose:
- print("Configuration written to '{}'".format(filename))
+ 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"):
@@ -1416,34 +1574,53 @@
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):
"""
@@ -1484,6 +1661,11 @@
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
@@ -1502,29 +1684,16 @@
if not exists(path):
os.mkdir(path, 0o755)
- # This setup makes sure that at least the current working directory
- # gets reset if things fail
- prev_dir = os.getcwd()
- try:
- # cd'ing into the symbol file directory simplifies
- # _sync_deps() and saves some work
- os.chdir(path)
- self._sync_deps()
- finally:
- os.chdir(prev_dir)
-
- def _sync_deps(self):
# Load old values from auto.conf, if any
- self._load_old_vals()
+ 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 \
@@ -1546,31 +1715,16 @@
continue
# 'sym' has a new value. Flag it.
- _touch_dep_file(sym.name)
+ _touch_dep_file(path, sym.name)
# Remember the current values as the "new old" values.
#
# This call could go anywhere after the call to _load_old_vals(), but
# putting it last means _sync_deps() can be safely rerun if it fails
# before this point.
- self._write_old_vals()
+ self._write_old_vals(path)
- def _write_old_vals(self):
- # Helper for writing auto.conf. Basically just a simplified
- # write_config() that doesn't write any comments (including
- # '# CONFIG_FOO is not set' comments). The format matches the C
- # implementation, though the ordering is arbitrary there (depends on
- # the hash table implementation).
- #
- # 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("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)
-
- def _load_old_vals(self):
+ def _load_old_vals(self, path):
# Loads old symbol values from auto.conf into a dedicated
# Symbol._old_val field. Mirrors load_config().
#
@@ -1581,11 +1735,15 @@
for sym in self.unique_defined_syms:
sym._old_val = None
- if not exists("auto.conf"):
- # No old values
- return
+ try:
+ auto_conf = self._open(join(path, "auto.conf"), "r")
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ # No old values
+ return
+ raise
- with self._open("auto.conf", "r") as f:
+ with auto_conf as f:
for line in f:
match = self._set_match(line)
if not match:
@@ -1607,7 +1765,30 @@
else:
# Flag that the symbol no longer exists, in
# case something still depends on it
- _touch_dep_file(name)
+ _touch_dep_file(path, name)
+
+ def _write_old_vals(self, path):
+ # Helper for writing auto.conf. Basically just a simplified
+ # write_config() that doesn't write any comments (including
+ # '# CONFIG_FOO is not set' comments). The format matches the C
+ # implementation, though the ordering is arbitrary there (depends on
+ # the hash table implementation).
+ #
+ # 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.
+
+ 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):
"""
@@ -1685,20 +1866,19 @@
self._filename = None
- # Don't include the "if " from below to avoid giving confusing error
- # messages
- self._line = s
self._tokens = self._tokenize("if " + s)
+ # Strip "if " to avoid giving confusing error messages
+ self._line = s
self._tokens_i = 1 # Skip the 'if' token
return expr_value(self._expect_expr_and_eol())
def unset_values(self):
"""
- Resets the user values of all symbols, as if Kconfig.load_config() or
- Symbol.set_value() had never been called.
+ 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
@@ -1709,98 +1889,100 @@
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)
)))
#
@@ -1838,17 +2020,23 @@
"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)
@@ -1883,14 +2071,14 @@
"\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,
+ e, "{}:{}: Could not open '{}' (in '{}') ({}: {})"
+ .format(self._filename, self._linenr, filename,
+ self._line.strip(),
errno.errorcode[e.errno], e.strerror))
self._filename = rel_filename
@@ -1920,8 +2108,8 @@
# 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
@@ -1932,7 +2120,6 @@
line = line[:-2] + self._readline()
self._linenr += 1
- self._line = line # Used for error reporting
self._tokens = self._tokenize(line)
# Initialize to 1 instead of 0 to factor out code from _parse_block()
# and _parse_properties(). They immediately fetch self._tokens[0].
@@ -1954,10 +2141,36 @@
line = line[:-2] + self._readline()
self._linenr += 1
- self._line = line
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
@@ -2009,9 +2222,11 @@
# 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.
+
+ self._line = s # Used for error reporting
# Initial token on the line
match = _command_match(s)
@@ -2260,7 +2475,6 @@
return True
return False
-
#
# Preprocessor logic
#
@@ -2518,7 +2732,6 @@
return ""
-
#
# Parsing
#
@@ -2622,19 +2835,19 @@
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(
@@ -2648,23 +2861,13 @@
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:
- # We have reached the end of the block. Terminate the final
- # node and return it.
+ # Reached the end of the block. Terminate the final node and
+ # return it.
if self._tokens[1] is not None:
self._trailing_tokens_error()
@@ -2759,8 +2962,6 @@
elif t0 is _T_MAINMENU:
self.top_node.prompt = (self._expect_str_and_eol(), self.y)
- self.top_node.filename = self._filename
- self.top_node.linenr = self._linenr
else:
# A valid endchoice/endif/endmenu is caught by the 'end_token'
@@ -2953,7 +3154,7 @@
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),
@@ -3227,7 +3428,6 @@
for choice in self.unique_choices:
choice._invalidate()
-
#
# Post-parsing menu tree processing, including dependency propagation and
# implicit submenu creation
@@ -3325,18 +3525,15 @@
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:
@@ -3358,6 +3555,11 @@
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
@@ -3394,7 +3596,6 @@
target.weak_rev_dep,
self._make_and(sym, cond))
-
#
# Misc.
#
@@ -3570,9 +3771,9 @@
# The "U" flag would currently work for both Python 2 and 3, but it's
# deprecated on Python 3, so play it future-safe.
#
- # A simpler solution would be to use io.open(), which defaults to
- # universal newlines on both Python 2 and 3 (and is an alias for
- # open() on Python 3), but it's appreciably slower on Python 2:
+ # io.open() defaults to universal newlines on Python 2 (and is an
+ # alias for open() on Python 3), but it returns 'unicode' strings and
+ # slows things down:
#
# Parsing x86 Kconfigs on Python 2
#
@@ -3636,37 +3837,25 @@
sym.name != "MODULES":
msg = "undefined symbol {}:".format(sym.name)
-
for node in self.node_iter():
if sym in node.referenced:
msg += "\n\n- Referenced at {}:{}:\n\n{}" \
.format(node.filename, node.linenr, node)
-
self._warn(msg)
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)
-
- self.warnings.append(msg)
- if self._warn_to_stderr:
- sys.stderr.write(msg + "\n")
-
- 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 not self.warn:
+ return
- if self._warn_for_redun_assign:
- self._warn(msg, filename, linenr)
+ msg = "warning: " + msg
+ if filename is not None:
+ msg = "{}:{}: {}".format(filename, linenr, msg)
+
+ self.warnings.append(msg)
+ if self.warn_to_stderr:
+ sys.stderr.write(msg + "\n")
class Symbol(object):
@@ -3828,7 +4017,7 @@
ranges:
List of (low, high, cond) tuples for the symbol's 'range' properties. For
example, 'range 1 2 if A' is represented as (1, 2, A). If there is no
- condition, 'cond' is self.config.y.
+ condition, 'cond' is self.kconfig.y.
Note that 'depends on' and parent dependencies are propagated to 'range'
conditions.
@@ -3849,21 +4038,45 @@
Like rev_dep, for imply.
direct_dep:
- The 'depends on' dependencies. If a symbol is defined in multiple
- locations, the dependencies at each location are ORed together.
+ The direct ('depends on') dependencies for the symbol, or self.kconfig.y
+ if there are no direct dependencies.
+
+ This attribute includes any dependencies from surrounding menus and if's.
+ Those get propagated to the direct dependencies, and the resulting direct
+ dependencies in turn get propagated to the conditions of all properties.
- Internally, this is used to implement 'imply', which only applies if the
- implied symbol has expr_value(self.direct_dep) != 0. 'depends on' and
- parent dependencies are automatically propagated to the conditions of
- properties, so normally it's redundant to check the direct dependencies.
+ If the symbol is defined in multiple locations, the dependencies from the
+ different locations get ORed together.
referenced:
A set() with all symbols and choices referenced in the properties and
property conditions of the symbol.
- Also includes dependencies inherited from surrounding menus and if's.
+ Also includes dependencies from surrounding menus and if's, because those
+ get propagated to the symbol (see the 'Intro to symbol values' section in
+ the module docstring).
+
Choices appear in the dependencies of choice symbols.
+ For the following definitions, only B and not C appears in A's
+ 'referenced'. To get transitive references, you'll have to recursively
+ expand 'references' until no new items appear.
+
+ config A
+ bool
+ depends on B
+
+ config B
+ bool
+ depends on C
+
+ config C
+ bool
+
+ See the Symbol.direct_dep attribute if you're only interested in the
+ direct dependencies of the symbol (its 'depends on'). You can extract the
+ symbols in it with the global expr_items() function.
+
env_var:
If the Symbol has an 'option env="FOO"' option, this contains the name
("FOO") of the environment variable. None for symbols without no
@@ -4178,8 +4391,8 @@
"""
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 ""
@@ -4286,8 +4499,8 @@
def unset_value(self):
"""
- Resets the user value of the symbol, as if the symbol had never gotten
- a user value via Kconfig.load_config() or Symbol.set_value().
+ Removes any user value from the symbol, as if the symbol had never
+ gotten a user value via Kconfig.load_config() or Symbol.set_value().
"""
if self.user_value is not None:
self.user_value = None
@@ -4358,8 +4571,9 @@
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
@@ -4526,7 +4740,7 @@
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")
@@ -4721,7 +4935,7 @@
defaults:
List of (symbol, cond) tuples for the choice's 'defaults' properties. For
example, 'default A if B && C' is represented as (A, (AND, B, C)). If
- there is no condition, 'cond' is self.config.y.
+ there is no condition, 'cond' is self.kconfig.y.
Note that 'depends on' and parent dependencies are propagated to
'default' conditions.
@@ -4733,7 +4947,9 @@
A set() with all symbols referenced in the properties and property
conditions of the choice.
- Also includes dependencies inherited from surrounding menus and if's.
+ Also includes dependencies from surrounding menus and if's, because those
+ get propagated to the choice (see the 'Intro to symbol values' section in
+ the module docstring).
is_optional:
True if the choice has the 'optional' flag set on it and can be in
@@ -4932,9 +5148,10 @@
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.
@@ -5113,6 +5330,18 @@
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.
@@ -5124,10 +5353,12 @@
was undocumented.
dep:
- The 'depends on' dependencies for the menu node, or self.kconfig.y if
- there are no dependencies. Parent dependencies are propagated to this
- attribute, and this attribute is then in turn propagated to the
- properties of symbols and choices.
+ The direct ('depends on') dependencies for the menu node, or
+ self.kconfig.y if there are no direct dependencies.
+
+ This attribute includes any dependencies from surrounding menus and if's.
+ Those get propagated to the direct dependencies, and the resulting direct
+ dependencies in turn get propagated to the conditions of all properties.
If a symbol or choice is defined in multiple locations, only the
properties defined at a particular location get the corresponding
@@ -5210,6 +5441,47 @@
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):
"""
See the class documentation.
@@ -5294,8 +5566,9 @@
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
@@ -5349,13 +5622,20 @@
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 '<type> "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:
@@ -5370,13 +5650,13 @@
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)
@@ -5384,10 +5664,10 @@
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:
@@ -5400,6 +5680,21 @@
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):
"""
@@ -5418,9 +5713,9 @@
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 =).
@@ -5457,7 +5752,12 @@
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
@@ -5552,7 +5852,9 @@
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 "<choice {}>".format(sc.name) if sc.name else "<choice>"
@@ -5720,8 +6022,8 @@
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")
@@ -5733,8 +6035,8 @@
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
@@ -5748,38 +6050,39 @@
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"):
- 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:
+ if allconfig in ("", "1"):
+ try:
+ print(kconf.load_config(filename, False))
+ except IOError as e1:
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)))
-
- # 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()
+ 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)))
+
+ kconf.warn_assign_override = old_warn_assign_override
+ kconf.warn_assign_redun = old_warn_assign_redun
#
@@ -5879,13 +6182,13 @@
int(sym.str_value, _TYPE_TO_BASE[sym.orig_type])
-def _touch_dep_file(sym_name):
+def _touch_dep_file(path, sym_name):
# If sym_name is MY_SYM_NAME, touches my/sym/name.h. See the sync_deps()
# docstring.
- sym_path = sym_name.lower().replace("_", os.sep) + ".h"
+ sym_path = path + os.sep + sym_name.lower().replace("_", os.sep) + ".h"
sym_path_dir = dirname(sym_path)
- if sym_path_dir and not exists(sym_path_dir):
+ if not exists(sym_path_dir):
os.makedirs(sym_path_dir, 0o755)
# A kind of truncating touch, mirroring the C tools
@@ -5896,52 +6199,35 @@
def _save_old(path):
# See write_config()
- dirname, basename = split(path)
- backup = join(dirname,
- basename + ".old" if basename.startswith(".")
- else "." + basename + ".old")
+ 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 <filename>.old on both *nix and Windows.
+ copy_fn = os.replace
+ elif os.name == "posix":
+ # Removes <filename>.old on POSIX systems
+ copy_fn = os.rename
+ else:
+ # Fall back on copying
+ copy_fn = copy
- # os.replace() would be nice here, but it's Python 3 (3.3+) only
try:
- # Use copyfile() if 'path' is a symlink. The intention is probably to
- # overwrite the target in that case.
- if os.name == "posix" and not islink(path):
- # Will remove .<filename>.old if it already exists on POSIX
- # systems
- os.rename(path, backup)
- else:
- # Only import as needed, to save some startup time
- import shutil
- shutil.copyfile(path, backup)
- except:
- # Ignore errors from 'filename' missing as well as other errors. The
- # backup file is more of a nice-to-have, and not worth erroring out
- # over e.g. if .<filename>.old happens to be a directory.
+ copy_fn(path, path + ".old")
+ except Exception:
+ # Ignore errors from 'path' missing as well as other errors.
+ # <filename>.old file is usually more of a nice-to-have, and not worth
+ # erroring out over e.g. if <filename>.old happens to be a directory or
+ # <filename> 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
@@ -5994,7 +6280,6 @@
# node2 has a prompt, we check its condition. Otherwise, we look directly
# at node2.dep.
- # If node2 has no prompt, use its menu node dependencies instead
return _expr_depends_on(node2.prompt[1] if node2.prompt else node2.dep,
node1.item)
@@ -6240,6 +6525,38 @@
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
@@ -6332,8 +6649,8 @@
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,
@@ -6631,11 +6948,11 @@
#
# '$' is included to detect preprocessor variable assignments with macro
# expansions in the left-hand side.
-_command_match = _re_match(r"\s*([$A-Za-z0-9_-]+)\s*")
+_command_match = _re_match(r"\s*([A-Za-z0-9_$-]+)\s*")
# An identifier/keyword after the first token. Also eats trailing whitespace.
# '$' is included to detect identifiers containing macro expansions.
-_id_keyword_match = _re_match(r"([$A-Za-z0-9_/.-]+)\s*")
+_id_keyword_match = _re_match(r"([A-Za-z0-9_$/.-]+)\s*")
# A fragment in the left-hand side of a preprocessor variable assignment. These
# are the portions between macro expansions ($(foo)). Macros are supported in
Modified: jhalfs/trunk/menu/menuconfig.py
==============================================================================
--- jhalfs/trunk/menu/menuconfig.py Sat Jun 15 08:26:23 2019 (r4107)
+++ jhalfs/trunk/menu/menuconfig.py Sat Jun 15 10:25:10 2019 (r4108)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
# Copyright (c) 2018-2019, Nordic Semiconductor ASA and Ulf Magnusson
# SPDX-License-Identifier: ISC
@@ -7,8 +7,8 @@
Overview
========
-A curses-based menuconfig implementation. The interface should feel familiar to
-people used to mconf ('make menuconfig').
+A curses-based Python 2/3 menuconfig implementation. The interface should feel
+familiar to people used to mconf ('make menuconfig').
Supports the same keys as mconf, and also supports a set of keybindings
inspired by Vi:
@@ -20,14 +20,14 @@
G/End : Jump to end of list
g/Home : Jump to beginning of list
+[Space] toggles values if possible, and enters menus otherwise. [Enter] works
+the other way around.
+
The mconf feature where pressing a key jumps to a menu entry with that
character in it in the current menu isn't supported. A jump-to feature for
jumping directly to any symbol (including invisible symbols), choice, menu or
comment (as in a Kconfig 'comment "Foo"') is available instead.
-Space and Enter are "smart" and try to do what you'd expect for the given menu
-entry.
-
A few different modes are available:
F: Toggle show-help mode, which shows the help text of the currently selected
@@ -55,6 +55,9 @@
The KCONFIG_CONFIG environment variable specifies the .config file to load (if
it exists) and save. If KCONFIG_CONFIG is unset, ".config" is used.
+When overwriting a configuration file, the old version is saved to
+<filename>.old (e.g. .config.old).
+
$srctree is supported through Kconfiglib.
@@ -96,7 +99,7 @@
- fg:COLOR Set the foreground/background colors. COLOR can be one of
* or * the basic 16 colors (black, red, green, yellow, blue,
- - bg:COLOR magenta,cyan, white and brighter versions, for example,
+ - bg:COLOR magenta, cyan, white and brighter versions, for example,
brightred). On terminals that support more than 8 colors,
you can also directly put in a color number, e.g. fg:123
(hexadecimal and octal constants are accepted as well).
@@ -171,19 +174,15 @@
Limitations
===========
- - Python 3 only
-
- This is mostly due to Python 2 not having curses.get_wch(), which is needed
- for Unicode support.
-
- - Doesn't work out of the box on Windows
-
- Can be made to work with 'pip install windows-curses' though. See the
- https://github.com/zephyrproject-rtos/windows-curses repository.
+Doesn't work out of the box on Windows, but can be made to work with 'pip
+install windows-curses'. See the
+https://github.com/zephyrproject-rtos/windows-curses repository.
- 'pip install kconfiglib' on Windows automatically installs windows-curses
- to make the menuconfig usable.
+'pip install kconfiglib' on Windows automatically installs windows-curses
+to make the menuconfig usable.
"""
+from __future__ import print_function
+
import curses
import errno
import locale
@@ -193,7 +192,7 @@
import textwrap
from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \
- BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN, \
+ BOOL, TRISTATE, STRING, INT, HEX, \
AND, OR, \
expr_str, expr_value, split_expr, \
standard_sc_expr_str, \
@@ -244,7 +243,7 @@
# Lines of help text shown at the bottom of the information dialog
_INFO_HELP_LINES = """
-[ESC/q] Return to menu [/] Jump to symbol
+[ESC/q] Return to menu [/] Jump to symbol
"""[1:-1].split("\n")
# Lines of help text shown at the bottom of the search dialog
@@ -628,7 +627,6 @@
#
-# Used as the entry point in setup.py
def _main():
menuconfig(standard_kconfig())
@@ -648,12 +646,12 @@
_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"
@@ -671,7 +669,7 @@
# 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, "")
@@ -708,7 +706,8 @@
# 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
@@ -728,7 +727,7 @@
if sym.config_string:
# Unwritten symbol
return True
- elif sym.type in (BOOL, TRISTATE):
+ elif sym.orig_type in (BOOL, TRISTATE):
if sym.tri_value != sym.user_value:
# Written bool/tristate symbol, new value
return True
@@ -769,7 +768,7 @@
# If True, the corresponding mode is on. See the module docstring.
#
# _conf_filename:
-# .config file to save the configuration to
+# File to save the configuration to
#
# _minconf_filename:
# File to save minimal configurations to
@@ -801,7 +800,7 @@
curses.doupdate()
- c = _get_wch_compat(_menu_win)
+ c = _getch_compat(_menu_win)
if c == curses.KEY_RESIZE:
_resize_main()
@@ -828,26 +827,17 @@
elif c in (curses.KEY_HOME, "g"):
_select_first_menu_entry()
- elif c in (curses.KEY_RIGHT, " ", "\n", "l", "L"):
- # Do appropriate node action. Only Space is treated specially,
- # preferring to toggle nodes rather than enter menus.
-
+ elif c == " ":
+ # Toggle the node if possible
sel_node = _shown[_sel_node_i]
-
- if sel_node.is_menuconfig and not \
- (c == " " and _prefer_toggle(sel_node.item)):
-
+ if not _change_node(sel_node):
_enter_menu(sel_node)
- else:
+ elif c in (curses.KEY_RIGHT, "\n", "l", "L"):
+ # Enter the node if possible
+ sel_node = _shown[_sel_node_i]
+ if not _enter_menu(sel_node):
_change_node(sel_node)
- if _is_y_mode_choice_sym(sel_node.item) and not sel_node.list:
- # Immediately jump to the parent menu after making a choice
- # selection, like 'make menuconfig' does, except if the
- # menu node has children (which can happen if a symbol
- # 'depends on' a choice symbol that immediately precedes
- # it).
- _leave_menu()
elif c in ("n", "N"):
_set_sel_node_tri_val(0)
@@ -929,8 +919,10 @@
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)
@@ -961,10 +953,12 @@
# Looking for this in addition to KEY_BACKSPACE (which is unreliable) makes
# backspace work with TERM=vt100. That makes it likely to work in sane
# environments.
- #
- # erasechar() returns a 'bytes' object. Since we use get_wch(), we need to
- # decode it. Just give up and avoid crashing if it can't be decoded.
- _ERASE_CHAR = curses.erasechar().decode("utf-8", "ignore")
+ _ERASE_CHAR = curses.erasechar()
+ if sys.version_info[0] >= 3:
+ # erasechar() returns a one-byte bytes object on Python 3. This sets
+ # _ERASE_CHAR to a blank string if it can't be decoded, which should be
+ # harmless.
+ _ERASE_CHAR = _ERASE_CHAR.decode("utf-8", "ignore")
_init_styles()
@@ -1059,39 +1053,40 @@
return win.getmaxyx()[1]
-def _prefer_toggle(item):
- # For nodes with menus, determines whether Space should change the value of
- # the node's item or enter its menu. We toggle symbols (which have menus
- # when they're defined with 'menuconfig') and choices that can be in more
- # than one mode (e.g. optional choices). In other cases, we enter the menu.
-
- return isinstance(item, Symbol) or \
- (isinstance(item, Choice) and len(item.assignable) > 1)
-
-
def _enter_menu(menu):
- # Makes 'menu' the currently displayed menu. "Menu" here includes choices
- # and symbols defined with the 'menuconfig' keyword.
+ # Makes 'menu' the currently displayed menu. In addition to actual 'menu's,
+ # "menu" here includes choices and symbols defined with the 'menuconfig'
+ # keyword.
+ #
+ # Returns False if 'menu' can't be entered.
global _cur_menu
global _shown
global _sel_node_i
global _menu_scroll
+ if not menu.is_menuconfig:
+ # Not a menu
+ return False
+
shown_sub = _shown_nodes(menu)
# Never enter empty menus. We depend on having a current node.
- if shown_sub:
- # Remember where the current node appears on the screen, so we can try
- # to get it to appear in the same place when we leave the menu
- _parent_screen_rows.append(_sel_node_i - _menu_scroll)
-
- # Jump into menu
- _cur_menu = menu
- _shown = shown_sub
- _sel_node_i = _menu_scroll = 0
+ if not shown_sub:
+ return False
+
+ # Remember where the current node appears on the screen, so we can try
+ # to get it to appear in the same place when we leave the menu
+ _parent_screen_rows.append(_sel_node_i - _menu_scroll)
+
+ # Jump into menu
+ _cur_menu = menu
+ _shown = shown_sub
+ _sel_node_i = _menu_scroll = 0
+
+ if isinstance(menu.item, Choice):
+ _select_selected_choice_sym()
- if isinstance(menu.item, Choice):
- _select_selected_choice_sym()
+ return True
def _select_selected_choice_sym():
@@ -1225,7 +1220,7 @@
_sel_node_i -= 1
# See _select_next_menu_entry()
- if _sel_node_i <= _menu_scroll + _SCROLL_OFFSET:
+ if _sel_node_i < _menu_scroll + _SCROLL_OFFSET:
_menu_scroll = max(_menu_scroll - 1, 0)
@@ -1418,7 +1413,7 @@
_path_win.erase()
- # Draw the menu path ("(top menu) -> menu -> submenu -> ...")
+ # Draw the menu path ("(Top) -> Menu -> Submenu -> ...")
menu_prompts = []
@@ -1430,7 +1425,7 @@
menu_prompts.append(menu.prompt[0] if menu.prompt else
standard_sc_expr_str(menu.item))
menu = menu.parent
- menu_prompts.append("(top menu)")
+ menu_prompts.append("(Top)")
menu_prompts.reverse()
# Hack: We can't put ACS_RARROW directly in the string. Temporarily
@@ -1471,10 +1466,6 @@
res = []
while node:
- # This code is minorly performance-sensitive. Make it too slow
- # (e.g., by always recursing the entire tree), and going in and out
- # of menus no longer feels instant.
-
if _visible(node) or _show_all:
res.append(node)
if node.list and not node.is_menuconfig:
@@ -1483,14 +1474,11 @@
# menus and choices as well as 'menuconfig' symbols.
res += rec(node.list)
- elif node.list and isinstance(node.item, Symbol) and \
- expr_value(node.dep):
+ elif node.list and isinstance(node.item, Symbol):
# Show invisible symbols if they have visible children. This
# can happen for an m/y-valued symbol with an optional prompt
- # ('prompt "foo" is COND') that is currently disabled. The
- # expr_value(node.dep) check safely prunes the search: A node
- # with unsatisfied direct dependencies can never have visible
- # children.
+ # ('prompt "foo" is COND') that is currently disabled. Note
+ # that it applies to both 'config' and 'menuconfig' symbols.
shown_children = rec(node.list)
if shown_children:
res.append(node)
@@ -1553,36 +1541,32 @@
# Changes the value of the menu node 'node' if it is a symbol. Bools and
# tristates are toggled, while other symbol types pop up a text entry
# dialog.
+ #
+ # Returns False if the value of 'node' can't be changed.
- if not isinstance(node.item, (Symbol, Choice)):
- return
-
- # This will hit for invisible symbols, which appear in show-all mode and
- # when an invisible symbol has visible children (which can happen e.g. for
- # symbols with optional prompts)
- if not (node.prompt and expr_value(node.prompt[1])):
- return
+ if not _changeable(node):
+ return False
# sc = symbol/choice
sc = node.item
- if sc.type in (INT, HEX, STRING):
+ if sc.orig_type in (INT, HEX, STRING):
s = sc.str_value
while True:
s = _input_dialog(
- "{} ({})".format(node.prompt[0], TYPE_TO_STR[sc.type]),
+ "{} ({})".format(node.prompt[0], TYPE_TO_STR[sc.orig_type]),
s, _range_info(sc))
if s is None:
break
- if sc.type in (INT, HEX):
+ if sc.orig_type in (INT, HEX):
s = s.strip()
# 'make menuconfig' does this too. Hex values not starting with
# '0x' are accepted when loading .config files though.
- if sc.type == HEX and not s.startswith(("0x", "0X")):
+ if sc.orig_type == HEX and not s.startswith(("0x", "0X")):
s = "0x" + s
if _check_valid(sc, s):
@@ -1594,13 +1578,42 @@
# case: .assignable can be (2,) while .tri_value is 0.
_set_val(sc, sc.assignable[0])
- elif sc.assignable:
+ else:
# Set the symbol to the value after the current value in
# sc.assignable, with wrapping
val_index = sc.assignable.index(sc.tri_value)
_set_val(sc, sc.assignable[(val_index + 1) % len(sc.assignable)])
+ if _is_y_mode_choice_sym(sc) and not node.list:
+ # Immediately jump to the parent menu after making a choice selection,
+ # like 'make menuconfig' does, except if the menu node has children
+ # (which can happen if a symbol 'depends on' a choice symbol that
+ # immediately precedes it).
+ _leave_menu()
+
+
+ return True
+
+
+def _changeable(node):
+ # Returns True if the value if 'node' can be changed
+
+ sc = node.item
+
+ if not isinstance(sc, (Symbol, Choice)):
+ return False
+
+ # This will hit for invisible symbols, which appear in show-all mode and
+ # when an invisible symbol has visible children (which can happen e.g. for
+ # symbols with optional prompts)
+ if not (node.prompt and expr_value(node.prompt[1])):
+ return False
+
+ return sc.orig_type in (STRING, INT, HEX) or len(sc.assignable) > 1 \
+ or _is_y_mode_choice_sym(sc)
+
+
def _set_sel_node_tri_val(tri_val):
# Sets the value of the currently selected menu entry to 'tri_val', if that
# value can be assigned
@@ -1702,7 +1715,7 @@
curses.doupdate()
- c = _get_wch_compat(win)
+ c = _getch_compat(win)
if c == curses.KEY_RESIZE:
# Resize the main display too. The dialog floats above it.
@@ -1846,29 +1859,33 @@
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):
@@ -1902,7 +1919,7 @@
curses.doupdate()
- c = _get_wch_compat(win)
+ c = _getch_compat(win)
if c == curses.KEY_RESIZE:
# Resize the main display too. The dialog floats above it.
@@ -2000,30 +2017,29 @@
_safe_curs_set(2)
- # TODO: Code duplication with _select_{next,prev}_menu_entry(). Can this be
- # factored out in some nice way?
+ # Logic duplication with _select_{next,prev}_menu_entry(), except we do a
+ # functional variant that returns the new (sel_node_i, scroll) values to
+ # avoid 'nonlocal'. TODO: Can this be factored out in some nice way?
def select_next_match():
- nonlocal sel_node_i
- nonlocal scroll
+ if sel_node_i == len(matches) - 1:
+ return sel_node_i, scroll
- if sel_node_i < len(matches) - 1:
- sel_node_i += 1
+ if sel_node_i + 1 >= scroll + _height(matches_win) - _SCROLL_OFFSET \
+ and scroll < _max_scroll(matches, matches_win):
- if sel_node_i >= scroll + _height(matches_win) - _SCROLL_OFFSET \
- and scroll < _max_scroll(matches, matches_win):
+ return sel_node_i + 1, scroll + 1
- scroll += 1
+ return sel_node_i + 1, scroll
def select_prev_match():
- nonlocal sel_node_i
- nonlocal scroll
+ if sel_node_i == 0:
+ return sel_node_i, scroll
- if sel_node_i > 0:
- sel_node_i -= 1
+ if sel_node_i - 1 < scroll + _SCROLL_OFFSET:
+ return sel_node_i - 1, max(scroll - 1, 0)
- if sel_node_i <= scroll + _SCROLL_OFFSET:
- scroll = max(scroll - 1, 0)
+ return sel_node_i - 1, scroll
while True:
if s != prev_s:
@@ -2099,7 +2115,7 @@
curses.doupdate()
- c = _get_wch_compat(edit_box)
+ c = _getch_compat(edit_box)
if c == "\n":
if matches:
@@ -2130,21 +2146,21 @@
sel_node_i, scroll)
elif c == curses.KEY_DOWN:
- select_next_match()
+ sel_node_i, scroll = select_next_match()
elif c == curses.KEY_UP:
- select_prev_match()
+ sel_node_i, scroll = select_prev_match()
elif c in (curses.KEY_NPAGE, "\x04"): # Page Down/Ctrl-D
# Keep it simple. This way we get sane behavior for small windows,
# etc., for free.
for _ in range(_PG_JUMP):
- select_next_match()
+ sel_node_i, scroll = select_next_match()
# Page Up (no Ctrl-U, as it's already used by the edit box)
elif c == curses.KEY_PPAGE:
for _ in range(_PG_JUMP):
- select_prev_match()
+ sel_node_i, scroll = select_prev_match()
elif c == curses.KEY_END:
sel_node_i = len(matches) - 1
@@ -2364,7 +2380,7 @@
curses.doupdate()
- c = _get_wch_compat(text_win)
+ c = _getch_compat(text_win)
if c == curses.KEY_RESIZE:
_resize_info_dialog(top_line_win, text_win, bot_sep_win, help_win)
@@ -2600,8 +2616,7 @@
for node in sc.nodes:
if node.help is not None:
- s += "Help:\n\n{}\n\n" \
- .format(textwrap.indent(node.help, " "))
+ s += "Help:\n\n{}\n\n".format(_indent(node.help, 2))
return s
@@ -2670,7 +2685,7 @@
s = ""
for i, term in enumerate(split_expr(expr, split_op)):
- s += "{}{} {}".format(" "*indent,
+ s += "{}{} {}".format(indent*" ",
" " if i == 0 else op_str,
_expr_str(term))
@@ -2689,34 +2704,34 @@
# 'sym'. The selecting/implying symbols are grouped according to which
# value they select/imply 'sym' to (n/m/y).
- s = ""
-
- def add_sis(expr, val, title):
- nonlocal s
-
+ def sis(expr, val, title):
# sis = selects/implies
sis = [si for si in split_expr(expr, OR) if expr_value(si) == val]
- if sis:
- s += title
- for si in sis:
- s += " - {}\n".format(split_expr(si, AND)[0].name)
- s += "\n"
+ if not sis:
+ return ""
+
+ res = title
+ for si in sis:
+ res += " - {}\n".format(split_expr(si, AND)[0].name)
+ return res + "\n"
+
+ s = ""
if sym.rev_dep is not _kconf.n:
- add_sis(sym.rev_dep, 2,
- "Symbols currently y-selecting this symbol:\n")
- add_sis(sym.rev_dep, 1,
- "Symbols currently m-selecting this symbol:\n")
- add_sis(sym.rev_dep, 0,
- "Symbols currently n-selecting this symbol (no effect):\n")
+ s += sis(sym.rev_dep, 2,
+ "Symbols currently y-selecting this symbol:\n")
+ s += sis(sym.rev_dep, 1,
+ "Symbols currently m-selecting this symbol:\n")
+ s += sis(sym.rev_dep, 0,
+ "Symbols currently n-selecting this symbol (no effect):\n")
if sym.weak_rev_dep is not _kconf.n:
- add_sis(sym.weak_rev_dep, 2,
- "Symbols currently y-implying this symbol:\n")
- add_sis(sym.weak_rev_dep, 1,
- "Symbols currently m-implying this symbol:\n")
- add_sis(sym.weak_rev_dep, 0,
- "Symbols currently n-implying this symbol (no effect):\n")
+ s += sis(sym.weak_rev_dep, 2,
+ "Symbols currently y-implying this symbol:\n")
+ s += sis(sym.weak_rev_dep, 1,
+ "Symbols currently m-implying this symbol:\n")
+ s += sis(sym.weak_rev_dep, 0,
+ "Symbols currently n-implying this symbol (no effect):\n")
return s
@@ -2727,7 +2742,7 @@
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)*"="
@@ -2740,7 +2755,7 @@
.format(node.filename, node.linenr,
_include_path_info(node),
_menu_path_info(node),
- textwrap.indent(node.custom_str(_name_and_val_str), " "))
+ _indent(node.custom_str(_name_and_val_str), 2))
return s
@@ -2769,7 +2784,14 @@
path = " -> " + (node.prompt[0] if node.prompt else
standard_sc_expr_str(node.item)) + path
- return "(top menu)" + path
+ return "(Top)" + path
+
+
+def _indent(s, n):
+ # Returns 's' with each line indented 'n' spaces. textwrap.indent() is not
+ # available in Python 2 (it's 3.3+).
+
+ return "\n".join(n*" " + line for line in s.split("\n"))
def _name_and_val_str(sc):
@@ -2952,8 +2974,7 @@
# Print "(NEW)" next to symbols without a user value (from e.g. a
# .config), but skip it for choice symbols in choices in y mode,
# and for symbols of UNKNOWN type (which generate a warning though)
- if sym.user_value is None and \
- sym.type != UNKNOWN and \
+ if sym.user_value is None and sym.orig_type and \
not (sym.choice and sym.choice.tri_value == 2):
s += " (NEW)"
@@ -3005,10 +3026,10 @@
return ""
# Wouldn't normally happen, and generates a warning
- if item.type == UNKNOWN:
+ if not item.orig_type:
return ""
- if item.type in (STRING, INT, HEX):
+ if item.orig_type in (STRING, INT, HEX):
return "({})".format(item.str_value)
# BOOL or TRISTATE
@@ -3042,28 +3063,26 @@
# Returns True if the string 's' is a well-formed value for 'sym'.
# Otherwise, displays an error and returns False.
- if sym.type not in (INT, HEX):
+ if sym.orig_type not in (INT, HEX):
# Anything goes for non-int/hex symbols
return True
- base = 10 if sym.type == INT else 16
+ base = 10 if sym.orig_type == INT else 16
try:
int(s, base)
except ValueError:
_error("'{}' is a malformed {} value"
- .format(s, TYPE_TO_STR[sym.type]))
+ .format(s, TYPE_TO_STR[sym.orig_type]))
return False
for low_sym, high_sym, cond in sym.ranges:
if expr_value(cond):
- low = int(low_sym.str_value, base)
- val = int(s, base)
- high = int(high_sym.str_value, base)
+ low_s = low_sym.str_value
+ high_s = high_sym.str_value
- if not low <= val <= high:
+ if not int(low_s, base) <= int(s, base) <= int(high_s, base):
_error("{} is outside the range {}-{}"
- .format(s, low_sym.str_value, high_sym.str_value))
-
+ .format(s, low_s, high_s))
return False
break
@@ -3075,7 +3094,7 @@
# Returns a string with information about the valid range for the symbol
# 'sym', or None if 'sym' doesn't have a range
- if sym.type in (INT, HEX):
+ if sym.orig_type in (INT, HEX):
for low, high, cond in sym.ranges:
if expr_value(cond):
return "Range: {}-{}".format(low.str_value, high.str_value)
@@ -3102,7 +3121,17 @@
return True
-def _get_wch_compat(win):
+def _getch_compat(win):
+ # Uses get_wch() if available (Python 3.3+) and getch() otherwise. Also
+ # handles a PDCurses resizing quirk.
+
+ if hasattr(win, "get_wch"):
+ c = win.get_wch()
+ else:
+ c = win.getch()
+ if 0 <= c <= 255:
+ c = chr(c)
+
# Decent resizing behavior on PDCurses requires calling resize_term(0, 0)
# after receiving KEY_RESIZE, while ncurses (usually) handles terminal
# resizing automatically in get(_w)ch() (see the end of the
@@ -3111,8 +3140,6 @@
# resize_term(0, 0) reliably fails and does nothing on ncurses, so this
# hack gives ncurses/PDCurses compatibility for resizing. I don't know
# whether it would cause trouble for other implementations.
-
- c = win.get_wch()
if c == curses.KEY_RESIZE:
try:
curses.resize_term(0, 0)
--
http://lists.linuxfromscratch.org/listinfo/alfs-log
Unsubscribe: See the above information page