public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-commits] proj/R_overlay:gsoc13/next commit in: roverlay/overlay/pkgdir/manifest/, roverlay/tools/, roverlay/config/, ...
@ 2013-06-08 16:21 André Erdmann
  2013-06-13 16:34 ` [gentoo-commits] proj/R_overlay:master " André Erdmann
  0 siblings, 1 reply; 2+ messages in thread
From: André Erdmann @ 2013-06-08 16:21 UTC (permalink / raw
  To: gentoo-commits

commit:     e10fad59fff1d89e49df904cbba61049184257e7
Author:     André Erdmann <dywi <AT> mailerd <DOT> de>
AuthorDate: Sat Jun  8 16:12:29 2013 +0000
Commit:     André Erdmann <dywi <AT> mailerd <DOT> de>
CommitDate: Sat Jun  8 16:12:29 2013 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=e10fad59

additions dir / ebuild manifest creation

* additions dir, PatchView: support an arbitrary number of patches per file,
  and use non-version-specific "default" patches

* additions dir, EbuildView: find ebuilds that could be imported

Ebuild manifest creation has been partially merged into
packagedir_ebuildmanifest / moved to roverlay.tools.ebuild[env].

Also added ebuild import support to packagedir_base (work in progress).

---
 roverlay/config/const.py                           |   8 +-
 roverlay/config/entrymap.py                        |   2 +-
 roverlay/overlay/additionsdir.py                   | 114 ++++++++++++---
 roverlay/overlay/category.py                       |   3 +-
 roverlay/overlay/pkgdir/__init__.py                |   3 +
 roverlay/overlay/pkgdir/manifest/__init__.py       |  49 -------
 roverlay/overlay/pkgdir/manifest/env.py            | 122 ----------------
 .../overlay/pkgdir/manifest/manifest_ebuild.py     | 111 ---------------
 roverlay/overlay/pkgdir/packagedir_base.py         | 156 +++++++++++++++++++--
 .../overlay/pkgdir/packagedir_ebuildmanifest.py    |  67 ++++++++-
 roverlay/tools/ebuild.py                           |  55 ++++++++
 roverlay/tools/ebuildenv.py                        | 135 ++++++++++++++++++
 roverlay/tools/patch.py                            |  36 ++---
 roverlay/tools/runcmd.py                           |  37 +++++
 14 files changed, 550 insertions(+), 348 deletions(-)

diff --git a/roverlay/config/const.py b/roverlay/config/const.py
index b3e8dc4..19086f9 100644
--- a/roverlay/config/const.py
+++ b/roverlay/config/const.py
@@ -71,15 +71,15 @@ _CONSTANTS = dict (
 
    TOOLS = dict (
       EBUILD = dict (
-         prog   = "/usr/bin/ebuild",
-         target = "manifest",
+         exe    = "/usr/bin/ebuild",
       ),
       PATCH = dict (
          exe    = "patch",
          opts   = (
             '--no-backup-if-mismatch',
-            '--reject-file=-'
-            '--quiet', '--force',
+            '--reject-file=-',
+            '--verbose',
+            # '--quiet', # '--force',
          )
       ),
    ),

diff --git a/roverlay/config/entrymap.py b/roverlay/config/entrymap.py
index c98bc68..4551435 100644
--- a/roverlay/config/entrymap.py
+++ b/roverlay/config/entrymap.py
@@ -254,7 +254,7 @@ CONFIG_ENTRY_MAP = dict (
    ),
    # ebuild is used to create Manifest files
    ebuild_prog = dict (
-      path        = [ 'TOOLS', 'ebuild_prog' ],
+      path        = [ 'TOOLS', 'ebuild_exe' ],
       value_type  = 'fs_path',
       description = "name of/path to the ebuild executable",
    ),

diff --git a/roverlay/overlay/additionsdir.py b/roverlay/overlay/additionsdir.py
index 7136bb5..f0a6be4 100644
--- a/roverlay/overlay/additionsdir.py
+++ b/roverlay/overlay/additionsdir.py
@@ -7,6 +7,9 @@
 import os
 import re
 
+EMPTY_TUPLE = ()
+
+
 class AdditionsDir ( object ):
 
    def __init__ ( self, fspath, name=None, parent=None ):
@@ -54,6 +57,9 @@ class _AdditionsDirView ( object ):
 
 class _EbuildAdditionsView ( _AdditionsDirView ):
 
+   # with leading '-'
+   RE_PVR = '[-](?P<pvr>[0-9.]+([-]r[0-9]+)?)'
+
    def __init__ ( self, additions_dir ):
       super ( _EbuildAdditionsView, self ).__init__ (
          additions_dir=additions_dir
@@ -62,37 +68,113 @@ class _EbuildAdditionsView ( _AdditionsDirView ):
          self.prepare()
    # --- end of __init__ (...) ---
 
-   def _get_files_with_suffix ( self, suffix ):
-      assert '.' not in self._additions_dir.name
+   def _fs_iter_regex ( self, regex ):
+      fre = re.compile ( regex )
 
-      fre = re.compile (
-         self._additions_dir.name + '[-](?P<pvr>[0-9.]+([-]r[0-9]+)?)'
-         + suffix.replace ( '.', '[.]' )
-      )
       root = self._additions_dir.root
       for fname in os.listdir ( root ):
          fmatch = fre.match ( fname )
          if fmatch:
-            yield ( fmatch.group ( 'pvr' ), ( root + os.sep + fname ), fname )
-   # --- end of _get_files_with_suffix (...) ---
+            yield ( fmatch, ( root + os.sep + fname ), fname )
+   # --- end of _fs_iter_regex (...) ---
+
+
+class EbuildView ( _EbuildAdditionsView ):
+
+   RE_EBUILD_SUFFIX = '[.]ebuild'
+
+   def has_ebuilds ( self ):
+      return bool ( getattr ( self, '_ebuilds', None ) )
+   # --- end of has_ebuilds (...) ---
+
+   def get_ebuilds ( self ):
+      return self._ebuilds
+   # --- end of get_ebuilds (...) ---
+
+   def __iter__ ( self ):
+      return iter ( self.get_ebuilds )
+   # --- end of __iter__ (...) ---
+
+   def _prepare ( self ):
+      if self._additions_dir.exists():
+         ebuilds = list()
+
+         for fmatch, fpath, fname in self._fs_iter_regex (
+            self._additions_dir.name + self.RE_PVR + self.RE_EBUILD_SUFFIX
+         ):
+            # deref symlinks
+            ebuilds.append (
+               fmatch.group ( 'pvr' ), os.path.abspath ( fpath ), fname
+            )
+   # --- end of _prepare (...) --
+
 
 
 class PatchView ( _EbuildAdditionsView ):
 
+   RE_PATCH_SUFFIX = '(?P<patchno>[0-9]{4})?[.]patch'
+
    def has_patches ( self ):
       return bool ( getattr ( self, '_patches', None ) )
    # --- end of has_patches (...) ---
 
-   def get_patches ( self, pvr ):
-      patch = self._patches.get ( pvr, None )
-      return ( patch, ) if patch is not None else None
+   def get_patches ( self, pvr, fallback_to_default=True ):
+      patches = self._patches.get ( pvr, None )
+      if patches:
+         return patches
+      elif fallback_to_default:
+         return getattr ( self, '_default_patches', EMPTY_TUPLE )
+      else:
+         return EMPTY_TUPLE
    # --- end of get_patches (...) ---
 
+   def get_default_patches ( self ):
+      return getattr ( self, '_default_patches', EMPTY_TUPLE )
+   # --- end of get_default_patches (...) ---
+
    def prepare ( self ):
+      def patchno_sort ( iterable ):
+         return list (
+            v[1] for v in sorted ( iterable, key=lambda k: k[0] )
+         )
+      # --- end of patchno_sort (...) ---
+
       if self._additions_dir.exists():
-         # dict { pvr => patch_file }
-         self._patches = {
-            pvr: fpath
-            for pvr, fpath, fname in self._get_files_with_suffix ( '.patch' )
-         }
+         # dict { pvr => *(patch_no, patch_file) }
+         patches = dict()
+
+
+         # find version-specific patches
+         for fmatch, fpath, fname in self._fs_iter_regex (
+            self._additions_dir.name + self.RE_PVR + self.RE_PATCH_SUFFIX
+         ):
+            patchno = fmatch.group ( 'patchno' )
+            patchno = -1 if patchno is None else int ( patchno )
+            pvr     = fmatch.group ( 'pvr' )
+            if pvr in patches:
+               patches [pvr].append ( ( patchno, fpath ) )
+            else:
+               patches [pvr] = [ ( patchno, fpath ) ]
+
+         # -- end for;
+
+         self._patches = { k: patchno_sort ( v ) for k, v in patches.items() }
+
+
+         # find default patches
+
+         default_patches = []
+
+         for fmatch, fpath, fname in self._fs_iter_regex (
+            self._additions_dir.name + self.RE_PATCH_SUFFIX
+         ):
+            patchno = fmatch.group ( 'patchno' )
+
+            default_patches.append (
+               ( ( -1 if patchno is None else int ( patchno ) ), fpath )
+            )
+         # -- end for;
+
+         if default_patches:
+            self._default_patches = patchno_sort ( default_patches )
    # --- end of prepare (...) ---

diff --git a/roverlay/overlay/category.py b/roverlay/overlay/category.py
index 7ccd86e..0e33fc7 100644
--- a/roverlay/overlay/category.py
+++ b/roverlay/overlay/category.py
@@ -318,8 +318,9 @@ class Category ( object ):
                additions_dir = additions_dir.get_obj_subdir ( package ),
                **write_kwargs
             )
+      # -- end if;
 
-         self.remove_empty()
+      self.remove_empty()
    # --- end of write (...) ---
 
    def write_manifest ( self, **manifest_kw ):

diff --git a/roverlay/overlay/pkgdir/__init__.py b/roverlay/overlay/pkgdir/__init__.py
index cc4b79b..baf4eea 100644
--- a/roverlay/overlay/pkgdir/__init__.py
+++ b/roverlay/overlay/pkgdir/__init__.py
@@ -57,6 +57,9 @@ def _configure():
       else:
          _package_dir_class = _package_dir_module.PackageDirBase
 
+      if hasattr ( _package_dir_class, 'init_cls' ):
+         _package_dir_class.init_cls()
+
       logging.getLogger ('pkgdir').debug (
          'Using {!r} as manifest implementation.'.format ( mf_impl )
       )

diff --git a/roverlay/overlay/pkgdir/manifest/__init__.py b/roverlay/overlay/pkgdir/manifest/__init__.py
deleted file mode 100644
index 795fd0b..0000000
--- a/roverlay/overlay/pkgdir/manifest/__init__.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# R overlay -- manifest package (__init__)
-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 André Erdmann <dywi@mailerd.de>
-# Distributed under the terms of the GNU General Public License;
-# either version 2 of the License, or (at your option) any later version.
-
-"""manifest package
-
-This package provides Manifest creation for ebuilds.
-This module has one function create_manifest() that creates an Manifest
-for ebuilds from a package directory.
-"""
-
-__all__ = [ 'create_manifest', ]
-import logging
-import threading
-
-from roverlay.overlay.pkgdir.manifest import manifest_ebuild
-
-_manifest_creation = manifest_ebuild.ExternalManifestCreation ( lazy_init=True )
-# ExternalManifestCreation does not support threads (-> multiprocesses)
-# for one directory/overlay
-_manifest_lock = threading.Lock()
-
-def create_manifest ( package_info_list, nofail=False ):
-   """Creates a Manifest for package_info, using the <<best>> implementation
-   available.
-
-   current implementation: ExternalManifestCreation (using ebuild(1))
-
-   arguments:
-   * package_info --
-   * nofail -- catch exceptions and return False
-   """
-   ret = False
-   try:
-      _manifest_lock.acquire()
-      ret = _manifest_creation.create_for ( package_info_list )
-   except Exception as e:
-      logging.exception ( e )
-      if nofail:
-         ret = False
-      else:
-         raise
-   finally:
-      _manifest_lock.release()
-
-   return ret
-# --- end of create_manifest (...) ---

diff --git a/roverlay/overlay/pkgdir/manifest/env.py b/roverlay/overlay/pkgdir/manifest/env.py
deleted file mode 100644
index e24ace8..0000000
--- a/roverlay/overlay/pkgdir/manifest/env.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# R overlay -- manifest package, manifest environment for subprocess.Popen
-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 André Erdmann <dywi@mailerd.de>
-# Distributed under the terms of the GNU General Public License;
-# either version 2 of the License, or (at your option) any later version.
-
-"""manifest environment for subprocess.Popen
-
-This module implements a minimal (filtered) environment based on os.environ
-for manifest creation implementations that use external programs, e.g.
-ebuild(1).
-"""
-
-__all__ = [ 'ManifestEnv', ]
-
-import os
-import copy
-import logging
-
-from roverlay import util
-
-class ManifestEnv ( object ):
-   """per-repo environment container for Manifest creation using ebuild."""
-
-   @classmethod
-   def get_new ( cls ):
-      return cls ( filter_env=True ).get_env (
-         distdir="", portage_ro_distdirs=""
-      )
-   # --- end of get_new (...) ---
-
-   def __init__ ( self, filter_env=True ):
-      """Initializes a ManifestEnv.
-
-      arguments:
-      * filter_env -- if True: start with an empty env and copy vars
-                               from os.environ selectively
-                      else   : start with os.environ as env
-      """
-      self.filter_env  = filter_env
-      self._manenv     = dict()
-      self.logger      = logging.getLogger ( 'ManifestEnv' )
-      self._common_env = None
-   # --- end of __init__ (...) ---
-
-   def get_env ( self, distdir, portage_ro_distdirs ):
-      """Returns an env dict for repo_dir.
-
-      arguments:
-      * repo_dir --
-      """
-      env                         = self._get_common_manifest_env()
-      env ['DISTDIR']             = distdir
-      env ['PORTAGE_RO_DISTDIRS'] = portage_ro_distdirs
-      return env
-   # --- end of get_env (...) ---
-
-   def _get_common_manifest_env ( self, noret=False ):
-      """Creates an environment suitable for an
-      "ebuild <ebuild> digest|manifest" call (or uses an already existing env).
-      Returns a shallow copy of this env which can then be locally modified
-      (setting DISTDIR, PORTAGE_RO_DISTDIRS).
-
-      arguments:
-      * noret -- do not return copied env if True
-      """
-
-      if self._common_env is None:
-
-         if self.filter_env:
-
-            # selectively import os.environ
-            our_env = util.keepenv (
-               ( 'PATH', '' ),
-               'LANG',
-               'PWD',
-               'EBUILD_DEFAULT_OPTS'
-            )
-         else:
-            # copy os.environ
-            our_env = dict ( os.environ )
-
-         # -- common env part
-
-         # set FEATURES
-         # * digest -- needed? (works without it)
-         # * assume-digests --
-         # * unknown-features-warn -- should FEATURES ever change
-         #
-         # * noauto -- should prevent ebuild from adding additional actions,
-         #   it still tries to download source packages, which is just wrong
-         #   here 'cause it is expected that the R package file exists when
-         #   calling this function, so FETCHCOMMAND/RESUMECOMMAND will be set
-         #   to /bin/true if possible.
-         #
-         # * distlocks -- disabled if FETCHCOMMAND/RESUMECOMMAND set to no-op
-         #
-         our_env ['FEATURES'] = \
-            "noauto digest assume-digests unknown-features-warn"
-
-         # try to prevent src fetching
-         fetch_nop = util.sysnop (
-            nop_returns_success=True,
-            format_str="{nop} \${{DISTDIR}} \${{FILE}} \${{URI}}"
-         )
-
-         if not fetch_nop is None:
-            self.logger.debug (
-               fetch_nop [0] + " disables/replaces FETCHCOMMAND,RESUMECOMMAND."
-            )
-            our_env ['FETCHCOMMAND']  = fetch_nop [1]
-            our_env ['RESUMECOMMAND'] = fetch_nop [1]
-            our_env ['FEATURES']     += " -distlocks"
-
-
-         self._common_env = our_env
-      # -- end if
-      if noret:
-         return None
-      else:
-         return copy.copy ( self._common_env )
-   # --- end of _get_common_manifest_env (...) ---

diff --git a/roverlay/overlay/pkgdir/manifest/manifest_ebuild.py b/roverlay/overlay/pkgdir/manifest/manifest_ebuild.py
deleted file mode 100644
index 1004381..0000000
--- a/roverlay/overlay/pkgdir/manifest/manifest_ebuild.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# R overlay -- manifest package, manifest helpers (actual implementation)
-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 André Erdmann <dywi@mailerd.de>
-# Distributed under the terms of the GNU General Public License;
-# either version 2 of the License, or (at your option) any later version.
-
-"""manifest helpers (actual implementation)
-
-This module implements Manifest creation using ebuild(1).
-"""
-
-__all__ = [ 'ExternalManifestCreation', ]
-
-import os.path
-import copy
-import logging
-import subprocess
-
-
-import roverlay.overlay.pkgdir.distroot.static
-
-from roverlay import config, strutil
-
-from roverlay.overlay.pkgdir.manifest.env import ManifestEnv
-
-class ExternalManifestCreation ( object ):
-   """This class implements Manifest creation using the low level ebuild
-   interface, ebuild(1), which is called in a filtered environment.
-   """
-   # NOTE:
-   # ebuild <ebuild> digest does not support multiprocesses for one overlay,
-
-   def _doinit ( self ):
-      """Initializes self's data, needs an initialized ConfigTree."""
-      self.manifest_env = ManifestEnv.get_new()
-      self.ebuild_tgt   = config.get_or_fail ( 'TOOLS.EBUILD.target' )
-      self.ebuild_prog  = config.get_or_fail ( 'TOOLS.EBUILD.prog' )
-
-      # set PORDIR_OVERLAY and DISTDIR
-      self.manifest_env ['PORTDIR_OVERLAY'] = config.get_or_fail (
-         'OVERLAY.dir'
-      )
-      # self.distroot[.get_distdir(...)] replaces the __tmp__ directory
-      self.distroot = (
-         roverlay.overlay.pkgdir.distroot.static.get_configured ( static=True )
-      )
-
-      self._initialized = True
-   # --- end of _doinit (...) ---
-
-   def __init__ ( self, lazy_init=False ):
-      self.logger = logging.getLogger ( 'ManifestCreation' )
-      self._initialized = False
-      if not lazy_init:
-         self._doinit()
-   # --- end of __init__ (...) ---
-
-   def create_for ( self, package_info_list ):
-      """See ManifestCreation.create_for.
-      Calls ebuild, returns True on success else False.
-
-      raises: *passes Exceptions from failed config lookups
-      """
-      if not self._initialized:
-         self._doinit()
-
-      # choosing one ebuild for calling "ebuild <ebuild>" is sufficient
-      ebuild_file = package_info_list [0] ['ebuild_file']
-
-      distdir = self.distroot.get_distdir ( package_info_list [0] ['name'] )
-
-      #
-      self.manifest_env ['DISTDIR'] = distdir.get_root()
-
-      # add hardlinks to DISTROOT (replacing existing files/links)
-      for p in package_info_list:
-         # TODO: optimize this further?
-         # -> "not has physical_only?"
-         #     (should be covered by "has package_file")
-         distdir.add ( p ['package_file'], p ['package_src_destpath'] )
-
-      ebuild_call = subprocess.Popen (
-         (
-            self.ebuild_prog,
-            ebuild_file,
-            self.ebuild_tgt
-         ),
-         stdin=None,
-         stdout=subprocess.PIPE,
-         stderr=subprocess.PIPE,
-         env=self.manifest_env
-      )
-
-      output = ebuild_call.communicate()
-
-      # log stderr
-      for line in strutil.pipe_lines ( output [1], use_filter=True ):
-         self.logger.warning ( line )
-
-      if ebuild_call.returncode == 0:
-         self.logger.debug ( "Manifest written." )
-         return True
-      else:
-         self.logger.error (
-            'Couldn\'t create Manifest for {ebuild}! '
-            'Return code was {ret}.'.format (
-               ebuild=ebuild_file, ret=ebuild_call.returncode
-            )
-         )
-         return False
-   # --- end of create_for (...) ---

diff --git a/roverlay/overlay/pkgdir/packagedir_base.py b/roverlay/overlay/pkgdir/packagedir_base.py
index 0e5ee81..e2f0f09 100644
--- a/roverlay/overlay/pkgdir/packagedir_base.py
+++ b/roverlay/overlay/pkgdir/packagedir_base.py
@@ -119,6 +119,7 @@ class PackageDirBase ( object ):
          physical_only=True, pvr=pvr, ebuild_file=efile
       )
       self._packages [ p ['ebuild_verstr'] ] = p
+      return p
    # --- end of _scan_add_package (...) ---
 
    def add ( self, package_info, add_if_physical=False ):
@@ -364,7 +365,7 @@ class PackageDirBase ( object ):
       for pvr in pvr_list:
          self.purge_package ( pvr )
 
-      assert not self.empty()
+      assert self.empty()
       self.fs_cleanup()
    # --- end of fs_destroy (...) ---
 
@@ -521,7 +522,7 @@ class PackageDirBase ( object ):
 
             # write manifest (only if shared_fh is None)
             if self._need_manifest and write_manifest:
-               if not self.write_manifest():
+               if not self.write_manifest ( ignore_empty=True ):
                   success = False
          # -- has_ebuilds?
 
@@ -533,6 +534,99 @@ class PackageDirBase ( object ):
       return success
    # --- end of write (...) ---
 
+   def import_extra_ebuilds ( self, overwrite, additions_dir ):
+      """Imports ebuilds from an additions dir into this package dir.
+
+      arguments:
+      * overwrite     -- whether to overwrite existing ebuilds or not
+      * additions_dir -- additions dir for this package dir
+      """
+
+      def import_ebuild_efile ( pvr, efile_src, fname ):
+         """Imports an ebuild file into this package dir and registers it
+         in self._packages.
+
+         Returns the PackageInfo instance of the imported ebuild.
+
+         arguments:
+         * pvr       --
+         * efile_src -- path to the (real, non-symlink) ebuild file
+         * fname     -- name of the ebuild file
+         """
+         # this assertion is always true (see EbuildView)
+         assert fname == self.name + '-' + pvr + '.ebuild'
+
+         efile_dest = self.physical_location + os.sep + fname
+
+         ##efile_dest = (
+         ##   self.physical_location + os.sep
+         ##   + self.name + '-' + pvr + '.ebuild'
+         ##)
+
+         try:
+            # copy the ebuild file
+            shutil.copyfile ( efile_src, efile_dest )
+
+            # create PackageInfo and register it
+            p = PackageInfo (
+               imported=True, pvr=pvr, ebuild_file=efile
+            )
+            self._packages [ p ['ebuild_verstr'] ] = p
+
+
+            # manifest needs to be rewritten
+            self._need_manifest = True
+
+            # fetch SRC_URI?
+            #  ebuild <ebuild> fetch?
+
+            # imported ebuilds cannot be used for generating metadata.xml
+            ##self._need_metadata = True
+
+            return p
+         except:
+            # this package dir is "broken" now,
+            # so a new manifest would be good...
+            #
+            # (the program stops when importing fails,
+            #  so this is a theoretical case only)
+            #
+            self._need_manifest = True
+
+            if pvr in self._packages:
+               self.purge_package ( pvr )
+
+            if os.access ( efile_dest, os.F_OK ):
+               os.unlink ( efile_dest )
+
+            raise
+      # --- end of import_ebuild_efile (...) ---
+
+      eview = roverlay.overlay.additionsdir.EbuildView ( additions_dir )
+
+      if not self.physical_location:
+         raise Exception (
+            "import_extra_ebuilds() needs a non-virtual package dir!"
+         )
+      elif eview.has_ebuilds():
+         util.dodir ( self.physical_location, mkdir_p=True )
+
+         if not self._packages:
+            for pvr, efile, fname in eview.get_ebuilds():
+               import_ebuild_efile ( pvr, efile, fname )
+
+         elif overwrite:
+            for pvr, efile, fname in eview.get_ebuilds():
+               if pvr in self._packages:
+                  self.purge_package ( pvr )
+               import_ebuild_efile ( pvr, efile, fname )
+
+         else:
+            for pvr, efile, fname in eview.get_ebuilds():
+               if pvr not in self._packages:
+                  import_ebuild_efile ( pvr, efile, fname )
+   # --- end of import_extra_ebuilds (...) ---
+
    def write_ebuilds ( self, overwrite, additions_dir, shared_fh=None ):
       """Writes all ebuilds.
 
@@ -571,25 +665,59 @@ class PackageDirBase ( object ):
          return _success
       # --- end of write_ebuild (...) ---
 
-      def patch_ebuild ( efile, patches ):
+      def patch_ebuild ( efile, pvr, patches ):
+         """Applies zero or more patches to an ebuild (file).
+
+         Returns True on success (all patches applied cleanly,
+         where all >= 0), else False.
+
+         Removes the ebuild file if one or more patches failed.
+
+         arguments:
+         * efile   -- path to the file that should be patched
+         * pvr     -- ${PVR} of the ebuild (used for removing the ebuild)
+         * patches -- list of patch files to be applied, in order
+         """
          if patches:
             self.logger.info ( "Patching " + str ( efile ) )
             self.logger.debug (
                "Patches for {} (in that order): {}".format ( efile, patches )
             )
 
-            patch_ret = None
+            try:
+               patch_success = True
 
-            for patch in patches:
-               patch_ret = roverlay.tools.patch.dopatch (
-                  filepath=efile, patch=patch, logger=self.logger
-               )
+               for patch in patches:
+                  patch_ret = roverlay.tools.patch.dopatch (
+                     filepath=efile, patch=patch, logger=self.logger
+                  )
 
-               if patch_ret != os.EX_OK:
-                  # TODO
-                  raise Exception ( patch )
+                  if patch_ret != os.EX_OK:
+                     self.logger.error (
+                        "failed to apply patch {!r}!".format ( patch )
+                     )
+                     patch_success = False
+                     break
 
-            return True
+            except Exception as err:
+               # ^ which exceptions exactly?
+               self.logger.exception ( err )
+               patch_success = False
+            # -- end try;
+
+            if patch_success:
+               return True
+            else:
+               self.logger.error (
+                  'Removing ebuild {!r} due to errors '
+                  'while patching it.'.format ( efile )
+               )
+               # don't set need_manifest here (not required and
+               # write_manifest() would fail if no ebuild written)
+               #
+               ##self._need_manifest = True
+               self.purge_package ( pvr )
+               return False
          else:
             return True
       # --- end of patch_ebuild (...) ---
@@ -614,7 +742,7 @@ class PackageDirBase ( object ):
       patchview  = roverlay.overlay.additionsdir.PatchView ( additions_dir )
       haspatch   = patchview.has_patches()
 
-      for pvr, efile, p_info in ebuilds_to_write():
+      for pvr, efile, p_info in list ( ebuilds_to_write() ):
          if not hasdir:
             util.dodir ( self.physical_location, mkdir_p=True )
             hasdir = True
@@ -623,7 +751,7 @@ class PackageDirBase ( object ):
             write_ebuild ( efile, p_info ['ebuild'] )
          ) and (
             not haspatch
-            or patch_ebuild ( efile, patchview.get_patches ( pvr ) )
+            or patch_ebuild ( efile, pvr, patchview.get_patches ( pvr ) )
          ):
 
             self._need_manifest = True

diff --git a/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py b/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
index 1801a08..c9e3d84 100644
--- a/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
+++ b/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
@@ -6,17 +6,36 @@
 
 __all__ = [ 'PackageDir', ]
 
-from roverlay.overlay.pkgdir import manifest
-from roverlay.overlay.pkgdir import packagedir_base
+import threading
 
+import roverlay.config
+import roverlay.tools.ebuild
+import roverlay.tools.ebuildenv
+import roverlay.overlay.pkgdir.packagedir_base
+import roverlay.overlay.pkgdir.distroot.static
 
-class PackageDir ( packagedir_base.PackageDirBase ):
+
+class PackageDir ( roverlay.overlay.pkgdir.packagedir_base.PackageDirBase ):
    """
    PackageDir class that uses the ebuild executable for Manifest writing.
    """
-
+   #DISTROOT            = None
+   #MANIFEST_ENV        = None
+   MANIFEST_LOCK       = threading.Lock()
    MANIFEST_THREADSAFE = False
 
+   @classmethod
+   def init_cls ( cls ):
+      env = roverlay.tools.ebuildenv.ManifestEnv()
+      env.add_overlay_dir ( roverlay.config.get_or_fail ( 'OVERLAY.dir' ) )
+
+      cls.DISTROOT = (
+         roverlay.overlay.pkgdir.distroot.static.get_configured ( static=True )
+      )
+
+      cls.MANIFEST_ENV = env
+   # --- end of init_cls (...) ---
+
    def _write_manifest ( self, pkgs_for_manifest ):
       """Generates and writes the Manifest file for this package.
 
@@ -24,6 +43,44 @@ class PackageDir ( packagedir_base.PackageDirBase ):
 
       returns: success (True/False)
       """
+      try:
+         self.MANIFEST_LOCK.acquire()
+
+         # choosing one ebuild for calling "ebuild <ebuild>" is sufficient
+         ebuild_file = pkgs_for_manifest [0] ['ebuild_file']
+
+         distdir = self.DISTROOT.get_distdir ( pkgs_for_manifest [0] ['name'] )
+
+         # add hardlinks to DISTROOT (replacing existing files/links)
+         for p in pkgs_for_manifest:
+            # TODO: optimize this further?
+            # -> "not has physical_only?"
+            #     (should be covered by "has package_file")
+            distdir.add ( p ['package_file'], p ['package_src_destpath'] )
+
+
+         if roverlay.tools.ebuild.doebuild_manifest (
+            ebuild_file, self.logger,
+            self.MANIFEST_ENV.get_env ( distdir.get_root() )
+         ):
+            self.logger.debug ( "Manifest written." )
+            ret = True
+
+         else:
+            self.logger.error (
+               'Couldn\'t create Manifest for {ebuild}! '
+               'Return code was {ret}.'.format (
+                  ebuild=ebuild_file, ret=ebuild_call.returncode
+               )
+            )
+            ret = False
+
+      except Exception as err:
+         self.logger.exception ( err )
+         raise
+
+      finally:
+         self.MANIFEST_LOCK.release()
 
-      return manifest.create_manifest ( pkgs_for_manifest, nofail=False )
+      return ret
    # --- end of write_manifest (...) ---

diff --git a/roverlay/tools/ebuild.py b/roverlay/tools/ebuild.py
new file mode 100644
index 0000000..d9f4e9a
--- /dev/null
+++ b/roverlay/tools/ebuild.py
@@ -0,0 +1,55 @@
+# R overlay -- tools, run ebuild(1)
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+# NOTE:
+#  this module has to be loaded after reading roverlay's config
+#
+
+import roverlay.tools.runcmd
+
+import roverlay.config
+import roverlay.util
+
+_EBUILD_CMDV = (
+   roverlay.config.get_or_fail ( 'TOOLS.EBUILD.exe' ),
+)
+
+def doebuild (
+   ebuild_file, command, logger, env=None, opts=(), return_success=True
+):
+   return roverlay.tools.runcmd.run_command (
+      cmdv           = ( _EBUILD_CMDV + opts + ( ebuild_file, command ) ),
+      env            = env,
+      logger         = logger,
+      return_success = return_success
+   )
+# --- end of doebuild (...) ---
+
+def doebuild_manifest (
+   ebuild_file, logger, env=None, opts=(), return_success=True
+):
+   return doebuild (
+      ebuild_file    = ebuild_file,
+      command        = "manifest",
+      logger         = logger,
+      env            = env,
+      opts           = opts,
+      return_success = return_success,
+   )
+# --- end of doebuild_manifest (...) ---
+
+def doebuild_fetch (
+   ebuild_file, logger, env=None, opts=(), return_success=True
+):
+   return doebuild (
+      ebuild_file    = ebuild_file,
+      command        = "fetch",
+      logger         = logger,
+      env            = env,
+      opts           = ( '--skip-manifest', ) + opts,
+      return_success = return_success,
+   )
+# --- end of doebuild_fetch (...) ---

diff --git a/roverlay/tools/ebuildenv.py b/roverlay/tools/ebuildenv.py
new file mode 100644
index 0000000..40f6495
--- /dev/null
+++ b/roverlay/tools/ebuildenv.py
@@ -0,0 +1,135 @@
+# R overlay -- tools, env for ebuild.py
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import os
+import copy
+import logging
+
+import roverlay.util
+
+class EbuildEnv ( object ):
+
+   def __init__ ( self, filter_env=True ):
+      """Initializes an EbuildEnv.
+
+      arguments:
+      * filter_env -- if True: start with an empty env and copy vars
+                               from os.environ selectively
+                      else   : start with os.environ as env
+      """
+      self.filter_env    = filter_env
+      self.logger        = logging.getLogger ( 'ManifestEnv' )
+      self._common_env   = None
+   # --- end of __init__ (...) ---
+
+   def add_info ( self, info, **kwargs ):
+      self._get_common_env ( True )
+      self._common_env.update ( info )
+      self._common_env.update ( kwargs )
+   # --- end of add_info (...) ---
+
+   def _get_env ( self, additional_info ):
+      env = self._get_common_env()
+      env.update ( additional_info )
+      return env
+   # --- end of get_env (...) ---
+
+   def get_distdir_env ( self, distdir ):
+      """Returns an onv dict for distdir.
+
+      arguments:
+      * distdir --
+      """
+      return self._get_env ( { 'DISTDIR': distdir } )
+   # --- end of get_distdir_env (...) ---
+
+   get_env = get_distdir_env
+
+   def add_overlay_dir ( self, overlay_dir ):
+      self._make_common_env()
+      self._common_env ['PORTDIR_OVERLAY'] = overlay_dir
+   # --- end of add_overlay_dir (...) ---
+
+   def _make_common_env ( self ):
+      if self.filter_env:
+
+         # selectively import os.environ
+         our_env = roverlay.util.keepenv (
+            ( 'PATH', '' ),
+            'LANG',
+            'PWD',
+            'EBUILD_DEFAULT_OPTS'
+         )
+      else:
+         # copy os.environ
+         our_env = dict ( os.environ )
+
+      # -- common env part
+
+      # set FEATURES
+      # * digest -- needed? (works without it)
+      # * assume-digests --
+      # * unknown-features-warn -- should FEATURES ever change
+      #
+      # * noauto -- should prevent ebuild from adding additional actions,
+      #   it still tries to download source packages, which is just wrong
+      #   here 'cause it is expected that the R package file exists when
+      #   calling this function, so FETCHCOMMAND/RESUMECOMMAND will be set
+      #   to /bin/true if possible.
+      #
+      our_env ['FEATURES'] = \
+         "noauto digest assume-digests unknown-features-warn"
+
+      self._common_env = our_env
+   # --- end of _make_common_env (...) ---
+
+   def _get_common_env ( self ):
+      """Creates an environment suitable for an
+      "ebuild <ebuild> digest|manifest" call (or uses an already existing env).
+      Returns a shallow copy of this env which can then be locally modified
+      (setting DISTDIR, PORTAGE_RO_DISTDIRS).
+      """
+
+      if self._common_env is None:
+         self._make_common_env()
+
+      return copy.copy ( self._common_env )
+   # --- end of _get_common_env (...) ---
+
+# --- end of EbuildEnv ---
+
+class FetchEnv ( EbuildEnv ):
+   pass
+# --- end of FetchEnv ---
+
+
+class ManifestEnv ( EbuildEnv ):
+   """per-repo environment container for Manifest creation using ebuild."""
+
+   def _make_common_env ( self ):
+      super ( ManifestEnv, self )._make_common_env()
+
+      # set FETCHCOMMAND, RESUMECOMMAND and extend FEATURES:
+      # * distlocks -- disabled if FETCHCOMMAND/RESUMECOMMAND set to no-op
+      #
+
+      # try to prevent src fetching
+      fetch_nop = roverlay.util.sysnop (
+         nop_returns_success=True,
+         format_str="{nop} \${{DISTDIR}} \${{FILE}} \${{URI}}"
+      )
+
+      if not fetch_nop is None:
+         self.logger.debug (
+            fetch_nop [0] + " disables/replaces FETCHCOMMAND,RESUMECOMMAND."
+         )
+
+         self._common_env ['FETCHCOMMAND']  = fetch_nop [1]
+         self._common_env ['RESUMECOMMAND'] = fetch_nop [1]
+         self._common_env ['FEATURES']     += " -distlocks"
+   # --- end of _make_common_env (...) ---
+
+# --- end of ManifestEnv ---

diff --git a/roverlay/tools/patch.py b/roverlay/tools/patch.py
index 119ff80..ff48968 100644
--- a/roverlay/tools/patch.py
+++ b/roverlay/tools/patch.py
@@ -4,42 +4,28 @@
 # Distributed under the terms of the GNU General Public License;
 # either version 2 of the License, or (at your option) any later version.
 
-import subprocess
+# NOTE:
+#  this module has to be loaded after reading roverlay's config
+#
+
+import roverlay.tools.runcmd
 
 import roverlay.config
-import roverlay.strutil
 import roverlay.util
 
 _PATCHENV = roverlay.util.keepenv (
    ( 'PATH', '' ), 'LANG', 'LC_ALL', 'PWD', 'TMPDIR'
 )
 
-# NOTE:
-#  this module has to be loaded after reading roverlay's config
-#
-
-_PATCHOPTS = (
+_PATCH_CMDV = (
    ( roverlay.config.get_or_fail ( "TOOLS.PATCH.exe" ), )
    + roverlay.config.get_or_fail ( "TOOLS.PATCH.opts" )
 )
 
 def dopatch ( filepath, patch, logger ):
-   print ( "RUN PATCH", filepath, patch )
-   patch_call = subprocess.Popen (
-      _PATCHOPTS + (
-         filepath, patch
-      ),
-      stdin=None,
-      stdout=subprocess.PIPE,
-      stderr=subprocess.PIPE,
-      env=_PATCHENV,
-   )
-
-   output = patch_call.communicate()
-
-   # log stderr
-   for line in roverlay.strutil.pipe_lines ( output [1], use_filter=True ):
-      logger.warning ( line )
-
-   return patch_call.returncode
+   return roverlay.tools.runcmd.run_command (
+      cmdv   = ( _PATCH_CMDV + ( filepath, patch ) ),
+      env    = _PATCHENV,
+      logger = logger
+   ).returncode
 # --- end of dopatch (...) ---

diff --git a/roverlay/tools/runcmd.py b/roverlay/tools/runcmd.py
new file mode 100644
index 0000000..e6b4f19
--- /dev/null
+++ b/roverlay/tools/runcmd.py
@@ -0,0 +1,37 @@
+# R overlay -- tools, run a command
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import os
+import subprocess
+
+import roverlay.strutil
+
+DEBUG_TO_CONSOLE = True
+
+def run_command ( cmdv, env, logger, return_success=False ):
+   if DEBUG_TO_CONSOLE:
+      cmd_call = subprocess.Popen ( cmdv, stdin=None, env=env )
+   else:
+      cmd_call = subprocess.Popen (
+         cmdv,
+         stdin=None,
+         stdout=subprocess.PIPE,
+         stderr=subprocess.PIPE,
+         env=env,
+      )
+
+   output = cmd_call.communicate()
+
+   # log stderr
+   if output [1]:
+      for line in roverlay.strutil.pipe_lines ( output [1], use_filter=True ):
+         logger.warning ( line )
+
+   if return_success:
+      return cmd_call.returncode == os.EX_OK
+   else:
+      return cmd_call
+# --- end of run_command (...) ---


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* [gentoo-commits] proj/R_overlay:master commit in: roverlay/overlay/pkgdir/manifest/, roverlay/tools/, roverlay/config/, ...
  2013-06-08 16:21 [gentoo-commits] proj/R_overlay:gsoc13/next commit in: roverlay/overlay/pkgdir/manifest/, roverlay/tools/, roverlay/config/, André Erdmann
@ 2013-06-13 16:34 ` André Erdmann
  0 siblings, 0 replies; 2+ messages in thread
