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

commit:     fe217214d62189a5157188c9f7ddfafa607f26c3
Author:     André Erdmann <dywi <AT> mailerd <DOT> de>
AuthorDate: Wed Jun 12 20:59:47 2013 +0000
Commit:     André Erdmann <dywi <AT> mailerd <DOT> de>
CommitDate: Wed Jun 12 20:59:47 2013 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=fe217214

additions dir: code comments/fixup

Besides other changes, this commit makes the "import hand-written ebuilds"
feature available. Note that this (currently) only works with the (default)
ebuildmanifest pkg dir implementation.

Other changes include:

* additions dir:
** more lenient regex in _EbuildAdditionsView
** handle ambiguous patch file names (where version could be patchno)
* pkgdir_base
** fetch src of imported ebuilds
* packageinfo
** update(): put unknown keys in quotes
** get_create(): get an info key by creating it, if necessary

---
 roverlay/overlay/additionsdir.py                   | 170 ++++++++++++++++++---
 roverlay/overlay/category.py                       |  19 +++
 roverlay/overlay/pkgdir/__init__.py                |   2 +
 roverlay/overlay/pkgdir/packagedir_base.py         |  87 +++++++----
 .../overlay/pkgdir/packagedir_ebuildmanifest.py    | 115 ++++++++------
 .../overlay/pkgdir/packagedir_portagemanifest.py   |  14 +-
 roverlay/overlay/root.py                           | 129 ++++++++++------
 roverlay/packageinfo.py                            |  51 ++++++-
 roverlay/tools/ebuild.py                           |   1 +
 roverlay/tools/ebuildenv.py                        |   8 +-
 10 files changed, 458 insertions(+), 138 deletions(-)

diff --git a/roverlay/overlay/additionsdir.py b/roverlay/overlay/additionsdir.py
index f0a6be4..3031a69 100644
--- a/roverlay/overlay/additionsdir.py
+++ b/roverlay/overlay/additionsdir.py
@@ -9,8 +9,10 @@ import re
 
 EMPTY_TUPLE = ()
 
-
 class AdditionsDir ( object ):
+   """AdditionsDir represents a filesystem directory (that does not need
+   to exist).
+   """
 
    def __init__ ( self, fspath, name=None, parent=None ):
       self.root   = str ( fspath ) if fspath else None
@@ -24,7 +26,38 @@ class AdditionsDir ( object ):
 
    __bool__ = exists
 
+   def iter_entries ( self ):
+      """Generator that yields the directory content of this dir."""
+      if self.exists():
+         for name in os.listdir ( self.root ):
+            yield ( name, ( self.root + os.sep + name ) )
+   # --- end of iter_entries (...) ---
+
+   def __iter__ ( self ):
+      return iter ( self.iter_entries() )
+   # --- end of __iter__ (...) ---
+
+   def get_child ( self, fspath, name ):
+      """Returns a new instance with the given fspath/name and this object
+      as parent.
+      arguments:
+
+      * fspath --
+      * name   --
+      """
+      return self.__class__ (
+         fspath = fspath,
+         name   = name,
+         parent = self
+      )
+   # --- end of get_child (...) ---
+
    def get_subdir ( self, relpath ):
+      """Returns a new instance which represents a subdirectory of this dir.
+
+      arguments:
+      * relpath -- path of the new instance, relative to the root of this dir
+      """
       if self.root:
          return self.__class__ (
             fspath = ( self.root + os.sep + relpath ),
@@ -36,6 +69,11 @@ class AdditionsDir ( object ):
    # --- end of get_subdir (...) ---
 
    def get_obj_subdir ( self, obj ):
+      """Like get_obj_subdir(), but uses obj.name as relpath.
+
+      arguments:
+      * obj --
+      """
       return self.get_subdir ( obj.name )
    # --- end of get_obj_subdir (...) ---
 
@@ -43,8 +81,11 @@ class AdditionsDir ( object ):
       return self.root or ""
    # --- end of __str__ (...) ---
 
+# --- end of AdditionsDir ---
+
 
 class _AdditionsDirView ( object ):
+   """view objects implement AdditionsDir actions, e.g. find certain files."""
 
    def __init__ ( self, additions_dir ):
       self._additions_dir = additions_dir
@@ -54,48 +95,72 @@ class _AdditionsDirView ( object ):
       return bool ( self._additions_dir )
    # --- end of __bool__ (...) ---
 
+   @property
+   def name ( self ):
+      return self._additions_dir.name
+   # --- end of name (...) ---
+
+   def _fs_iter_regex ( self, regex ):
+      """Iterates over the content of the additions dir and yields
+      3-tuples ( match, path, name ) for each name that matches the given
+      regex.
+
+      arguments:
+      * regex --
+      """
+      fre = re.compile ( regex )
+
+      for name, fspath in self._additions_dir:
+         fmatch = fre.match ( name )
+         if fmatch:
+            yield ( fmatch, fspath, name )
+   # --- end of _fs_iter_regex (...) ---
+
+# --- end of _AdditionsDirView ---
+
 
 class _EbuildAdditionsView ( _AdditionsDirView ):
 
    # with leading '-'
-   RE_PVR = '[-](?P<pvr>[0-9.]+([-]r[0-9]+)?)'
+   RE_PVR = '[-](?P<pvr>[0-9].*?([-]r[0-9]+)?)'
 
    def __init__ ( self, additions_dir ):
+      """Ebuild additions dir view constructor.
+      Also calls prepare() if declared.
+
+      arguments:
+      * additions_dir --
+      """
       super ( _EbuildAdditionsView, self ).__init__ (
          additions_dir=additions_dir
       )
-      if hasattr ( self, 'prepare' ):
-         self.prepare()
+      if hasattr ( self, 'prepare' ): self.prepare()
    # --- end of __init__ (...) ---
 
-   def _fs_iter_regex ( self, regex ):
-      fre = re.compile ( regex )
-
-      root = self._additions_dir.root
-      for fname in os.listdir ( root ):
-         fmatch = fre.match ( fname )
-         if fmatch:
-            yield ( fmatch, ( root + os.sep + fname ), fname )
-   # --- end of _fs_iter_regex (...) ---
+# --- end of _EbuildAdditionsView ---
 
 
 class EbuildView ( _EbuildAdditionsView ):
+   """View object for finding/importing ebuilds."""
 
    RE_EBUILD_SUFFIX = '[.]ebuild'
 
    def has_ebuilds ( self ):
+      """Returns True if there are any ebuilds that could be imported."""
       return bool ( getattr ( self, '_ebuilds', None ) )
    # --- end of has_ebuilds (...) ---
 
    def get_ebuilds ( self ):
+      """Returns all ebuilds as list of 3-tuples ( pvr, path, name )."""
       return self._ebuilds
    # --- end of get_ebuilds (...) ---
 
    def __iter__ ( self ):
-      return iter ( self.get_ebuilds )
+      return iter ( self.get_ebuilds() )
    # --- end of __iter__ (...) ---
 
-   def _prepare ( self ):
+   def prepare ( self ):
+      """Searches for ebuilds and create self._ebuilds."""
       if self._additions_dir.exists():
          ebuilds = list()
 
@@ -104,21 +169,72 @@ class EbuildView ( _EbuildAdditionsView ):
          ):
             # deref symlinks
             ebuilds.append (
-               fmatch.group ( 'pvr' ), os.path.abspath ( fpath ), fname
+               ( fmatch.group ( 'pvr' ), os.path.abspath ( fpath ), fname )
+            )
+
+         self._ebuilds = ebuilds
+   # --- end of prepare (...) --
+
+# --- end of EbuildView ---
+
+
+class CategoryView ( _AdditionsDirView ):
+   """View object that creates EbuildView objects."""
+
+   def iter_packages ( self ):
+      for name, fspath in self._additions_dir:
+         if os.path.isdir ( fspath ):
+            yield EbuildView (
+               self._additions_dir.get_child ( fspath, name )
+            )
+   # --- end of iter_packages (...) ---
+
+   def __iter__ ( self ):
+      return iter ( self.iter_packages() )
+   # --- end of __iter__ (...) ---
+
+# --- end of CategoryView ---
+
+
+class CategoryRootView ( _AdditionsDirView ):
+   """View object that creates CategoryView objects."""
+
+   def iter_categories ( self ):
+      for name, fspath in self._additions_dir:
+         if os.path.isdir ( fspath ) and (
+            '-' in name or name == 'virtual'
+         ):
+            yield CategoryView (
+               self._additions_dir.get_child ( fspath, name )
             )
-   # --- end of _prepare (...) --
+   # --- end of iter_categories (...) ---
+
+   def __iter__ ( self ):
+      return iter ( self.iter_categories() )
+   # --- end of __iter__ (...) ---
 
+# --- end of CategoryRootView ---
 
 
 class PatchView ( _EbuildAdditionsView ):
+   """View object for finding ebuild patches."""
 
    RE_PATCH_SUFFIX = '(?P<patchno>[0-9]{4})?[.]patch'
 
    def has_patches ( self ):
+      """Returns True if one or more patches are available."""
       return bool ( getattr ( self, '_patches', None ) )
    # --- end of has_patches (...) ---
 
    def get_patches ( self, pvr, fallback_to_default=True ):
+      """Returns a list of patches that should be applied to the ebuild
+      referenced by pvr.
+
+      arguments:
+      * pvr                 -- $PVR of the ebuild
+      * fallback_to_default -- return default patches if no version-specific
+                               ones are available (defaults to True)
+      """
       patches = self._patches.get ( pvr, None )
       if patches:
          return patches
