From 35303ec6e5bd79b3063502b2902c4930e20b5beb Mon Sep 17 00:00:00 2001 From: PopSolutions Date: Wed, 15 Apr 2026 14:36:45 +0000 Subject: [PATCH] fix: plan-resolve silently drops packages not found in pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs in Plan.resolve(): 1. The 'missing' set was never populated — packages.missing was not accumulated across while-loop iterations. Fixed by adding 'missing |= packages.missing' inside the loop. 2. The 'all_missing' computation was inside the loop but should be after it, using the fully accumulated 'missing' set. 3. The 'brokendeps' list iterated over 'missing' without filtering by 'all_missing', including virtual packages already satisfied by bootstrap Provides. Also adds iter_provided() to resolve.py to extract virtual package names (Provides) from the bootstrap dpkg status, so packages like 'awk' (provided by mawk) are correctly recognized as satisfied. Without this fix, packages like locales, curl, nginx, git, iptables, fail2ban, postfix, ssh, build-essential, etc. were silently dropped from root.spec, causing build failures. --- fablib/plan.py | 12 ++++++------ fablib/resolve.py | 25 ++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/fablib/plan.py b/fablib/plan.py index 96f0218..0a32d81 100644 --- a/fablib/plan.py +++ b/fablib/plan.py @@ -352,7 +352,7 @@ def dctrls(self) -> dict[Dependency, deb822.Deb822]: return dctrls - def resolve(self) -> tuple[Iterable[str], Iterable[str]]: + def resolve(self, bootstrap_provided: set[str] | None = None) -> tuple[Iterable[str], Iterable[str]]: """resolve plan dependencies recursively -> return spec""" logger.debug("resolve") @@ -364,7 +364,7 @@ def resolve(self) -> tuple[Iterable[str], Iterable[str]]: resolved: set[Dependency] = set() missing: set[Dependency] = set() - provided: set[str] = set() + provided: set[str] = set(bootstrap_provided or set()) def reformat2dep(pkg: str) -> str: if "=" not in pkg: @@ -402,8 +402,10 @@ def reformat2dep(pkg: str) -> str: ) provided |= self._get_provided(pkg_control) + missing |= packages.missing unresolved = new_deps - resolved - all_missing = set(map(str, (missing | packages.missing))) - provided + + all_missing = set(map(str, missing)) - provided if all_missing: @@ -417,9 +419,7 @@ def get_origins(dep: Dependency) -> Generator[str, None, None]: except KeyError: depname = None - brokendeps = [] - for dep in missing: - brokendeps.append(dep.name) + brokendeps = [dep.name for dep in missing if str(dep) in all_missing] logger.debug( f"could not find these packages in pool: {brokendeps!r}" diff --git a/fablib/resolve.py b/fablib/resolve.py index fd61175..09ea3c4 100644 --- a/fablib/resolve.py +++ b/fablib/resolve.py @@ -7,6 +7,8 @@ from .plan import PackageOrigins, Plan +import re + logger = logging.getLogger("fab.resolve") @@ -23,6 +25,25 @@ def iter_packages(root: str) -> Generator[str, None, None]: control += line +def iter_provided(root: str) -> Generator[str, None, None]: + """Yield virtual package names provided by installed bootstrap packages.""" + control = "" + with open(join(root, "var/lib/dpkg/status")) as fob: + for line in fob: + if not line.strip(): + deb = Deb822(control.splitlines()) + if deb["Status"] == "install ok installed": + provides = deb.get("Provides", "") + if provides: + for p in re.split(r"\s*,\s*", provides.strip()): + name = p.split()[0].strip() + if name: + yield name + control = "" + else: + control += line + + def annotate_spec( repo_spec: Iterable[str], pool_spec: Iterable[str], @@ -54,8 +75,10 @@ def resolve_plan( plans: list[str], ) -> None: plan = Plan(pool_path=pool_path) + bootstrap_provided: set[str] = set() if bootstrap_path: bootstrap_packages = set(iter_packages(bootstrap_path)) + bootstrap_provided = set(iter_provided(bootstrap_path)) plan |= bootstrap_packages for package in bootstrap_packages: @@ -72,7 +95,7 @@ def resolve_plan( plan.add(plan_path) plan.packageorigins.add(plan_path, "_") - spec, unresolved = plan.resolve() + spec, unresolved = plan.resolve(bootstrap_provided=bootstrap_provided) logger.debug("unresolved" + "\n".join(unresolved)) spec = annotate_spec(unresolved, spec, plan.packageorigins) logger.debug(spec)