cmds.projects.lib.pkg_relations: Add module
BaseCmdPkgRelations contains pkg_relations(), a function doing package graph analysis code. The function needs to be made available to code outside BaseCmdPkgRelations, so move it to cmds.projects.lib.pkg_relations.
The commit also applies style fixes to both BaseCmdPkgRelations and pkg_relations which anticipate broader style changes to jw-pkg in general.
Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
3b65bcabb8
commit
d449472ceb
2 changed files with 269 additions and 175 deletions
|
|
@ -1,162 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from argparse import ArgumentParser, Namespace
|
||||
|
||||
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
|
||||
from .Cmd import Cmd, Parent
|
||||
from .lib.pkg_relations import VersionSyntax
|
||||
from .lib.pkg_relations import pkg_relations as pkg_relations_list
|
||||
|
||||
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,
|
||||
pkg_relations_list(
|
||||
self.app,
|
||||
rel_type = rel_type,
|
||||
flavours = args.flavours.split(','),
|
||||
seed_pkgs = args.modules,
|
||||
subsections = None if args.subsections is None else args.subsections.split(','),
|
||||
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('-', '_')],
|
||||
syntax = VersionSyntax[args.syntax.replace('-', '_')],
|
||||
recursive = args.recursive,
|
||||
dont_expand_version_macros = args.dont_expand_version_macros,
|
||||
ignore = set(args.ignore.split(',')),
|
||||
|
|
@ -170,38 +33,100 @@ class BaseCmdPkgRelations(Cmd):
|
|||
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)
|
||||
def __init__(self, parent: Parent, 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')
|
||||
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 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)
|
||||
|
|
|
|||
169
src/python/jw/pkg/cmds/projects/lib/pkg_relations.py
Normal file
169
src/python/jw/pkg/cmds/projects/lib/pkg_relations.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
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()
|
||||
|
||||
def pkg_relations(
|
||||
app: App,
|
||||
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: VersionSyntax = VersionSyntax.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 = app.distro.os_cascade
|
||||
subsections.append('jw')
|
||||
|
||||
expand_semver_revision_range = expand_semver_revision_range
|
||||
if syntax == VersionSyntax.debian:
|
||||
expand_semver_revision_range = True
|
||||
|
||||
if skip_excluded:
|
||||
excluded = 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)}", '
|
||||
f'subsections="{", ".join(subsections)}", '
|
||||
f'"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 = app.get_value(cur_pkg, section, flavour)
|
||||
if not value:
|
||||
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:
|
||||
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
|
||||
and dep_name not 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 = 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:
|
||||
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.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 '
|
||||
f'dependency "{dep[0]} {dep[1]} {dep[3]}"'
|
||||
)
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue