handle dependencies recursively

This commit is contained in:
Evgenii Alekseev 2021-12-22 19:35:09 +03:00
parent 603c5449a8
commit bfca7e41ab
8 changed files with 126 additions and 24 deletions

View File

@ -346,6 +346,8 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository", parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository",
description="force rebuild whole repository", formatter_class=_formatter) description="force rebuild whole repository", formatter_class=_formatter)
parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append") parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append")
parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself",
action="store_true")
parser.set_defaults(handler=handlers.Rebuild) parser.set_defaults(handler=handlers.Rebuild)
return parser return parser

View File

@ -22,6 +22,7 @@ import argparse
from typing import Type from typing import Type
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.formatters.update_printer import UpdatePrinter
from ahriman.application.handlers.handler import Handler from ahriman.application.handlers.handler import Handler
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -44,9 +45,10 @@ class Rebuild(Handler):
depends_on = set(args.depends_on) if args.depends_on else None depends_on = set(args.depends_on) if args.depends_on else None
application = Application(architecture, configuration, no_report) application = Application(architecture, configuration, no_report)
packages = [ updates = application.repository.packages_depends_on(depends_on)
package if args.dry_run:
for package in application.repository.packages() for package in updates:
if depends_on is None or depends_on.intersection(package.depends) UpdatePrinter(package, package.version).print(verbose=True)
] # we have to use explicit list here for testing purpose return
application.update(packages)
application.update(updates)

View File

@ -18,7 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from pathlib import Path from pathlib import Path
from typing import Dict, Iterable, List from typing import Dict, Iterable, List, Optional
from ahriman.core.repository.executor import Executor from ahriman.core.repository.executor import Executor
from ahriman.core.repository.update_handler import UpdateHandler from ahriman.core.repository.update_handler import UpdateHandler
@ -68,3 +68,20 @@ class Repository(Executor, UpdateHandler):
:return: list of filenames from the directory :return: list of filenames from the directory
""" """
return list(filter(package_like, self.paths.packages.iterdir())) return list(filter(package_like, self.paths.packages.iterdir()))
def packages_depends_on(self, depends_on: Optional[Iterable[str]]) -> List[Package]:
"""
extract list of packages which depends on specified package
:param: depends_on: dependencies of the packages
:return: list of repository packages which depend on specified packages
"""
packages = self.packages()
if depends_on is None:
return packages # no list provided extract everything by default
depends_on = set(depends_on)
return [
package
for package in packages
if depends_on is None or depends_on.intersection(package.full_depends(self.pacman, packages))
]

View File

@ -20,13 +20,14 @@
from __future__ import annotations from __future__ import annotations
import aur # type: ignore import aur # type: ignore
import copy
import logging import logging
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from pathlib import Path from pathlib import Path
from pyalpm import vercmp # type: ignore from pyalpm import vercmp # type: ignore
from srcinfo.parse import parse_srcinfo # type: ignore from srcinfo.parse import parse_srcinfo # type: ignore
from typing import Any, Dict, List, Optional, Set, Type from typing import Any, Dict, Iterable, List, Optional, Set, Type
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
@ -257,6 +258,36 @@ class Package:
return self.version return self.version
def full_depends(self, pacman: Pacman, packages: Iterable[Package]) -> List[str]:
"""
generate full dependencies list including transitive dependencies
:param pacman: alpm wrapper instance
:param packages: repository package list
:return: all dependencies of the package
"""
dependencies = {}
# load own package dependencies
for package_base in packages:
for name, repo_package in package_base.packages.items():
dependencies[name] = repo_package.depends
for provides in repo_package.provides:
dependencies[provides] = repo_package.depends
# load repository dependencies
for database in pacman.handle.get_syncdbs():
for pacman_package in database.pkgcache:
dependencies[pacman_package.name] = pacman_package.depends
for provides in pacman_package.provides:
dependencies[provides] = pacman_package.depends
result = set(self.depends)
current_depends: Set[str] = set()
while result != current_depends:
current_depends = copy.deepcopy(result)
for package in current_depends:
result.update(dependencies.get(package, []))
return sorted(result)
def is_outdated(self, remote: Package, paths: RepositoryPaths, calculate_version: bool = True) -> bool: def is_outdated(self, remote: Package, paths: RepositoryPaths, calculate_version: bool = True) -> bool:
""" """
check if package is out-of-dated check if package is out-of-dated

View File

