summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.rst9
-rw-r--r--kconfiglib.py95
-rw-r--r--testsuite.py41
3 files changed, 141 insertions, 4 deletions
diff --git a/README.rst b/README.rst
index 59f86bf..207b08c 100644
--- a/README.rst
+++ b/README.rst
@@ -23,11 +23,12 @@ configuration systems. It can do the following, among other things:
produce identical output to the standard ``make allnoconfig`` and ``make
allyesconfig``.
-- **Read and write .config files**
+- **Read and write .config and defconfig files**
- The generated ``.config`` files are character-for-character identical to what
- the C implementation would generate (except for the header comment). The test
- suite relies on this, as it compares the generated files.
+ The generated ``.config`` and ``defconfig`` (minimal configuration) files are
+ character-for-character identical to what the C implementation would generate
+ (except for the header comment). The test suite relies on this, as it
+ compares the generated files.
- **Write C headers**
diff --git a/kconfiglib.py b/kconfiglib.py
index 1a07da3..85a0054 100644
--- a/kconfiglib.py
+++ b/kconfiglib.py
@@ -961,6 +961,62 @@ class Kconfig(object):
else:
return
+ def write_min_config(self, filename,
+ header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"):
+ """
+ Writes out a "minimal" configuration file, omitting symbols whose value
+ matches their default value. The format matches the one produced by
+ 'make savedefconfig'.
+
+ The resulting configuration file is incomplete, but a complete
+ configuration can be derived from it by loading it. Minimal
+ configuration files can serve as a more manageable configuration format
+ compared to a "full" .config file, especially when configurations files
+ are merged or edited by hand.
+
+ filename:
+ Self-explanatory.
+
+ header (default: "# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"):
+ Text that will be inserted verbatim at the beginning of the file. You
+ would usually want each line to start with '#' to make it a comment,
+ and include a final terminating newline.
+ """
+ with open(filename, "w") as f:
+ f.write(header)
+
+ # Avoid duplicates -- see write_config()
+ for sym in self.defined_syms:
+ sym._written = False
+
+ for sym in self.defined_syms:
+ if not sym._written:
+ sym._written = True
+
+ # 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 == BOOL and \
+ sym.tri_value == 2:
+ continue
+
+ f.write(sym.config_string)
+
def sync_deps(self, path):
"""
Creates or updates a directory structure that can be used to avoid
@@ -3233,6 +3289,41 @@ class Symbol(object):
self.kconfig._warn(_name_and_loc_str(self) + " has no prompt, "
"meaning user values have no effect on it")
+ def _str_default(self):
+ # write_min_config() helper function. Returns the value the symbol
+ # would get from defaults if it didn't have a user value. Uses exactly
+ # the same algorithm as the C implementation (though a bit cleaned up),
+ # for compatibility.
+
+ if self.orig_type in (BOOL, TRISTATE):
+ val = 0
+
+ # Defaults, selects, and implies do not affect choice symbols
+ if not self.choice:
+ for default, cond in self.defaults:
+ cond_val = expr_value(cond)
+ if cond_val:
+ val = min(expr_value(default), cond_val)
+ break
+
+ val = max(expr_value(self.rev_dep),
+ expr_value(self.weak_rev_dep),
+ val)
+
+ # Transpose mod to yes if type is bool (possibly due to modules
+ # being disabled)
+ if val == 1 and self.type == BOOL:
+ val = 2
+
+ return TRI_TO_STR[val]
+
+ if self.orig_type in (STRING, INT, HEX):
+ for default, cond in self.defaults:
+ if expr_value(cond):
+ return default.str_value
+
+ return ""
+
def _warn_select_unsatisfied_deps(self):
# Helper for printing an informative warning when a symbol with
# unsatisfied direct dependencies (dependencies from 'depends on', ifs,
@@ -3684,6 +3775,10 @@ class Choice(object):
return self.user_selection
# Otherwise, check if we have a default
+ return self._get_selection_from_defaults()
+
+ def _get_selection_from_defaults(self):
+ # Check if we have a default
for sym, cond in self.defaults:
# The default symbol must be visible too
if expr_value(cond) and sym.visibility:
diff --git a/testsuite.py b/testsuite.py
index 176de91..4b3d0f2 100644
--- a/testsuite.py
+++ b/testsuite.py
@@ -24,6 +24,9 @@
# by an order of magnitude. Occasionally finds (usually obscure) bugs, and I
# make sure everything passes with it.
#
+# - obsessive-min-config:
+# Like obsessive, for the minimal configuation (defconfig) tests.
+#
# - log:
# Log timestamped defconfig test failures to the file test_defconfig_fails.
# Handy in obsessive mode.
@@ -87,6 +90,7 @@ os.environ.pop("KCONFIG_ALLCONFIG", None)
speedy = False
obsessive = False
+obsessive_min_config = False
log = False
def run_tests():
@@ -98,6 +102,9 @@ def run_tests():
elif s == "obsessive":
obsessive = True
print("Obsessive mode enabled")
+ elif s == "obsessive-min-config":
+ obsessive_min_config = True
+ print("Obsessive minimal config mode enabled")
elif s == "log":
log = True
print("Log mode enabled")
@@ -1715,6 +1722,9 @@ def run_compatibility_tests():
test_fns = (test_alldefconfig,
test_defconfig,
+ # Fails for a few defconfigs due to a bug in the C tools. Will
+ # be enabled once patches get in.
+ #test_min_config,
test_sanity,
test_all_no,
test_all_no_simpler,
@@ -2022,6 +2032,37 @@ def test_defconfig(conf, arch, srcarch):
fail_log.write("{} with {} did not match\n"
.format(arch, defconfig))
+def test_min_config(conf, arch, srcarch):
+ """
+ Verify that Kconfiglib generates the same .config as 'make savedefconfig'
+ for each architecture/defconfig pair.
+ """
+ if obsessive_min_config:
+ defconfigs = []
+ for srcarch_ in os.listdir("arch"):
+ defconfigs.extend(defconfig_files(srcarch_))
+ else:
+ defconfigs = defconfig_files(srcarch)
+
+ for defconfig in defconfigs:
+ conf.load_config(defconfig)
+ conf.write_min_config("._config")
+
+ shell("cp {} .config".format(defconfig))
+
+ if speedy:
+ shell("scripts/kconfig/conf --savedefconfig=.config Kconfig")
+ else:
+ shell("make savedefconfig")
+ shell("mv defconfig .config")
+
+ arch_defconfig_str = " {:14}with {:60} ".format(arch, defconfig)
+
+ if equal_configs():
+ print(arch_defconfig_str + "OK")
+ else:
+ print(arch_defconfig_str + "FAIL")
+
#
# Helper functions
#