@@ -129,10 +245,12 @@ class PatchView ( _EbuildAdditionsView ):
    # --- end of get_patches (...) ---
 
    def get_default_patches ( self ):
+      """Returns the default patches."""
       return getattr ( self, '_default_patches', EMPTY_TUPLE )
    # --- end of get_default_patches (...) ---
 
    def prepare ( self ):
+      """Searches for ebuild patch files."""
       def patchno_sort ( iterable ):
          return list (
             v[1] for v in sorted ( iterable, key=lambda k: k[0] )
@@ -170,11 +288,23 @@ class PatchView ( _EbuildAdditionsView ):
          ):
             patchno = fmatch.group ( 'patchno' )
 
-            default_patches.append (
-               ( ( -1 if patchno is None else int ( patchno ) ), fpath )
-            )
+            if patchno in self._patches:
+               if len ( self._patches [patchno] ) < 2:
+
+                  del self._patches [patchno]
+                  default_patches.append (
+                     ( ( -1 if patchno is None else int ( patchno ) ), fpath )
+                  )
+               else:
+                  pass
+            else:
+               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 (...) ---
+
+# --- end of PatchView ---

diff --git a/roverlay/overlay/category.py b/roverlay/overlay/category.py
index 0e33fc7..558aa71 100644
--- a/roverlay/overlay/category.py
+++ b/roverlay/overlay/category.py
@@ -91,6 +91,11 @@ class Category ( object ):
    # --- end of add (...) ---
 
    def drop_package ( self, name ):
+      """Removes a package and its fs content from this category.
+
+      arguments:
+      * name -- name of the package
+      """
       p = self._subdirs [name]
       del self._subdirs [name]
       p.fs_destroy()
@@ -117,6 +122,20 @@ class Category ( object ):
       return os.path.isdir ( self.physical_location + os.sep + _dir )
    # --- end of has_category (...) ---
 
+   def import_ebuilds ( self, catview, *args, **kwargs ):
+      """Imports ebuilds into this category.
+
+      arguments:
+      * catview         -- view object that creates EbuildView objects
+      * *args, **kwargs -- (keyword) arguments that will be passed to
+                           package dirs
+      """
+      for eview in catview:
+         self._get_package_dir ( eview.name ).import_ebuilds (
+            eview, *args, **kwargs
+         )
+   # --- end of import_ebuilds (...) ---
+
    def list_package_names ( self ):
       for name, subdir in self._subdirs.items():
          if not subdir.empty():

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

diff --git a/roverlay/overlay/pkgdir/packagedir_base.py b/roverlay/overlay/pkgdir/packagedir_base.py
index e2f0f09..e5c0558 100644
--- a/roverlay/overlay/pkgdir/packagedir_base.py
+++ b/roverlay/overlay/pkgdir/packagedir_base.py
@@ -15,24 +15,33 @@ Each PackageDir instance represents one package name (e.g. "seewave").
 
 __all__ = [ 'PackageDirBase', ]
 
+
 import os
+import shutil
 import sys
 import threading
-import shutil
 
 
-import roverlay.overlay.additionsdir
+import roverlay.config
+import roverlay.packageinfo
+import roverlay.util
+
+import roverlay.tools.ebuild
+import roverlay.tools.ebuildenv
 import roverlay.tools.patch
 
-from roverlay                         import util
-from roverlay.packageinfo             import PackageInfo
-from roverlay.overlay.pkgdir.metadata import MetadataJob
+import roverlay.overlay.additionsdir
+
+import roverlay.overlay.pkgdir.distroot.static
+import roverlay.overlay.pkgdir.metadata
 
 class PackageDirBase ( object ):
    """The PackageDir base class that implements most functionality except
    for Manifest file creation."""
 
+   #DISTROOT =
    EBUILD_SUFFIX       = '.ebuild'
+   #FETCHENV =
    SUPPRESS_EXCEPTIONS = True
 
    # MANIFEST_THREADSAFE (tri-state)
@@ -42,6 +51,20 @@ class PackageDirBase ( object ):
    #
    MANIFEST_THREADSAFE = None
 
+   @classmethod
+   def init_base_cls ( cls ):
+      # env for calling "ebuild <ebuild file> fetch"
+      fetch_env = roverlay.tools.ebuildenv.FetchEnv()
+      fetch_env.add_overlay_dir (
+         roverlay.config.get_or_fail ( 'OVERLAY.dir' )
+      )
+
+      cls.DISTROOT = (
+         roverlay.overlay.pkgdir.distroot.static.get_configured ( static=True )
+      )
+      cls.FETCHENV = fetch_env
+   # --- end of init_cls (...) ---
+
    def __init__ ( self,
       name, logger, directory, get_header, runtime_incremental, parent
    ):
@@ -73,15 +96,16 @@ class PackageDirBase ( object ):
       self.get_header          = get_header
       self.runtime_incremental = runtime_incremental
 
