Unify the project dependency walks in App and pkg_relations() #118

Merged
Jan Lindemann merged 2 commits from jan/feature/20260922-unify-dep-walks into master 2026-09-22 05:47:35 +02:00 AGit
2 changed files with 111 additions and 79 deletions

View file

@ -198,7 +198,28 @@ class App(Base):
except FileNotFoundError:
return None
def __get_project_refs(
def read_dep_edges(
self,
name: str,
sections: list[str],
key: str,
) -> list[str]:
"""The deduplicated dependency values of name for the given
sections and key (flavour), in section and value order.
"""
ret: list[str] = []
for section in sections:
vals = self.get_value(name, section, key)
log(DEBUG, f'name={name}, section={section}, key={key}, deps={vals}')
if not vals:
continue
for val in vals.split(','):
val = val.strip()
if (len(val)) and (val not in ret):
ret.append(val)
return ret
def walk_project_deps(
self,
buf: list[str],
visited: set[str],
@ -208,7 +229,21 @@ class App(Base):
add_self: bool,
scope: Scope,
names_only: bool,
*,
check_installed: bool = True,
exclude: set[str] = set(),
recurse: bool = True,
) -> None:
"""Walk the dependency edges of sections and key (flavour)
starting at spec, appending the visited specs to buf in
postorder; buf and visited are updated in place.
check_installed applies the installed-package check, which
skips the dependencies of an installed run package and fails
on an unmet dependency. exclude names specs that are neither
traversed nor appended. recurse = False stops the walk at the
starting spec.
"""
dep = Dependency(spec)
name = dep.base_name
mod = dep.full_name
@ -216,11 +251,14 @@ class App(Base):
spec = name
if spec in buf:
return
if spec in exclude:
return
if spec in visited:
if add_self:
buf.append(spec)
return
visited.add(spec)
if check_installed:
needed_subpackage = 'devel' if mod.endswith(('-dev', '-devel')) else 'run'
if not self.is_installed(name, devel = True):
if not mod.endswith(('-dev', '-devel')):
@ -230,29 +268,15 @@ class App(Base):
f'Unmet dependency on {mod}: the -{needed_subpackage} package '
f'of project {name} is not installed'
)
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)
vals_list = self.read_dep_edges(name, sections, key)
match scope:
case Scope.Self:
buf += vals_list
case Scope.One | Scope.Subtree:
if recurse:
subscope = scope.Self if scope == Scope.One else scope
for val in vals_list:
self.__get_project_refs(
self.walk_project_deps(
buf,
visited,
val,
@ -261,6 +285,9 @@ class App(Base):
add_self = True,
scope = subscope,
names_only = names_only,
check_installed = check_installed,
exclude = exclude,
recurse = recurse,
)
if add_self:
buf.append(spec)
@ -567,7 +594,7 @@ class App(Base):
visited: set[str] = set()
for name in projects:
rr: list[str] = []
self.__get_project_refs(
self.walk_project_deps(
rr, visited, name, sections, key, add_self, scope, names_only
)
# TODO: this looks like a performance hogger

View file

@ -52,18 +52,26 @@ def pkg_relations(
ret: list[str] = []
for flavour in flavours: # build / release / run / devel
cur_pkgs = seed_pkgs.copy()
visited_pkgs: set[str] = set()
while len(cur_pkgs):
cur_pkg = cur_pkgs.pop(0)
if cur_pkg in visited_pkgs or cur_pkg in ignore:
continue
closure: list[str] = []
visited: set[str] = set()
for seed in seed_pkgs:
app.walk_project_deps(
closure,
visited,
seed,
['pkg.' + rel_type + '.jw'],
flavour,
add_self = True,
scope = Scope.Subtree,
names_only = True,
check_installed = False,
exclude = ignore,
recurse = recursive,
)
for cur_pkg in closure:
for subsec in subsections:
section = 'pkg.' + rel_type + '.' + subsec
visited_pkgs.add(cur_pkg)
deps_spec = app.get_value(cur_pkg, section, flavour)
if not deps_spec:
continue
for deps_spec in app.read_dep_edges(cur_pkg, [section], flavour):
for dep in Dependency.parse_deps_spec(
deps_spec,
lookup_version = app.get_version,
@ -73,13 +81,10 @@ def pkg_relations(
if dep_name in ignore or dep.full_name in ignore:
continue
if subsec == 'jw':
if (recursive and dep_name not in visited_pkgs
and dep_name not in cur_pkgs):
cur_pkgs.append(dep_name)
if hide_jw_pkg and dep_name == 'jw-pkg':
continue
expand_version_macros = subsec == 'jw'
if dont_expand_version_macros and dep_name in cur_pkgs:
if dont_expand_version_macros and dep_name in visited:
expand_version_macros = False
if hide_self and dep_name in seed_pkgs:
continue