patch support (#35)

This commit is contained in:
2021-10-03 15:20:36 +03:00
committed by GitHub
parent e897e2cde2
commit 3e0058620b
12 changed files with 313 additions and 122 deletions

View File

@ -23,7 +23,7 @@ import shutil
from pathlib import Path
from typing import Callable, Iterable, List, Set
from ahriman.core.build_tools.task import Task
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.repository.repository import Repository
from ahriman.core.tree import Tree
@ -112,9 +112,9 @@ class Application:
def add_manual(src: str) -> Path:
package = Package.load(src, self.repository.pacman, self.configuration.get("alpm", "aur_url"))
path = self.repository.paths.manual / package.base
Task.fetch(path, package.git_url)
return path
Sources.load(self.repository.paths.manual_for(package.base), package.git_url,
self.repository.paths.patches_for(package.base))
return self.repository.paths.manual_for(package.base)
def add_archive(src: Path) -> None:
dst = self.repository.paths.packages / src.name
@ -238,7 +238,7 @@ class Application:
process_update(packages)
# process manual packages
tree = Tree.load(updates)
tree = Tree.load(updates, self.repository.paths)
for num, level in enumerate(tree.levels()):
self.logger.info("processing level #%i %s", num, [package.base for package in level])
packages = self.repository.process_build(level)

View File

@ -0,0 +1,83 @@
#
# Copyright (c) 2021 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 logging
from pathlib import Path
from ahriman.core.util import check_output
class Sources:
"""
helper to download package sources (PKGBUILD etc)
"""
_check_output = check_output
@staticmethod
def fetch(local_path: Path, remote: str, branch: str = "master") -> None:
"""
either clone repository or update it to origin/`branch`
:param local_path: local path to fetch
:param remote: remote target (from where to fetch)
:param branch: branch name to checkout, master by default
"""
logger = logging.getLogger("build_details")
# local directory exists and there is .git directory
if (local_path / ".git").is_dir():
logger.info("update HEAD to remote to %s", local_path)
Sources._check_output("git", "fetch", "origin", branch, exception=None, cwd=local_path, logger=logger)
else:
logger.info("clone remote %s to %s", remote, local_path)
Sources._check_output("git", "clone", remote, str(local_path), exception=None, logger=logger)
# and now force reset to our branch
Sources._check_output("git", "checkout", "--force", branch, exception=None, cwd=local_path, logger=logger)
Sources._check_output("git", "reset", "--hard", f"origin/{branch}",
exception=None, cwd=local_path, logger=logger)
@staticmethod
def load(local_path: Path, remote: str, patch_path: Path) -> None:
"""
fetch sources from remote and apply patches
:param local_path: local path to fetch
:param remote: remote target (from where to fetch)
:param patch_path: path to directory with package patches
"""
Sources.fetch(local_path, remote)
Sources.patch(local_path, patch_path)
@staticmethod
def patch(local_path: Path, patch_path: Path) -> None:
"""
apply patches if any
:param local_path: local path to directory with git sources
:param patch_path: path to directory with package patches
"""
# check if even there are patches
if not patch_path.is_dir():
return # no patches provided
logger = logging.getLogger("build_details")
# find everything that looks like patch and sort it
patches = sorted(patch_path.glob("*.patch"))
logger.info("found %s patches", patches)
for patch in patches:
logger.info("apply patch %s", patch.name)
Sources._check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace", str(patch),
exception=None, cwd=local_path, logger=logger)

View File

@ -23,6 +23,7 @@ import shutil
from pathlib import Path
from typing import List, Optional
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import BuildFailed
from ahriman.core.util import check_output
@ -58,38 +59,6 @@ class Task:
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[])
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
@property
def cache_path(self) -> Path:
"""
:return: path to cached packages
"""
return self.paths.cache / self.package.base
@property
def git_path(self) -> Path:
"""
:return: path to clone package from git
"""
return self.paths.sources / self.package.base
@staticmethod
def fetch(local: Path, remote: str, branch: str = "master") -> None:
"""
either clone repository or update it to origin/`branch`
:param local: local path to fetch
:param remote: remote target (from where to fetch)
:param branch: branch name to checkout, master by default
"""
logger = logging.getLogger("build_details")
# local directory exists and there is .git directory
if (local / ".git").is_dir():
Task._check_output("git", "fetch", "origin", branch, exception=None, cwd=local, logger=logger)
else:
Task._check_output("git", "clone", remote, str(local), exception=None, logger=logger)
# and now force reset to our branch
Task._check_output("git", "checkout", "--force", branch, exception=None, cwd=local, logger=logger)
Task._check_output("git", "reset", "--hard", f"origin/{branch}", exception=None, cwd=local, logger=logger)
def build(self) -> List[Path]:
"""
run package build
@ -104,13 +73,13 @@ class Task:
Task._check_output(
*command,
exception=BuildFailed(self.package.base),
cwd=self.git_path,
cwd=self.paths.sources_for(self.package.base),
logger=self.build_logger)
# well it is not actually correct, but we can deal with it
packages = Task._check_output("makepkg", "--packagelist",
exception=BuildFailed(self.package.base),
cwd=self.git_path,
cwd=self.paths.sources_for(self.package.base),
logger=self.build_logger).splitlines()
return [Path(package) for package in packages]
@ -119,8 +88,8 @@ class Task:
fetch package from git
:param path: optional local path to fetch. If not set default path will be used
"""
git_path = path or self.git_path
if self.cache_path.is_dir():
git_path = path or self.paths.sources_for(self.package.base)
if self.paths.cache_for(self.package.base).is_dir():
# no need to clone whole repository, just copy from cache first
shutil.copytree(self.cache_path, git_path)
return self.fetch(git_path, self.package.git_url)
shutil.copytree(self.paths.cache_for(self.package.base), git_path)
Sources.load(git_path, self.package.git_url, self.paths.patches_for(self.package.base))

