diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8fa1309..75bdb5c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,6 @@ jobs: # shell). Linux/macOS run selftests, compatibility tests, and # example scripts. target: - # Python 3.12 - python: '3.12' os: Linux builder: ubuntu-24.04 @@ -59,13 +58,27 @@ jobs: run: | python -m pip install --user setuptools wheel pytest + - name: Install GNU toolchain (macOS) + # v6.18 kernel Kconfig probes require GNU Make >= 4.0 and a + # supported linker (GNU ld or LLD). macOS ships Make 3.81 + # and Apple ld, neither of which is compatible. + # We add ld.lld to PATH and set LD=ld.lld; reltest forwards LD + # as a make command-line variable to override the kernel + # Makefile's unconditional LD=$(CROSS_COMPILE)ld assignment. + if: ${{ matrix.target.os == 'macOS' }} + run: | + brew install make lld + echo "$(brew --prefix make)/libexec/gnubin" >> $GITHUB_PATH + echo "$(brew --prefix lld)/bin" >> $GITHUB_PATH + echo "LD=ld.lld" >> $GITHUB_ENV + - name: Check out Linux source code # Skip for Windows (headless-only mode) if: ${{ matrix.target.headless-only != true }} uses: actions/checkout@v6 with: repository: torvalds/linux - ref: v5.4 + ref: v6.18 - name: Check out Kconfiglib source code uses: actions/checkout@v6 diff --git a/kconfiglib.py b/kconfiglib.py index 258c213..272a581 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -1573,6 +1573,9 @@ def _autoconf_contents(self, header): if not sym._write_to_conf: continue + if sym.is_transitional: + continue + if sym.orig_type in _BOOL_TRISTATE: if val == "y": add("#define {}{} 1\n".format(self.config_prefix, sym.name)) @@ -1799,6 +1802,9 @@ def _min_config_contents(self, header): add = chunks.append for sym in self.unique_defined_syms: + if sym.is_transitional: + continue + # Skip symbols that cannot be changed. Only check # non-choice symbols, as selects don't affect choice # symbols. @@ -1903,6 +1909,9 @@ def sync_deps(self, path): # (though it's likely to keep working). val = sym.str_value + if sym.is_transitional: + continue + # n tristate values do not get written to auto.conf and autoconf.h, # making a missing symbol logically equivalent to n @@ -4815,6 +4824,9 @@ def config_string(self): if not self._write_to_conf: return "" + if self.is_transitional: + return "" + if self.orig_type in _BOOL_TRISTATE: return ( "{}{}={}\n".format(self.kconfig.config_prefix, self.name, val) diff --git a/makefile.patch b/makefile.patch index a617ebd..9e719c4 100644 --- a/makefile.patch +++ b/makefile.patch @@ -1,21 +1,14 @@ -From 93daf46f309b0c8f86149ef58c4906387d054c22 Mon Sep 17 00:00:00 2001 -From: Ulf Magnusson -Date: Tue, 9 Jun 2015 13:01:34 +0200 -Subject: [PATCH] Kconfiglib scripts/kconfig/Makefile patch - ---- - scripts/kconfig/Makefile | 29 +++++++++++++++++++++++++++++ - 1 file changed, 29 insertions(+) - diff --git a/scripts/kconfig/Makefile b/scripts/kconfig/Makefile -index 3f327e21f60e..8b7dd1292005 100644 +index fb50bd4..85f47ce 100644 --- a/scripts/kconfig/Makefile +++ b/scripts/kconfig/Makefile -@@ -27,2 +27,31 @@ gconfig: $(obj)/gconf +@@ -55,6 +55,35 @@ endef + + $(foreach c, config menuconfig nconfig gconfig xconfig, $(eval $(call config_rule,$(c)))) +PHONY += scriptconfig iscriptconfig kmenuconfig guiconfig dumpvarsconfig + -+PYTHONCMD ?= python ++PYTHONCMD ?= python3 +kpython := PYTHONPATH=$(srctree)/Kconfiglib:$$PYTHONPATH $(PYTHONCMD) + +ifneq ($(filter scriptconfig,$(MAKECMDGOALS)),) @@ -42,7 +35,6 @@ index 3f327e21f60e..8b7dd1292005 100644 +dumpvarsconfig: + $(Q)$(kpython) $(srctree)/Kconfiglib/examples/dumpvars.py $(Kconfig) + - menuconfig: $(obj)/mconf --- -2.20.1 - + PHONY += localmodconfig localyesconfig + localyesconfig localmodconfig: $(obj)/conf + $(Q)$(PERL) $(src)/streamline_config.pl --$@ $(srctree) $(Kconfig) > .tmp.config diff --git a/tests/Ktransitional b/tests/Ktransitional new file mode 100644 index 0000000..ca833d2 --- /dev/null +++ b/tests/Ktransitional @@ -0,0 +1,23 @@ +config NEW_BOOL + bool "new bool option" + default LEGACY_BOOL + +config LEGACY_BOOL + bool "legacy bool (deprecated)" + transitional + +config NEW_INT + int "new int option" + default LEGACY_INT + +config LEGACY_INT + int "legacy int (deprecated)" + transitional + +config NORMAL_BOOL + bool "normal bool" + default y + +config NORMAL_INT + int "normal int" + default 42 diff --git a/tests/config_transitional b/tests/config_transitional new file mode 100644 index 0000000..56d6fa4 --- /dev/null +++ b/tests/config_transitional @@ -0,0 +1,2 @@ +CONFIG_LEGACY_BOOL=y +CONFIG_LEGACY_INT=99 diff --git a/tests/reltest b/tests/reltest index 91da6df..5979ed9 100755 --- a/tests/reltest +++ b/tests/reltest @@ -12,10 +12,15 @@ test_script() { echo -e "\n================= $1 with $py =================\n" + # Forward LD as a make command-line variable so it overrides the + # kernel Makefile's unconditional LD=$(CROSS_COMPILE)ld assignment. + # Environment variables alone do not override Makefile assignments. + local _ld_override="" + [ -n "${LD:-}" ] && _ld_override="LD=$LD" if (($# == 1)); then - make scriptconfig PYTHONCMD=$py SCRIPT=$1 + ${MAKE:-make} $_ld_override scriptconfig PYTHONCMD=$py SCRIPT=$1 else - make scriptconfig PYTHONCMD=$py SCRIPT=$1 SCRIPT_ARG="$2" + ${MAKE:-make} $_ld_override scriptconfig PYTHONCMD=$py SCRIPT=$1 SCRIPT_ARG="$2" fi if (($?)); then diff --git a/tests/test_compat.py b/tests/test_compat.py index edadf72..ec4eee1 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -64,19 +64,21 @@ def kernel_env(): These are referenced inside the kernel Kconfig files and must be present before any Kconfig object is instantiated. """ + os.environ["srctree"] = "." + os.environ.setdefault("CC", "gcc") + os.environ.setdefault("LD", "ld") + _make = os.environ.get("MAKE", "make") + _cc = os.environ["CC"] os.environ["KERNELVERSION"] = ( - subprocess.check_output("make kernelversion", shell=True) + subprocess.check_output(f"{_make} kernelversion", shell=True) .decode("utf-8") .rstrip() ) os.environ["CC_VERSION_TEXT"] = ( - subprocess.check_output("gcc --version | head -n1", shell=True) + subprocess.check_output(f"{_cc} --version | head -n1", shell=True) .decode("utf-8") .rstrip() ) - os.environ["srctree"] = "." - os.environ["CC"] = "gcc" - os.environ["LD"] = "ld" yield @@ -126,7 +128,12 @@ def run_conf_and_compare(script, conf_flag, arch): """Run a Kconfiglib script via 'make scriptconfig', then run the C implementation with *conf_flag*, and compare the resulting .config files. """ - shell(f"make scriptconfig SCRIPT={script} PYTHONCMD='{sys.executable}'") + _make = os.environ.get("MAKE", "make") + _ld = os.environ.get("LD") + _ld_override = f"LD={_ld}" if _ld else "" + shell( + f"{_make} {_ld_override} scriptconfig SCRIPT={script} PYTHONCMD='{sys.executable}'" + ) shell("mv .config ._config") shell(f"scripts/kconfig/conf --{conf_flag} Kconfig") compare_configs(arch) diff --git a/tests/test_transitional.py b/tests/test_transitional.py new file mode 100644 index 0000000..82fea91 --- /dev/null +++ b/tests/test_transitional.py @@ -0,0 +1,122 @@ +"""Tests for the 'transitional' keyword (Linux >= 6.18). + +Transitional symbols are read from old .config files to populate new symbol +defaults, but must never appear in generated .config, autoconf.h, or +min-config output. +""" + +import os +import tempfile + +import kconfiglib + +KCONFIG_PATH = "tests/Ktransitional" +CONFIG_PATH = "tests/config_transitional" + + +def _load(config_path=None): + """Load the transitional test Kconfig, optionally with a .config.""" + kconf = kconfiglib.Kconfig(KCONFIG_PATH, warn=False) + if config_path: + kconf.load_config(config_path) + return kconf + + +def test_transitional_flag(): + """is_transitional is True for transitional syms, False for normal.""" + kconf = _load() + + assert kconf.syms["LEGACY_BOOL"].is_transitional is True + assert kconf.syms["LEGACY_INT"].is_transitional is True + assert kconf.syms["NEW_BOOL"].is_transitional is False + assert kconf.syms["NEW_INT"].is_transitional is False + assert kconf.syms["NORMAL_BOOL"].is_transitional is False + assert kconf.syms["NORMAL_INT"].is_transitional is False + + +def test_transitional_migration(): + """Loading old .config with LEGACY_BOOL=y causes NEW_BOOL to default to y.""" + kconf = _load(CONFIG_PATH) + + # LEGACY_BOOL=y was loaded, so NEW_BOOL (default LEGACY_BOOL) should be y + assert kconf.syms["NEW_BOOL"].str_value == "y" + # LEGACY_INT=99 was loaded, so NEW_INT (default LEGACY_INT) should be 99 + assert kconf.syms["NEW_INT"].str_value == "99" + + +def test_transitional_write_config(): + """Transitional symbols absent from write_config output; normal symbols present.""" + kconf = _load(CONFIG_PATH) + + with tempfile.NamedTemporaryFile(mode="r", suffix=".config", delete=False) as f: + tmppath = f.name + try: + kconf.write_config(tmppath) + with open(tmppath) as f: + content = f.read() + finally: + os.unlink(tmppath) + + assert "LEGACY_BOOL" not in content + assert "LEGACY_INT" not in content + assert "NEW_BOOL" in content + assert "NORMAL_BOOL" in content + assert "NORMAL_INT" in content + + +def test_transitional_write_autoconf(): + """Transitional symbols absent from write_autoconf output.""" + kconf = _load(CONFIG_PATH) + + with tempfile.NamedTemporaryFile(mode="r", suffix=".h", delete=False) as f: + tmppath = f.name + try: + kconf.write_autoconf(tmppath) + with open(tmppath) as f: + content = f.read() + finally: + os.unlink(tmppath) + + assert "LEGACY_BOOL" not in content + assert "LEGACY_INT" not in content + # NEW_BOOL=y should produce a #define + assert "NEW_BOOL" in content + + +def test_transitional_write_min_config(): + """Transitional symbols absent from write_min_config output.""" + kconf = _load(CONFIG_PATH) + + with tempfile.NamedTemporaryFile(mode="r", suffix=".config", delete=False) as f: + tmppath = f.name + try: + kconf.write_min_config(tmppath) + with open(tmppath) as f: + content = f.read() + finally: + os.unlink(tmppath) + + assert "LEGACY_BOOL" not in content + assert "LEGACY_INT" not in content + + +def test_transitional_config_string(): + """config_string returns '' for transitional symbols.""" + kconf = _load(CONFIG_PATH) + + assert kconf.syms["LEGACY_BOOL"].config_string == "" + assert kconf.syms["LEGACY_INT"].config_string == "" + # Normal symbols should have non-empty config_string + assert kconf.syms["NORMAL_BOOL"].config_string != "" + assert kconf.syms["NORMAL_INT"].config_string != "" + + +def test_transitional_repr(): + """repr() includes 'transitional' for flagged symbols.""" + kconf = _load() + + # Use ", transitional," to avoid false-matching the filename Ktransitional + assert ", transitional," in repr(kconf.syms["LEGACY_BOOL"]) + assert ", transitional," in repr(kconf.syms["LEGACY_INT"]) + assert ", transitional," not in repr(kconf.syms["NEW_BOOL"]) + assert ", transitional," not in repr(kconf.syms["NORMAL_BOOL"])