summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.rst5
-rwxr-xr-xgenconfig.py10
-rw-r--r--kconfiglib.py155
3 files changed, 125 insertions, 45 deletions
diff --git a/README.rst b/README.rst
index 0ed074f..a6d4a8d 100644
--- a/README.rst
+++ b/README.rst
@@ -149,6 +149,11 @@ Getting started
``config.h``.
Normally, ``genconfig`` would be run automatically as part of the build.
+
+ Before writing a header file or other configuration output, Kconfiglib
+ compares the old contents of the file against the new contents. If there's
+ no change, the write is skipped. This avoids updating file metadata like the
+ modification time, and might save work depending on your build setup.
Adding new configuration output formats should be relatively straightforward.
See the implementation of ``write_config()`` in `kconfiglib.py
diff --git a/genconfig.py b/genconfig.py
index c575c9a..ba2bb73 100755
--- a/genconfig.py
+++ b/genconfig.py
@@ -7,10 +7,18 @@
Generates a header file with #defines from the configuration, matching the
format of include/generated/autoconf.h in the Linux kernel.
-Optionally creates/updates a directory structure with one file per symbol that
+Optionally, also writes the configuration output as a .config file. See
+--config-out.
+
+Optionally, creates/updates a directory structure with one file per symbol that
can be used to implement incremental builds. See the docstring for
Kconfig.sync_deps() in kconfiglib.py.
+Before writing a header file or other configuration output, Kconfiglib compares
+the old contents of the file against the new contents. If there's no change,
+the write is skipped. This avoids updating file metadata like the modification
+time, and might save work depending on your build setup.
+
By default, the configuration is generated from '.config'. A different
configuration file can be passed in the KCONFIG_CONFIG environment variable.
"""
diff --git a/kconfiglib.py b/kconfiglib.py
index 3bdcb45..11527a7 100644
--- a/kconfiglib.py
+++ b/kconfiglib.py
@@ -1300,6 +1300,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 +1313,45 @@ 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 not sym._write_to_conf:
- continue
+ 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
+
+ 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 not sym._write_to_conf:
+ continue
- if sym.orig_type in _BOOL_TRISTATE:
- if val == "y":
- f.write("#define {}{} 1\n".format(
- self.config_prefix, sym.name))
- elif val == "m":
- f.write("#define {}{}_MODULE 1\n".format(
- self.config_prefix, sym.name))
+ 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:
- f.write('#define {}{} "{}"\n'
- .format(self.config_prefix, sym.name,
- escape(val)))
+ 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
+ else: # sym.orig_type in _INT_HEX:
+ if sym.orig_type is HEX and \
+ not val.startswith(("0x", "0X")):
+ val = "0x" + val
- f.write("#define {}{} {}\n"
- .format(self.config_prefix, sym.name, 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",
@@ -1355,6 +1368,11 @@ 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).
@@ -1386,18 +1404,27 @@ class Kconfig(object):
else:
verbose = False
+ contents = self._config_contents(header)
+ if self._contents_eq(filename, contents):
+ if verbose:
+ print("No change to '{}'".format(filename))
+ return
+
if save_old:
_save_old(filename)
with self._open(filename, "w") as f:
- f.write(header)
- self._write_config_syms(f)
+ f.write(contents)
if verbose:
print("Configuration written to '{}'".format(filename))
- def _write_config_syms(self, f):
- # write_config() helper. Writes the actual configuration output to 'f'.
+ 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
@@ -1409,6 +1436,10 @@ class Kconfig(object):
# 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
@@ -1420,11 +1451,11 @@ class Kconfig(object):
while node.parent:
node = node.parent
- # Print a comment when leaving visible menus
+ # 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:
- f.write("# end of {}\n".format(node.prompt[0]))
+ add("# end of {}\n".format(node.prompt[0]))
after_end_comment = True
if node.next:
@@ -1432,7 +1463,7 @@ class Kconfig(object):
break
else:
# No more nodes
- return
+ return "".join(chunks)
# Generate configuration output for the node
@@ -1451,14 +1482,14 @@ class Kconfig(object):
# Add a blank line before the first symbol printed after an
# '# end of ...' comment
after_end_comment = False
- f.write("\n")
- f.write(conf_string)
+ add("\n")
+ add(conf_string)
elif expr_value(node.dep) and \
((item is MENU and expr_value(node.visibility)) or
item is COMMENT):
- f.write("\n#\n# {}\n#\n".format(node.prompt[0]))
+ add("\n#\n# {}\n#\n".format(node.prompt[0]))
after_end_comment = False
def write_min_config(self, filename,
@@ -1549,6 +1580,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
@@ -1661,10 +1697,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):
"""
@@ -2015,6 +2059,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 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
@@ -2317,7 +2388,6 @@ class Kconfig(object):
return True
return False
-
#
# Preprocessor logic
#
@@ -2575,7 +2645,6 @@ class Kconfig(object):
return ""
-
#
# Parsing
#
@@ -3282,7 +3351,6 @@ class Kconfig(object):
for choice in self.unique_choices:
choice._invalidate()
-
#
# Post-parsing menu tree processing, including dependency propagation and
# implicit submenu creation
@@ -3451,7 +3519,6 @@ class Kconfig(object):
target.weak_rev_dep,
self._make_and(sym, cond))
-
#
# Misc.
#