mirror of
ssh://git.janware.com/janware/proj/jw-pkg
synced 2026-04-24 17:23:36 +02:00
Commit a19679fec reverted the first attempt to make AsyncSSH reuse
one connection during an instance lifetime. That failed because a lot
of distribution-specific properties were filled in a new event loop
thread started by AsyncRunner, and AsyncSSH didn't like that.
This commit is the first part of the solution: Move those properties
from the App class to the Distro class, and load the Distro class
in an async loader. As soon as it's instantiated, it can provide all
its properties without cluttering the code with async keywords.
Signed-off-by: Jan Lindemann <jan@janware.com>
207 lines
10 KiB
Python
207 lines
10 KiB
Python
# -*- 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)
|