mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-16 07:19:57 +00:00
add repo-structure subcommand
This commit also changes Tree class, replacing load method by resolve
This commit is contained in:
@ -109,6 +109,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
_set_repo_sign_parser(subparsers)
|
||||
_set_repo_status_update_parser(subparsers)
|
||||
_set_repo_sync_parser(subparsers)
|
||||
_set_repo_tree_parser(subparsers)
|
||||
_set_repo_triggers_parser(subparsers)
|
||||
_set_repo_update_parser(subparsers)
|
||||
_set_shell_parser(subparsers)
|
||||
@ -703,6 +704,23 @@ def _set_repo_sync_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
return parser
|
||||
|
||||
|
||||
def _set_repo_tree_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for repository tree subcommand
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("repo-tree", help="dump repository tree",
|
||||
description="dump repository tree based on packages dependencies",
|
||||
formatter_class=_formatter)
|
||||
parser.set_defaults(handler=handlers.Structure, lock=None, report=False, quiet=True)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_repo_triggers_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for repository triggers subcommand
|
||||
|
@ -145,8 +145,8 @@ class ApplicationRepository(ApplicationProperties):
|
||||
process_update(packages, build_result)
|
||||
|
||||
# process manual packages
|
||||
tree = Tree.load(updates, self.repository.paths, self.database)
|
||||
for num, level in enumerate(tree.levels()):
|
||||
tree = Tree.resolve(updates, self.repository.paths, self.database)
|
||||
for num, level in enumerate(tree):
|
||||
self.logger.info("processing level #%i %s", num, [package.base for package in level])
|
||||
build_result = self.repository.process_build(level)
|
||||
packages = self.repository.packages_built()
|
||||
@ -181,8 +181,12 @@ class ApplicationRepository(ApplicationProperties):
|
||||
|
||||
local_versions = {package.base: package.version for package in self.repository.packages()}
|
||||
updated_packages = [package for _, package in sorted(updates.items())]
|
||||
for package in updated_packages:
|
||||
UpdatePrinter(package, local_versions.get(package.base)).print(
|
||||
verbose=True, log_fn=log_fn, separator=" -> ")
|
||||
|
||||
# reorder updates according to the dependency tree
|
||||
tree = Tree.resolve(updated_packages, self.repository.paths, self.database)
|
||||
for level in tree:
|
||||
for package in level:
|
||||
UpdatePrinter(package, local_versions.get(package.base)).print(
|
||||
verbose=True, log_fn=log_fn, separator=" -> ")
|
||||
|
||||
return updated_packages
|
||||
|
@ -37,6 +37,7 @@ from ahriman.application.handlers.shell import Shell
|
||||
from ahriman.application.handlers.sign import Sign
|
||||
from ahriman.application.handlers.status import Status
|
||||
from ahriman.application.handlers.status_update import StatusUpdate
|
||||
from ahriman.application.handlers.structure import Structure
|
||||
from ahriman.application.handlers.triggers import Triggers
|
||||
from ahriman.application.handlers.unsafe_commands import UnsafeCommands
|
||||
from ahriman.application.handlers.update import Update
|
||||
|
56
src/ahriman/application/handlers/structure.py
Normal file
56
src/ahriman/application/handlers/structure.py
Normal file
@ -0,0 +1,56 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
|
||||
from typing import Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import TreePrinter
|
||||
from ahriman.core.tree import Tree
|
||||
|
||||
|
||||
class Structure(Handler):
|
||||
"""
|
||||
dump repository structure handler
|
||||
"""
|
||||
|
||||
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration, *,
|
||||
report: bool, unsafe: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
architecture(str): repository architecture
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
application = Application(architecture, configuration, report=report, unsafe=unsafe)
|
||||
packages = application.repository.packages()
|
||||
|
||||
tree = Tree.resolve(packages, application.repository.paths, application.database)
|
||||
for num, level in enumerate(tree):
|
||||
TreePrinter(num, level).print(verbose=True, separator=" ")
|
@ -26,6 +26,7 @@ from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
|
||||
from ahriman.core.formatters.package_printer import PackagePrinter
|
||||
from ahriman.core.formatters.patch_printer import PatchPrinter
|
||||
from ahriman.core.formatters.status_printer import StatusPrinter
|
||||
from ahriman.core.formatters.tree_printer import TreePrinter
|
||||
from ahriman.core.formatters.update_printer import UpdatePrinter
|
||||
from ahriman.core.formatters.user_printer import UserPrinter
|
||||
from ahriman.core.formatters.version_printer import VersionPrinter
|
||||
|
53
src/ahriman/core/formatters/tree_printer.py
Normal file
53
src/ahriman/core/formatters/tree_printer.py
Normal file
@ -0,0 +1,53 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import Iterable, List
|
||||
|
||||
from ahriman.core.formatters import StringPrinter
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class TreePrinter(StringPrinter):
|
||||
"""
|
||||
print content of the package tree level
|
||||
|
||||
Attributes:
|
||||
packages(Iterable[Package]): packages which belong to this level
|
||||
"""
|
||||
|
||||
def __init__(self, level: int, packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
level(int): dependencies tree level
|
||||
packages(Iterable[Package]): packages which belong to this level
|
||||
"""
|
||||
StringPrinter.__init__(self, f"level {level}")
|
||||
self.packages = packages
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
|
||||
Returns:
|
||||
List[Property]: list of content properties
|
||||
"""
|
||||
return [Property(package.base, package.version, is_required=True) for package in self.packages]
|
@ -19,9 +19,11 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Iterable, List, Set, Type
|
||||
from typing import Callable, Iterable, List, Set, Tuple, Type
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.database import SQLite
|
||||
@ -77,6 +79,21 @@ class Leaf:
|
||||
dependencies = Package.dependencies(clone_dir)
|
||||
return cls(package, dependencies)
|
||||
|
||||
def is_dependency(self, packages: Iterable[Leaf]) -> bool:
|
||||
"""
|
||||
check if the package is dependency of any other package from list or not
|
||||
|
||||
Args:
|
||||
packages(Iterable[Leaf]): list of known leaves
|
||||
|
||||
Returns:
|
||||
bool: True in case if package is dependency of others and False otherwise
|
||||
"""
|
||||
for leaf in packages:
|
||||
if leaf.dependencies.intersection(self.items):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_root(self, packages: Iterable[Leaf]) -> bool:
|
||||
"""
|
||||
check if package depends on any other package from list of not
|
||||
@ -113,8 +130,8 @@ class Tree:
|
||||
>>> repository = Repository.load("x86_64", configuration, database, report=True, unsafe=False)
|
||||
>>> packages = repository.packages()
|
||||
>>>
|
||||
>>> tree = Tree.load(packages, configuration.repository_paths, database)
|
||||
>>> for tree_level in tree.levels():
|
||||
>>> tree = Tree.resolve(packages, configuration.repository_paths, database)
|
||||
>>> for tree_level in tree:
|
||||
>>> for package in tree_level:
|
||||
>>> print(package.base)
|
||||
>>> print()
|
||||
@ -141,9 +158,10 @@ class Tree:
|
||||
self.leaves = leaves
|
||||
|
||||
@classmethod
|
||||
def load(cls: Type[Tree], packages: Iterable[Package], paths: RepositoryPaths, database: SQLite) -> Tree:
|
||||
def resolve(cls: Type[Tree], packages: Iterable[Package], paths: RepositoryPaths,
|
||||
database: SQLite) -> List[List[Package]]:
|
||||
"""
|
||||
load tree from packages
|
||||
resolve dependency tree
|
||||
|
||||
Args:
|
||||
packages(Iterable[Package]): packages list
|
||||
@ -151,22 +169,45 @@ class Tree:
|
||||
database(SQLite): database instance
|
||||
|
||||
Returns:
|
||||
Tree: loaded class
|
||||
List[List[Package]]: list of packages lists based on their dependencies
|
||||
"""
|
||||
return cls([Leaf.load(package, paths, database) for package in packages])
|
||||
leaves = [Leaf.load(package, paths, database) for package in packages]
|
||||
tree = cls(leaves)
|
||||
return tree.levels()
|
||||
|
||||
def levels(self) -> List[List[Package]]:
|
||||
"""
|
||||
get build levels starting from the packages which do not require any other package to build
|
||||
|
||||
Returns:
|
||||
List[List[Package]]: list of packages lists
|
||||
List[List[Package]]: sorted list of packages lists based on their dependencies
|
||||
"""
|
||||
result: List[List[Package]] = []
|
||||
# https://docs.python.org/dev/library/itertools.html#itertools-recipes
|
||||
def partition(source: List[Leaf]) -> Tuple[List[Leaf], Iterable[Leaf]]:
|
||||
first_iter, second_iter = itertools.tee(source)
|
||||
filter_fn: Callable[[Leaf], bool] = lambda leaf: leaf.is_dependency(next_level)
|
||||
# materialize first list and leave second as iterator
|
||||
return list(filter(filter_fn, first_iter)), itertools.filterfalse(filter_fn, second_iter)
|
||||
|
||||
unsorted: List[List[Leaf]] = []
|
||||
|
||||
# build initial tree
|
||||
unprocessed = self.leaves[:]
|
||||
while unprocessed:
|
||||
result.append([leaf.package for leaf in unprocessed if leaf.is_root(unprocessed)])
|
||||
unsorted.append([leaf for leaf in unprocessed if leaf.is_root(unprocessed)])
|
||||
unprocessed = [leaf for leaf in unprocessed if not leaf.is_root(unprocessed)]
|
||||
|
||||
return result
|
||||
# move leaves to the end if they are not required at the next level
|
||||
for current_num, current_level in enumerate(unsorted[:-1]):
|
||||
next_num = current_num + 1
|
||||
next_level = unsorted[next_num]
|
||||
|
||||
# change lists inside the collection
|
||||
unsorted[current_num], to_be_moved = partition(current_level)
|
||||
unsorted[next_num].extend(to_be_moved)
|
||||
|
||||
comparator: Callable[[Package], str] = lambda package: package.base
|
||||
return [
|
||||
sorted([leaf.package for leaf in level], key=comparator)
|
||||
for level in unsorted if level
|
||||
]
|
||||
|
Reference in New Issue
Block a user