mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-15 23:09:56 +00:00
patch support (#35)
This commit is contained in:
@ -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)
|
||||
|
83
src/ahriman/core/build_tools/sources.py
Normal file
83
src/ahriman/core/build_tools/sources.py
Normal 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)
|
@ -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))
|
||||
|
@ -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]]:
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user