From: André Erdmann @ 2013-06-13 16:34 UTC (permalink / raw
  To: gentoo-commits

commit:     e10fad59fff1d89e49df904cbba61049184257e7
Author:     André Erdmann <dywi <AT> mailerd <DOT> de>
AuthorDate: Sat Jun  8 16:12:29 2013 +0000
Commit:     André Erdmann <dywi <AT> mailerd <DOT> de>
CommitDate: Sat Jun  8 16:12:29 2013 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=e10fad59

additions dir / ebuild manifest creation

* additions dir, PatchView: support an arbitrary number of patches per file,
  and use non-version-specific "default" patches

* additions dir, EbuildView: find ebuilds that could be imported

Ebuild manifest creation has been partially merged into
packagedir_ebuildmanifest / moved to roverlay.tools.ebuild[env].

Also added ebuild import support to packagedir_base (work in progress).

---
 roverlay/config/const.py                           |   8 +-
 roverlay/config/entrymap.py                        |   2 +-
 roverlay/overlay/additionsdir.py                   | 114 ++++++++++++---
 roverlay/overlay/category.py                       |   3 +-
 roverlay/overlay/pkgdir/__init__.py                |   3 +
 roverlay/overlay/pkgdir/manifest/__init__.py       |  49 -------
 roverlay/overlay/pkgdir/manifest/env.py            | 122 ----------------
 .../overlay/pkgdir/manifest/manifest_ebuild.py     | 111 ---------------
 roverlay/overlay/pkgdir/packagedir_base.py         | 156 +++++++++++++++++++--
 .../overlay/pkgdir/packagedir_ebuildmanifest.py    |  67 ++++++++-
 roverlay/tools/ebuild.py                           |  55 ++++++++
 roverlay/tools/ebuildenv.py                        | 135 ++++++++++++++++++
 roverlay/tools/patch.py                            |  36 ++---
 roverlay/tools/runcmd.py                           |  37 +++++
 14 files changed, 550 insertions(+), 348 deletions(-)

diff --git a/roverlay/config/const.py b/roverlay/config/const.py
index b3e8dc4..19086f9 100644
--- a/roverlay/config/const.py
+++ b/roverlay/config/const.py
@@ -71,15 +71,15 @@ _CONSTANTS = dict (
 
    TOOLS = dict (
       EBUILD = dict (
-         prog   = "/usr/bin/ebuild",
-         target = "manifest",
+         exe    = "/usr/bin/ebuild",
       ),
       PATCH = dict (
          exe    = "patch",
          opts   = (
             '--no-backup-if-mismatch',
-            '--reject-file=-'
-            '--quiet', '--force',
+            '--reject-file=-',
+            '--verbose',
+            # '--quiet', # '--force',
          )
       ),
    ),

diff --git a/roverlay/config/entrymap.py b/roverlay/config/entrymap.py
index c98bc68..4551435 100644
--- a/roverlay/config/entrymap.py
+++ b/roverlay/config/entrymap.py
@@ -254,7 +254,7 @@ CONFIG_ENTRY_MAP = dict (
    ),
    # ebuild is used to create Manifest files
    ebuild_prog = dict (
-      path        = [ 'TOOLS', 'ebuild_prog' ],
+      path        = [ 'TOOLS', 'ebuild_exe' ],
       value_type  = 'fs_path',
       description = "name of/path to the ebuild executable",
    ),

diff --git a/roverlay/overlay/additionsdir.py b/roverlay/overlay/additionsdir.py
index 7136bb5..f0a6be4 100644
--- a/roverlay/overlay/additionsdir.py
+++ b/roverlay/overlay/additionsdir.py
@@ -7,6 +7,9 @@
 import os
 import re
 
+EMPTY_TUPLE = ()
+
+
 class AdditionsDir ( object ):
 
    def __init__ ( self, fspath, name=None, parent=None ):
@@ -54,6 +57,9 @@ class _AdditionsDirView ( object ):
 
 class _EbuildAdditionsView ( _AdditionsDirView ):
 
+   # with leading '-'
+   RE_PVR = '[-](?P<pvr>[0-9.]+([-]r[0-9]+)?)'
+
    def __init__ ( self, additions_dir ):
       super ( _EbuildAdditionsView, self ).__init__ (
          additions_dir=additions_dir
@@ -62,37 +68,113 @@ class _EbuildAdditionsView ( _AdditionsDirView ):
          self.prepare()
    # --- end of __init__ (...) ---
 
-   def _get_files_with_suffix ( self, suffix ):
-      assert '.' not in self._additions_dir.name
+   def _fs_iter_regex ( self, regex ):
+      fre = re.compile ( regex )
 
-      fre = re.compile (
-         self._additions_dir.name + '[-](?P<pvr>[0-9.]+([-]r[0-9]+)?)'
-         + suffix.replace ( '.', '[.]' )
-      )
       root = self._additions_dir.root
       for fname in os.listdir ( root ):
          fmatch = fre.match ( fname )
          if fmatch:
-            yield ( fmatch.group ( 'pvr' ), ( root + os.sep + fname ), fname )
-   # --- end of _get_files_with_suffix (...) ---
+            yield ( fmatch, ( root + os.sep + fname ), fname )
+   # --- end of _fs_iter_regex (...) ---
+
+
+class EbuildView ( _EbuildAdditionsView ):
+
+   RE_EBUILD_SUFFIX = '[.]ebuild'
+
+   def has_ebuilds ( self ):
+      return bool ( getattr ( self, '_ebuilds', None ) )
+   # --- end of has_ebuilds (...) ---
+
+   def get_ebuilds ( self ):
+      return self._ebuilds
+   # --- end of get_ebuilds (...) ---
+
+   def __iter__ ( self ):
+      return iter ( self.get_ebuilds )
+   # --- end of __iter__ (...) ---
+
+   def _prepare ( self ):
+      if self._additions_dir.exists():
+         ebuilds = list()
+
+         for fmatch, fpath, fname in self._fs_iter_regex (
+            self._additions_dir.name + self.RE_PVR + self.RE_EBUILD_SUFFIX
+         ):
+            # deref symlinks
+            ebuilds.append (
+               fmatch.group ( 'pvr' ), os.path.abspath ( fpath ), fname
+            )
+   # --- end of _prepare (...) --
+
 
 
 class PatchView ( _EbuildAdditionsView ):
 
+   RE_PATCH_SUFFIX = '(?P<patchno>[0-9]{4})?[.]patch'
+
    def has_patches ( self ):
       return bool ( getattr ( self, '_patches', None ) )
    # --- end of has_patches (...) ---
 
-   def get_patches ( self, pvr ):
-      patch = self._patches.get ( pvr, None )
-      return ( patch, ) if patch is not None else None
+   def get_patches ( self, pvr, fallback_to_default=True ):
+      patches = self._patches.get ( pvr, None )
+      if patches:
+         return patches
+      elif fallback_to_default:
+         return getattr ( self, '_default_patches', EMPTY_TUPLE )
+      else:
+         return EMPTY_TUPLE
    # --- end of get_patches (...) ---
 
+   def get_default_patches ( self ):
+      return getattr ( self, '_default_patches', EMPTY_TUPLE )
+   # --- end of get_default_patches (...) ---
+
    def prepare ( self ):
+      def patchno_sort ( iterable ):
+         return list (
+            v[1] for v in sorted ( iterable, key=lambda k: k[0] )
+         )
+      # --- end of patchno_sort (...) ---
+
       if self._additions_dir.exists():
-         # dict { pvr => patch_file }
-         self._patches = {
-            pvr: fpath
-            for pvr, fpath, fname in self._get_files_with_suffix ( '.patch' )
-         }
+         # dict { pvr => *(patch_no, patch_file) }
+         patches = dict()
+
+
+         # find version-specific patches
+         for fmatch, fpath, fname in self._fs_iter_regex (
+            self._additions_dir.name + self.RE_PVR + self.RE_PATCH_SUFFIX
+         ):
+            patchno = fmatch.group ( 'patchno' )
+            patchno = -1 if patchno is None else int ( patchno )
+            pvr     = fmatch.group ( 'pvr' )
+            if pvr in patches:
+               patches [pvr].append ( ( patchno, fpath ) )
+            else:
+               patches [pvr] = [ ( patchno, fpath ) ]
+
+         # -- end for;
+
+         self._patches = { k: patchno_sort ( v ) for k, v in patches.items() }
+
+
+         # find default patches
+
+         default_patches = []
+
+         for fmatch, fpath, fname in self._fs_iter_regex (
+            self._additions_dir.name + self.RE_PATCH_SUFFIX
+         ):
+            patchno = fmatch.group ( 'patchno' )
+
+            default_patches.append (
+               ( ( -1 if patchno is None else int ( patchno ) ), fpath )
+            )
+         # -- end for;
+
+         if default_patches:
+            self._default_patches = patchno_sort ( default_patches )
    # --- end of prepare (...) ---

diff --git a/roverlay/overlay/category.py b/roverlay/overlay/category.py
index 7ccd86e..0e33fc7 100644
--- a/roverlay/overlay/category.py
+++ b/roverlay/overlay/category.py
@@ -318,8 +318,9 @@ class Category ( object ):
                additions_dir = additions_dir.get_obj_subdir ( package ),
                **write_kwargs
             )
+      # -- end if;
 
-         self.remove_empty()
+      self.remove_empty()
    # --- end of write (...) ---
 
    def write_manifest ( self, **manifest_kw ):

diff --git a/roverlay/overlay/pkgdir/__init__.py b/roverlay/overlay/pkgdir/__init__.py
index cc4b79b..baf4eea 100644
--- a/roverlay/overlay/pkgdir/__init__.py
+++ b/roverlay/overlay/pkgdir/__init__.py
@@ -57,6 +57,9 @@ def _configure():
       else:
          _package_dir_class = _package_dir_module.PackageDirBase
 
+      if hasattr ( _package_dir_class, 'init_cls' ):
+         _package_dir_class.init_cls()
+
       logging.getLogger ('pkgdir').debug (
          'Using {!r} as manifest implementation.'.format ( mf_impl )
       )

diff --git a/roverlay/overlay/pkgdir/manifest/__init__.py b/roverlay/overlay/pkgdir/manifest/__init__.py
deleted file mode 100644
index 795fd0b..0000000
--- a/roverlay/overlay/pkgdir/manifest/__init__.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# R overlay -- manifest package (__init__)
-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 André Erdmann <dywi@mailerd.de>
-# Distributed under the terms of the GNU General Public License;
-# either version 2 of the License, or (at your option) any later version.
-
-"""manifest package
-
-This package provides Manifest creation for ebuilds.
-This module has one function create_manifest() that creates an Manifest
-for ebuilds from a package directory.
-"""
-
-__all__ = [ 'create_manifest', ]
-import logging
-import threading
-
-from roverlay.overlay.pkgdir.manifest import manifest_ebuild
-
-_manifest_creation = manifest_ebuild.ExternalManifestCreation ( lazy_init=True )
-# ExternalManifestCreation does not support threads (-> multiprocesses)
-# for one directory/overlay
-_manifest_lock = threading.Lock()
-
-def create_manifest ( package_info_list, nofail=False ):
-   """Creates a Manifest for package_info, using the <<best>> implementation
-   available.
-
-   current implementation: ExternalManifestCreation (using ebuild(1))
-
-   arguments:
-   * package_info --
-   * nofail -- catch exceptions and return False
-   """
-   ret = False
-   try:
-      _manifest_lock.acquire()
-      ret = _manifest_creation.create_for ( package_info_list )
-   except Exception as e:
-      logging.exception ( e )
-      if nofail:
-         ret = False
-      else:
-         raise
-   finally:
-      _manifest_lock.release()
-
-   return ret
-# --- end of create_manifest (...) ---

diff --git a/roverlay/overlay/pkgdir/manifest/env.py b/roverlay/overlay/pkgdir/manifest/env.py
deleted file mode 100644
index e24ace8..0000000
--- a/roverlay/overlay/pkgdir/manifest/env.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# R overlay -- manifest package, manifest environment for subprocess.Popen
-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 André Erdmann <dywi@mailerd.de>
-# Distributed under the terms of the GNU General Public License;
-# either version 2 of the License, or (at your option) any later version.
-
-"""manifest environment for subprocess.Popen
-
-This module implements a minimal (filtered) environment based on os.environ
-for manifest creation implementations that use external programs, e.g.
-ebuild(1).
-"""
-
-__all__ = [ 'ManifestEnv', ]
-
-import os
-import copy
-import logging
-
-from roverlay import util
-
-class ManifestEnv ( object ):
-   """per-repo environment container for Manifest creation using ebuild."""
-
-   @classmethod
-   def get_new ( cls ):
-      return cls ( filter_env=True ).get_env (
-         distdir="", portage_ro_distdirs=""
-      )
-   # --- end of get_new (...) ---
-
-   def __init__ ( self, filter_env=True ):
-      """Initializes a ManifestEnv.
-
-      arguments:
-      * filter_env -- if True: start with an empty env and copy vars
-                               from os.environ selectively
-                      else   : start with os.environ as env
-      """
-      self.filter_env  = filter_env
-      self._manenv     = dict()
-      self.logger      = logging.getLogger ( 'ManifestEnv' )
-      self._common_env = None
-   # --- end of __init__ (...) ---
-
-   def get_env ( self, distdir, portage_ro_distdirs ):
-      """Returns an env dict for repo_dir.
-
-      arguments:
-      * repo_dir --
-      """
-      env                         = self._get_common_manifest_env()
-      env ['DISTDIR']             = distdir
-      env ['PORTAGE_RO_DISTDIRS'] = portage_ro_distdirs
-      return env
-   # --- end of get_env (...) ---
-
-   def _get_common_manifest_env ( self, noret=False ):
-      """Creates an environment suitable for an
-      "ebuild <ebuild> digest|manifest" call (or uses an already existing env).
-      Returns a shallow copy of this env which can then be locally modified
-      (setting DISTDIR, PORTAGE_RO_DISTDIRS).
-
-      arguments:
-      * noret -- do not return copied env if True
-      """
-
-      if self._common_env is None:
-
-         if self.filter_env:
-
-            # selectively import os.environ
-            our_env = util.keepenv (
-               ( 'PATH', '' ),
-               'LANG',
-               'PWD',
-               'EBUILD_DEFAULT_OPTS'
-            )
-         else:
-            # copy os.environ
-            our_env = dict ( os.environ )
-
-         # -- common env part
-
-         # set FEATURES
-         # * digest -- needed? (works without it)
-         # * assume-digests --
-         # * unknown-features-warn -- should FEATURES ever change
-         #
-         # * noauto -- should prevent ebuild from adding additional actions,
-         #   it still tries to download source packages, which is just wrong
-         #   here 'cause it is expected that the R package file exists when
-         #   calling this function, so FETCHCOMMAND/RESUMECOMMAND will be set
-         #   to /bin/true if possible.
-         #
-         # * distlocks -- disabled if FETCHCOMMAND/RESUMECOMMAND set to no-op
-         #
-         our_env ['FEATURES'] = \
-            "noauto digest assume-digests unknown-features-warn"
-
-         # try to prevent src fetching
-         fetch_nop = util.sysnop (
-            nop_returns_success=True,
-            format_str="{nop} \${{DISTDIR}} \${{FILE}} \${{URI}}"
-         )
-
-         if not fetch_nop is None:
-            self.logger.debug (
-               fetch_nop [0] + " disables/replaces FETCHCOMMAND,RESUMECOMMAND."
-            )
-            our_env ['FETCHCOMMAND']  = fetch_nop [1]
-            our_env ['RESUMECOMMAND'] = fetch_nop [1]
-            our_env ['FEATURES']     += " -distlocks"
-
-
-         self._common_env = our_env
-      # -- end if
-      if noret:
-         return None
-      else:
-         return copy.copy ( self._common_env )
-   # --- end of _get_common_manifest_env (...) ---

diff --git a/roverlay/overlay/pkgdir/manifest/manifest_ebuild.py b/roverlay/overlay/pkgdir/manifest/manifest_ebuild.py
deleted file mode 100644
index 1004381..0000000
--- a/roverlay/overlay/pkgdir/manifest/manifest_ebuild.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# R overlay -- manifest package, manifest helpers (actual implementation)
-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 André Erdmann <dywi@mailerd.de>
-# Distributed under the terms of the GNU General Public License;
-# either version 2 of the License, or (at your option) any later version.
-
-"""manifest helpers (actual implementation)
-
-This module implements Manifest creation using ebuild(1).
-"""
-
-__all__ = [ 'ExternalManifestCreation', ]
-
-import os.path
-import copy
-import logging
-import subprocess
-
-
-import roverlay.overlay.pkgdir.distroot.static
-
-from roverlay import config, strutil
-
-from roverlay.overlay.pkgdir.manifest.env import ManifestEnv
-
-class ExternalManifestCreation ( object ):
-   """This class implements Manifest creation using the low level ebuild
-   interface, ebuild(1), which is called in a filtered environment.
-   """
-   # NOTE:
-   # ebuild <ebuild> digest does not support multiprocesses for one overlay,
-
-   def _doinit ( self ):
-      """Initializes self's data, needs an initialized ConfigTree."""
-      self.manifest_env = ManifestEnv.get_new()
-      self.ebuild_tgt   = config.get_or_fail ( 'TOOLS.EBUILD.target' )
-      self.ebuild_prog  = config.get_or_fail ( 'TOOLS.EBUILD.prog' )
-
-      # set PORDIR_OVERLAY and DISTDIR
-      self.manifest_env ['PORTDIR_OVERLAY'] = config.get_or_fail (
-         'OVERLAY.dir'
-      )
-      # self.distroot[.get_distdir(...)] replaces the __tmp__ directory
-      self.distroot = (
-         roverlay.overlay.pkgdir.distroot.static.get_configured ( static=True )
-      )
-
-      self._initialized = True
-   # --- end of _doinit (...) ---
-
-   def __init__ ( self, lazy_init=False ):
-      self.logger = logging.getLogger ( 'ManifestCreation' )
-      self._initialized = False
-      if not lazy_init:
-         self._doinit()
-   # --- end of __init__ (...) ---
-
-   def create_for ( self, package_info_list ):
-      """See ManifestCreation.create_for.
-      Calls ebuild, returns True on success else False.
-
-      raises: *passes Exceptions from failed config lookups
-      """
-      if not self._initialized:
-         self._doinit()
-
-      # choosing one ebuild for calling "ebuild <ebuild>" is sufficient
-      ebuild_file = package_info_list [0] ['ebuild_file']
-
-      distdir = self.distroot.get_distdir ( package_info_list [0] ['name'] )
-
-      #
-      self.manifest_env ['DISTDIR'] = distdir.get_root()
-
-      # add hardlinks to DISTROOT (replacing existing files/links)
-      for p in package_info_list:
-         # TODO: optimize this further?
-         # -> "not has physical_only?"
-         #     (should be covered by "has package_file")
-         distdir.add ( p ['package_file'], p ['package_src_destpath'] )
-
-      ebuild_call = subprocess.Popen (
-         (
-            self.ebuild_prog,
-            ebuild_file,
-            self.ebuild_tgt
-         ),
-         stdin=None,
-         stdout=subprocess.PIPE,
-         stderr=subprocess.PIPE,
-         env=self.manifest_env
-      )
-
-      output = ebuild_call.communicate()
-
-      # log stderr
-      for line in strutil.pipe_lines ( output [1], use_filter=True ):
-         self.logger.warning ( line )
-
-      if ebuild_call.returncode == 0:
-         self.logger.debug ( "Manifest written." )
-         return True
-      else:
-         self.logger.error (
-            'Couldn\'t create Manifest for {ebuild}! '
-            'Return code was {ret}.'.format (
-               ebuild=ebuild_file, ret=ebuild_call.returncode
-            )
-         )
-         return False
-   # --- end of create_for (...) ---

diff --git a/roverlay/overlay/pkgdir/packagedir_base.py b/roverlay/overlay/pkgdir/packagedir_base.py
index 0e5ee81..e2f0f09 100644
--- a/roverlay/overlay/pkgdir/packagedir_base.py
+++ b/roverlay/overlay/pkgdir/packagedir_base.py
@@ -119,6 +119,7 @@ class PackageDirBase ( object ):
          physical_only=True, pvr=pvr, ebuild_file=efile
       )
       self._packages [ p ['ebuild_verstr'] ] = p
+      return p
    # --- end of _scan_add_package (...) ---
 
    def add ( self, package_info, add_if_physical=False ):
@@ -364,7 +365,7 @@ class PackageDirBase ( object ):
       for pvr in pvr_list:
          self.purge_package ( pvr )
 
-      assert not self.empty()
+      assert self.empty()
       self.fs_cleanup()
    # --- end of fs_destroy (...) ---
 
@@ -521,7 +522,7 @@ class PackageDirBase ( object ):
 
             # write manifest (only if shared_fh is None)
             if self._need_manifest and write_manifest:
-               if not self.write_manifest():
+               if not self.write_manifest ( ignore_empty=True ):
                   success = False
          # -- has_ebuilds?
 
@@ -533,6 +534,99 @@ class PackageDirBase ( object ):
       return success
    # --- end of write (...) ---
 
+   def import_extra_ebuilds ( self, overwrite, additions_dir ):
+      """Imports ebuilds from an additions dir into this package dir.
+
+      arguments:
+      * overwrite     -- whether to overwrite existing ebuilds or not
+      * additions_dir -- additions dir for this package dir
+      """
+
+      def import_ebuild_efile ( pvr, efile_src, fname ):
+         """Imports an ebuild file into this package dir and registers it
+         in self._packages.
+
+         Returns the PackageInfo instance of the imported ebuild.
+
+         arguments:
+         * pvr       --
+         * efile_src -- path to the (real, non-symlink) ebuild file
+         * fname     -- name of the ebuild file
+         """
+         # this assertion is always true (see EbuildView)
+         assert fname == self.name + '-' + pvr + '.ebuild'
+
+         efile_dest = self.physical_location + os.sep + fname
+
+         ##efile_dest = (
+         ##   self.physical_location + os.sep
+         ##   + self.name + '-' + pvr + '.ebuild'
+         ##)
+
+         try:
+            # copy the ebuild file
+            shutil.copyfile ( efile_src, efile_dest )
+
+            # create PackageInfo and register it
+            p = PackageInfo (
+               imported=True, pvr=pvr, ebuild_file=efile
+            )
+            self._packages [ p ['ebuild_verstr'] ] = p
+
+
+            # manifest needs to be rewritten
+            self._need_manifest = True
+
+            # fetch SRC_URI?
+            #  ebuild <ebuild> fetch?
+
+            # imported ebuilds cannot be used for generating metadata.xml
+            ##self._need_metadata = True
+
+            return p
+         except:
+            # this package dir is "broken" now,
+            # so a new manifest would be good...
+            #
+            # (the program stops when importing fails,
+            #  so this is a theoretical case only)
+            #
+            self._need_manifest = True
+
+            if pvr in self._packages:
+               self.purge_package ( pvr )
+
+            if os.access ( efile_dest, os.F_OK ):
+               os.unlink ( efile_dest )
+
+            raise
+      # --- end of import_ebuild_efile (...) ---
+
+      eview = roverlay.overlay.additionsdir.EbuildView ( additions_dir )
+
+      if not self.physical_location:
+         raise Exception (
+            "import_extra_ebuilds() needs a non-virtual package dir!"
+         )
+      elif eview.has_ebuilds():
+         util.dodir ( self.physical_location, mkdir_p=True )
+
+         if not self._packages:
+            for pvr, efile, fname in eview.get_ebuilds():
+               import_ebuild_efile ( pvr, efile, fname )
+
+         elif overwrite:
+            for pvr, efile, fname in eview.get_ebuilds():
+               if pvr in self._packages:
+                  self.purge_package ( pvr )
+               import_ebuild_efile ( pvr, efile, fname )
+
+         else:
+            for pvr, efile, fname in eview.get_ebuilds():
+               if pvr not in self._packages:
+                  import_ebuild_efile ( pvr, efile, fname )
+   # --- end of import_extra_ebuilds (...) ---
+
    def write_ebuilds ( self, overwrite, additions_dir, shared_fh=None ):
       """Writes all ebuilds.
 
@@ -571,25 +665,59 @@ class PackageDirBase ( object ):
          return _success
       # --- end of write_ebuild (...) ---
 
-      def patch_ebuild ( efile, patches ):
+      def patch_ebuild ( efile, pvr, patches ):
+         """Applies zero or more patches to an ebuild (file).
+
+         Returns True on success (all patches applied cleanly,
+         where all >= 0), else False.
+
+         Removes the ebuild file if one or more patches failed.
+
+         arguments:
+         * efile   -- path to the file that should be patched
+         * pvr     -- ${PVR} of the ebuild (used for removing the ebuild)
+         * patches -- list of patch files to be applied, in order
+         """
          if patches:
             self.logger.info ( "Patching " + str ( efile ) )
             self.logger.debug (
                "Patches for {} (in that order): {}".format ( efile, patches )
             )
 
-            patch_ret = None
+            try:
+               patch_success = True
 
-            for patch in patches:
-               patch_ret = roverlay.tools.patch.dopatch (
-                  filepath=efile, patch=patch, logger=self.logger
-               )
+               for patch in patches:
+                  patch_ret = roverlay.tools.patch.dopatch (
+                     filepath=efile, patch=patch, logger=self.logger
+                  )
 
-               if patch_ret != os.EX_OK:
-                  # TODO
-                  raise Exception ( patch )
+                  if patch_ret != os.EX_OK:
+                     self.logger.error (
+                        "failed to apply patch {!r}!".format ( patch )
+                     )
+                     patch_success = False
+                     break
 
-            return True
+            except Exception as err:
+               # ^ which exceptions exactly?
+               self.logger.exception ( err )
+               patch_success = False
+            # -- end try;
+
+            if patch_success:
+               return True
+            else:
+               self.logger.error (
+                  'Removing ebuild {!r} due to errors '
+                  'while patching it.'.format ( efile )
+               )
+               # don't set need_manifest here (not required and
+               # write_manifest() would fail if no ebuild written)
+               #
+               ##self._need_manifest = True
+               self.purge_package ( pvr )
+               return False
          else:
             return True
       # --- end of patch_ebuild (...) ---
@@ -614,7 +742,7 @@ class PackageDirBase ( object ):
       patchview  = roverlay.overlay.additionsdir.PatchView ( additions_dir )
       haspatch   = patchview.has_patches()
 
-      for pvr, efile, p_info in ebuilds_to_write():
+      for pvr, efile, p_info in list ( ebuilds_to_write() ):
          if not hasdir:
             util.dodir ( self.physical_location, mkdir_p=True )
             hasdir = True
@@ -623,7 +751,7 @@ class PackageDirBase ( object ):
             write_ebuild ( efile, p_info ['ebuild'] )
          ) and (
             not haspatch
-            or patch_ebuild ( efile, patchview.get_patches ( pvr ) )
+            or patch_ebuild ( efile, pvr, patchview.get_patches ( pvr ) )
          ):
 
             self._need_manifest = True

diff --git a/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py b/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
index 1801a08..c9e3d84 100644
--- a/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
+++ b/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
@@ -6,17 +6,36 @@
 
 __all__ = [ 'PackageDir', ]
 
-from roverlay.overlay.pkgdir import manifest
-from roverlay.overlay.pkgdir import packagedir_base
+import threading
 
+import roverlay.config
+import roverlay.tools.ebuild
+import roverlay.tools.ebuildenv
+import roverlay.overlay.pkgdir.packagedir_base
+import roverlay.overlay.pkgdir.distroot.static
 
-class PackageDir ( packagedir_base.PackageDirBase ):
+
+class PackageDir ( roverlay.overlay.pkgdir.packagedir_base.PackageDirBase ):
    """
    PackageDir class that uses the ebuild executable for Manifest writing.
    """
-
+   #DISTROOT            = None
+   #MANIFEST_ENV        = None
+   MANIFEST_LOCK       = threading.Lock()
    MANIFEST_THREADSAFE = False
 
+   @classmethod
+   def init_cls ( cls ):
+      env = roverlay.tools.ebuildenv.ManifestEnv()
+      env.add_overlay_dir ( roverlay.config.get_or_fail ( 'OVERLAY.dir' ) )
+
+      cls.DISTROOT = (
+         roverlay.overlay.pkgdir.distroot.static.get_configured ( static=True )
+      )
+
+      cls.MANIFEST_ENV = env
+   # --- end of init_cls (...) ---
+
    def _write_manifest ( self, pkgs_for_manifest ):
       """Generates and writes the Manifest file for this package.
 
@@ -24,6 +43,44 @@ class PackageDir ( packagedir_base.PackageDirBase ):
 
       returns: success (True/False)
       """
+      try:
+         self.MANIFEST_LOCK.acquire()
+
+         # choosing one ebuild for calling "ebuild <ebuild>" is sufficient
+         ebuild_file = pkgs_for_manifest [0] ['ebuild_file']
+
+         distdir = self.DISTROOT.get_distdir ( pkgs_for_manifest [0] ['name'] )
+
+         # add hardlinks to DISTROOT (replacing existing files/links)
+         for p in pkgs_for_manifest:
+            # TODO: optimize this further?
+            # -> "not has physical_only?"
+            #     (should be covered by "has package_file")
+            distdir.add ( p ['package_file'], p ['package_src_destpath'] )
+
+
+         if roverlay.tools.ebuild.doebuild_manifest (
+            ebuild_file, self.logger,
+            self.MANIFEST_ENV.get_env ( distdir.get_root() )
+         ):
+            self.logger.debug ( "Manifest written." )
+            ret = True
+
+         else:
+            self.logger.error (
+               'Couldn\'t create Manifest for {ebuild}! '
+               'Return code was {ret}.'.format (
+                  ebuild=ebuild_file, ret=ebuild_call.returncode
+               )
+            )
+            ret = False
+
+      except Exception as err:
+         self.logger.exception ( err )
+         raise
+
+      finally:
+         self.MANIFEST_LOCK.release()
 
-      return manifest.create_manifest ( pkgs_for_manifest, nofail=False )
+      return ret
    # --- end of write_manifest (...) ---

diff --git a/roverlay/tools/ebuild.py b/roverlay/tools/ebuild.py
new file mode 100644
index 0000000..d9f4e9a
--- /dev/null
+++ b/roverlay/tools/ebuild.py
@@ -0,0 +1,55 @@
+# R overlay -- tools, run ebuild(1)
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+# NOTE:
+#  this module has to be loaded after reading roverlay's config
+#
+
+import roverlay.tools.runcmd
+
+import roverlay.config
+import roverlay.util
+
+_EBUILD_CMDV = (
+   roverlay.config.get_or_fail ( 'TOOLS.EBUILD.exe' ),
+)
+
+def doebuild (
+   ebuild_file, command, logger, env=None, opts=(), return_success=True
+):
+   return roverlay.tools.runcmd.run_command (
+      cmdv           = ( _EBUILD_CMDV + opts + ( ebuild_file, command ) ),
+      env            = env,
+      logger         = logger,
+      return_success = return_success
+   )
+# --- end of doebuild (...) ---
+
+def doebuild_manifest (
+   ebuild_file, logger, env=None, opts=(), return_success=True
+):
+   return doebuild (
+      ebuild_file    = ebuild_file,
+      command        = "manifest",
+      logger         = logger,
+      env            = env,
+      opts           = opts,
+      return_success = return_success,
+   )
+# --- end of doebuild_manifest (...) ---
+
+def doebuild_fetch (
+   ebuild_file, logger, env=None, opts=(), return_success=True
+):
+   return doebuild (
+      ebuild_file    = ebuild_file,
+      command        = "fetch",
+      logger         = logger,
+      env            = env,
+      opts           = ( '--skip-manifest', ) + opts,
+      return_success = return_success,
+   )
+# --- end of doebuild_fetch (...) ---

diff --git a/roverlay/tools/ebuildenv.py b/roverlay/tools/ebuildenv.py
new file mode 100644
index 0000000..40f6495
--- /dev/null
+++ b/roverlay/tools/ebuildenv.py
@@ -0,0 +1,135 @@
+# R overlay -- tools, env for ebuild.py
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import os
+import copy
+import logging
+
+import roverlay.util
+
+class EbuildEnv ( object ):
+
+   def __init__ ( self, filter_env=True ):
+      """Initializes an EbuildEnv.
+
+      arguments:
+      * filter_env -- if True: start with an empty env and copy vars
+                               from os.environ selectively
+                      else   : start with os.environ as env
+      """
+      self.filter_env    = filter_env
+      self.logger        = logging.getLogger ( 'ManifestEnv' )
+      self._common_env   = None
+   # --- end of __init__ (...) ---
+
+   def add_info ( self, info, **kwargs ):
+      self._get_common_env ( True )
+      self._common_env.update ( info )
+      self._common_env.update ( kwargs )
+   # --- end of add_info (...) ---
+
+   def _get_env ( self, additional_info ):
+      env = self._get_common_env()
+      env.update ( additional_info )
+      return env
+   # --- end of get_env (...) ---
+
+   def get_distdir_env ( self, distdir ):
+      """Returns an onv dict for distdir.
+
+      arguments:
+      * distdir --
+      """
+      return self._get_env ( { 'DISTDIR': distdir } )
+   # --- end of get_distdir_env (...) ---
+
+   get_env = get_distdir_env
+
+   def add_overlay_dir ( self, overlay_dir ):
+      self._make_common_env()
+      self._common_env ['PORTDIR_OVERLAY'] = overlay_dir
+   # --- end of add_overlay_dir (...) ---
+
+   def _make_common_env ( self ):
+      if self.filter_env:
+
+         # selectively import os.environ
+         our_env = roverlay.util.keepenv (
+            ( 'PATH', '' ),
+            'LANG',
+            'PWD',
+            'EBUILD_DEFAULT_OPTS'
+         )
+      else:
+         # copy os.environ
+         our_env = dict ( os.environ )
+
+      # -- common env part
+
+      # set FEATURES
+      # * digest -- needed? (works without it)
+      # * assume-digests --
+      # * unknown-features-warn -- should FEATURES ever change
+      #
+      # * noauto -- should prevent ebuild from adding additional actions,
+      #   it still tries to download source packages, which is just wrong
+      #   here 'cause it is expected that the R package file exists when
+      #   calling this function, so FETCHCOMMAND/RESUMECOMMAND will be set
+      #   to /bin/true if possible.
+      #
+      our_env ['FEATURES'] = \
+         "noauto digest assume-digests unknown-features-warn"
+
+      self._common_env = our_env
+   # --- end of _make_common_env (...) ---
+
+   def _get_common_env ( self ):
+      """Creates an environment suitable for an
+      "ebuild <ebuild> digest|manifest" call (or uses an already existing env).
+      Returns a shallow copy of this env which can then be locally modified
+      (setting DISTDIR, PORTAGE_RO_DISTDIRS).
+      """
+
+      if self._common_env is None:
+         self._make_common_env()
+
+      return copy.copy ( self._common_env )
+   # --- end of _get_common_env (...) ---
+
+# --- end of EbuildEnv ---
+
+class FetchEnv ( EbuildEnv ):
+   pass
+# --- end of FetchEnv ---
+
+
+class ManifestEnv ( EbuildEnv ):
+   """per-repo environment container for Manifest creation using ebuild."""
+
+   def _make_common_env ( self ):
+      super ( ManifestEnv, self )._make_common_env()
+
+      # set FETCHCOMMAND, RESUMECOMMAND and extend FEATURES:
+      # * distlocks -- disabled if FETCHCOMMAND/RESUMECOMMAND set to no-op
+      #
+
+      # try to prevent src fetching
+      fetch_nop = roverlay.util.sysnop (
+         nop_returns_success=True,
+         format_str="{nop} \${{DISTDIR}} \${{FILE}} \${{URI}}"
+      )
+
+      if not fetch_nop is None:
+         self.logger.debug (
+            fetch_nop [0] + " disables/replaces FETCHCOMMAND,RESUMECOMMAND."
+         )
+
+         self._common_env ['FETCHCOMMAND']  = fetch_nop [1]
+         self._common_env ['RESUMECOMMAND'] = fetch_nop [1]
+         self._common_env ['FEATURES']     += " -distlocks"
+   # --- end of _make_common_env (...) ---
+
+# --- end of ManifestEnv ---

diff --git a/roverlay/tools/patch.py b/roverlay/tools/patch.py
index 119ff80..ff48968 100644
--- a/roverlay/tools/patch.py
+++ b/roverlay/tools/patch.py
@@ -4,42 +4,28 @@
 # Distributed under the terms of the GNU General Public License;
 # either version 2 of the License, or (at your option) any later version.
 
-import subprocess
+# NOTE:
+#  this module has to be loaded after reading roverlay's config
+#
+
+import roverlay.tools.runcmd
 
 import roverlay.config
-import roverlay.strutil
 import roverlay.util
 
 _PATCHENV = roverlay.util.keepenv (
    ( 'PATH', '' ), 'LANG', 'LC_ALL', 'PWD', 'TMPDIR'
 )
 
-# NOTE:
-#  this module has to be loaded after reading roverlay's config
-#
-
-_PATCHOPTS = (
+_PATCH_CMDV = (
    ( roverlay.config.get_or_fail ( "TOOLS.PATCH.exe" ), )
    + roverlay.config.get_or_fail ( "TOOLS.PATCH.opts" )
 )
 
 def dopatch ( filepath, patch, logger ):
-   print ( "RUN PATCH", filepath, patch )
-   patch_call = subprocess.Popen (
-      _PATCHOPTS + (
-         filepath, patch
-      ),
-      stdin=None,
-      stdout=subprocess.PIPE,
-      stderr=subprocess.PIPE,
-      env=_PATCHENV,
-   )
-
-   output = patch_call.communicate()
-
-   # log stderr
-   for line in roverlay.strutil.pipe_lines ( output [1], use_filter=True ):
-      logger.warning ( line )
-
-   return patch_call.returncode
+   return roverlay.tools.runcmd.run_command (
+      cmdv   = ( _PATCH_CMDV + ( filepath, patch ) ),
+      env    = _PATCHENV,
+      logger = logger
+   ).returncode
 # --- end of dopatch (...) ---

diff --git a/roverlay/tools/runcmd.py b/roverlay/tools/runcmd.py
new file mode 100644
index 0000000..e6b4f19
--- /dev/null
+++ b/roverlay/tools/runcmd.py
@@ -0,0 +1,37 @@
+# R overlay -- tools, run a command
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import os
+import subprocess
+
+import roverlay.strutil
+
+DEBUG_TO_CONSOLE = True
+
+def run_command ( cmdv, env, logger, return_success=False ):
+   if DEBUG_TO_CONSOLE:
+      cmd_call = subprocess.Popen ( cmdv, stdin=None, env=env )
+   else:
+      cmd_call = subprocess.Popen (
+         cmdv,
+         stdin=None,
+         stdout=subprocess.PIPE,
+         stderr=subprocess.PIPE,
+         env=env,
+      )
+
+   output = cmd_call.communicate()
+
+   # log stderr
+   if output [1]:
+      for line in roverlay.strutil.pipe_lines ( output [1], use_filter=True ):
+         logger.warning ( line )
+
+   if return_success:
+      return cmd_call.returncode == os.EX_OK
+   else:
+      return cmd_call
+# --- end of run_command (...) ---


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2013-06-13 16:34 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-06-08 16:21 [gentoo-commits] proj/R_overlay:gsoc13/next commit in: roverlay/overlay/pkgdir/manifest/, roverlay/tools/, roverlay/config/, André Erdmann
2013-06-13 16:34 ` [gentoo-commits] proj/R_overlay:master " André Erdmann

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox