aboutsummaryrefslogtreecommitdiff
path: root/pomu
diff options
context:
space:
mode:
authorMykyta Holubakha <hilobakho@gmail.com>2017-07-07 10:40:11 +0300
committerMykyta Holubakha <hilobakho@gmail.com>2017-07-18 23:26:27 +0300
commit1c7929604b84d982296f082c060fcc305fd9146e (patch)
tree7bba82a909ec6596d6c9572587241a8ea9b51a89 /pomu
parentFixed a logic error in Dispatcher (diff)
downloadpomu-1c7929604b84d982296f082c060fcc305fd9146e.tar.gz
pomu-1c7929604b84d982296f082c060fcc305fd9146e.tar.bz2
pomu-1c7929604b84d982296f082c060fcc305fd9146e.zip
Overhauled patching support
dropped patch package source module added --patch option to the install command added a patch command to patch an existing package integrated patch support into the Package class created a MergedPackage module for operations on packages already in the pomu repo added ways to patch an existing package added a base package source module (template), with base classes for package-specific metadata and the source module per se added a list_add utility function implemented and_, and_then, and or in util.result
Diffstat (limited to 'pomu')
-rw-r--r--pomu/cli.py16
-rw-r--r--pomu/package.py18
-rw-r--r--pomu/repo/repo.py42
-rw-r--r--pomu/source/base.py84
-rw-r--r--pomu/source/file.py5
-rw-r--r--pomu/source/manager.py6
-rw-r--r--pomu/source/patch.py72
-rw-r--r--pomu/source/portage.py5
-rw-r--r--pomu/util/misc.py (renamed from pomu/util/str.py)13
-rw-r--r--pomu/util/pkg.py2
-rw-r--r--pomu/util/result.py13
11 files changed, 190 insertions, 86 deletions
diff --git a/pomu/cli.py b/pomu/cli.py
index 72cec9b..4e7ea7a 100644
--- a/pomu/cli.py
+++ b/pomu/cli.py
@@ -6,6 +6,7 @@ from os import path
from pomu.repo.init import init_plain_repo, init_portage_repo
from pomu.repo.repo import portage_repo_path, portage_repos, pomu_active_repo
from pomu.source import dispatcher
+from pomu.util.pkg import cpv_split
from pomu.util.result import ResultException
#TODO: global --repo option, (env var?)
@@ -71,13 +72,26 @@ def status():
@main.command()
@click.argument('package', required=True)
+@click.argument('--patch', nargs=-1)
+ #help='Patches for the package')
@needs_repo
def install(package):
"""Install a package"""
- res = dispatcher.install_package(pomu_active_repo(), package).expect()
+ pkg = dispatcher.get_package(package).expect()
+ pkg.patch(patch)
+ res = pomu_active_repo().merge(pkg).expect()
print(res)
@main.command()
+@click.argument('package', required=True)
+@click.argument('patch', type=click.Path(exists=True), nargs=-1, required=True)
+def patch(package):
+ """Patch an existing package"""
+ category, name, *_ = cpv_split(package)
+ pkg = pomu_active_repo().get_package(name=name, category=category).expect()
+ pkg.patch(patch).expect()
+
+@main.command()
@click.option('--uri', is_flag=True,
help='Specify the package to remove by uri, instead of its name')
@click.argument('package', required=True)
diff --git a/pomu/package.py b/pomu/package.py
index 5027569..4a39cd3 100644
--- a/pomu/package.py
+++ b/pomu/package.py
@@ -13,10 +13,11 @@ import subprocess
from patch import PatchSet
from pomu.util.fs import strip_prefix
+from pomu.util.misc import list_add
from pomu.util.result import Result
class Package():
- def __init__(self, name, root, backend=None, category=None, version=None, slot='0', d_path=None, files=None, filemap=None):
+ def __init__(self, name, root, backend=None, category=None, version=None, slot='0', d_path=None, files=None, filemap=None, patches=[]):
"""
Parameters:
backend - specific source module object/class
@@ -35,6 +36,7 @@ class Package():
self.version = version
self.slot = slot
self.filemap = {}
+ self.patches = patches
if d_path is None and files is None and filemap is None:
self.d_path = None
self.read_path(self.root)
@@ -82,14 +84,18 @@ class Package():
copy2(src, dest)
except PermissionError:
return Result.Err('You do not have enough permissions')
- return Result.Ok()
+ return Result.Ok().and_(self.apply_patches())
+
+ def patch(self, patch):
+ list_add(self.patches, patch)
- def apply_patches(self, location, patches):
- """Applies a sequence of patches at the location"""
+ def apply_patches(self):
+ """Applies a sequence of patches at the root (after merging)"""
ps = PatchSet()
- for p in patches:
+ for p in self.patches:
ps.parse(open(p, 'r'))
- ps.apply(root=location)
+ ps.apply(root=self.root)
+ return Result.Ok()
def gen_manifests(self, dst):
"""
diff --git a/pomu/repo/repo.py b/pomu/repo/repo.py
index f41e0e4..b60f6c1 100644
--- a/pomu/repo/repo.py
+++ b/pomu/repo/repo.py
@@ -1,7 +1,9 @@
"""Subroutines with repositories"""
from os import path, rmdir, makedirs
+from shutil import copy2
from git import Repo
+from patch import PatchSet
import portage
from pomu.package import Package
@@ -64,6 +66,13 @@ class Repository():
f.write('{}/{}\n'.format(wd, fil))
for m in manifests:
f.write('{}\n'.format(strip_prefix(m, self.root)))
+ if package.patches:
+ patch_dir = path.join(pkgdir, 'patches')
+ makedirs(patch_dir, exist_ok=True)
+ with open(path.join(pkgdir, 'PATCH_ORDER'), 'w') as f:
+ for patch in package.patches:
+ copy2(patch, patch_dir)
+ f.write(path.basename(patch) + '\n')
if package.backend:
with open(path.join(pkgdir, 'BACKEND'), 'w+') as f:
f.write('{}\n'.format(package.backend.__name__))
@@ -107,7 +116,13 @@ class Repository():
version = f.readline().strip()
with open(path.join(pkgdir, 'FILES'), 'r') as f:
files = [x.strip() for x in f]
- return Package(name, self.root, backend, category=category, version=version, slot=slot, files=files)
+ patches=[]
+ if path.isfile(path.join(pkgdir, 'PATCH_ORDER')):
+ with open(path.join(pkgdir, 'PATCH_ORDER'), 'r') as f:
+ patches = [x.strip() for x in f]
+ pkg = Package(name, self.root, backend, category=category, version=version, slot=slot, files=files, patches=[path.join(pkgdir, 'patches', x) for x in patches])
+ pkg.__class__ = MergedPackage
+ return pkg
def get_package(self, name, category=None, slot=None):
"""Get a package by name, category and slot"""
@@ -161,3 +176,28 @@ def pomu_active_repo(no_portage=None, repo_path=None):
if repo:
return Result.Ok(Repository(portage_repo_path(repo), repo))
return Result.Err('pomu is not initialized')
+
+class MergedPackage(Package):
+ def patch(self, patch):
+ pkgdir = path.join(self.root, 'metadata', 'pomu', self.category, self.name)
+ if self.slot != '0':
+ pkgdir = path.join(pkgdir, self.slot)
+ if isinstance(patch, list):
+ for x in patch:
+ self.patch(x)
+ return Result.Ok()
+ ps = PatchSet()
+ ps.parse(open(patch, 'r'))
+ ps.apply(root=self.root)
+ self.add_patch(patch)
+ return Result.Ok()
+
+ def add_patch(self, patch):
+ pkgdir = path.join(self.root, 'metadata', 'pomu', self.category, self.name)
+ if self.slot != '0':
+ pkgdir = path.join(pkgdir, self.slot)
+ patch_dir = path.join(pkgdir, 'patches')
+ makedirs(patch_dir, exist_ok=True)
+ copy2(patch, patch_dir)
+ with open(path.join(pkgdir, 'PATCH_ORDER'), 'w+') as f:
+ f.write(path.basename(patch) + '\n')
diff --git a/pomu/source/base.py b/pomu/source/base.py
new file mode 100644
index 0000000..5d110db
--- /dev/null
+++ b/pomu/source/base.py
@@ -0,0 +1,84 @@
+"""
+A base package source module.
+A package source module shall provide two classes: a class representing
+a package (implementation details of the source, package-specific metadata,
+fetching logic etc.), and the source per se: it is responsible for parsing
+package specifications and fetching the specified package, as well as
+instantiating specific packages from the metadata directory.
+"""
+
+from pomu.source import dispatcher
+
+class PackageBase():
+ """
+ This is a base class for storing package-specific metadata.
+ It shall be subclassed explicitely.
+ The class is responsible for fetching the package, and reading/writing
+ the package-specific metadata.
+ """
+
+ """The implementation shall provide a name for the package type"""
+ __name__ = None
+
+ def fetch(self):
+ """
+ A method which is responsible for fetching the package: it should return a Package object (specifying set of files with sufficient metadata), and specify this package object as the backend for the Package object (to store source-specific metadata).
+ """
+ raise NotImplementedError()
+
+ @staticmethod
+ def from_data_dir(pkgdir):
+ """
+ This method is responsible for instantiating source-specific metadata
+ from the metadata directory.
+ It shall return an instance of this class, and take a path to the meta
+ directory.
+ """
+ raise NotImplementedError()
+
+ def write_meta(self, pkgdir):
+ """
+ This method shall write source-specific metadata to the provided
+ metadata directory.
+ """
+ raise NotImplementedError()
+
+ def __str__(self):
+ """
+ The implementation shall provide a method to pretty-print
+ package-specific metadata (displayed in show command).
+ Example:
+ return '{}/{}-{} (from {})'.format(self.category, self.name, self.version, self.path)
+ """
+ raise NotImplementedError()
+
+@dispatcher.source
+class BaseSource:
+ """
+ This is the base package source class.
+ It should be decorated with @dispatcher.source.
+ The implementation shall provide methods to parse the package specification,
+ which would be called by the dispatcher (see manager.py for details).
+ Parser methods shall be decorated with @dispatcher.handler(priority=...)
+ decorator (default is 5).
+ It shall provide a method to instantiate a package of this type from the
+ metadata directory.
+ """
+ @dispatcher.handler()
+ def parse_full(uri):
+ """
+ This method shall parse a full package specification (which starts with
+ the backend name, followed by a colon).
+ It shall return a package, wrapped in Result.
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def from_meta_dir(cls, metadir):
+ """
+ This method is responsible for instantiating package-specific metadata
+ from the metadata directory.
+ Example:
+ return PackageBase.from_data_dir(metadir)
+ """
+ raise NotImplementedError()
diff --git a/pomu/source/file.py b/pomu/source/file.py
index f42474d..04624e3 100644
--- a/pomu/source/file.py
+++ b/pomu/source/file.py
@@ -6,11 +6,12 @@ from os import path
from pomu.package import Package
from pomu.source import dispatcher
+from pomu.source.base import PackageBase, BaseSource
from pomu.util.pkg import cpv_split, ver_str
from pomu.util.query import query
from pomu.util.result import Result
-class LocalEbuild():
+class LocalEbuild(PackageBase):
"""A class to represent a local ebuild"""
__name__ = 'fs'
@@ -43,7 +44,7 @@ class LocalEbuild():
return '{}/{}-{} (from {})'.format(self.category, self.name, self.version, self.path)
@dispatcher.source
-class LocalEbuildSource():
+class LocalEbuildSource(BaseSource):
"""The source module responsible for importing local ebuilds"""
@dispatcher.handler(priority=5)
def parse_ebuild_path(uri):
diff --git a/pomu/source/manager.py b/pomu/source/manager.py
index 4989b12..61ed9be 100644
--- a/pomu/source/manager.py
+++ b/pomu/source/manager.py
@@ -42,6 +42,12 @@ class PackageDispatcher():
It would register all the methods of the class marked by @handler
with the dispatcher.
"""
+ try:
+ from pomu.source.base import BaseSource
+ except ImportError: #circular import
+ return cls
+ if cls == BaseSource:
+ return cls
self.backends[cls.__name__] = cls
for m, obj in inspect.getmembers(cls):
if isinstance(obj, self.handler._handler):
diff --git a/pomu/source/patch.py b/pomu/source/patch.py
deleted file mode 100644
index 69a0821..0000000
--- a/pomu/source/patch.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-A package source module to import packages from filesystem locations (ebuilds)
-"""
-
-from os import path, mkdtemp
-
-from pomu.package import Package
-from pomu.source import dispatcher
-from pomu.util.pkg import cpv_split, ver_str
-from pomu.util.query import query
-from pomu.util.result import Result
-
-class Patcher():
- """A class to represent a local ebuild"""
- __name__ = 'patch'
-
- def __init__(self, wrapped, *patches):
- self.patches = patches
- self.wrapped = wrapped
- # nested patching
- if wrapped is Patcher:
- self.patches = wrapped.patches + self.patches
- self.wrapped = wrapped.wrapped
-
- def fetch(self):
- pkg = self.wrapped.fetch()
- pkg.backend = self
- pd = mkdtemp()
- pkg.merge_into(pd)
- pkg.apply_patches(pd, self.patches)
- return Package(pkg.name, pd, self, pkg.category, pkg.version)
-
- @staticmethod
- def from_data_dir(pkgdir):
- with open(path.join(pkgdir, 'PATCH_ORIG_BACKEND'), 'r') as f:
- wrapped = dispatcher.backends[bname].from_meta_dir(pkgdir)
- patches = [path.join(pkgdir, 'patches', x.strip())
- for x in open(path.join(pkgdir, 'PATCH_PATCHES_ORDER'))]
-
- def write_meta(self, pkgdir):
- with open(path.join(pkgdir, 'PATCH_ORIG_BACKEND'), 'w') as f:
- f.write('{}\n'.format(self.wrapped.__name__))
- with open(path.join(pkgdir, 'PATCH_PATCHES_ORDER'), 'w') as f:
- for p in self.patches:
- f.write(path.basename(p) + '\n')
- os.makedirs(path.join(pkgdir, 'patches'), exist_ok=True)
- for p in self.patches:
- shutil.copy2(p, path.join(pkgdir, 'patches'))
- # write originals?
-
- def __str__(self):
- return '{}/{}-{} (from {})'.format(self.category, self.name, self.version, self.path)
-
-@dispatcher.source
-class LocalEbuildSource():
- """The source module responsible for importing and patching various ebuilds"""
- @dispatcher.handler()
- def parse_full(uri):
- if not uri.startswith('patch:'):
- return Result.Err()
- uri = uri[6:]
- patchf, _, pks = uri.partition(':')
- if not path.isfile(patchf):
- return Result.Err('Invalid patch file')
- if not pks:
- return Result.Err('Package not provided')
- pkg = dispatcher.get_package(pks)
- return Result.Ok(Patcher(patchf, pkg)
-
- @classmethod
- def from_meta_dir(cls, metadir):
- return Patcher.from_data_dir(cls, metadir)
diff --git a/pomu/source/portage.py b/pomu/source/portage.py
index f4f112c..89eca7e 100644
--- a/pomu/source/portage.py
+++ b/pomu/source/portage.py
@@ -9,11 +9,12 @@ from portage.versions import vercmp
from pomu.package import Package
from pomu.repo.repo import portage_repos, portage_repo_path
from pomu.source import dispatcher
+from pomu.source.base import PackageBase, BaseSource
from pomu.util.pkg import cpv_split, ver_str
from pomu.util.portage import repo_pkgs
from pomu.util.result import Result
-class PortagePackage():
+class PortagePackage(PackageBase):
"""A class to represent a portage package"""
__name__ = 'portage'
@@ -58,7 +59,7 @@ class PortagePackage():
@dispatcher.source
-class PortageSource():
+class PortageSource(BaseSource):
"""The source module responsible for fetching portage packages"""
@dispatcher.handler(priority=5)
def parse_spec(uri, repo=None):
diff --git a/pomu/util/str.py b/pomu/util/misc.py
index 11fc514..a6ebb1b 100644
--- a/pomu/util/str.py
+++ b/pomu/util/misc.py
@@ -1,4 +1,15 @@
-"""String processing utilities"""
+"""Miscellaneous utility functions"""
+
+def list_add(dst, src):
+ """
+ Extends the target list with a scalar, or contents of the given list
+ """
+ if isinstance(src, list):
+ dst.extend(src)
+ else:
+ dst.append(src)
+
+
def pivot(string, idx, keep_pivot=False):
"""
A function to split a string in two, pivoting at string[idx].
diff --git a/pomu/util/pkg.py b/pomu/util/pkg.py
index 7544e42..56afff6 100644
--- a/pomu/util/pkg.py
+++ b/pomu/util/pkg.py
@@ -6,7 +6,7 @@ import re
from portage.versions import suffix_value
-from pomu.util.str import pivot
+from pomu.util.misc import pivot
suffixes = [x[0] for x in sorted(suffix_value.items(), key=lambda x:x[1])]
diff --git a/pomu/util/result.py b/pomu/util/result.py
index cd67fa0..d6933d8 100644
--- a/pomu/util/result.py
+++ b/pomu/util/result.py
@@ -55,6 +55,19 @@ class Result():
return self._val
raise ResultException(msg + ': ' + self._val)
+ def and_(self, rhs):
+ if not self.is_ok():
+ return Result.Err(self.err())
+ return rhs
+
+ def and_then(self, f):
+ return self.map(f)
+
+ def or_(self, rhs):
+ if self.is_ok():
+ return Result.Ok(self.ok())
+ return rhs
+
def __iter__(self):
if self._is_val:
yield self._val