From 68a628d39341e8369eed8ed78e4fbff49942b45c Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Thu, 26 Jun 2025 11:05:15 +0300 Subject: [PATCH] support provides in aur --- .../application/application/application.py | 14 +++++----- src/ahriman/core/alpm/pacman.py | 14 ++++++++-- src/ahriman/core/alpm/remote/aur.py | 20 +++++++++++--- src/ahriman/core/alpm/remote/official.py | 15 ++++++++--- .../core/alpm/remote/official_syncdb.py | 10 ++++--- src/ahriman/core/alpm/remote/remote.py | 26 +++++++++++++------ src/ahriman/models/package.py | 12 ++++++--- 7 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/ahriman/application/application/application.py b/src/ahriman/application/application/application.py index f6032a5c..2ae9874c 100644 --- a/src/ahriman/application/application/application.py +++ b/src/ahriman/application/application/application.py @@ -133,18 +133,18 @@ class Application(ApplicationPackages, ApplicationRepository): if not process_dependencies or not packages: return packages - def missing_dependencies(source: Iterable[Package]) -> dict[str, str | None]: + def missing_dependencies(sources: Iterable[Package]) -> dict[str, str | None]: # append list of known packages with packages which are in current sources satisfied_packages = known_packages | { single - for package in source - for single in package.packages_full + for source in sources + for single in source.packages_full } return { - dependency: package.packager - for package in source - for dependency in package.depends_build + dependency: source.packager + for source in sources + for dependency in source.depends_build if dependency not in satisfied_packages } @@ -156,7 +156,7 @@ class Application(ApplicationPackages, ApplicationRepository): # 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) + leaf = Package.from_aur(package_name, packager, include_provides=True) portion[leaf.base] = leaf # register package in the database diff --git a/src/ahriman/core/alpm/pacman.py b/src/ahriman/core/alpm/pacman.py index 9f616699..376cc128 100644 --- a/src/ahriman/core/alpm/pacman.py +++ b/src/ahriman/core/alpm/pacman.py @@ -223,22 +223,32 @@ class Pacman(LazyLogging): return result - def package(self, package_name: str) -> Generator[Package, None, None]: + def package(self, package_name: str, include_provides: bool = False) -> Generator[Package, None, None]: """ - retrieve list of the packages from the repository by name + retrieve list of the packages from the repository by name. If ``include_provides`` is set to ``True``, then + additionally this method will search through :attr:`alpm.Package.provides`; these packages will be returned + after exact match Args: package_name(str): package name to search + include_provides(bool, optional): search by provides if no exact match found (Default value = False) Yields: Package: list of packages which were returned by the query """ + def is_package_provided(package: Package) -> bool: + return package_name in package.provides + for database in self.handle.get_syncdbs(): package = database.get_pkg(package_name) if package is None: continue yield package + if include_provides: + for database in self.handle.get_syncdbs(): + yield from filter(is_package_provided, database.search(package_name)) + def packages(self) -> set[str]: """ get list of packages known for alpm diff --git a/src/ahriman/core/alpm/remote/aur.py b/src/ahriman/core/alpm/remote/aur.py index d7df1a91..34c56e8c 100644 --- a/src/ahriman/core/alpm/remote/aur.py +++ b/src/ahriman/core/alpm/remote/aur.py @@ -113,13 +113,17 @@ class AUR(Remote): response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query) return self.parse_response(response.json()) - def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage: + def package_info(self, package_name: str, *, pacman: Pacman | None, include_provides: bool) -> AURPackage: """ - get package info by its name + get package info by its name. If ``include_provides`` is set to ``True``, then, in addition, this method + will perform search by :attr:`ahriman.models.aur_package.AURPackage.provides` and return first package found. + Note, however, that in this case some implementation might not provide this method and search result will might + not be stable Args: package_name(str): package name to search pacman(Pacman | None): alpm wrapper instance, required for official repositories search + include_provides(bool): search by provides if no exact match found Returns: AURPackage: package which match the package name @@ -127,21 +131,29 @@ class AUR(Remote): Raises: UnknownPackageError: package doesn't exist """ + def is_package_provided(package: AURPackage) -> bool: + return package_name in package.provides + packages = self.aur_request("info", package_name) try: return next(package for package in packages if package.name == package_name) except StopIteration: + if include_provides: + for stub in filter(is_package_provided, self.search(package_name, search_by="provides")): + return self.package_info(stub.package_base, pacman=pacman, include_provides=False) raise UnknownPackageError(package_name) from None - def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]: + def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]: """ search package in AUR web Args: *keywords(str): keywords to search pacman(Pacman | None): alpm wrapper instance, required for official repositories search + search_by(str | None): search by keywords Returns: list[AURPackage]: list of packages which match the criteria """ - return self.aur_request("search", *keywords, by="name-desc") + search_by = search_by or "name-desc" + return self.aur_request("search", *keywords, by=search_by) diff --git a/src/ahriman/core/alpm/remote/official.py b/src/ahriman/core/alpm/remote/official.py index e931b1f9..6aaf9e8c 100644 --- a/src/ahriman/core/alpm/remote/official.py +++ b/src/ahriman/core/alpm/remote/official.py @@ -107,13 +107,17 @@ class Official(Remote): response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query) return self.parse_response(response.json()) - def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage: + def package_info(self, package_name: str, *, pacman: Pacman | None, include_provides: bool) -> AURPackage: """ - get package info by its name + get package info by its name. If ``include_provides`` is set to ``True``, then, in addition, this method + will perform search by :attr:`ahriman.models.aur_package.AURPackage.provides` and return first package found. + Note, however, that in this case some implementation might not provide this method and search result will might + not be stable Args: package_name(str): package name to search pacman(Pacman | None): alpm wrapper instance, required for official repositories search + include_provides(bool): search by provides if no exact match found Returns: AURPackage: package which match the package name @@ -125,17 +129,20 @@ class Official(Remote): try: return next(package for package in packages if package.name == package_name) except StopIteration: + # it does not support search by provides raise UnknownPackageError(package_name) from None - def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]: + def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]: """ search package in AUR web Args: *keywords(str): keywords to search pacman(Pacman | None): alpm wrapper instance, required for official repositories search + search_by(str | None): search by keywords Returns: list[AURPackage]: list of packages which match the criteria """ - return self.arch_request(*keywords, by="q") + search_by = search_by or "q" + return self.arch_request(*keywords, by=search_by) diff --git a/src/ahriman/core/alpm/remote/official_syncdb.py b/src/ahriman/core/alpm/remote/official_syncdb.py index 95abeb8b..1b0304c6 100644 --- a/src/ahriman/core/alpm/remote/official_syncdb.py +++ b/src/ahriman/core/alpm/remote/official_syncdb.py @@ -38,13 +38,17 @@ class OfficialSyncdb(Official): Still we leave search function based on the official repositories RPC. """ - def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage: + def package_info(self, package_name: str, *, pacman: Pacman | None, include_provides: bool) -> AURPackage: """ - get package info by its name + get package info by its name. If ``include_provides`` is set to ``True``, then, in addition, this method + will perform search by :attr:`ahriman.models.aur_package.AURPackage.provides` and return first package found. + Note, however, that in this case some implementation might not provide this method and search result will might + not be stable Args: package_name(str): package name to search pacman(Pacman | None): alpm wrapper instance, required for official repositories search + include_provides(bool): search by provides if no exact match found Returns: AURPackage: package which match the package name @@ -56,6 +60,6 @@ class OfficialSyncdb(Official): raise UnknownPackageError(package_name) try: - return next(AURPackage.from_pacman(package) for package in pacman.package(package_name)) + return next(AURPackage.from_pacman(package) for package in pacman.package(package_name, include_provides)) except StopIteration: raise UnknownPackageError(package_name) from None diff --git a/src/ahriman/core/alpm/remote/remote.py b/src/ahriman/core/alpm/remote/remote.py index 6e11b896..d749baee 100644 --- a/src/ahriman/core/alpm/remote/remote.py +++ b/src/ahriman/core/alpm/remote/remote.py @@ -41,19 +41,23 @@ class Remote(SyncHttpClient): """ @classmethod - def info(cls, package_name: str, *, pacman: Pacman | None = None) -> AURPackage: + def info(cls, package_name: str, *, pacman: Pacman | None = None, include_provides: bool = False) -> AURPackage: """ - get package info by its name + get package info by its name. If ``include_provides`` is set to ``True``, then, in addition, this method + will perform search by :attr:`ahriman.models.aur_package.AURPackage.provides` and return first package found. + Note, however, that in this case some implementation might not provide this method and search result will might + not be stable Args: package_name(str): package name to search pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search (Default value = None) + include_provides(bool, optional): search by provides if no exact match found (Default value = False) Returns: AURPackage: package which match the package name """ - return cls().package_info(package_name, pacman=pacman) + return cls().package_info(package_name, pacman=pacman, include_provides=include_provides) @classmethod def multisearch(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]: @@ -114,7 +118,7 @@ class Remote(SyncHttpClient): raise NotImplementedError @classmethod - def search(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]: + def search(cls, *keywords: str, pacman: Pacman | None = None, search_by: str | None = None) -> list[AURPackage]: """ search package in AUR web @@ -122,19 +126,24 @@ class Remote(SyncHttpClient): *keywords(str): search terms, e.g. "ahriman", "is", "cool" pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search (Default value = None) + search_by(str | None, optional): search by keywords (Default value = None) Returns: list[AURPackage]: list of packages which match the criteria """ - return cls().package_search(*keywords, pacman=pacman) + return cls().package_search(*keywords, pacman=pacman, search_by=search_by) - def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage: + def package_info(self, package_name: str, *, pacman: Pacman | None, include_provides: bool) -> AURPackage: """ - get package info by its name + get package info by its name. If ``include_provides`` is set to ``True``, then, in addition, this method + will perform search by :attr:`ahriman.models.aur_package.AURPackage.provides` and return first package found. + Note, however, that in this case some implementation might not provide this method and search result will might + not be stable Args: package_name(str): package name to search pacman(Pacman | None): alpm wrapper instance, required for official repositories search + include_provides(bool): search by provides if no exact match found Returns: AURPackage: package which match the package name @@ -144,13 +153,14 @@ class Remote(SyncHttpClient): """ raise NotImplementedError - def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]: + def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]: """ search package in AUR web Args: *keywords(str): keywords to search pacman(Pacman | None): alpm wrapper instance, required for official repositories search + search_by(str | None): search by keywords Returns: list[AURPackage]: list of packages which match the criteria diff --git a/src/ahriman/models/package.py b/src/ahriman/models/package.py index 27d7d6a3..4a74a893 100644 --- a/src/ahriman/models/package.py +++ b/src/ahriman/models/package.py @@ -213,18 +213,19 @@ class Package(LazyLogging): ) @classmethod - def from_aur(cls, name: str, packager: str | None = None) -> Self: + def from_aur(cls, name: str, packager: str | None = None, *, include_provides: bool = False) -> Self: """ construct package properties from AUR page Args: name(str): package name (either base or normal name) packager(str | None, optional): packager to be used for this build (Default value = None) + include_provides(bool, optional): search by provides if no exact match found (Default value = False) Returns: Self: package properties """ - package = AUR.info(name) + package = AUR.info(name, include_provides=include_provides) remote = RemoteSource( source=PackageSource.AUR, @@ -310,7 +311,8 @@ class Package(LazyLogging): ) @classmethod - def from_official(cls, name: str, pacman: Pacman, packager: str | None = None, *, use_syncdb: bool = True) -> Self: + def from_official(cls, name: str, pacman: Pacman, packager: str | None = None, *, use_syncdb: bool = True, + include_provides: bool = False) -> Self: """ construct package properties from official repository page @@ -319,11 +321,13 @@ class Package(LazyLogging): pacman(Pacman): alpm wrapper instance packager(str | None, optional): packager to be used for this build (Default value = None) use_syncdb(bool, optional): use pacman databases instead of official repositories RPC (Default value = True) + include_provides(bool, optional): search by provides if no exact match found (Default value = False) Returns: Self: package properties """ - package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name) + impl = OfficialSyncdb if use_syncdb else Official + package = impl.info(name, pacman=pacman, include_provides=include_provides) remote = RemoteSource( source=PackageSource.Repository,