diff --git a/src/ahriman/application/application/application.py b/src/ahriman/application/application/application.py index 797f1e8a..f6032a5c 100644 --- a/src/ahriman/application/application/application.py +++ b/src/ahriman/application/application/application.py @@ -117,7 +117,7 @@ class Application(ApplicationPackages, ApplicationRepository): Args: packages(list[Package]): list of source packages of which dependencies have to be processed - process_dependencies(bool): if no set, dependencies will not be processed + process_dependencies(bool): if set to ``False``, dependencies will not be processed Returns: list[Package]: updated packages list. Packager for dependencies will be copied from the original package @@ -130,6 +130,9 @@ class Application(ApplicationPackages, ApplicationRepository): >>> packages = application.with_dependencies(packages, process_dependencies=True) >>> application.print_updates(packages, log_fn=print) """ + if not process_dependencies or not packages: + return packages + def missing_dependencies(source: Iterable[Package]) -> dict[str, str | None]: # append list of known packages with packages which are in current sources satisfied_packages = known_packages | { @@ -145,22 +148,29 @@ class Application(ApplicationPackages, ApplicationRepository): if dependency not in satisfied_packages } - if not process_dependencies or not packages: - return packages + def new_packages(root: Package) -> dict[str, Package]: + portion = {root.base: root} + while missing := missing_dependencies(portion.values()): + for package_name, packager in missing.items(): + if (source_dir := self.repository.paths.cache_for(package_name)).is_dir(): + # there is local cache, load package from it + leaf = Package.from_build(source_dir, self.repository.architecture, packager) + else: + leaf = Package.from_aur(package_name, packager) + portion[leaf.base] = leaf + + # register package in the database + self.repository.reporter.set_unknown(leaf) + + return portion known_packages = self._known_packages() - with_dependencies = {package.base: package for package in packages} - - while missing := missing_dependencies(with_dependencies.values()): - for package_name, username in missing.items(): - if (source_dir := self.repository.paths.cache_for(package_name)).is_dir(): - # there is local cache, load package from it - package = Package.from_build(source_dir, self.repository.architecture, username) - else: - package = Package.from_aur(package_name, username) - with_dependencies[package.base] = package - - # register package in the database - self.repository.reporter.set_unknown(package) + with_dependencies: dict[str, Package] = {} + for package in packages: + with self.in_package_context(package.base, package.version): # use the same context for the logger + try: + with_dependencies |= new_packages(package) + except Exception: + self.logger.exception("could not process dependencies of %s, skip the package", package.base) return list(with_dependencies.values()) diff --git a/src/ahriman/application/handlers/handler.py b/src/ahriman/application/handlers/handler.py index 7f63b493..b88012d2 100644 --- a/src/ahriman/application/handlers/handler.py +++ b/src/ahriman/application/handlers/handler.py @@ -53,7 +53,7 @@ class Handler: Wrapper for all command line actions, though each derived class implements :func:`run()` method, it usually must not be called directly. The recommended way is to call :func:`execute()` class method, e.g.:: - >>> from ahriman.application.handlers import Add + >>> from ahriman.application.handlers.add import Add >>> >>> Add.execute(args) """ diff --git a/tests/ahriman/application/application/test_application.py b/tests/ahriman/application/application/test_application.py index a53750f7..ec15b97f 100644 --- a/tests/ahriman/application/application/test_application.py +++ b/tests/ahriman/application/application/test_application.py @@ -113,6 +113,40 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p ], any_order=True) +def test_with_dependencies_exception(application: Application, package_ahriman: Package, + package_python_schedule: Package, mocker: MockerFixture) -> None: + """ + must skip packages if exception occurs + """ + def create_package_mock(package_base) -> MagicMock: + mock = MagicMock() + mock.base = package_base + mock.depends_build = [] + mock.packages_full = [package_base] + return mock + + package_python_schedule.packages = { + package_python_schedule.base: package_python_schedule.packages[package_python_schedule.base] + } + package_ahriman.packages[package_ahriman.base].depends = ["devtools", "python", package_python_schedule.base] + package_ahriman.packages[package_ahriman.base].make_depends = ["python-build", "python-installer"] + + packages = { + package_ahriman.base: package_ahriman, + package_python_schedule.base: package_python_schedule, + "python": create_package_mock("python"), + "python-installer": create_package_mock("python-installer"), + } + + mocker.patch("pathlib.Path.is_dir", autospec=True, side_effect=lambda p: p.name == "python") + mocker.patch("ahriman.models.package.Package.from_aur", side_effect=lambda *args: packages[args[0]]) + mocker.patch("ahriman.models.package.Package.from_build", side_effect=Exception) + mocker.patch("ahriman.application.application.Application._known_packages", + return_value={"devtools", "python-build", "python-pytest"}) + + assert not application.with_dependencies([package_ahriman], process_dependencies=True) + + def test_with_dependencies_skip(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: """ must skip processing of dependencies