-      self._metadata = MetadataJob (
+      self._metadata = roverlay.overlay.pkgdir.metadata.MetadataJob (
          filepath = self.physical_location + os.sep + 'metadata.xml',
          logger   = self.logger
       )
 
       # <dir>/<PN>-<PVR>.ebuild
-      self.ebuild_filepath_format = \
-         self.physical_location + os.sep + \
-         self.name + "-{PVR}" + self.__class__.EBUILD_SUFFIX
+      self.ebuild_filepath_format = (
+         self.physical_location + os.sep
+         + self.name + "-{PVR}" + self.__class__.EBUILD_SUFFIX
+      )
 
       # used to track changes for this package dir
       self.modified          = False
@@ -115,7 +139,7 @@ class PackageDirBase ( object ):
       * efile -- full path to the ebuild file
       * pvr   -- version ($PVR) of the ebuild
       """
-      p = PackageInfo (
+      p = roverlay.packageinfo.PackageInfo (
          physical_only=True, pvr=pvr, ebuild_file=efile
       )
       self._packages [ p ['ebuild_verstr'] ] = p
@@ -253,7 +277,7 @@ class PackageDirBase ( object ):
    def has_ebuilds ( self ):
       """Returns True if this PackageDir has any ebuild files (filesystem)."""
       for p in self._packages.values():
-         if p ['physical_only'] or p.has ( 'ebuild' ):
+         if p ['physical_only'] or p.has ( 'ebuild' ) or p ['imported']:
             return True
       return False
    # --- end of has_ebuilds (...) ---
@@ -361,6 +385,7 @@ class PackageDirBase ( object ):
    # --- end of purge_package (...) ---
 
    def fs_destroy ( self ):
+      """Destroys the filesystem content of this package dir."""
       pvr_list = list ( self._packages.keys() )
       for pvr in pvr_list:
          self.purge_package ( pvr )
@@ -534,12 +559,13 @@ class PackageDirBase ( object ):
       return success
    # --- end of write (...) ---
 
-   def import_extra_ebuilds ( self, overwrite, additions_dir ):
+   def import_ebuilds ( self, eview, overwrite, nosync=False ):
       """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
+      * eview      -- additions dir ebuild view
+      * overwrite  -- whether to overwrite existing ebuilds or not
+      * nosync     -- if True: don't fetch src files (defaults to False)
       """
 
       def import_ebuild_efile ( pvr, efile_src, fname ):
@@ -568,17 +594,22 @@ class PackageDirBase ( object ):
             shutil.copyfile ( efile_src, efile_dest )
 
             # create PackageInfo and register it
-            p = PackageInfo (
-               imported=True, pvr=pvr, ebuild_file=efile
+            p = roverlay.packageinfo.PackageInfo (
+               imported=True, pvr=pvr, ebuild_file=efile_dest
             )
             self._packages [ p ['ebuild_verstr'] ] = p
 
-
             # manifest needs to be rewritten
             self._need_manifest = True
 
-            # fetch SRC_URI?
-            #  ebuild <ebuild> fetch?
+            # fetch SRC_URI using ebuild(1)
+            if not nosync and not roverlay.tools.ebuild.doebuild_fetch (
+               efile_dest, self.logger,
+               self.FETCHENV.get_env (
+                  self.DISTROOT.get_distdir ( self.name ).get_root()
+               )
+            ):
+               raise Exception ( "doebuild_fetch() failed." )
 
             # imported ebuilds cannot be used for generating metadata.xml
             ##self._need_metadata = True
@@ -602,14 +633,12 @@ class PackageDirBase ( object ):
             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!"
+            "import_ebuilds() needs a non-virtual package dir!"
          )
       elif eview.has_ebuilds():
-         util.dodir ( self.physical_location, mkdir_p=True )
+         roverlay.util.dodir ( self.physical_location, mkdir_p=True )
 
          if not self._packages:
             for pvr, efile, fname in eview.get_ebuilds():
@@ -625,7 +654,7 @@ class PackageDirBase ( object ):
             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 (...) ---
+   # --- end of import_ebuilds (...) ---
 
    def write_ebuilds ( self, overwrite, additions_dir, shared_fh=None ):
       """Writes all ebuilds.
@@ -744,7 +773,7 @@ class PackageDirBase ( object ):
 
       for pvr, efile, p_info in list ( ebuilds_to_write() ):
          if not hasdir:
-            util.dodir ( self.physical_location, mkdir_p=True )
+            roverlay.util.dodir ( self.physical_location, mkdir_p=True )
             hasdir = True
 
          if (
@@ -823,6 +852,12 @@ class PackageDirBase ( object ):
             return True
          else:
             return False
+      elif (
+         hasattr ( self, '_write_import_manifest' )
+         and self._write_import_manifest()
+      ):
+         self._need_manifest = False
+         return True
       elif ignore_empty:
          return True
       else:
@@ -843,7 +878,7 @@ class PackageDirBase ( object ):
          self.generate_metadata ( skip_if_existent=True )
 
          if shared_fh is None:
-            util.dodir ( self.physical_location, mkdir_p=True )
+            roverlay.util.dodir ( self.physical_location, mkdir_p=True )
             if self._metadata.write():
                self._need_metadata = False
                self._need_manifest = True

diff --git a/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py b/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
index c9e3d84..5217f6a 100644
--- a/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
+++ b/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
@@ -6,81 +6,106 @@
 
 __all__ = [ 'PackageDir', ]
 
+import os
 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 ( 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 ):
+      cls.init_base_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.
+   def _do_ebuildmanifest ( self, ebuild_file, distdir=None ):
+      """Calls doebuild_manifest().
+      Returns True on success, else False. Also handles result logging.
 
-      expects: called after writing metadata/ebuilds
-
-      returns: success (True/False)
+      arguments:
+      * ebuild_file -- ebuild file that should be used for the doebuild call
+      * distdir     -- distdir object (optional)
       """
       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 (
+         call = 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
+            self.MANIFEST_ENV.get_env (
+               distdir.get_root() if distdir is not None else (
+               self.DISTROOT.get_distdir ( self.name ).get_root()
                )
-            )
-            ret = False
-
+            ),
+            return_success=False
+         )
       except Exception as err:
          self.logger.exception ( err )
          raise
+      # -- end try
+
+      if call.returncode == os.EX_OK:
+         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=ret )
+         )
+         return False
+   # --- end of _do_ebuildmanifest (...) ---
+
+   def _write_import_manifest ( self ):
+      """Writes a Manifest file if this package has any imported ebuilds.
+
+      Returns True if a Manifest has been written, else False.
+      """
+      try:
+         pkg = next (
+            p for p in self._packages.values()
+            if p.has ( 'imported', 'ebuild_file' )
+         )
+      except StopIteration:
+         # no imported ebuilds
+         return False
+      # -- end try
+
+      self.logger.debug ( "Writing (import-)Manifest" )
+      return self._do_ebuildmanifest ( pkg ['ebuild_file'] )
+   # --- end of _write_import_manifest (...) ---
+
+   def _write_manifest ( self, pkgs_for_manifest ):
+      """Generates and writes the Manifest file for this package.
 
-      finally:
-         self.MANIFEST_LOCK.release()
+      expects: called after writing metadata/ebuilds
 
-      return ret
+      returns: success (True/False)
+      """
+      # choosing one ebuild for calling "ebuild <ebuild>" is sufficient
+      ebuild_file = pkgs_for_manifest [0] ['ebuild_file']
+      distdir     = self.DISTROOT.get_distdir ( self.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'] )
+      # -- end for;
+
+      return self._do_ebuildmanifest ( ebuild_file, distdir )
    # --- end of write_manifest (...) ---
+
+# --- end of PackageDir #ebuildmanifest ---

diff --git a/roverlay/overlay/pkgdir/packagedir_portagemanifest.py b/roverlay/overlay/pkgdir/packagedir_portagemanifest.py
index 3c4d728..6596dec 100644
--- a/roverlay/overlay/pkgdir/packagedir_portagemanifest.py
+++ b/roverlay/overlay/pkgdir/packagedir_portagemanifest.py
@@ -13,7 +13,9 @@ import portage.manifest
 import portage.exception
 
 import logging
-logging.getLogger ( __name__ ).warning ( "experimental code" )
+logging.getLogger ( __name__ ).warning (
+   "experimental code, importing ebuilds doesn't work!"
+)
 del logging
 
 import roverlay.config
@@ -23,9 +25,17 @@ import roverlay.overlay.pkgdir.packagedir_base
 import roverlay.overlay.pkgdir.distroot
 
 class PackageDir ( roverlay.overlay.pkgdir.packagedir_base.PackageDirBase ):
+   # FIXME: portagemanifest is broken, it cannot create Manifest files for
+   # package dirs with imported ebuilds
 
    MANIFEST_THREADSAFE = True
 
+   def import_ebuilds ( self, *args, **kwargs ):
+      raise NotImplementedError (
+         "ebuild imports not supported by portagemanifest!"
+      )
+   # --- end of import_ebuilds (...) ---
+
    def _scan_add_package ( self, efile, pvr ):
       """Called for each ebuild that is found during scan().
       Creates a PackageInfo for the ebuild and adds it to self._packages.
@@ -107,7 +117,7 @@ class PackageDir ( roverlay.overlay.pkgdir.packagedir_base.PackageDirBase ):
       # * ...
       #
 
-      distdir = roverlay.overlay.pkgdir.distroot.get_distdir ( self.name )
+      distdir = self.DISTROOT.get_distdir ( self.name )
 
       # allow_missing=True -- don't write empty Manifest files
       manifest = portage.manifest.Manifest (

diff --git a/roverlay/overlay/root.py b/roverlay/overlay/root.py
index ca71b81..a9d16c4 100644
--- a/roverlay/overlay/root.py
+++ b/roverlay/overlay/root.py
@@ -17,24 +17,24 @@ due do double-linkage between PackageInfo and PackageDir.
 
 __all__ = [ 'Overlay', ]
 
-import threading
 import logging
-import shutil
 import os
+import shutil
+import threading
 
-from roverlay import config, util
-
-from roverlay.overlay.category import Category
-from roverlay.overlay.header   import EbuildHeader
-
+import roverlay.config
+import roverlay.util
 import roverlay.overlay.additionsdir
+import roverlay.overlay.category
+import roverlay.overlay.header
+
 
 
 class Overlay ( object ):
-   DEFAULT_USE_DESC = '\n'.join ( (
-      'byte-compile - enable byte compiling',
+   DEFAULT_USE_DESC = (
+      'byte-compile - enable byte compiling\n'
       'R_suggests - install recommended packages'
-   ) )
+   )
 
    @classmethod
    def new_configured ( cls,
@@ -51,23 +51,24 @@ class Overlay ( object ):
       * write_allowed       --
       * skip_manifest       --
       * runtime_incremental --
-      * readonly            -- see Overlay.__init__
       """
-      name = config.get_or_fail ( 'OVERLAY.name' )
+      optional  = roverlay.config.get
+      mandatory = roverlay.config.get_or_fail
+
       return cls (
-         name                = name,
-         logger              = (
-            logger or logging.getLogger ( 'Overlay:' + name )
-         ),
-         directory           = config.get_or_fail ( 'OVERLAY.dir' ),
-         default_category    = config.get_or_fail ( 'OVERLAY.category' ),
-         eclass_files        = config.get ( 'OVERLAY.eclass_files',  None ),
-         ebuild_header       = config.get ( 'EBUILD.default_header', None ),
-         incremental         = incremental,
+         name                = mandatory ( 'OVERLAY.name' ),
+         logger              = logger,
+         directory           = mandatory ( 'OVERLAY.dir' ),
+         default_category    = mandatory ( 'OVERLAY.category' ),
+         eclass_files        = optional  ( 'OVERLAY.eclass_files' ),
+         ebuild_header       = optional  ( 'EBUILD.default_header' ),
          write_allowed       = write_allowed,
+         incremental         = incremental,
          skip_manifest       = skip_manifest,
-         additions_dir       = config.get_or_fail ( 'OVERLAY.additions_dir' ),
+         additions_dir       = optional  ( 'OVERLAY.additions_dir' ),
+         use_desc            = optional  ( 'OVERLAY.use_desc' ),
          runtime_incremental = runtime_incremental,
+         keep_n_ebuilds      = optional  ( 'OVERLAY.keep_nth_latest' ),
       )
    # --- end of new_configured (...) ---
 
@@ -83,7 +84,9 @@ class Overlay ( object ):
       incremental,
       skip_manifest,
       additions_dir,
-      runtime_incremental=False
+      use_desc=None,
+      runtime_incremental=False,
+      keep_n_ebuilds=None
    ):
       """Initializes an overlay.
 
@@ -108,9 +111,11 @@ class Overlay ( object ):
                                patches. The directory has to exist (it will
                                be checked here).
                                A value of None or "" disables additions.
+      * use_desc            -- text for profiles/use.desc
       * runtime_incremental -- see package.py:PackageDir.__init__ (...),
                                 Defaults to False (saves memory but costs time)
-
+      * keep_n_ebuilds      -- number of ebuilds to keep (per package),
+                               any "false" Value (None, 0, ...) disables this
       """
       self.name                 = name
       self.logger               = logger.getChild ( 'overlay' )
@@ -126,17 +131,31 @@ class Overlay ( object ):
       self._profiles_dir        = self.physical_location + os.sep + 'profiles'
       self._catlock             = threading.Lock()
       self._categories          = dict()
-      self._header              = EbuildHeader ( ebuild_header )
 
       self.skip_manifest        = skip_manifest
 
+      self._header   = roverlay.overlay.header.EbuildHeader ( ebuild_header )
+      self._use_desc = (
+         use_desc.rstrip() if use_desc is not None else self.DEFAULT_USE_DESC
+      )
+
+      if keep_n_ebuilds:
+         self.keep_n_ebuilds = keep_n_ebuilds
+
       if additions_dir:
          if os.path.isdir ( additions_dir ):
-            self.additions_dirpath = os.path.abspath ( additions_dir )
+            additions_dirpath = os.path.abspath ( additions_dir )
          else:
             raise ValueError (
                "additions dir {} does not exist!".format ( additions_dir )
             )
