cmds.projects.lib.pkg_relations: Integrate lib.version

- The pkg_relations() function is a horribly bad read. It contains a
  mind-bending amount of interwoven case distinctions that are not clearly
  reflected in the participating variable and function names. Much of it is
  version handling. That's intricate by nature, but much of it has now been
  implemented in the lib.version module, so moving the logic there makes
  the function a good deal more readable than before. That's most of what
  this commit does.

- Use version syntax macros from lib.version instead of
  pkg_relations-defined macros, and fix the fallout in BaseCmdPkgRelations
  and CmdCreateFile.

- Use lib.version also as a central place for dep string parsing from App.

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2026-09-09 14:44:37 +02:00
commit 0c808f9383
4 changed files with 46 additions and 113 deletions

View file

@ -11,10 +11,11 @@ from enum import Enum, auto
from functools import cache from functools import cache
from typing import TYPE_CHECKING, override from typing import TYPE_CHECKING, override
from .lib.ExecApp import ExecApp as Base
from .lib.Distro import Distro from .lib.Distro import Distro
from .lib.ExecApp import ExecApp as Base
from .lib.log import DEBUG, ERR, log from .lib.log import DEBUG, ERR, log
from .lib.ProjectConf import ProjectConf from .lib.ProjectConf import ProjectConf
from .lib.version.Dependency import Dependency
if TYPE_CHECKING: if TYPE_CHECKING:
import argparse import argparse
@ -435,7 +436,7 @@ class App(Base):
return self.find_dir(name, ['/tmpl'], ['/opt/' + name + '/share/tmpl']) return self.find_dir(name, ['/tmpl'], ['/opt/' + name + '/share/tmpl'])
def strip_module_from_spec(self, mod: str) -> str: def strip_module_from_spec(self, mod: str) -> str:
return re.sub(r'-dev$|-devel$|-run$', '', re.split('([=><]+)', mod)[0].strip()) return Dependency(mod).base_name
@cache @cache
def get_value(self, project: str, section: str, key: str) -> str | None: def get_value(self, project: str, section: str, key: str) -> str | None:

View file

@ -4,8 +4,8 @@ import re
from typing import TYPE_CHECKING, cast, override from typing import TYPE_CHECKING, cast, override
from ...lib.version.base import Syntax
from .Cmd import Cmd, Parent from .Cmd import Cmd, Parent
from .lib.pkg_relations import VersionSyntax
from .lib.pkg_relations import pkg_relations as pkg_relations_list from .lib.pkg_relations import pkg_relations as pkg_relations_list
if TYPE_CHECKING: if TYPE_CHECKING:
@ -30,7 +30,11 @@ class BaseCmdPkgRelations(Cmd):
no_subpackages = args.no_subpackages, no_subpackages = args.no_subpackages,
dont_strip_revision = args.dont_strip_revision, dont_strip_revision = args.dont_strip_revision,
expand_semver_revision_range = args.expand_semver_revision_range, expand_semver_revision_range = args.expand_semver_revision_range,
syntax = VersionSyntax[args.syntax.replace('-', '_')], syntax = {
'semver': Syntax.SEM_VER,
'debian': Syntax.DEBIAN,
'names-only': Syntax.NAMES_ONLY,
}[args.syntax],
recursive = args.recursive, recursive = args.recursive,
dont_expand_version_macros = args.dont_expand_version_macros, dont_expand_version_macros = args.dont_expand_version_macros,
ignore = set(re.split(self.arg_sep, args.ignore)), ignore = set(re.split(self.arg_sep, args.ignore)),

View file

@ -3,8 +3,9 @@ from enum import Enum, auto
from typing import override from typing import override
from ...lib.log import WARNING, log from ...lib.log import WARNING, log
from ...lib.version.base import Syntax
from .Cmd import Cmd, Parent from .Cmd import Cmd, Parent
from .lib.pkg_relations import VersionSyntax, pkg_relations from .lib.pkg_relations import pkg_relations
from .lib.templates import ListDict, RenderValues, tmpl_render from .lib.templates import ListDict, RenderValues, tmpl_render
def key_value(s: str) -> tuple[str, str]: def key_value(s: str) -> tuple[str, str]:
@ -36,7 +37,7 @@ class CmdCreateFile(Cmd): # export
flavours = ['run'], flavours = ['run'],
subsections = ['jw'], subsections = ['jw'],
seed_pkgs = [module], seed_pkgs = [module],
syntax = VersionSyntax.names_only, syntax = Syntax.NAMES_ONLY,
no_subpackages = True, no_subpackages = True,
recursive = True, recursive = True,
quote = False, quote = False,

View file

