diff --git a/pmb/build/checksum.py b/pmb/build/checksum.py index da786ed4..6a15e3f7 100644 --- a/pmb/build/checksum.py +++ b/pmb/build/checksum.py @@ -11,10 +11,11 @@ import pmb.helpers.pmaports from pmb.core import Chroot -def update(pkgname: str) -> None: +def update(pkgname: str, skip_init: bool=False) -> None: """Fetch all sources and update the checksums in the APKBUILD.""" - pmb.build.init_abuild_minimal() - pmb.build.copy_to_buildpath(pkgname, no_override=True) + if not skip_init: + pmb.build.init_abuild_minimal() + pmb.build.copy_to_buildpath(pkgname, no_override=True) logging.info("(native) generate checksums for " + pkgname) pmb.chroot.user(["abuild", "checksum"], working_dir=Path(pmb.config.abuild_basedir)) diff --git a/pmb/build/kconfig.py b/pmb/build/kconfig.py index 822bbf27..e9d52569 100644 --- a/pmb/build/kconfig.py +++ b/pmb/build/kconfig.py @@ -104,7 +104,7 @@ def get_outputdir(pkgname: str, apkbuild: Apkbuild, must_exist: bool = True) -> # New style ($builddir) ret = "" if "builddir" in apkbuild: - ret = apkbuild["builddir"] + ret = Path(apkbuild["builddir"]) if not must_exist: # For fragment-based configs, check if old style exists first @@ -187,7 +187,7 @@ def _make( config = f"config-{apkbuild['_flavor']}.{arch}" target = aport / config pmb.helpers.run.user(["cp", source, target]) - pmb.build.checksum.update(pkgname) + pmb.build.checksum.update(pkgname, skip_init=True) def _init(pkgname: str, arch: Arch | None) -> tuple[str, Arch, Any, Chroot, Env]: @@ -267,7 +267,7 @@ def generate_config(pkgname: str, arch: Arch | None) -> None: fragments += defconfig # Generate fragment based on categories for kernel, using kconfigcheck.toml - pmos_frag = pmb.parse.kconfig.create_fragment(apkbuild, arch) + pmos_frag, syms_dict = pmb.parse.kconfig.create_fragment(apkbuild, arch) # Write the pmos fragment to the kernel source tree outputdir = get_outputdir(pkgname, apkbuild, must_exist=False) @@ -318,6 +318,9 @@ def generate_config(pkgname: str, arch: Arch | None) -> None: # Generate the config using all fragments _make(chroot, fragments, env, pkgname, arch, apkbuild, outputdir) + print("Parsing kconfig!") + pmb.parse.kconfig.add_missing_dependencies(apkbuild, syms_dict, outputdir / ".config") + # Validate the generated config if not pmb.parse.kconfig.check(pkgname, details=True): raise RuntimeError("Generated kernel config does not pass all checks") diff --git a/pmb/parse/kconfig.py b/pmb/parse/kconfig.py index 38834a67..4e603c64 100644 --- a/pmb/parse/kconfig.py +++ b/pmb/parse/kconfig.py @@ -1,6 +1,8 @@ # Copyright 2023 Attila Szollosi # SPDX-License-Identifier: GPL-3.0-or-later from pathlib import Path +from queue import Queue +from pmb.core.chroot import Chroot from pmb.helpers import logging import re import os @@ -350,7 +352,59 @@ def check_file( return check_config(config_path, arch, version, components_list, details=details) -def create_fragment(apkbuild: Apkbuild, arch: Arch) -> str: +def add_missing_dependencies(apkbuild: Apkbuild, pmos_frag: OrderedDict[str, str], dotconfig: Path) -> OrderedDict[str, str]: + from pmb.parse import kconfiglib + + os.environ["srctree"] = "." #str(Chroot.native() / apkbuild["builddir"]) + os.environ["CC"] = "gcc" + os.environ["LD"] = "ld" + os.environ["HOSTCC"] = "gcc" + os.environ["ARCH"] = "arm64" + os.environ["SRCARCH"] = "arm64" + os.chdir(Chroot.native() / apkbuild["builddir"]) + kconfig = kconfiglib.Kconfig("Kconfig", warn=False) + print("Loading .config...") + kconfig.load_config(str(Chroot.native() / dotconfig)) + print("Finding missing dependencies!") + + for (sname, sval) in pmos_frag.items(): + if sname not in kconfig.syms: + print(f"symbol {sname} not found") + continue + + sym = kconfig.syms.get(sname) + if not isinstance(sym, kconfiglib.Symbol): + print(f"sym {sname} not a Symbol? got {sym}") + # print(f"sym {sym.config_string} dep type {type(sym.direct_dep)}") + if not sym.direct_dep or not isinstance(sym.direct_dep, tuple): + # print(f"CONFIG_{sym.name}={sym.str_value} # no deps?") + continue + + missing_deps: list[kconfiglib.Symbol] = [] + dept: Queue[tuple[kconfiglib.Symbol]] = Queue() + dept.put(sym.direct_dep) + while not dept.empty(): + next_dep = dept.get_nowait() + for dep in next_dep: + if isinstance(dep, int): + continue + if isinstance(dep, tuple): + dept.put(dep) + elif dep.str_value == "n": + missing_deps.append(dep) + # print(f" {dep.name}={dep.str_value}") + + if missing_deps and sym.str_value == "n": + print(f"\nCONFIG_{sym.name}={sym.str_value} # missing dependencies:") + for dep in missing_deps: + if dep.assignable: + print(f" CONFIG_{dep.name}={dep.assignable[-1]} (current: {dep.str_value})") + else: + print(f" # CONFIG_{dep.name} can't be assigned!") + # print() + + +def create_fragment(apkbuild: Apkbuild, arch: Arch) -> tuple[str, dict[str, str]]: """ Generate a kconfig fragment based on categories and version from a kernel's APKBUILD. @@ -378,6 +432,7 @@ def create_fragment(apkbuild: Apkbuild, arch: Arch) -> str: logging.warning(f"Failed to read category {category}: {e}") fragment_lines = [] + syms_dict: OrderedDict[str, str] = {} # Process each category for category_key, category_rules in all_rules.items(): @@ -410,6 +465,7 @@ def create_fragment(apkbuild: Apkbuild, arch: Arch) -> str: # Add each option for option, value in sorted(options.items()): + syms_dict[option] = str(value) if isinstance(value, bool): if value: fragment_lines.append(f"CONFIG_{option}=y") @@ -426,4 +482,4 @@ def create_fragment(apkbuild: Apkbuild, arch: Arch) -> str: # Padding between categories fragment_lines.append("") - return "\n".join(fragment_lines) + return "\n".join(fragment_lines), syms_dict diff --git a/pmb/parse/kconfiglib.py b/pmb/parse/kconfiglib.py index 27abbf9a..e7ceb591 100644 --- a/pmb/parse/kconfiglib.py +++ b/pmb/parse/kconfiglib.py @@ -986,7 +986,7 @@ class Kconfig(object): self.config_header = os.getenv("KCONFIG_CONFIG_HEADER", "") self.header_header = os.getenv("KCONFIG_AUTOHEADER_HEADER", "") - self.syms = {} + self.syms: dict[str, Symbol] = {} self.const_syms = {} self.defined_syms = [] self.missing_syms = [] @@ -3199,6 +3199,9 @@ class Kconfig(object): node.visibility = self._make_and(node.visibility, self._expect_expr_and_eol()) + elif t0 is _T_MODULES: + pass + elif t0 is _T_OPTION: if self._check_token(_T_ENV): if not self._check_token(_T_EQUAL): @@ -4267,6 +4270,9 @@ class Symbol(object): "weak_rev_dep", ) + # tuple with number of entries followed by each Symbol + direct_dep: list[tuple[int, ...]] + # # Public interface #