+      else:
+         additions_dirpath = None
+      # -- end if
+
+      self.additions_dir = (
+         roverlay.overlay.additionsdir.AdditionsDir ( additions_dirpath )
+      )
 
       # calculating eclass names twice,
       # once here and another time when calling _init_overlay
@@ -150,6 +169,8 @@ class Overlay ( object ):
          # incremental writing, which writes ebuilds as soon as they're
          # ready)
          self.scan()
+
+      self.import_ebuilds ( overwrite=( not incremental ) )
    # --- end of __init__ (...) ---
 
    def _get_category ( self, category ):
@@ -162,7 +183,7 @@ class Overlay ( object ):
          self._catlock.acquire()
          try:
             if not category in self._categories:
-               newcat = Category (
+               newcat = roverlay.overlay.category.Category (
                   category,
                   self.logger,
                   self.physical_location + os.sep + category,
@@ -218,7 +239,7 @@ class Overlay ( object ):
          eclass_dir = self.physical_location + os.sep +  'eclass'
          try:
             eclass_names = list()
-            util.dodir ( eclass_dir )
+            roverlay.util.dodir ( eclass_dir )
 
             for destname, eclass in self._get_eclass_import_info ( False ):
                dest = eclass_dir + os.sep +  destname + '.eclass'
@@ -271,7 +292,7 @@ class Overlay ( object ):
          self._get_category ( self.default_category )
 
          # profiles/
-         util.dodir ( self._profiles_dir )
+         roverlay.util.dodir ( self._profiles_dir )
 
          # profiless/repo_name
          write_profiles_file ( 'repo_name', self.name + '\n' )
@@ -284,17 +305,13 @@ class Overlay ( object ):
             write_profiles_file ( 'categories', cats + '\n' )
 
          # profiles/use.desc
-         use_desc = config.get (
-            'OVERLAY.use_desc',
-            fallback_value=self.__class__.DEFAULT_USE_DESC
-         )
-         if use_desc:
-            write_profiles_file ( 'use.desc', use_desc + '\n' )
+         if self._use_desc:
+            write_profiles_file ( 'use.desc', self._use_desc + '\n' )
       # --- end of write_profiles_dir (...) ---
 
       try:
          # mkdir overlay root
-         util.dodir ( self.physical_location, mkdir_p=True )
+         roverlay.util.dodir ( self.physical_location, mkdir_p=True )
 
          self._import_eclass ( reimport_eclass )
 
@@ -340,13 +357,19 @@ class Overlay ( object ):
    # --- end of has_category (...) ---
 
    def find_duplicate_packages ( self, _default_category=None ):
+      """Searches for packages that exist in the default category and
+      another one and returns a set of package names.
+
+      arguments:
+      * _default_category -- category object
+      """
       default_category = (
          _default_category if _default_category is None
          else self._categories.get ( self.default_category, None )
       )
 
       if default_category:
-         duplicate_pkg  = set()
+         duplicate_pkg = set()
 
          for category in self._categories.values():
             if category is not default_category:
@@ -361,6 +384,13 @@ class Overlay ( object ):
    # --- end of find_duplicate_packages (...) ---
 
    def remove_duplicate_ebuilds ( self, reverse ):
+      """Searcges for packages that exist in the default category and
+      another one and removes them from either one, depending on whether
+      reverse if True (other will be removed) or False (default category).
+
+      arguments:
+      * reverse
+      """
       default_category = self._categories.get ( self.default_category, None )
       if default_category:
          if reverse:
@@ -398,6 +428,7 @@ class Overlay ( object ):
    # --- end of remove_duplicate_ebuilds (...) ---
 
    def remove_empty_categories ( self ):
+      """Removes empty categories."""
       catlist = self._categories.items()
       for cat in catlist:
          cat[1].remove_empty()
@@ -430,6 +461,21 @@ class Overlay ( object ):
       return not self._writeable
    # --- end of readonly (...) ---
 
+   def import_ebuilds ( self, overwrite, nosync=False ):
+      """Imports ebuilds from the additions dir.
+
+      arguments:
+      * overwrite -- whether to overwrite existing ebuilds
+      * nosync    -- if True: don't fetch src files (defaults to False)
+      """
+      for catview in (
+         roverlay.overlay.additionsdir.CategoryRootView ( self.additions_dir )
+      ):
+         self._get_category ( catview.name ).import_ebuilds (
+            catview, overwrite=overwrite
+         )
+   # --- end of import_ebuilds (...) ---
+
    def scan ( self, **kw ):
       def scan_categories():
          for x in os.listdir ( self.physical_location ):
@@ -478,20 +524,17 @@ class Overlay ( object ):
       Note: This is not thread-safe, it's expected to be called when
       ebuild creation is done.
       """
+
       if self._writeable:
          self._init_overlay ( reimport_eclass=True )
 
-         additions_dir = roverlay.overlay.additionsdir.AdditionsDir (
-            getattr ( self, 'additions_dirpath', None )
-         )
-
          for cat in self._categories.values():
             cat.write (
                overwrite_ebuilds = False,
-               keep_n_ebuilds    = config.get ( 'OVERLAY.keep_nth_latest', None ),
+               keep_n_ebuilds    = getattr ( self, 'keep_n_ebuilds', None ),
                cautious          = True,
                write_manifest    = not self.skip_manifest,
-               additions_dir     = additions_dir.get_obj_subdir ( cat ),
+               additions_dir     = self.additions_dir.get_obj_subdir ( cat ),
             )
       else:
          # FIXME debug print

diff --git a/roverlay/packageinfo.py b/roverlay/packageinfo.py
index 84a7d45..90b4250 100644
--- a/roverlay/packageinfo.py
+++ b/roverlay/packageinfo.py
@@ -98,6 +98,7 @@ class PackageInfo ( object ):
       'origin',
       'ebuild',
       'ebuild_file',
+      'imported',
       'physical_only',
       'src_uri',
    ))
@@ -341,6 +342,10 @@ class PackageInfo ( object ):
          # 'physical_only' not in self._info -> assume False
          return False
 
+      elif key_low == 'imported':
+         # 'imported' not in self._info -> assume False
+         return False
+
       elif key_low == 'src_uri':
          if 'src_uri_base' in self._info:
             return \
@@ -386,6 +391,50 @@ class PackageInfo ( object ):
          raise KeyError ( key )
    # --- end of get (...) ---
 
+   def get_create (
+      self, key, newtype, convert=False, check_type=True, create_kw=None
+   ):
+      """Tries to get a value from the info dict. Creates it as newtype if
+      necessary.
+
+      Note: This operation is "unsafe". No locks will be acquired etc.
+
+      arguments:
+      * key         -- info key
+      * newtype     -- "expected type", also used for creating new values
+      * convert     -- if True: convert existing value (defaults to False)
+      * check_type  -- if True: check whether the type of existing value is
+                       a (sub-)type of newtype (defaults to True)
+                       This arg can also be a type.
+                       Has no effect if convert is set to True
+      * create_kw   -- either None or a dict that will used as keyword args
+                       when creating newtype
+      """
+      v = self.get ( key, do_fallback=True )
+      if v is None:
+         newv = newtype ( **create_kw ) if create_kw else newtype()
+         self._info [key] = newv
+         return newv
+      elif convert:
+         return newtype ( v )
+      elif check_type:
+         want_type = (
+            check_type if ( type ( check_type ) is type ) else newtype
+         )
+
+         #if type ( v ) is want_type:
+         if isinstance ( v, want_type ):
+            return v
+         else:
+            raise TypeError (
+               "key {k} should have type {t0}, but is a {t1}!".format (
+                  k=key, t0=want_type, t1=type(v)
+               )
+            )
+      else:
+         return v
+   # --- end of get_create (...) ---
+
    def get_desc_data ( self ):
       """Returns the DESCRIPTION data for this PackageInfo (by reading the
       R package file if necessary).
@@ -557,7 +606,7 @@ class PackageInfo ( object ):
 
          else:
             self.logger.error (
-               "in _update(): unknown info key {}!".format ( key )
+               "in _update(): unknown info key {!r}!".format ( key )
             )
       # -- end for;
 

diff --git a/roverlay/tools/ebuild.py b/roverlay/tools/ebuild.py
index d9f4e9a..e8f7e37 100644
--- a/roverlay/tools/ebuild.py
+++ b/roverlay/tools/ebuild.py
@@ -20,6 +20,7 @@ _EBUILD_CMDV = (
 def doebuild (
    ebuild_file, command, logger, env=None, opts=(), return_success=True
 ):
+   logger.debug ( "doebuild: {c}, {e!r}".format ( e=ebuild_file, c=command ) )
    return roverlay.tools.runcmd.run_command (
       cmdv           = ( _EBUILD_CMDV + opts + ( ebuild_file, command ) ),
       env            = env,

diff --git a/roverlay/tools/ebuildenv.py b/roverlay/tools/ebuildenv.py
index 40f6495..7c5accb 100644
--- a/roverlay/tools/ebuildenv.py
+++ b/roverlay/tools/ebuildenv.py
@@ -101,8 +101,14 @@ class EbuildEnv ( object ):
 
 # --- end of EbuildEnv ---
 
+
 class FetchEnv ( EbuildEnv ):
-   pass
+   def _make_common_env ( self ):
+      super ( FetchEnv, self )._make_common_env()
+      # "Cannot chown a lockfile"
+      self._common_env ['FEATURES'] += " -distlocks"
+   # --- end of _make_common_env (...) ---
+
 # --- end of FetchEnv ---
 
 


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

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

commit:     fe217214d62189a5157188c9f7ddfafa607f26c3
Author:     André Erdmann <dywi <AT> mailerd <DOT> de>
AuthorDate: Wed Jun 12 20:59:47 2013 +0000
Commit:     André Erdmann <dywi <AT> mailerd <DOT> de>
CommitDate: Wed Jun 12 20:59:47 2013 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=fe217214

additions dir: code comments/fixup

Besides other changes, this commit makes the "import hand-written ebuilds"
feature available. Note that this (currently) only works with the (default)
ebuildmanifest pkg dir implementation.

Other changes include:

* additions dir:
** more lenient regex in _EbuildAdditionsView
** handle ambiguous patch file names (where version could be patchno)
* pkgdir_base
** fetch src of imported ebuilds
* packageinfo
** update(): put unknown keys in quotes
** get_create(): get an info key by creating it, if necessary

---
 roverlay/overlay/additionsdir.py                   | 170 ++++++++++++++++++---
 roverlay/overlay/category.py                       |  19 +++
 roverlay/overlay/pkgdir/__init__.py                |   2 +
 roverlay/overlay/pkgdir/packagedir_base.py         |  87 +++++++----
 .../overlay/pkgdir/packagedir_ebuildmanifest.py    | 115 ++++++++------
 .../overlay/pkgdir/packagedir_portagemanifest.py   |  14 +-
 roverlay/overlay/root.py                           | 129 ++++++++++------
 roverlay/packageinfo.py                            |  51 ++++++-
 roverlay/tools/ebuild.py                           |   1 +
 roverlay/tools/ebuildenv.py                        |   8 +-
 10 files changed, 458 insertions(+), 138 deletions(-)

diff --git a/roverlay/overlay/additionsdir.py b/roverlay/overlay/additionsdir.py
index f0a6be4..3031a69 100644
--- a/roverlay/overlay/additionsdir.py
+++ b/roverlay/overlay/additionsdir.py
@@ -9,8 +9,10 @@ import re
 
 EMPTY_TUPLE = ()
 
-
 class AdditionsDir ( object ):
+   """AdditionsDir represents a filesystem directory (that does not need
+   to exist).
+   """
 
    def __init__ ( self, fspath, name=None, parent=None ):
       self.root   = str ( fspath ) if fspath else None
@@ -24,7 +26,38 @@ class AdditionsDir ( object ):
 
    __bool__ = exists
 
+   def iter_entries ( self ):
+      """Generator that yields the directory content of this dir."""
+      if self.exists():
+         for name in os.listdir ( self.root ):
+            yield ( name, ( self.root + os.sep + name ) )
+   # --- end of iter_entries (...) ---
+
+   def __iter__ ( self ):
+      return iter ( self.iter_entries() )
+   # --- end of __iter__ (...) ---
+
+   def get_child ( self, fspath, name ):
+      """Returns a new instance with the given fspath/name and this object
+      as parent.
+      arguments:
+
+      * fspath --
+      * name   --
+      """
+      return self.__class__ (
+         fspath = fspath,
+         name   = name,
+         parent = self
+      )
+   # --- end of get_child (...) ---
+
    def get_subdir ( self, relpath ):
+      """Returns a new instance which represents a subdirectory of this dir.
+
+      arguments:
+      * relpath -- path of the new instance, relative to the root of this dir
+      """
       if self.root:
          return self.__class__ (
             fspath = ( self.root + os.sep + relpath ),
@@ -36,6 +69,11 @@ class AdditionsDir ( object ):
    # --- end of get_subdir (...) ---
 
    def get_obj_subdir ( self, obj ):
+      """Like get_obj_subdir(), but uses obj.name as relpath.
+
+      arguments:
+      * obj --
+      """
       return self.get_subdir ( obj.name )
    # --- end of get_obj_subdir (...) ---
 
@@ -43,8 +81,11 @@ class AdditionsDir ( object ):
       return self.root or ""
    # --- end of __str__ (...) ---
 
+# --- end of AdditionsDir ---
+
 
 class _AdditionsDirView ( object ):
+   """view objects implement AdditionsDir actions, e.g. find certain files."""
 
    def __init__ ( self, additions_dir ):
       self._additions_dir = additions_dir
@@ -54,48 +95,72 @@ class _AdditionsDirView ( object ):
       return bool ( self._additions_dir )
    # --- end of __bool__ (...) ---
 
+   @property
+   def name ( self ):
+      return self._additions_dir.name
+   # --- end of name (...) ---
+
+   def _fs_iter_regex ( self, regex ):
+      """Iterates over the content of the additions dir and yields
+      3-tuples ( match, path, name ) for each name that matches the given
+      regex.
+
+      arguments:
+      * regex --
+      """
+      fre = re.compile ( regex )
+
+      for name, fspath in self._additions_dir:
+         fmatch = fre.match ( name )
+         if fmatch:
+            yield ( fmatch, fspath, name )
+   # --- end of _fs_iter_regex (...) ---
+
+# --- end of _AdditionsDirView ---
+
 
 class _EbuildAdditionsView ( _AdditionsDirView ):
 
    # with leading '-'
-   RE_PVR = '[-](?P<pvr>[0-9.]+([-]r[0-9]+)?)'
+   RE_PVR = '[-](?P<pvr>[0-9].*?([-]r[0-9]+)?)'
 
    def __init__ ( self, additions_dir ):
+      """Ebuild additions dir view constructor.
+      Also calls prepare() if declared.
+
+      arguments:
+      * additions_dir --
+      """
       super ( _EbuildAdditionsView, self ).__init__ (
          additions_dir=additions_dir
       )
-      if hasattr ( self, 'prepare' ):
-         self.prepare()
+      if hasattr ( self, 'prepare' ): self.prepare()
    # --- end of __init__ (...) ---
 
-   def _fs_iter_regex ( self, regex ):
-      fre = re.compile ( regex )
-
-      root = self._additions_dir.root
-      for fname in os.listdir ( root ):
-         fmatch = fre.match ( fname )
-         if fmatch:
-            yield ( fmatch, ( root + os.sep + fname ), fname )
-   # --- end of _fs_iter_regex (...) ---
+# --- end of _EbuildAdditionsView ---
 
 
 class EbuildView ( _EbuildAdditionsView ):
+   """View object for finding/importing ebuilds."""
 
    RE_EBUILD_SUFFIX = '[.]ebuild'
 
    def has_ebuilds ( self ):
+      """Returns True if there are any ebuilds that could be imported."""
       return bool ( getattr ( self, '_ebuilds', None ) )
    # --- end of has_ebuilds (...) ---
 
    def get_ebuilds ( self ):
+      """Returns all ebuilds as list of 3-tuples ( pvr, path, name )."""
       return self._ebuilds
    # --- end of get_ebuilds (...) ---
 
    def __iter__ ( self ):
-      return iter ( self.get_ebuilds )
+      return iter ( self.get_ebuilds() )
    # --- end of __iter__ (...) ---
 
-   def _prepare ( self ):
+   def prepare ( self ):
+      """Searches for ebuilds and create self._ebuilds."""
       if self._additions_dir.exists():
          ebuilds = list()
 
@@ -104,21 +169,72 @@ class EbuildView ( _EbuildAdditionsView ):
          ):
             # deref symlinks
             ebuilds.append (
-               fmatch.group ( 'pvr' ), os.path.abspath ( fpath ), fname
+               ( fmatch.group ( 'pvr' ), os.path.abspath ( fpath ), fname )
+            )
+
+         self._ebuilds = ebuilds
+   # --- end of prepare (...) --
+
+# --- end of EbuildView ---
+
+
+class CategoryView ( _AdditionsDirView ):
+   """View object that creates EbuildView objects."""
+
+   def iter_packages ( self ):
+      for name, fspath in self._additions_dir:
+         if os.path.isdir ( fspath ):
+            yield EbuildView (
+               self._additions_dir.get_child ( fspath, name )
+            )
+   # --- end of iter_packages (...) ---
+
+   def __iter__ ( self ):
+      return iter ( self.iter_packages() )
+   # --- end of __iter__ (...) ---
+
+# --- end of CategoryView ---
+
+
+class CategoryRootView ( _AdditionsDirView ):
+   """View object that creates CategoryView objects."""
+
+   def iter_categories ( self ):
+      for name, fspath in self._additions_dir:
+         if os.path.isdir ( fspath ) and (
+            '-' in name or name == 'virtual'
+         ):
+            yield CategoryView (
+               self._additions_dir.get_child ( fspath, name )
             )
-   # --- end of _prepare (...) --
+   # --- end of iter_categories (...) ---
+
+   def __iter__ ( self ):
+      return iter ( self.iter_categories() )
+   # --- end of __iter__ (...) ---
 
+# --- end of CategoryRootView ---
 
 
 class PatchView ( _EbuildAdditionsView ):
+   """View object for finding ebuild patches."""
 
    RE_PATCH_SUFFIX = '(?P<patchno>[0-9]{4})?[.]patch'
 
    def has_patches ( self ):
+      """Returns True if one or more patches are available."""
       return bool ( getattr ( self, '_patches', None ) )
    # --- end of has_patches (...) ---
 
    def get_patches ( self, pvr, fallback_to_default=True ):
+      """Returns a list of patches that should be applied to the ebuild
+      referenced by pvr.
+
+      arguments:
+      * pvr                 -- $PVR of the ebuild
+      * fallback_to_default -- return default patches if no version-specific
+                               ones are available (defaults to True)
+      """
       patches = self._patches.get ( pvr, None )
       if patches:
          return patches
@@ -129,10 +245,12 @@ class PatchView ( _EbuildAdditionsView ):
    # --- end of get_patches (...) ---
 
    def get_default_patches ( self ):
+      """Returns the default patches."""
       return getattr ( self, '_default_patches', EMPTY_TUPLE )
    # --- end of get_default_patches (...) ---
 
    def prepare ( self ):
+      """Searches for ebuild patch files."""
       def patchno_sort ( iterable ):
          return list (
             v[1] for v in sorted ( iterable, key=lambda k: k[0] )
@@ -170,11 +288,23 @@ class PatchView ( _EbuildAdditionsView ):
          ):
             patchno = fmatch.group ( 'patchno' )
 
-            default_patches.append (
-               ( ( -1 if patchno is None else int ( patchno ) ), fpath )
-            )
+            if patchno in self._patches:
+               if len ( self._patches [patchno] ) < 2:
+
+                  del self._patches [patchno]
+                  default_patches.append (
+                     ( ( -1 if patchno is None else int ( patchno ) ), fpath )
+                  )
+               else:
+                  pass
+            else:
+               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 (...) ---
+
+# --- end of PatchView ---

diff --git a/roverlay/overlay/category.py b/roverlay/overlay/category.py
index 0e33fc7..558aa71 100644
--- a/roverlay/overlay/category.py
+++ b/roverlay/overlay/category.py
@@ -91,6 +91,11 @@ class Category ( object ):
    # --- end of add (...) ---
 
    def drop_package ( self, name ):
+      """Removes a package and its fs content from this category.
+
+      arguments:
+      * name -- name of the package
+      """
       p = self._subdirs [name]
       del self._subdirs [name]
       p.fs_destroy()
@@ -117,6 +122,20 @@ class Category ( object ):
       return os.path.isdir ( self.physical_location + os.sep + _dir )
    # --- end of has_category (...) ---
 
+   def import_ebuilds ( self, catview, *args, **kwargs ):
+      """Imports ebuilds into this category.
+
+      arguments:
+      * catview         -- view object that creates EbuildView objects
+      * *args, **kwargs -- (keyword) arguments that will be passed to
+                           package dirs
+      """
+      for eview in catview:
+         self._get_package_dir ( eview.name ).import_ebuilds (
+            eview, *args, **kwargs
+         )
+   # --- end of import_ebuilds (...) ---
+
    def list_package_names ( self ):
       for name, subdir in self._subdirs.items():
          if not subdir.empty():

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

diff --git a/roverlay/overlay/pkgdir/packagedir_base.py b/roverlay/overlay/pkgdir/packagedir_base.py
index e2f0f09..e5c0558 100644
--- a/roverlay/overlay/pkgdir/packagedir_base.py
+++ b/roverlay/overlay/pkgdir/packagedir_base.py
@@ -15,24 +15,33 @@ Each PackageDir instance represents one package name (e.g. "seewave").
 
 __all__ = [ 'PackageDirBase', ]
 
+
 import os
+import shutil
 import sys
 import threading
-import shutil
 
 
-import roverlay.overlay.additionsdir
+import roverlay.config
+import roverlay.packageinfo
+import roverlay.util
+
+import roverlay.tools.ebuild
+import roverlay.tools.ebuildenv
 import roverlay.tools.patch
 
-from roverlay                         import util
-from roverlay.packageinfo             import PackageInfo
-from roverlay.overlay.pkgdir.metadata import MetadataJob
+import roverlay.overlay.additionsdir
+
+import roverlay.overlay.pkgdir.distroot.static
+import roverlay.overlay.pkgdir.metadata
 
 class PackageDirBase ( object ):
    """The PackageDir base class that implements most functionality except
    for Manifest file creation."""
 
+   #DISTROOT =
    EBUILD_SUFFIX       = '.ebuild'
+   #FETCHENV =
    SUPPRESS_EXCEPTIONS = True
 
    # MANIFEST_THREADSAFE (tri-state)
@@ -42,6 +51,20 @@ class PackageDirBase ( object ):
    #
    MANIFEST_THREADSAFE = None
 
+   @classmethod
+   def init_base_cls ( cls ):
+      # env for calling "ebuild <ebuild file> fetch"
+      fetch_env = roverlay.tools.ebuildenv.FetchEnv()
+      fetch_env.add_overlay_dir (
+         roverlay.config.get_or_fail ( 'OVERLAY.dir' )
+      )
+
+      cls.DISTROOT = (
+         roverlay.overlay.pkgdir.distroot.static.get_configured ( static=True )
+      )
+      cls.FETCHENV = fetch_env
+   # --- end of init_cls (...) ---
+
    def __init__ ( self,
       name, logger, directory, get_header, runtime_incremental, parent
    ):
@@ -73,15 +96,16 @@ class PackageDirBase ( object ):
       self.get_header          = get_header
       self.runtime_incremental = runtime_incremental
 
-      self._metadata = MetadataJob (
+      self._metadata = roverlay.overlay.pkgdir.metadata.MetadataJob (
          filepath = self.physical_location + os.sep + 'metadata.xml',
          logger   = self.logger
       )
 
       # <dir>/<PN>-<PVR>.ebuild
-      self.ebuild_filepath_format = \
-         self.physical_location + os.sep + \
-         self.name + "-{PVR}" + self.__class__.EBUILD_SUFFIX
+      self.ebuild_filepath_format = (
+         self.physical_location + os.sep
+         + self.name + "-{PVR}" + self.__class__.EBUILD_SUFFIX
+      )
 
       # used to track changes for this package dir
       self.modified          = False
@@ -115,7 +139,7 @@ class PackageDirBase ( object ):
       * efile -- full path to the ebuild file
       * pvr   -- version ($PVR) of the ebuild
       """
-      p = PackageInfo (
+      p = roverlay.packageinfo.PackageInfo (
          physical_only=True, pvr=pvr, ebuild_file=efile
       )
       self._packages [ p ['ebuild_verstr'] ] = p
@@ -253,7 +277,7 @@ class PackageDirBase ( object ):
    def has_ebuilds ( self ):
       """Returns True if this PackageDir has any ebuild files (filesystem)."""
       for p in self._packages.values():
-         if p ['physical_only'] or p.has ( 'ebuild' ):
+         if p ['physical_only'] or p.has ( 'ebuild' ) or p ['imported']:
             return True
       return False
    # --- end of has_ebuilds (...) ---
@@ -361,6 +385,7 @@ class PackageDirBase ( object ):
    # --- end of purge_package (...) ---
 
    def fs_destroy ( self ):
+      """Destroys the filesystem content of this package dir."""
       pvr_list = list ( self._packages.keys() )
       for pvr in pvr_list:
          self.purge_package ( pvr )
@@ -534,12 +559,13 @@ class PackageDirBase ( object ):
       return success
    # --- end of write (...) ---
 
-   def import_extra_ebuilds ( self, overwrite, additions_dir ):
+   def import_ebuilds ( self, eview, overwrite, nosync=False ):
       """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
+      * eview      -- additions dir ebuild view
+      * overwrite  -- whether to overwrite existing ebuilds or not
+      * nosync     -- if True: don't fetch src files (defaults to False)
       """
 
       def import_ebuild_efile ( pvr, efile_src, fname ):
@@ -568,17 +594,22 @@ class PackageDirBase ( object ):
             shutil.copyfile ( efile_src, efile_dest )
 
             # create PackageInfo and register it
-            p = PackageInfo (
-               imported=True, pvr=pvr, ebuild_file=efile
+            p = roverlay.packageinfo.PackageInfo (
+               imported=True, pvr=pvr, ebuild_file=efile_dest
             )
             self._packages [ p ['ebuild_verstr'] ] = p
 
-
             # manifest needs to be rewritten
             self._need_manifest = True
 
-            # fetch SRC_URI?
-            #  ebuild <ebuild> fetch?
+            # fetch SRC_URI using ebuild(1)
+            if not nosync and not roverlay.tools.ebuild.doebuild_fetch (
+               efile_dest, self.logger,
+               self.FETCHENV.get_env (
+                  self.DISTROOT.get_distdir ( self.name ).get_root()
+               )
+            ):
+               raise Exception ( "doebuild_fetch() failed." )
 
             # imported ebuilds cannot be used for generating metadata.xml
             ##self._need_metadata = True
@@ -602,14 +633,12 @@ class PackageDirBase ( object ):
             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!"
+            "import_ebuilds() needs a non-virtual package dir!"
          )
       elif eview.has_ebuilds():
-         util.dodir ( self.physical_location, mkdir_p=True )
+         roverlay.util.dodir ( self.physical_location, mkdir_p=True )
 
          if not self._packages:
             for pvr, efile, fname in eview.get_ebuilds():
@@ -625,7 +654,7 @@ class PackageDirBase ( object ):
             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 (...) ---
+   # --- end of import_ebuilds (...) ---
 
    def write_ebuilds ( self, overwrite, additions_dir, shared_fh=None ):
       """Writes all ebuilds.
@@ -744,7 +773,7 @@ class PackageDirBase ( object ):
 
       for pvr, efile, p_info in list ( ebuilds_to_write() ):
          if not hasdir:
-            util.dodir ( self.physical_location, mkdir_p=True )
+            roverlay.util.dodir ( self.physical_location, mkdir_p=True )
             hasdir = True
 
          if (
@@ -823,6 +852,12 @@ class PackageDirBase ( object ):
             return True
          else:
             return False
+      elif (
+         hasattr ( self, '_write_import_manifest' )
+         and self._write_import_manifest()
+      ):
+         self._need_manifest = False
+         return True
       elif ignore_empty:
          return True
       else:
@@ -843,7 +878,7 @@ class PackageDirBase ( object ):
          self.generate_metadata ( skip_if_existent=True )
 
          if shared_fh is None:
-            util.dodir ( self.physical_location, mkdir_p=True )
+            roverlay.util.dodir ( self.physical_location, mkdir_p=True )
             if self._metadata.write():
                self._need_metadata = False
                self._need_manifest = True

diff --git a/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py b/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
index c9e3d84..5217f6a 100644
--- a/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
+++ b/roverlay/overlay/pkgdir/packagedir_ebuildmanifest.py
@@ -6,81 +6,106 @@
 
 __all__ = [ 'PackageDir', ]
 
+import os
 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 ( 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 ):
+      cls.init_base_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.
+   def _do_ebuildmanifest ( self, ebuild_file, distdir=None ):
+      """Calls doebuild_manifest().
+      Returns True on success, else False. Also handles result logging.
 
-      expects: called after writing metadata/ebuilds
-
-      returns: success (True/False)
+      arguments:
+      * ebuild_file -- ebuild file that should be used for the doebuild call
+      * distdir     -- distdir object (optional)
       """
       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 (
+         call = 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
+            self.MANIFEST_ENV.get_env (
+               distdir.get_root() if distdir is not None else (
+               self.DISTROOT.get_distdir ( self.name ).get_root()
                )
-            )
-            ret = False
-
+            ),
+            return_success=False
+         )
       except Exception as err:
          self.logger.exception ( err )
          raise
+      # -- end try
+
+      if call.returncode == os.EX_OK:
+         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=ret )
+         )
+         return False
+   # --- end of _do_ebuildmanifest (...) ---
+
+   def _write_import_manifest ( self ):
+      """Writes a Manifest file if this package has any imported ebuilds.
+
+      Returns True if a Manifest has been written, else False.
+      """
+      try:
+         pkg = next (
+            p for p in self._packages.values()
+            if p.has ( 'imported', 'ebuild_file' )
+         )
+      except StopIteration:
+         # no imported ebuilds
+         return False
+      # -- end try
+
+      self.logger.debug ( "Writing (import-)Manifest" )
+      return self._do_ebuildmanifest ( pkg ['ebuild_file'] )
+   # --- end of _write_import_manifest (...) ---
+
+   def _write_manifest ( self, pkgs_for_manifest ):
+      """Generates and writes the Manifest file for this package.
 
-      finally:
-         self.MANIFEST_LOCK.release()
+      expects: called after writing metadata/ebuilds
 
-      return ret
+      returns: success (True/False)
+      """
+      # choosing one ebuild for calling "ebuild <ebuild>" is sufficient
+      ebuild_file = pkgs_for_manifest [0] ['ebuild_file']
+      distdir     = self.DISTROOT.get_distdir ( self.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'] )
+      # -- end for;
+
+      return self._do_ebuildmanifest ( ebuild_file, distdir )
    # --- end of write_manifest (...) ---
+
+# --- end of PackageDir #ebuildmanifest ---

diff --git a/roverlay/overlay/pkgdir/packagedir_portagemanifest.py b/roverlay/overlay/pkgdir/packagedir_portagemanifest.py
index 3c4d728..6596dec 100644
--- a/roverlay/overlay/pkgdir/packagedir_portagemanifest.py
+++ b/roverlay/overlay/pkgdir/packagedir_portagemanifest.py
@@ -13,7 +13,9 @@ import portage.manifest
 import portage.exception
 
 import logging
-logging.getLogger ( __name__ ).warning ( "experimental code" )
+logging.getLogger ( __name__ ).warning (
+   "experimental code, importing ebuilds doesn't work!"
+)
 del logging
 
 import roverlay.config
@@ -23,9 +25,17 @@ import roverlay.overlay.pkgdir.packagedir_base
 import roverlay.overlay.pkgdir.distroot
 
 class PackageDir ( roverlay.overlay.pkgdir.packagedir_base.PackageDirBase ):
+   # FIXME: portagemanifest is broken, it cannot create Manifest files for
+   # package dirs with imported ebuilds
 
    MANIFEST_THREADSAFE = True
 
+   def import_ebuilds ( self, *args, **kwargs ):
+      raise NotImplementedError (
+         "ebuild imports not supported by portagemanifest!"
+      )
+   # --- end of import_ebuilds (...) ---
+
    def _scan_add_package ( self, efile, pvr ):
       """Called for each ebuild that is found during scan().
       Creates a PackageInfo for the ebuild and adds it to self._packages.
@@ -107,7 +117,7 @@ class PackageDir ( roverlay.overlay.pkgdir.packagedir_base.PackageDirBase ):
       # * ...
       #
 
-      distdir = roverlay.overlay.pkgdir.distroot.get_distdir ( self.name )
+      distdir = self.DISTROOT.get_distdir ( self.name )
 
       # allow_missing=True -- don't write empty Manifest files
       manifest = portage.manifest.Manifest (

diff --git a/roverlay/overlay/root.py b/roverlay/overlay/root.py
index ca71b81..a9d16c4 100644
--- a/roverlay/overlay/root.py
+++ b/roverlay/overlay/root.py
@@ -17,24 +17,24 @@ due do double-linkage between PackageInfo and PackageDir.
 
 __all__ = [ 'Overlay', ]
 
-import threading
 import logging
-import shutil
 import os
+import shutil
+import threading
 
-from roverlay import config, util
-
-from roverlay.overlay.category import Category
-from roverlay.overlay.header   import EbuildHeader
-
+import roverlay.config
+import roverlay.util
 import roverlay.overlay.additionsdir
+import roverlay.overlay.category
+import roverlay.overlay.header
+
 
 
 class Overlay ( object ):
-   DEFAULT_USE_DESC = '\n'.join ( (
-      'byte-compile - enable byte compiling',
+   DEFAULT_USE_DESC = (
+      'byte-compile - enable byte compiling\n'
       'R_suggests - install recommended packages'
-   ) )
+   )
 
    @classmethod
    def new_configured ( cls,
@@ -51,23 +51,24 @@ class Overlay ( object ):
       * write_allowed       --
       * skip_manifest       --
       * runtime_incremental --
-      * readonly            -- see Overlay.__init__
       """
-      name = config.get_or_fail ( 'OVERLAY.name' )
+      optional  = roverlay.config.get
+      mandatory = roverlay.config.get_or_fail
+
       return cls (
-         name                = name,
-         logger              = (
-            logger or logging.getLogger ( 'Overlay:' + name )
-         ),
-         directory           = config.get_or_fail ( 'OVERLAY.dir' ),
-         default_category    = config.get_or_fail ( 'OVERLAY.category' ),
-         eclass_files        = config.get ( 'OVERLAY.eclass_files',  None ),
-         ebuild_header       = config.get ( 'EBUILD.default_header', None ),
-         incremental         = incremental,
+         name                = mandatory ( 'OVERLAY.name' ),
+         logger              = logger,
+         directory           = mandatory ( 'OVERLAY.dir' ),
+         default_category    = mandatory ( 'OVERLAY.category' ),
+         eclass_files        = optional  ( 'OVERLAY.eclass_files' ),
+         ebuild_header       = optional  ( 'EBUILD.default_header' ),
          write_allowed       = write_allowed,
+         incremental         = incremental,
          skip_manifest       = skip_manifest,
-         additions_dir       = config.get_or_fail ( 'OVERLAY.additions_dir' ),
+         additions_dir       = optional  ( 'OVERLAY.additions_dir' ),
+         use_desc            = optional  ( 'OVERLAY.use_desc' ),
          runtime_incremental = runtime_incremental,
+         keep_n_ebuilds      = optional  ( 'OVERLAY.keep_nth_latest' ),
       )
    # --- end of new_configured (...) ---
 
@@ -83,7 +84,9 @@ class Overlay ( object ):
       incremental,
       skip_manifest,
       additions_dir,
-      runtime_incremental=False
+      use_desc=None,
+      runtime_incremental=False,
+      keep_n_ebuilds=None
    ):
       """Initializes an overlay.
 
@@ -108,9 +111,11 @@ class Overlay ( object ):
                                patches. The directory has to exist (it will
                                be checked here).
                                A value of None or "" disables additions.
+      * use_desc            -- text for profiles/use.desc
       * runtime_incremental -- see package.py:PackageDir.__init__ (...),
                                 Defaults to False (saves memory but costs time)
-
+      * keep_n_ebuilds      -- number of ebuilds to keep (per package),
+                               any "false" Value (None, 0, ...) disables this
       """
       self.name                 = name
       self.logger               = logger.getChild ( 'overlay' )
@@ -126,17 +131,31 @@ class Overlay ( object ):
       self._profiles_dir        = self.physical_location + os.sep + 'profiles'
       self._catlock             = threading.Lock()
       self._categories          = dict()
-      self._header              = EbuildHeader ( ebuild_header )
 
       self.skip_manifest        = skip_manifest
 
+      self._header   = roverlay.overlay.header.EbuildHeader ( ebuild_header )
+      self._use_desc = (
+         use_desc.rstrip() if use_desc is not None else self.DEFAULT_USE_DESC
+      )
+
+      if keep_n_ebuilds:
+         self.keep_n_ebuilds = keep_n_ebuilds
+
       if additions_dir:
          if os.path.isdir ( additions_dir ):
-            self.additions_dirpath = os.path.abspath ( additions_dir )
+            additions_dirpath = os.path.abspath ( additions_dir )
          else:
             raise ValueError (
                "additions dir {} does not exist!".format ( additions_dir )
             )
+      else:
+         additions_dirpath = None
+      # -- end if
+
+      self.additions_dir = (
+         roverlay.overlay.additionsdir.AdditionsDir ( additions_dirpath )
+      )
 
       # calculating eclass names twice,
       # once here and another time when calling _init_overlay
@@ -150,6 +169,8 @@ class Overlay ( object ):
          # incremental writing, which writes ebuilds as soon as they're
          # ready)
          self.scan()
+
+      self.import_ebuilds ( overwrite=( not incremental ) )
    # --- end of __init__ (...) ---
 
    def _get_category ( self, category ):
@@ -162,7 +183,7 @@ class Overlay ( object ):
          self._catlock.acquire()
          try:
             if not category in self._categories:
-               newcat = Category (
+               newcat = roverlay.overlay.category.Category (
                   category,
                   self.logger,
                   self.physical_location + os.sep + category,
@@ -218,7 +239,7 @@ class Overlay ( object ):
          eclass_dir = self.physical_location + os.sep +  'eclass'
          try:
             eclass_names = list()
-            util.dodir ( eclass_dir )
+            roverlay.util.dodir ( eclass_dir )
 
             for destname, eclass in self._get_eclass_import_info ( False ):
                dest = eclass_dir + os.sep +  destname + '.eclass'
@@ -271,7 +292,7 @@ class Overlay ( object ):
          self._get_category ( self.default_category )
 
          # profiles/
-         util.dodir ( self._profiles_dir )
+         roverlay.util.dodir ( self._profiles_dir )
 
          # profiless/repo_name
          write_profiles_file ( 'repo_name', self.name + '\n' )
@@ -284,17 +305,13 @@ class Overlay ( object ):
             write_profiles_file ( 'categories', cats + '\n' )
 
          # profiles/use.desc
-         use_desc = config.get (
-            'OVERLAY.use_desc',
-            fallback_value=self.__class__.DEFAULT_USE_DESC
-         )
-         if use_desc:
-            write_profiles_file ( 'use.desc', use_desc + '\n' )
+         if self._use_desc:
+            write_profiles_file ( 'use.desc', self._use_desc + '\n' )
       # --- end of write_profiles_dir (...) ---
 
       try:
          # mkdir overlay root
-         util.dodir ( self.physical_location, mkdir_p=True )
+         roverlay.util.dodir ( self.physical_location, mkdir_p=True )
 
          self._import_eclass ( reimport_eclass )
 
@@ -340,13 +357,19 @@ class Overlay ( object ):
    # --- end of has_category (...) ---
 
    def find_duplicate_packages ( self, _default_category=None ):
+      """Searches for packages that exist in the default category and
+      another one and returns a set of package names.
+
+      arguments:
+      * _default_category -- category object
+      """
       default_category = (
          _default_category if _default_category is None
          else self._categories.get ( self.default_category, None )
       )
 
       if default_category:
-         duplicate_pkg  = set()
+         duplicate_pkg = set()
 
          for category in self._categories.values():
             if category is not default_category:
@@ -361,6 +384,13 @@ class Overlay ( object ):
    # --- end of find_duplicate_packages (...) ---
 
    def remove_duplicate_ebuilds ( self, reverse ):
+      """Searcges for packages that exist in the default category and
+      another one and removes them from either one, depending on whether
+      reverse if True (other will be removed) or False (default category).
+
+      arguments:
+      * reverse
+      """
       default_category = self._categories.get ( self.default_category, None )
       if default_category:
          if reverse:
@@ -398,6 +428,7 @@ class Overlay ( object ):
    # --- end of remove_duplicate_ebuilds (...) ---
 
    def remove_empty_categories ( self ):
+      """Removes empty categories."""
       catlist = self._categories.items()
       for cat in catlist:
          cat[1].remove_empty()
@@ -430,6 +461,21 @@ class Overlay ( object ):
       return not self._writeable
    # --- end of readonly (...) ---
 
+   def import_ebuilds ( self, overwrite, nosync=False ):
+      """Imports ebuilds from the additions dir.
+
+      arguments:
+      * overwrite -- whether to overwrite existing ebuilds
+      * nosync    -- if True: don't fetch src files (defaults to False)
+      """
+      for catview in (
+         roverlay.overlay.additionsdir.CategoryRootView ( self.additions_dir )
+      ):
+         self._get_category ( catview.name ).import_ebuilds (
+            catview, overwrite=overwrite
+         )
+   # --- end of import_ebuilds (...) ---
+
    def scan ( self, **kw ):
       def scan_categories():
          for x in os.listdir ( self.physical_location ):
@@ -478,20 +524,17 @@ class Overlay ( object ):
       Note: This is not thread-safe, it's expected to be called when
       ebuild creation is done.
       """
+
       if self._writeable:
          self._init_overlay ( reimport_eclass=True )
 
-         additions_dir = roverlay.overlay.additionsdir.AdditionsDir (
-            getattr ( self, 'additions_dirpath', None )
-         )
-
          for cat in self._categories.values():
             cat.write (
                overwrite_ebuilds = False,
-               keep_n_ebuilds    = config.get ( 'OVERLAY.keep_nth_latest', None ),
+               keep_n_ebuilds    = getattr ( self, 'keep_n_ebuilds', None ),
                cautious          = True,
                write_manifest    = not self.skip_manifest,
-               additions_dir     = additions_dir.get_obj_subdir ( cat ),
+               additions_dir     = self.additions_dir.get_obj_subdir ( cat ),
             )
       else:
          # FIXME debug print

diff --git a/roverlay/packageinfo.py b/roverlay/packageinfo.py
index 84a7d45..90b4250 100644
--- a/roverlay/packageinfo.py
+++ b/roverlay/packageinfo.py
@@ -98,6 +98,7 @@ class PackageInfo ( object ):
       'origin',
       'ebuild',
       'ebuild_file',
+      'imported',
       'physical_only',
       'src_uri',
    ))
@@ -341,6 +342,10 @@ class PackageInfo ( object ):
          # 'physical_only' not in self._info -> assume False
          return False
 
+      elif key_low == 'imported':
+         # 'imported' not in self._info -> assume False
+         return False
+
       elif key_low == 'src_uri':
          if 'src_uri_base' in self._info:
             return \
@@ -386,6 +391,50 @@ class PackageInfo ( object ):
          raise KeyError ( key )
    # --- end of get (...) ---
 
+   def get_create (
+      self, key, newtype, convert=False, check_type=True, create_kw=None
+   ):
+      """Tries to get a value from the info dict. Creates it as newtype if
+      necessary.
+
+      Note: This operation is "unsafe". No locks will be acquired etc.
+
+      arguments:
+      * key         -- info key
+      * newtype     -- "expected type", also used for creating new values
+      * convert     -- if True: convert existing value (defaults to False)
+      * check_type  -- if True: check whether the type of existing value is
+                       a (sub-)type of newtype (defaults to True)
+                       This arg can also be a type.
+                       Has no effect if convert is set to True
+      * create_kw   -- either None or a dict that will used as keyword args
+                       when creating newtype
+      """
+      v = self.get ( key, do_fallback=True )
+      if v is None:
+         newv = newtype ( **create_kw ) if create_kw else newtype()
+         self._info [key] = newv
+         return newv
+      elif convert:
+         return newtype ( v )
+      elif check_type:
+         want_type = (
+            check_type if ( type ( check_type ) is type ) else newtype
+         )
+
+         #if type ( v ) is want_type:
+         if isinstance ( v, want_type ):
+            return v
+         else:
+            raise TypeError (
+               "key {k} should have type {t0}, but is a {t1}!".format (
+                  k=key, t0=want_type, t1=type(v)
+               )
+            )
+      else:
+         return v
+   # --- end of get_create (...) ---
+
    def get_desc_data ( self ):
       """Returns the DESCRIPTION data for this PackageInfo (by reading the
       R package file if necessary).
@@ -557,7 +606,7 @@ class PackageInfo ( object ):
 
          else:
             self.logger.error (
-               "in _update(): unknown info key {}!".format ( key )
+               "in _update(): unknown info key {!r}!".format ( key )
             )
       # -- end for;
 

diff --git a/roverlay/tools/ebuild.py b/roverlay/tools/ebuild.py
index d9f4e9a..e8f7e37 100644
--- a/roverlay/tools/ebuild.py
+++ b/roverlay/tools/ebuild.py
@@ -20,6 +20,7 @@ _EBUILD_CMDV = (
 def doebuild (
    ebuild_file, command, logger, env=None, opts=(), return_success=True
 ):
+   logger.debug ( "doebuild: {c}, {e!r}".format ( e=ebuild_file, c=command ) )
    return roverlay.tools.runcmd.run_command (
       cmdv           = ( _EBUILD_CMDV + opts + ( ebuild_file, command ) ),
       env            = env,

diff --git a/roverlay/tools/ebuildenv.py b/roverlay/tools/ebuildenv.py
index 40f6495..7c5accb 100644
--- a/roverlay/tools/ebuildenv.py
+++ b/roverlay/tools/ebuildenv.py
@@ -101,8 +101,14 @@ class EbuildEnv ( object ):
 
 # --- end of EbuildEnv ---
 
+
 class FetchEnv ( EbuildEnv ):
-   pass
+   def _make_common_env ( self ):
+      super ( FetchEnv, self )._make_common_env()
+      # "Cannot chown a lockfile"
+      self._common_env ['FEATURES'] += " -distlocks"
+   # --- end of _make_common_env (...) ---
+
 # --- end of FetchEnv ---
 
 


^ 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-13 16:34 [gentoo-commits] proj/R_overlay:master commit in: roverlay/tools/, roverlay/, roverlay/overlay/pkgdir/, roverlay/overlay/ André Erdmann
2013-06-12 21:10 ` [gentoo-commits] proj/R_overlay:gsoc13/next " André Erdmann

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