@ -1,14 +1,7 @@
import re
from enum import Enum, auto
from ....App import App, Scope from ....App import App, Scope
from ....lib.log import DEBUG, log from ....lib.log import DEBUG, log
from ....lib.version.base import Syntax
class VersionSyntax(Enum): from ....lib.version.Dependency import Dependency
semver = auto()
debian = auto()
names_only = auto()
def pkg_relations( def pkg_relations(
app: App, app: App,
@ -20,7 +13,7 @@ def pkg_relations(
no_subpackages: bool = False, no_subpackages: bool = False,
dont_strip_revision: bool = False, dont_strip_revision: bool = False,
expand_semver_revision_range: bool = False, expand_semver_revision_range: bool = False,
syntax: VersionSyntax = VersionSyntax.semver, syntax: Syntax = Syntax.SEM_VER,
recursive: bool = False, recursive: bool = False,
dont_expand_version_macros: bool = False, dont_expand_version_macros: bool = False,
ignore: set[str] = set(), ignore: set[str] = set(),
@ -34,8 +27,7 @@ def pkg_relations(
subsections = app.distro.os_cascade subsections = app.distro.os_cascade
subsections.append('jw') subsections.append('jw')
expand_semver_revision_range = expand_semver_revision_range if syntax == Syntax.DEBIAN:
if syntax == VersionSyntax.debian:
expand_semver_revision_range = True expand_semver_revision_range = True
if skip_excluded: if skip_excluded:
@ -58,113 +50,48 @@ def pkg_relations(
), ),
) )
version_pattern = re.compile('[0-9-.]*')
ret: list[str] = [] ret: list[str] = []
for flavour in flavours: # build / release / run / devel for flavour in flavours: # build / release / run / devel
cur_pkgs = seed_pkgs.copy() cur_pkgs = seed_pkgs.copy()
visited = set() visited_pkgs: set[str] = set()
while len(cur_pkgs): while len(cur_pkgs):
cur_pkg = cur_pkgs.pop(0) cur_pkg = cur_pkgs.pop(0)
if cur_pkg in visited or cur_pkg in ignore: if cur_pkg in visited_pkgs or cur_pkg in ignore:
continue continue
for subsec in subsections: for subsec in subsections:
version: str | None = None
section = 'pkg.' + rel_type + '.' + subsec section = 'pkg.' + rel_type + '.' + subsec
visited.add(cur_pkg) visited_pkgs.add(cur_pkg)
value = app.get_value(cur_pkg, section, flavour) deps_spec = app.get_value(cur_pkg, section, flavour)
if not value: if not deps_spec:
continue continue
deps = value.split(',') for dep in Dependency.parse_deps_spec(
for spec in deps: deps_spec,
dep = re.split('([=><]+)', spec) lookup_version = app.get_version,
if syntax == VersionSyntax.names_only: ):
dep = dep[:1] dep_name = dep.base_name
dep = list(map(str.strip, dep)) if dep_name in ignore or dep.full_name in ignore:
dep_name = re.sub('-dev$|-devel$|-run$', '', dep[0])
if dep_name in ignore or dep[0] in ignore:
continue continue
if no_subpackages:
dep[0] = dep_name
for i, item in enumerate(dep):
dep[i] = item.strip()
if subsec == 'jw': if subsec == 'jw':
if (recursive and dep_name not in visited if (recursive and dep_name not in visited_pkgs
and dep_name not in cur_pkgs): and dep_name not in cur_pkgs):
cur_pkgs.append(dep_name) cur_pkgs.append(dep_name)
if hide_jw_pkg: if hide_jw_pkg and dep_name == 'jw-pkg':
continue continue
if len(dep) == 3: 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 cur_pkgs:
version = dep[2] expand_version_macros = False
else: if hide_self and dep_name in seed_pkgs:
version = app.get_version(dep_name) continue
if dep[2] == 'VERSION': dep_str = dep.constraint_str(
if dont_strip_revision: untemplated = expand_version_macros,
dep[2] = version include_revision = dont_strip_revision,
else: as_range = expand_semver_revision_range,
dep[2] = version.split('-')[0] no_subpackages = no_subpackages,
elif dep[2] == 'VERSION-REVISION': syntax = syntax,
dep[2] = version quote = '"' if quote else None,
elif version_pattern.match(dep[2]): )
# dep[2] = dep[2] if dep_str in ret:
pass continue
else: log(DEBUG, f'Appending dependency >{dep_str}<')
raise Exception('Unknown version specifier in ' + spec) ret.append(dep_str)
if len(dep) != 3 or not expand_semver_revision_range:
expanded_deps = [dep]
else:
assert version is not None
expanded_deps = []
semver = re.split(r'[.-]', version)
if len(semver) != 4:
expanded_deps = [dep]
else:
release = int(semver[2])
major_minor = f'{semver[0]}.{semver[1]}'
match dep[1]:
case '>' | '>=':
expanded_deps.append([dep[0], dep[1], dep[2]])
expanded_deps.append(
[dep[0], '<', f'{major_minor}.{release + 1}']
)
case '<' | '<=':
expanded_deps.append([dep[0], dep[1], dep[2]])
case '=':
expanded_deps.append(
[dep[0], '>=', f'{major_minor}.{release}']
)
expanded_deps.append(
[dep[0], '<', f'{major_minor}.{release + 1}']
)
case _:
raise NotImplementedError(
(
'Expanding SemVer range '
f'"{dep[0]} {dep[1]} {dep[3]}" '
'is not yet implemented'
)
)
for expanded_dep in expanded_deps:
if hide_self and dep_name in seed_pkgs:
continue
match syntax:
case VersionSyntax.semver:
pass
case VersionSyntax.names_only:
pass
case VersionSyntax.debian:
if len(expanded_dep) == 3:
match expanded_dep[1]:
case '<':
expanded_dep[1] = '<<'
case '>':
expanded_dep[1] = '>>'
case _:
pass
dep_str = ' '.join(expanded_dep)
if quote:
dep_str = '"' + dep_str + '"'
if dep_str not in ret:
log(DEBUG, f'Appending dependency >{dep_str}<')
ret.append(dep_str)
return ret return ret