mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
patch architecture list in runtime (#66)
This commit is contained in:
parent
6633766cc3
commit
cf3c48ffeb
@ -20,7 +20,7 @@ archbuild_flags =
|
||||
build_command = extra-x86_64-build
|
||||
ignore_packages =
|
||||
makechrootpkg_flags =
|
||||
makepkg_flags = --nocolor
|
||||
makepkg_flags = --nocolor --ignorearch
|
||||
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
|
||||
|
||||
[repository]
|
||||
|
@ -25,6 +25,7 @@ from typing import List, Optional
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
from ahriman.core.util import check_output, walk
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
@ -42,6 +43,24 @@ class Sources(LazyLogging):
|
||||
|
||||
_check_output = check_output
|
||||
|
||||
@staticmethod
|
||||
def extend_architectures(sources_dir: Path, architecture: str) -> None:
|
||||
"""
|
||||
extend existing PKGBUILD with repository architecture
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to directory with source files
|
||||
architecture(str): repository architecture
|
||||
"""
|
||||
pkgbuild_path = sources_dir / "PKGBUILD"
|
||||
if not pkgbuild_path.is_file():
|
||||
return
|
||||
|
||||
architectures = Package.supported_architectures(sources_dir)
|
||||
architectures.add(architecture)
|
||||
patch = PkgbuildPatch("arch", list(architectures))
|
||||
patch.write(pkgbuild_path)
|
||||
|
||||
@staticmethod
|
||||
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
|
||||
"""
|
||||
@ -128,10 +147,9 @@ class Sources(LazyLogging):
|
||||
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
|
||||
instance.fetch(sources_dir, package.remote)
|
||||
|
||||
if patch is None:
|
||||
instance.logger.info("no patches found")
|
||||
return
|
||||
if patch is not None:
|
||||
instance.patch_apply(sources_dir, patch)
|
||||
instance.extend_architectures(sources_dir, paths.architecture)
|
||||
|
||||
@staticmethod
|
||||
def patch_create(sources_dir: Path, *pattern: str) -> str:
|
||||
|
@ -267,6 +267,26 @@ class Package(LazyLogging):
|
||||
packages = set(srcinfo["packages"].keys())
|
||||
return (depends | makedepends) - packages
|
||||
|
||||
@staticmethod
|
||||
def supported_architectures(path: Path) -> Set[str]:
|
||||
"""
|
||||
load supported architectures from package sources
|
||||
|
||||
Args:
|
||||
path(Path): path to package sources directory
|
||||
|
||||
Returns:
|
||||
Set[str]: list of package supported architectures
|
||||
|
||||
Raises:
|
||||
InvalidPackageInfo: if there are parsing errors
|
||||
"""
|
||||
srcinfo_source = Package._check_output("makepkg", "--printsrcinfo", exception=None, cwd=path)
|
||||
srcinfo, errors = parse_srcinfo(srcinfo_source)
|
||||
if errors:
|
||||
raise InvalidPackageInfo(errors)
|
||||
return set(srcinfo.get("arch", []))
|
||||
|
||||
def actual_version(self, paths: RepositoryPaths) -> str:
|
||||
"""
|
||||
additional method to handle VCS package versions
|
||||
|
92
src/ahriman/models/pkgbuild_patch.py
Normal file
92
src/ahriman/models/pkgbuild_patch.py
Normal file
@ -0,0 +1,92 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import shlex
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PkgbuildPatch:
|
||||
"""
|
||||
wrapper for patching PKBGUILDs
|
||||
|
||||
Attributes:
|
||||
key(str): name of the property in PKGBUILD, e.g. version, url etc
|
||||
value(Union[str, List[str]]): value of the stored PKGBUILD property. It must be either string or list of string
|
||||
values
|
||||
unsafe(bool): if set, value will be not quoted, might break PKGBUILD
|
||||
"""
|
||||
|
||||
key: str
|
||||
value: Union[str, List[str]]
|
||||
unsafe: bool = field(default=False, kw_only=True)
|
||||
|
||||
@property
|
||||
def is_function(self) -> bool:
|
||||
"""
|
||||
parse key and define whether it function or not
|
||||
|
||||
Returns:
|
||||
bool: True in case if key ends with parentheses and False otherwise
|
||||
"""
|
||||
return self.key.endswith("()")
|
||||
|
||||
def quote(self, value: str) -> str:
|
||||
"""
|
||||
quote value according to the unsafe flag
|
||||
|
||||
Args:
|
||||
value(str): value to be quoted
|
||||
|
||||
Returns:
|
||||
str: quoted string in case if unsafe is False and as is otherwise
|
||||
"""
|
||||
return value if self.unsafe else shlex.quote(value)
|
||||
|
||||
def serialize(self) -> str:
|
||||
"""
|
||||
serialize key-value pair into PKBGBUILD string. List values will be put inside parentheses. All string
|
||||
values (including the ones inside list values) will be put inside quotes, no shell variables expanding supported
|
||||
at the moment
|
||||
|
||||
Returns:
|
||||
str: serialized key-value pair, print-friendly
|
||||
"""
|
||||
if isinstance(self.value, list): # list like
|
||||
value = " ".join(map(self.quote, self.value))
|
||||
return f"""{self.key}=({value})"""
|
||||
# we suppose that function values are only supported in string-like values
|
||||
if self.is_function:
|
||||
return f"{self.key} {self.value}" # no quoting enabled here
|
||||
return f"""{self.key}={self.quote(self.value)}"""
|
||||
|
||||
def write(self, pkgbuild_path: Path) -> None:
|
||||
"""
|
||||
write serialized value into PKGBUILD by specified path
|
||||
|
||||
Args:
|
||||
pkgbuild_path(Path): path to PKGBUILD file
|
||||
"""
|
||||
with pkgbuild_path.open("a") as pkgbuild:
|
||||
pkgbuild.write("\n") # in case if file ends without new line we are appending it at the end
|
||||
pkgbuild.write(self.serialize())
|
||||
pkgbuild.write("\n") # append new line after the values
|
@ -10,6 +10,30 @@ from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
def test_extend_architectures(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must update available architecture list
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
archs_mock = mocker.patch("ahriman.models.package.Package.supported_architectures", return_value={"x86_64"})
|
||||
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
||||
|
||||
Sources.extend_architectures(Path("local"), "i686")
|
||||
archs_mock.assert_called_once_with(Path("local"))
|
||||
write_mock.assert_called_once_with(Path("local") / "PKGBUILD")
|
||||
|
||||
|
||||
def test_extend_architectures_skip(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip extending list of the architectures in case if no PKGBUILD file found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=False)
|
||||
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
||||
|
||||
Sources.extend_architectures(Path("local"), "i686")
|
||||
write_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_fetch_empty(remote_source: RemoteSource, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must do nothing in case if no branches available
|
||||
@ -134,10 +158,12 @@ def test_load(package_ahriman: Package, repository_paths: RepositoryPaths, mocke
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
|
||||
architectures_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures")
|
||||
|
||||
Sources.load(Path("local"), package_ahriman, "patch", repository_paths)
|
||||
fetch_mock.assert_called_once_with(Path("local"), package_ahriman.remote)
|
||||
patch_mock.assert_called_once_with(Path("local"), "patch")
|
||||
architectures_mock.assert_called_once_with(Path("local"), repository_paths.architecture)
|
||||
|
||||
|
||||
def test_load_no_patch(package_ahriman: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
|
@ -154,7 +154,7 @@ def test_from_official(package_ahriman: Package, aur_package_ahriman: AURPackage
|
||||
|
||||
def test_dependencies_failed(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise exception if there are errors during srcinfo load
|
||||
must raise exception if there are errors during srcinfo load for dependencies
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package._check_output", return_value="")
|
||||
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
|
||||
@ -183,6 +183,27 @@ def test_dependencies_with_version_and_overlap(mocker: MockerFixture, resource_p
|
||||
assert Package.dependencies(Path("path")) == {"glibc", "doxygen", "binutils", "git", "libmpc", "python", "zstd"}
|
||||
|
||||
|
||||
def test_supported_architectures(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must generate list of available architectures
|
||||
"""
|
||||
srcinfo = (resource_path_root / "models" / "package_yay_srcinfo").read_text()
|
||||
mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
|
||||
assert Package.supported_architectures(Path("path")) == \
|
||||
{"i686", "pentium4", "x86_64", "arm", "armv7h", "armv6h", "aarch64"}
|
||||
|
||||
|
||||
def test_supported_architectures_failed(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise exception if there are errors during srcinfo load for architectures
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package._check_output", return_value="")
|
||||
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
|
||||
|
||||
with pytest.raises(InvalidPackageInfo):
|
||||
Package.supported_architectures(Path("path"))
|
||||
|
||||
|
||||
def test_actual_version(package_ahriman: Package, repository_paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
must return same actual_version as version is
|
||||
|
61
tests/ahriman/models/test_pkgbuild_patch.py
Normal file
61
tests/ahriman/models/test_pkgbuild_patch.py
Normal file
@ -0,0 +1,61 @@
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock, call
|
||||
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
def test_is_function() -> None:
|
||||
"""
|
||||
must correctly define key as function
|
||||
"""
|
||||
assert not PkgbuildPatch("key", "value").is_function
|
||||
assert PkgbuildPatch("key()", "value").is_function
|
||||
|
||||
|
||||
def test_quote() -> None:
|
||||
"""
|
||||
must quote strings if unsafe flag is not set
|
||||
"""
|
||||
assert PkgbuildPatch("key", "value").quote("value") == """value"""
|
||||
assert PkgbuildPatch("key", "va'lue").quote("va'lue") == """'va'"'"'lue'"""
|
||||
assert PkgbuildPatch("key", "va'lue", unsafe=True).quote("va'lue") == """va'lue"""
|
||||
|
||||
|
||||
def test_serialize() -> None:
|
||||
"""
|
||||
must correctly serialize string values
|
||||
"""
|
||||
assert PkgbuildPatch("key", "value").serialize() == "key=value"
|
||||
assert PkgbuildPatch("key", "42").serialize() == "key=42"
|
||||
assert PkgbuildPatch("key", "4'2").serialize() == """key='4'"'"'2'"""
|
||||
assert PkgbuildPatch("key", "4'2", unsafe=True).serialize() == "key=4'2"
|
||||
|
||||
|
||||
def test_serialize_function() -> None:
|
||||
"""
|
||||
must correctly serialize function values
|
||||
"""
|
||||
assert PkgbuildPatch("key()", "{ value }", unsafe=True).serialize() == "key() { value }"
|
||||
|
||||
|
||||
def test_serialize_list() -> None:
|
||||
"""
|
||||
must correctly serialize list values
|
||||
"""
|
||||
assert PkgbuildPatch("arch", ["i686", "x86_64"]).serialize() == """arch=(i686 x86_64)"""
|
||||
assert PkgbuildPatch("key", ["val'ue", "val\"ue2"]).serialize() == """key=('val'"'"'ue' 'val"ue2')"""
|
||||
assert PkgbuildPatch("key", ["val'ue", "val\"ue2"], unsafe=True).serialize() == """key=(val'ue val"ue2)"""
|
||||
|
||||
|
||||
def test_write(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must write serialized value to the file
|
||||
"""
|
||||
file_mock = MagicMock()
|
||||
open_mock = mocker.patch("pathlib.Path.open")
|
||||
open_mock.return_value.__enter__.return_value = file_mock
|
||||
|
||||
PkgbuildPatch("key", "value").write(Path("PKGBUILD"))
|
||||
open_mock.assert_called_once_with("a")
|
||||
file_mock.write.assert_has_calls([call("\n"), call("""key=value"""), call("\n")])
|
Loading…
Reference in New Issue
Block a user