jw-pkg/src/python/jw/pkg/cmds/projects/BaseCmdPkgRelations.py

207 lines
10 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
import re
from argparse import Namespace, ArgumentParser
from enum import Enum, auto
from ...lib.log import *
from ...App import Scope
from ..Cmd import Cmd
from ..CmdProjects import CmdProjects
class BaseCmdPkgRelations(Cmd):
class Syntax(Enum):
semver = auto()
debian = auto()
names_only = auto()
def pkg_relations_list(
self,
rel_type: str,
flavours: list[str],
seed_pkgs: list[str],
subsections: list[str]|None=None,
delimiter: str=' ',
no_subpackages: bool=False,
dont_strip_revision: bool=False,
expand_semver_revision_range: bool=False,
syntax: Syntax=Syntax.semver,
recursive: bool=False,
dont_expand_version_macros: bool=False,
ignore: set[str] = set(),
quote: bool = False,
skip_excluded: bool = False,
hide_self = False,
hide_jw_pkg = False
) -> list[str]:
if subsections is None:
subsections = self.app.distro.os_cascade
subsections.append('jw')
expand_semver_revision_range = expand_semver_revision_range
if syntax == self.Syntax.debian:
expand_semver_revision_range = True
if skip_excluded:
excluded = self.app.get_project_refs(seed_pkgs, ['build'], 'exclude',
scope = Scope.One, add_self=False, names_only=True)
ignore |= set(excluded)
log(DEBUG, f'flavours="{", ".join(flavours)}", subsections="{", ".join(subsections)}", "ignore="{", ".join(ignore)}"')
version_pattern = re.compile("[0-9-.]*")
ret: list[str] = []
for flavour in flavours: # build / release / run / devel
cur_pkgs = seed_pkgs.copy()
visited = set()
while len(cur_pkgs):
cur_pkg = cur_pkgs.pop(0)
if cur_pkg in visited or cur_pkg in ignore:
continue
for subsec in subsections:
section = 'pkg.' + rel_type + '.' + subsec
visited.add(cur_pkg)
value = self.app.get_value(cur_pkg, section, flavour)
if not value:
continue
deps = value.split(',')
for spec in deps:
dep = re.split('([=><]+)', spec)
if syntax == self.Syntax.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:
continue
if no_subpackages:
dep[0] = dep_name
for i, item in enumerate(dep):
dep[i] = item.strip()
if subsec == 'jw':
if recursive and not dep_name in visited and not dep_name in cur_pkgs:
cur_pkgs.append(dep_name)
if hide_jw_pkg:
continue
if len(dep) == 3:
if dont_expand_version_macros and dep_name in cur_pkgs:
version = dep[2]
else:
version = self.app.get_value(dep_name, 'version', '')
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:
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(f'Expanding SemVer range "{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 self.Syntax.semver:
pass
case self.Syntax.debian:
if len(expanded_dep) == 3:
match expanded_dep[1]:
case '<':
expanded_dep[1] = '<<'
case '>':
expanded_dep[1] = '>>'
case '_':
raise NotImplementedError(f'Unknown dependency syntax "{syntax}" for dependency "{dep[0]} {dep[1]} {dep[3]}"')
dep_str = ' '.join(expanded_dep)
if quote:
dep_str = '"' + dep_str + '"'
if not dep_str in ret:
log(DEBUG, f'Appending dependency >{dep_str}<')
ret.append(dep_str)
return ret
def pkg_relations(self, rel_type: str, args: Namespace) -> str:
return args.delimiter.join(
self.pkg_relations_list(
rel_type=rel_type,
flavours = args.flavours.split(','),
seed_pkgs = args.modules,
subsections = None if args.subsections is None else args.subsections.split(','),
no_subpackages = args.no_subpackages,
dont_strip_revision = args.dont_strip_revision,
expand_semver_revision_range = args.expand_semver_revision_range,
syntax = self.Syntax[args.syntax.replace('-', '_')],
recursive = args.recursive,
dont_expand_version_macros = args.dont_expand_version_macros,
ignore = set(args.ignore.split(',')),
quote = args.quote,
skip_excluded = args.skip_excluded,
hide_self = args.hide_self,
hide_jw_pkg = args.hide_jw_pkg,
)
)
def print_pkg_relations(self, rel_type: str, args: Namespace) -> None:
print(self.pkg_relations(rel_type, args))
def __init__(self, parent: CmdProjects, relation: str, help: str) -> None:
super().__init__(parent, 'pkg-' + relation, help=help)
self.relation = relation
def add_arguments(self, parser: ArgumentParser) -> None:
super().add_arguments(parser)
parser.add_argument('-S', '--subsections', nargs='?', default=None, help='Subsections to consider, comma-separated')
parser.add_argument('-d', '--delimiter', nargs='?', default=', ', help='Output words delimiter')
parser.add_argument('flavours', help='Dependency flavours (run, build, devel, release)')
parser.add_argument('modules', nargs='*', help='Modules')
parser.add_argument('-p', '--no-subpackages', action='store_true',
default=False, help='Cut -run and -devel from package names')
parser.add_argument('--dont-strip-revision', action='store_true',
default=False, help='Always treat VERSION macro as VERSION-REVISION')
parser.add_argument('--expand-semver-revision-range', action='store_true',
default=False, help='Always treat =VERSION macro as >= VERSION-0 and < (VERSION+1)-0')
parser.add_argument('--syntax', choices=['semver', 'debian', 'names-only'],
default='semver', help='Output syntax')
parser.add_argument('--recursive', action='store_true',
default=False, help='Find dependencies recursively')
parser.add_argument('--dont-expand-version-macros', action='store_true',
default=False, help='Don\'t expand VERSION and REVISION macros')
parser.add_argument('--ignore', nargs='?', default='', help='Packages that '
'should be ignored together with their dependencies')
parser.add_argument('--skip-excluded', action='store_true', default=False,
help='Don\'t consider or output modules matching the os cascade in their [build].exclude config')
parser.add_argument('--hide-self', action='store_true', default=False,
help='Don\'t include any of the projects listed in <modules> in the output')
parser.add_argument('--hide-jw-pkg', action='store_true', default=False,
help='Don\'t include packages from requires.jw in the output')
parser.add_argument('--quote', action='store_true', default=False,
help='Put double quotes around each listed dependency')
async def _run(self, args: Namespace) -> None:
return self.print_pkg_relations(self.relation, args)