fix: support provides in aur (#146)

* support provides in aur

* process provides during tree resolution

* stylish
This commit is contained in:
2025-06-28 22:39:54 +03:00
committed by GitHub
parent 765bbf486f
commit f41e44895d
16 changed files with 313 additions and 65 deletions

View File

@ -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

View File

@ -255,3 +255,19 @@ class Pacman(LazyLogging):
result.update(trim_package(provides) for provides in package.provides)
return result
def provided_by(self, package_name: str) -> Generator[Package, None, None]:
"""
search through databases and emit packages which provides the ``package_name``
Args:
package_name(str): package name to search
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():
yield from filter(is_package_provided, database.search(package_name))

View File

@ -130,15 +130,36 @@ class AUR(Remote):
except StopIteration:
raise UnknownPackageError(package_name) from None
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
return [
package
# search api provides reduced models
for stub in self.package_search(package_name, pacman=pacman, search_by="provides")
# verity that found package actually provides it
if package_name in (package := self.package_info(stub.package_base, pacman=pacman)).provides
]
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)

View File

@ -127,15 +127,17 @@ class Official(Remote):
except StopIteration:
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)

View File

@ -59,3 +59,22 @@ class OfficialSyncdb(Official):
return next(AURPackage.from_pacman(package) for package in pacman.package(package_name))
except StopIteration:
raise UnknownPackageError(package_name) from None
def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
if pacman is None:
return []
return [
AURPackage.from_pacman(package)
for package in pacman.provided_by(package_name)
]

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.http import SyncHttpClient
from ahriman.models.aur_package import AURPackage
@ -41,22 +42,36 @@ 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
Raises:
UnknownPackageError: if requested package not found
"""
return cls().package_info(package_name, pacman=pacman)
instance = cls()
try:
return instance.package_info(package_name, pacman=pacman)
except UnknownPackageError:
if include_provides and (provided_by := instance.package_provided_by(package_name, pacman=pacman)):
return next(iter(provided_by))
raise
@classmethod
def multisearch(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]:
def multisearch(cls, *keywords: str, pacman: Pacman | None = None,
search_by: str | None = None) -> list[AURPackage]:
"""
search in remote repository by using API with multiple words. This method is required in order to handle
https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
@ -65,6 +80,7 @@ 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 each of them matches all search terms
@ -72,7 +88,7 @@ class Remote(SyncHttpClient):
instance = cls()
packages: dict[str, AURPackage] = {}
for term in filter(lambda word: len(word) >= 3, keywords):
portion = instance.search(term, pacman=pacman)
portion = instance.package_search(term, pacman=pacman, search_by=search_by)
packages = {
package.name: package # not mistake to group them by name
for package in portion
@ -114,7 +130,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,11 +138,12 @@ 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:
"""
@ -144,13 +161,28 @@ class Remote(SyncHttpClient):
"""
raise NotImplementedError
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
del package_name, pacman
return []
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

View File

@ -33,6 +33,7 @@ class Leaf:
Attributes:
dependencies(set[str]): list of package dependencies
items(list[str]): list of packages in this leaf including provides
package(Package): leaf package properties
"""
@ -42,17 +43,9 @@ class Leaf:
package(Package): package properties
"""
self.package = package
# store frequently used properties
self.dependencies = package.depends_build
@property
def items(self) -> Iterable[str]:
"""
extract all packages from the leaf
Returns:
Iterable[str]: packages containing in this leaf
"""
return self.package.packages.keys()
self.items = self.package.packages_full
def is_dependency(self, packages: Iterable[Leaf]) -> bool:
"""

View File

@ -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,