View File

@ -25,8 +25,9 @@ import tempfile
from pathlib import Path
from typing import Iterable, List, Set, Type
from ahriman.core.build_tools.task import Task
from ahriman.core.build_tools.sources import Sources
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
class Leaf:
@ -53,15 +54,16 @@ class Leaf:
return self.package.packages.keys()
@classmethod
def load(cls: Type[Leaf], package: Package) -> Leaf:
def load(cls: Type[Leaf], package: Package, paths: RepositoryPaths) -> Leaf:
"""
load leaf from package with dependencies
:param package: package properties
:param paths: repository paths instance
:return: loaded class
"""
clone_dir = Path(tempfile.mkdtemp())
try:
Task.fetch(clone_dir, package.git_url)
Sources.load(clone_dir, package.git_url, paths.patches_for(package.base))
dependencies = Package.dependencies(clone_dir)
finally:
shutil.rmtree(clone_dir, ignore_errors=True)
@ -93,13 +95,14 @@ class Tree:
self.leaves = leaves
@classmethod
def load(cls: Type[Tree], packages: Iterable[Package]) -> Tree:
def load(cls: Type[Tree], packages: Iterable[Package], paths: RepositoryPaths) -> Tree:
"""
load tree from packages
:param packages: packages list
:param paths: repository paths instance
:return: loaded class
"""
return cls([Leaf.load(package) for package in packages])
return cls([Leaf.load(package, paths) for package in packages])
def levels(self) -> List[List[Package]]:
"""

View File

@ -231,22 +231,18 @@ class Package:
if not self.is_vcs:
return self.version
from ahriman.core.build_tools.task import Task
from ahriman.core.build_tools.sources import Sources
clone_dir = paths.cache / self.base
logger = logging.getLogger("build_details")
Task.fetch(clone_dir, self.git_url)
Sources.load(paths.cache_for(self.base), self.git_url, paths.patches_for(self.base))
try:
# update pkgver first
Package._check_output("makepkg", "--nodeps", "--nobuild", exception=None, cwd=clone_dir, logger=logger)
Package._check_output("makepkg", "--nodeps", "--nobuild",
exception=None, cwd=paths.cache_for(self.base), logger=logger)
# generate new .SRCINFO and put it to parser
srcinfo_source = Package._check_output(
"makepkg",
"--printsrcinfo",
exception=None,
cwd=clone_dir,
logger=logger)
srcinfo_source = Package._check_output("makepkg", "--printsrcinfo",
exception=None, cwd=paths.cache_for(self.base), logger=logger)
srcinfo, errors = parse_srcinfo(srcinfo_source)
if errors:
raise InvalidPackageInfo(errors)

View File

@ -64,6 +64,13 @@ class RepositoryPaths:
"""
return self.root / "packages" / self.architecture
@property
def patches(self) -> Path:
"""
:return: directory for source patches
"""
return self.root / "patches"
@property
def repository(self) -> Path:
"""
@ -92,6 +99,14 @@ class RepositoryPaths:
if path.is_dir()
}
def cache_for(self, package_base: str) -> Path:
"""
get cache path for specific package base
:param package_base: package base name
:return: full path to directory for specified package base cache
"""
return self.cache / package_base
def create_tree(self) -> None:
"""
create ahriman working tree
@ -100,5 +115,30 @@ class RepositoryPaths:
self.chroot.mkdir(mode=0o755, parents=True, exist_ok=True)
self.manual.mkdir(mode=0o755, parents=True, exist_ok=True)
self.packages.mkdir(mode=0o755, parents=True, exist_ok=True)
self.patches.mkdir(mode=0o755, parents=True, exist_ok=True)
self.repository.mkdir(mode=0o755, parents=True, exist_ok=True)
self.sources.mkdir(mode=0o755, parents=True, exist_ok=True)
def manual_for(self, package_base: str) -> Path:
"""
get manual path for specific package base
:param package_base: package base name
:return: full path to directory for specified package base manual updates
"""
return self.manual / package_base
def patches_for(self, package_base: str) -> Path:
"""
get patches path for specific package base
:param package_base: package base name
:return: full path to directory for specified package base patches
"""
return self.patches / package_base
def sources_for(self, package_base: str) -> Path:
"""
get sources path for specific package base
:param package_base: package base name
:return: full path to directory for specified package base sources
"""
return self.sources / package_base