mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-28 06:41:43 +00:00
support provides in aur
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,32 @@ 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:
|
||||
provides = self.package_search(package_name, pacman=pacman, search_by="provides")
|
||||
for stub in filter(is_package_provided, provides):
|
||||
# return first found package
|
||||
return self.package_info(stub.package_base, pacman=pacman, include_provides=False)
|
||||
# either provides search is disabled or was still not found
|
||||
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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -41,22 +41,27 @@ 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]:
|
||||
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 +70,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 +78,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 +120,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 +128,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 +155,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
|
||||
|
@ -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,
|
||||
|
@ -1,4 +1,6 @@
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, call as MockCall
|
||||
|
||||
from ahriman.application.application import Application
|
||||
@ -73,6 +75,10 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
|
||||
mock.packages_full = [package_base]
|
||||
return mock
|
||||
|
||||
def get_package(name: str | Path, *args: Any, **kwargs: Any) -> Package:
|
||||
name = name if isinstance(name, str) else name.name
|
||||
return packages[name]
|
||||
|
||||
package_python_schedule.packages = {
|
||||
package_python_schedule.base: package_python_schedule.packages[package_python_schedule.base]
|
||||
}
|
||||
@ -87,10 +93,8 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
|
||||
}
|
||||
|
||||
mocker.patch("pathlib.Path.is_dir", autospec=True, side_effect=lambda p: p.name == "python")
|
||||
package_aur_mock = mocker.patch("ahriman.models.package.Package.from_aur",
|
||||
side_effect=lambda *args: packages[args[0]])
|
||||
package_local_mock = mocker.patch("ahriman.models.package.Package.from_build",
|
||||
side_effect=lambda *args: packages[args[0].name])
|
||||
package_aur_mock = mocker.patch("ahriman.models.package.Package.from_aur", side_effect=get_package)
|
||||
package_local_mock = mocker.patch("ahriman.models.package.Package.from_build", side_effect=get_package)
|
||||
packages_mock = mocker.patch("ahriman.application.application.Application._known_packages",
|
||||
return_value={"devtools", "python-build", "python-pytest"})
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_unknown")
|
||||
@ -98,8 +102,8 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
|
||||
result = application.with_dependencies([package_ahriman], process_dependencies=True)
|
||||
assert {package.base: package for package in result} == packages
|
||||
package_aur_mock.assert_has_calls([
|
||||
MockCall(package_python_schedule.base, package_ahriman.packager),
|
||||
MockCall("python-installer", package_ahriman.packager),
|
||||
MockCall(package_python_schedule.base, package_ahriman.packager, include_provides=True),
|
||||
MockCall("python-installer", package_ahriman.packager, include_provides=True),
|
||||
], any_order=True)
|
||||
package_local_mock.assert_has_calls([
|
||||
MockCall(application.repository.paths.cache_for("python"), "x86_64", package_ahriman.packager),
|
||||
|
@ -13,8 +13,8 @@ def test_info(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
must call info method
|
||||
"""
|
||||
info_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_info")
|
||||
Remote.info("ahriman", pacman=pacman)
|
||||
info_mock.assert_called_once_with("ahriman", pacman=pacman)
|
||||
Remote.info("ahriman", pacman=pacman, include_provides=True)
|
||||
info_mock.assert_called_once_with("ahriman", pacman=pacman, include_provides=True)
|
||||
|
||||
|
||||
def test_multisearch(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
@ -22,10 +22,13 @@ def test_multisearch(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: Mo
|
||||
must search in AUR with multiple words
|
||||
"""
|
||||
terms = ["ahriman", "is", "cool"]
|
||||
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.search", return_value=[aur_package_ahriman])
|
||||
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_search", return_value=[aur_package_ahriman])
|
||||
|
||||
assert Remote.multisearch(*terms, pacman=pacman) == [aur_package_ahriman]
|
||||
search_mock.assert_has_calls([MockCall("ahriman", pacman=pacman), MockCall("cool", pacman=pacman)])
|
||||
assert Remote.multisearch(*terms, pacman=pacman, search_by="name") == [aur_package_ahriman]
|
||||
search_mock.assert_has_calls([
|
||||
MockCall("ahriman", pacman=pacman, search_by="name"),
|
||||
MockCall("cool", pacman=pacman, search_by="name"),
|
||||
])
|
||||
|
||||
|
||||
def test_multisearch_empty(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
@ -33,7 +36,7 @@ def test_multisearch_empty(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
must return empty list if no long terms supplied
|
||||
"""
|
||||
terms = ["it", "is"]
|
||||
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.search")
|
||||
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_search")
|
||||
|
||||
assert Remote.multisearch(*terms, pacman=pacman) == []
|
||||
search_mock.assert_not_called()
|
||||
@ -43,9 +46,9 @@ def test_multisearch_single(aur_package_ahriman: AURPackage, pacman: Pacman, moc
|
||||
"""
|
||||
must search in AUR with one word
|
||||
"""
|
||||
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.search", return_value=[aur_package_ahriman])
|
||||
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_search", return_value=[aur_package_ahriman])
|
||||
assert Remote.multisearch("ahriman", pacman=pacman) == [aur_package_ahriman]
|
||||
search_mock.assert_called_once_with("ahriman", pacman=pacman)
|
||||
search_mock.assert_called_once_with("ahriman", pacman=pacman, search_by=None)
|
||||
|
||||
|
||||
def test_remote_git_url(remote: Remote) -> None:
|
||||
@ -69,8 +72,8 @@ def test_search(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
must call search method
|
||||
"""
|
||||
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_search")
|
||||
Remote.search("ahriman", pacman=pacman)
|
||||
search_mock.assert_called_once_with("ahriman", pacman=pacman)
|
||||
Remote.search("ahriman", pacman=pacman, search_by="name")
|
||||
search_mock.assert_called_once_with("ahriman", pacman=pacman, search_by="name")
|
||||
|
||||
|
||||
def test_package_info(remote: Remote, pacman: Pacman) -> None:
|
||||
@ -78,7 +81,7 @@ def test_package_info(remote: Remote, pacman: Pacman) -> None:
|
||||
must raise NotImplemented for missing package info method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
remote.package_info("package", pacman=pacman)
|
||||
remote.package_info("package", pacman=pacman, include_provides=False)
|
||||
|
||||
|
||||
def test_package_search(remote: Remote, pacman: Pacman) -> None:
|
||||
@ -86,4 +89,4 @@ def test_package_search(remote: Remote, pacman: Pacman) -> None:
|
||||
must raise NotImplemented for missing package search method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
remote.package_search("package", pacman=pacman)
|
||||
remote.package_search("package", pacman=pacman, search_by=None)
|
||||
|
@ -267,6 +267,14 @@ def test_package_empty(pacman: Pacman) -> None:
|
||||
assert not list(pacman.package("some-random-name"))
|
||||
|
||||
|
||||
def test_package_include_provides(pacman: Pacman) -> None:
|
||||
"""
|
||||
must return packages by provides list
|
||||
"""
|
||||
assert not list(pacman.package("sh", include_provides=False))
|
||||
assert list(pacman.package("sh", include_provides=True))
|
||||
|
||||
|
||||
def test_packages(pacman: Pacman) -> None:
|
||||
"""
|
||||
package list must not be empty
|
||||
|
Reference in New Issue
Block a user