App.get_project_refs(): Fix multi-section results
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 6m2s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 4m41s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 5m3s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m45s
CI / Packaging test (push) Successful in 0s

get_project_refs() takes a list of sections to look for references in. To
now, that has never been exercised, i.e. only one section had ever been
passed in. With the introduction of pkg.recommends, that could change now,
and it has a subtle bug.

Say, a caller wants to retrieve all requires and recommends of package A
with this graph:

  A recommends B, requires nothing
  B requires C

then A, B, C should be returned, but the current implementation only
returns A and B. The cause is that every section is recursed into in
isolation, and the union of all results is returned. Since A doesn't
require anything, the requires-branch never reaches B, and C is missed.

The fix is to make __get_project_refs(), the recursing method, take a list
of sections instead of one section, and decide if it recurses deeper based
on all of its entries at every depth.

Signed-off-by: Jan Lindemann <jan@janware.com>
Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.86.1
This commit is contained in:
Jan Lindemann 2026-09-21 05:31:38 +02:00
commit 63bc5efdc3
Signed by: Jan Lindemann
GPG key ID: 3750640C9E25DD61

View file

@ -203,7 +203,7 @@ class App(Base):
buf: list[str],
visited: set[str],
spec: str,
section: str,
sections: list[str],
key: str,
add_self: bool,
scope: Scope,
@ -230,29 +230,33 @@ class App(Base):
f'Unmet dependency on {mod}: the -{needed_subpackage} package '
f'of project {name} is not installed'
)
vals = self.get_value(name, section, key)
log(
DEBUG,
(
f'name={name}, section={section}, key={key}, deps={vals}, '
f'scope={scope.name}, visited={visited}'
),
)
vals_list = vals.split(',') if vals else []
vals_list: list[str] = []
for section in sections:
vals = self.get_value(name, section, key)
log(
DEBUG,
(
f'name={name}, section={section}, key={key}, deps={vals}, '
f'scope={scope.name}, visited={visited}'
),
)
if not vals:
continue
for val in vals.split(','):
val = val.strip()
if (len(val)) and (val not in vals_list):
vals_list.append(val)
match scope:
case Scope.Self:
buf += vals_list
case Scope.One | Scope.Subtree:
subscope = scope.Self if scope == Scope.One else scope
for val in vals_list:
val = val.strip()
if not (len(val)):
continue
self.__get_project_refs(
buf,
visited,
val,
section,
sections,
key,
add_self = True,
scope = subscope,
@ -559,18 +563,17 @@ class App(Base):
if isinstance(keys, str):
keys = [keys]
ret: list[str] = []
for section in sections:
for key in keys:
visited: set[str] = set()
for name in projects:
rr: list[str] = []
self.__get_project_refs(
rr, visited, name, section, key, add_self, scope, names_only
)
# TODO: this looks like a performance hogger
for m in rr:
if m not in ret:
ret.append(m)
for key in keys:
visited: set[str] = set()
for name in projects:
rr: list[str] = []
self.__get_project_refs(
rr, visited, name, sections, key, add_self, scope, names_only
)
# TODO: this looks like a performance hogger
for m in rr:
if m not in ret:
ret.append(m)
return ret
def get_libname(self, spec: str) -> str | None: