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 typing import TYPE_CHECKING, override
from .lib.ExecApp import ExecApp as Base
from .lib.Distro import Distro
from .lib.ExecApp import ExecApp as Base
from .lib.log import DEBUG, ERR, log
from .lib.ProjectConf import ProjectConf
from .lib.version.Dependency import Dependency
if TYPE_CHECKING:
import argparse
@ -435,7 +436,7 @@ class App(Base):
return self.find_dir(name, ['/tmpl'], ['/opt/' + name + '/share/tmpl'])
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
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 ...lib.version.base import Syntax
from .Cmd import Cmd, Parent
from .lib.pkg_relations import VersionSyntax
from .lib.pkg_relations import pkg_relations as pkg_relations_list
if TYPE_CHECKING:
@ -30,7 +30,11 @@ class BaseCmdPkgRelations(Cmd):
no_subpackages = args.no_subpackages,
dont_strip_revision = args.dont_strip_revision,
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,
dont_expand_version_macros = args.dont_expand_version_macros,
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 ...lib.log import WARNING, log
from ...lib.version.base import Syntax
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
def key_value(s: str) -> tuple[str, str]:
@ -36,7 +37,7 @@ class CmdCreateFile(Cmd): # export
flavours = ['run'],
subsections = ['jw'],
seed_pkgs = [module],
syntax = VersionSyntax.names_only,
syntax = Syntax.NAMES_ONLY,
no_subpackages = True,
recursive = True,
quote = False,

View file

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