mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-24 11:19:56 +00:00
258 lines
12 KiB
Python
258 lines
12 KiB
Python
#
|
|
# Copyright (c) 2021-2025 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 argparse
|
|
import sys
|
|
|
|
from pathlib import Path
|
|
|
|
from ahriman.application.application import Application
|
|
from ahriman.application.handlers.handler import Handler, SubParserAction
|
|
from ahriman.core.build_tools.sources import Sources
|
|
from ahriman.core.configuration import Configuration
|
|
from ahriman.core.formatters import PatchPrinter
|
|
from ahriman.models.action import Action
|
|
from ahriman.models.package import Package
|
|
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
|
from ahriman.models.repository_id import RepositoryId
|
|
|
|
|
|
class Patch(Handler):
|
|
"""
|
|
patch control handler
|
|
"""
|
|
|
|
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
|
|
|
|
@classmethod
|
|
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
|
report: bool) -> None:
|
|
"""
|
|
callback for command line
|
|
|
|
Args:
|
|
args(argparse.Namespace): command line args
|
|
repository_id(RepositoryId): repository unique identifier
|
|
configuration(Configuration): configuration instance
|
|
report(bool): force enable or disable reporting
|
|
"""
|
|
application = Application(repository_id, configuration, report=report)
|
|
application.on_start()
|
|
|
|
match args.action:
|
|
case Action.Update if args.variable is not None:
|
|
patch = Patch.patch_create_from_function(args.variable, args.patch)
|
|
Patch.patch_set_create(application, args.package, patch)
|
|
case Action.Update:
|
|
package_base, patch = Patch.patch_create_from_diff(args.package, repository_id.architecture, args.track)
|
|
Patch.patch_set_create(application, package_base, patch)
|
|
case Action.List:
|
|
Patch.patch_set_list(application, args.package, args.variable, args.exit_code)
|
|
case Action.Remove:
|
|
Patch.patch_set_remove(application, args.package, args.variable)
|
|
|
|
@staticmethod
|
|
def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|
"""
|
|
add parser for new single-function patch subcommand
|
|
|
|
Args:
|
|
root(SubParserAction): subparsers for the commands
|
|
|
|
Returns:
|
|
argparse.ArgumentParser: created argument parser
|
|
"""
|
|
parser = root.add_parser("patch-add", help="add patch for PKGBUILD function",
|
|
description="create or update patched PKGBUILD function or variable",
|
|
epilog="Unlike ``patch-set-add``, this function allows to patch only one PKGBUILD "
|
|
"function, e.g. typing ``ahriman patch-add ahriman pkgver`` it will change the "
|
|
"``pkgver`` inside PKGBUILD, typing ``ahriman patch-add ahriman build()`` "
|
|
"it will change ``build()`` function inside PKGBUILD.")
|
|
parser.add_argument("package", help="package base")
|
|
parser.add_argument("variable", help="PKGBUILD variable or function name. If variable is a function, "
|
|
"it must end with ()")
|
|
parser.add_argument("patch", help="path to file which contains function or variable value. If not set, "
|
|
"the value will be read from stdin", type=Path, nargs="?")
|
|
parser.set_defaults(action=Action.Update, architecture="", exit_code=False, lock=None, report=False,
|
|
repository="")
|
|
return parser
|
|
|
|
@staticmethod
|
|
def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|
"""
|
|
add parser for list patches subcommand
|
|
|
|
Args:
|
|
root(SubParserAction): subparsers for the commands
|
|
|
|
Returns:
|
|
argparse.ArgumentParser: created argument parser
|
|
"""
|
|
parser = root.add_parser("patch-list", help="list patch sets",
|
|
description="list available patches for the package")
|
|
parser.add_argument("package", help="package base")
|
|
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
|
|
action="store_true")
|
|
parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables",
|
|
action="append")
|
|
parser.set_defaults(action=Action.List, architecture="", lock=None, report=False, repository="", unsafe=True)
|
|
return parser
|
|
|
|
@staticmethod
|
|
def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|
"""
|
|
add parser for remove patches subcommand
|
|
|
|
Args:
|
|
root(SubParserAction): subparsers for the commands
|
|
|
|
Returns:
|
|
argparse.ArgumentParser: created argument parser
|
|
"""
|
|
parser = root.add_parser("patch-remove", help="remove patch set", description="remove patches for the package")
|
|
parser.add_argument("package", help="package base")
|
|
parser.add_argument("-v", "--variable",
|
|
help="should be used for single-function patches in case if you wold like "
|
|
"to remove only specified PKGBUILD variables. In case if not set, "
|
|
"it will remove all patches related to the package",
|
|
action="append")
|
|
parser.set_defaults(action=Action.Remove, architecture="", exit_code=False, lock=None, report=False,
|
|
repository="")
|
|
return parser
|
|
|
|
@staticmethod
|
|
def _set_patch_set_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|
"""
|
|
add parser for new full-diff patch subcommand
|
|
|
|
Args:
|
|
root(SubParserAction): subparsers for the commands
|
|
|
|
Returns:
|
|
argparse.ArgumentParser: created argument parser
|
|
"""
|
|
parser = root.add_parser("patch-set-add", help="add patch set", description="create or update source patches",
|
|
epilog="In order to add a patch set for the package you will need to:\n\n"
|
|
"1. Clone the AUR package manually.\n"
|
|
"2. Add required changes (e.g. external patches, edit PKGBUILD).\n"
|
|
"3. Run command, e.g. ``ahriman patch-set-add path/to/directory``.\n\n"
|
|
"By default it tracks ``*.patch`` and ``*.diff`` files, but this behavior "
|
|
"can be changed by using ``--track`` option.")
|
|
parser.add_argument("package", help="path to directory with changed files for patch addition/update", type=Path)
|
|
parser.add_argument("-t", "--track", help="files which has to be tracked", action="append",
|
|
default=["*.diff", "*.patch"])
|
|
parser.set_defaults(action=Action.Update, architecture="", exit_code=False, lock=None, report=False,
|
|
repository="", variable=None)
|
|
return parser
|
|
|
|
@staticmethod
|
|
def patch_create_from_diff(sources_dir: Path, architecture: str, track: list[str]) -> tuple[str, PkgbuildPatch]:
|
|
"""
|
|
create PKGBUILD plain diff patches from sources directory
|
|
|
|
Args:
|
|
sources_dir(Path): path to directory with the package sources
|
|
architecture(str): repository architecture
|
|
track(list[str]): track files which match the glob before creating the patch
|
|
|
|
Returns:
|
|
tuple[str, PkgbuildPatch]: package base and created PKGBUILD patch based on the diff from master HEAD
|
|
to current files
|
|
"""
|
|
package = Package.from_build(sources_dir, architecture, None)
|
|
patch = Sources.patch_create(sources_dir, *track)
|
|
return package.base, PkgbuildPatch(None, patch)
|
|
|
|
@staticmethod
|
|
def patch_create_from_function(variable: str, patch_path: Path | None) -> PkgbuildPatch:
|
|
"""
|
|
create single-function patch set for the package base
|
|
|
|
Args:
|
|
variable(str): function or variable name inside PKGBUILD
|
|
patch_path(Path | None): optional path to patch content. If not set, it will be read from stdin
|
|
|
|
Returns:
|
|
PkgbuildPatch: created patch for the PKGBUILD function
|
|
"""
|
|
if patch_path is None:
|
|
# pylint: disable=bad-builtin
|
|
print("Post new function or variable value below. Press Ctrl-D to finish:", file=sys.stderr)
|
|
patch = "".join(list(sys.stdin))
|
|
else:
|
|
patch = patch_path.read_text(encoding="utf8")
|
|
# remove spaces around the patch and parse to correct type
|
|
return PkgbuildPatch.parse(variable, patch.strip())
|
|
|
|
@staticmethod
|
|
def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None:
|
|
"""
|
|
create patch set for the package base
|
|
|
|
Args:
|
|
application(Application): application instance
|
|
package_base(str): package base
|
|
patch(PkgbuildPatch): patch descriptor
|
|
"""
|
|
application.reporter.package_patches_update(package_base, patch)
|
|
|
|
@staticmethod
|
|
def patch_set_list(application: Application, package_base: str, variables: list[str] | None,
|
|
exit_code: bool) -> None:
|
|
"""
|
|
list patches available for the package base
|
|
|
|
Args:
|
|
application(Application): application instance
|
|
package_base(str): package base
|
|
variables(list[str] | None): extract patches only for specified PKGBUILD variables
|
|
exit_code(bool): exit with error on empty search result
|
|
"""
|
|
patches = [
|
|
patch
|
|
for patch in application.reporter.package_patches_get(package_base, None)
|
|
if variables is None or patch.key in variables
|
|
]
|
|
Patch.check_status(exit_code, patches)
|
|
|
|
PatchPrinter(package_base, patches)(verbose=True, separator=" = ")
|
|
|
|
@staticmethod
|
|
def patch_set_remove(application: Application, package_base: str, variables: list[str] | None) -> None:
|
|
"""
|
|
remove patch set for the package base
|
|
|
|
Args:
|
|
application(Application): application instance
|
|
package_base(str): package base
|
|
variables(list[str] | None): remove patches only for specified PKGBUILD variables
|
|
"""
|
|
if variables is not None:
|
|
for variable in variables: # iterate over single variable
|
|
application.reporter.package_patches_remove(package_base, variable)
|
|
else:
|
|
application.reporter.package_patches_remove(package_base, None) # just pass as is
|
|
|
|
arguments = [
|
|
_set_patch_add_parser,
|
|
_set_patch_list_parser,
|
|
_set_patch_remove_parser,
|
|
_set_patch_set_add_parser,
|
|
]
|