@ -14,6 +14,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
:return: generated arguments for these test cases :return: generated arguments for these test cases
""" """
args.depends_on = [] args.depends_on = []
args.dry_run = False
return args return args
@ -23,7 +24,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
""" """
args = _default_args(args) args = _default_args(args)
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages") application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on")
application_mock = mocker.patch("ahriman.application.application.Application.update") application_mock = mocker.patch("ahriman.application.application.Application.update")
Rebuild.run(args, "x86_64", configuration, True) Rebuild.run(args, "x86_64", configuration, True)
@ -31,34 +32,43 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
application_mock.assert_called_once() application_mock.assert_called_once()
def test_run_filter(args: argparse.Namespace, configuration: Configuration, def test_run_dry_run(args: argparse.Namespace, configuration: Configuration,
package_ahriman: Package, package_python_schedule: Package, package_ahriman: Package, mocker: MockerFixture) -> None:
mocker: MockerFixture) -> None:
""" """
must run command with depends filter must run command without update itself
""" """
args = _default_args(args) args = _default_args(args)
args.depends_on = ["python-aur"] args.dry_run = True
mocker.patch("ahriman.core.repository.repository.Repository.packages",
return_value=[package_ahriman, package_python_schedule])
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman])
application_mock = mocker.patch("ahriman.application.application.Application.update") application_mock = mocker.patch("ahriman.application.application.Application.update")
Rebuild.run(args, "x86_64", configuration, True) Rebuild.run(args, "x86_64", configuration, True)
application_mock.assert_called_once_with([package_ahriman]) application_mock.assert_not_called()
def test_run_without_filter(args: argparse.Namespace, configuration: Configuration, def test_run_filter(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
package_ahriman: Package, package_python_schedule: Package, """
mocker: MockerFixture) -> None: must run command with depends on filter
"""
args = _default_args(args)
args.depends_on = ["python-aur"]
mocker.patch("ahriman.application.application.Application.update")
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on")
Rebuild.run(args, "x86_64", configuration, True)
application_packages_mock.assert_called_once_with({"python-aur"})
def test_run_without_filter(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
""" """
must run command for all packages if no filter supplied must run command for all packages if no filter supplied
""" """
args = _default_args(args) args = _default_args(args)
mocker.patch("ahriman.core.repository.repository.Repository.packages", mocker.patch("ahriman.application.application.Application.update")
return_value=[package_ahriman, package_python_schedule])
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_mock = mocker.patch("ahriman.application.application.Application.update") application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on")
Rebuild.run(args, "x86_64", configuration, True) Rebuild.run(args, "x86_64", configuration, True)
application_mock.assert_called_once_with([package_ahriman, package_python_schedule]) application_packages_mock.assert_called_once_with(None)

View File

@ -80,3 +80,23 @@ def test_packages_built(repository: Repository, mocker: MockerFixture) -> None:
""" """
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz"), Path("b.pkg.tar.xz")]) mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz"), Path("b.pkg.tar.xz")])
assert repository.packages_built() == [Path("b.pkg.tar.xz")] assert repository.packages_built() == [Path("b.pkg.tar.xz")]
def test_packages_depends_on(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must filter packages by depends list
"""
mocker.patch("ahriman.core.repository.repository.Repository.packages",
return_value=[package_ahriman, package_python_schedule])
assert repository.packages_depends_on(["python-aur"]) == [package_ahriman]
def test_packages_depends_on_empty(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must return all packages in case if no filter is provided
"""
mocker.patch("ahriman.core.repository.repository.Repository.packages",
return_value=[package_ahriman, package_python_schedule])
assert repository.packages_depends_on(None) == [package_ahriman, package_python_schedule]

View File

@ -82,7 +82,9 @@ def pyalpm_package_ahriman(package_ahriman: Package) -> MagicMock:
""" """
mock = MagicMock() mock = MagicMock()
type(mock).base = PropertyMock(return_value=package_ahriman.base) type(mock).base = PropertyMock(return_value=package_ahriman.base)
type(mock).depends = PropertyMock(return_value=["python-aur"])
type(mock).name = PropertyMock(return_value=package_ahriman.base) type(mock).name = PropertyMock(return_value=package_ahriman.base)
type(mock).provides = PropertyMock(return_value=["python-ahriman"])
type(mock).version = PropertyMock(return_value=package_ahriman.version) type(mock).version = PropertyMock(return_value=package_ahriman.version)
return mock return mock

View File

@ -294,6 +294,24 @@ def test_actual_version_vcs_failed(package_tpacpi_bat_git: Package, repository_p
assert package_tpacpi_bat_git.actual_version(repository_paths) == package_tpacpi_bat_git.version assert package_tpacpi_bat_git.actual_version(repository_paths) == package_tpacpi_bat_git.version
def test_full_depends(package_ahriman: Package, package_python_schedule: Package, pyalpm_package_ahriman: MagicMock,
pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must extract all dependencies from the package
"""
package_python_schedule.packages[package_python_schedule.base].provides = ["python3-schedule"]
database_mock = MagicMock()
database_mock.pkgcache = [pyalpm_package_ahriman]
pyalpm_handle.handle.get_syncdbs.return_value = [database_mock]
assert package_ahriman.full_depends(pyalpm_handle, [package_python_schedule]) == package_ahriman.depends
package_python_schedule.packages[package_python_schedule.base].depends = [package_ahriman.base]
expected = sorted(set(package_python_schedule.depends + ["python-aur"]))
assert package_python_schedule.full_depends(pyalpm_handle, [package_python_schedule]) == expected
def test_is_outdated_false(package_ahriman: Package, repository_paths: RepositoryPaths) -> None: def test_is_outdated_false(package_ahriman: Package, repository_paths: RepositoryPaths) -> None:
""" """
must be not outdated for the same package must be not outdated for the same package