]> git.ipfire.org Git - people/stevee/pakfire.git/blobdiff - python/pakfire/base.py
Implement distro-sync.
[people/stevee/pakfire.git] / python / pakfire / base.py
index aec3d7372d34ab9f131b290df0004a85d1c29355..6b53a496c80c0bd720979358587b188ae8d16a4e 100644 (file)
@@ -19,7 +19,6 @@
 #                                                                             #
 ###############################################################################
 
-import logging
 import os
 import random
 import string
@@ -29,6 +28,7 @@ import builder
 import config
 import distro
 import filelist
+import keyring
 import logger
 import packages
 import repository
@@ -36,25 +36,19 @@ import satsolver
 import transaction
 import util
 
+import logging
+log = logging.getLogger("pakfire")
+
+from config import Config
 from constants import *
 from i18n import _
 
 class Pakfire(object):
-       RELATIONS = (
-               (">=", satsolver.REL_GE,),
-               ("<=", satsolver.REL_LE,),
-               ("=" , satsolver.REL_EQ,),
-               ("<" , satsolver.REL_LT,),
-               (">" , satsolver.REL_GT,),
-       )
-
-       def __init__(self, mode=None, path="/", configs=[],
-                       enable_repos=None, disable_repos=None,
-                       distro_config=None, **kwargs):
-
-               # Set the mode.
-               assert mode in ("normal", "builder", "server",)
-               self.mode = mode
+       mode = None
+
+       def __init__(self, path="/", config=None, configs=None, arch=None, **kwargs):
+               # Indicates if this instance has already been initialized.
+               self.initialized = False
 
                # Check if we are operating as the root user.
                self.check_root_user()
@@ -62,105 +56,77 @@ class Pakfire(object):
                # The path where we are operating in.
                self.path = path
 
-               # Configure the instance of Pakfire we just started.
-               if mode == "builder":
-                       self.path = os.path.join(BUILD_ROOT, util.random_string())
-
-               elif mode == "normal":
-                       # check if we are actually running on an ipfire system.
-                       if self.path == "/":
-                               self.check_is_ipfire()
-
-               # Read configuration file(s)
-               self.config = config.Config(type=mode)
-               for filename in configs:
-                       self.config.read(filename)
-               # Assume, that all other keyword arguments are configuration
-               # parameters.
-               self.config.update(kwargs)
-
-               # Setup the logger
-               logger.setup_logging(self.config)
-               self.config.dump()
+               # check if we are actually running on an ipfire system.
+               if not self.mode and self.path == "/":
+                       self.check_is_ipfire()
 
-               # Get more information about the distribution we are running
-               # or building
-               self.distro = distro.Distribution(self, distro_config)
-               self.pool   = satsolver.Pool(self.distro.arch)
-               self.repos  = repository.Repositories(self,
-                       enable_repos=enable_repos, disable_repos=disable_repos)
-
-       def __del__(self):
-               # Reset logging.
-               logger.setup_logging()
-
-       def create_solver(self):
-               return satsolver.Solver(self, self.pool)
+               # Get the configuration.
+               if config:
+                       self.config = config
+               else:
+                       self.config = self._load_config(configs)
 
-       def create_request(self, builder=False):
-               request = satsolver.Request(self.pool)
+               # Update configuration with additional arguments.
+               for section, settings in kwargs.items():
+                       self.config.update(section, settings)
 
-               # Add multiinstall information.
-               for solv in PAKFIRE_MULTIINSTALL:
-                       request.noobsoletes(solv)
+               # Dump the configuration.
+               self.config.dump()
 
-               return request
+               # Initialize the keyring.
+               self.keyring = keyring.Keyring(self)
 
-       def create_relation(self, s):
-               assert s
+               # Get more information about the distribution we are running
+               # or building
+               self.distro = distro.Distribution(self.config.get_distro_conf())
+               if arch:
+                       self.distro.arch = arch
 
-               if isinstance(s, filelist._File):
-                       return satsolver.Relation(self.pool, s.name)
+               self.pool = satsolver.Pool(self.distro.arch)
+               self.repos = repository.Repositories(self)
 
-               elif s.startswith("/"):
-                       return satsolver.Relation(self.pool, s)
+       def initialize(self):
+               """
+                       Initialize pakfire instance.
+               """
+               if self.initialized:
+                       return
 
-               for pattern, type in self.RELATIONS:
-                       if not pattern in s:
-                               continue
+               # Initialize repositories.
+               self.repos.initialize()
 
-                       name, version = s.split(pattern, 1)
+               self.initialized = True
 
-                       return satsolver.Relation(self.pool, name, version, type)
+       def _load_config(self, files=None):
+               """
+                       This method loads all needed configuration files.
+               """
+               return config.Config(files=files)
 
-               return satsolver.Relation(self.pool, s)
+       def __del__(self):
+               # Reset logging.
+               logger.setup_logging()
 
        def destroy(self):
-               if not self.path == "/":
-                       util.rm(self.path)
-
-       @property
-       def environ(self):
-               env = {}
-
-               # Get distribution information.
-               env.update(self.distro.environ)
+               self.repos.shutdown()
 
-               return env
+               self.initialized = False
 
        @property
        def supported_arches(self):
-               return self.config.supported_arches
+               return system.supported_arches
 
        @property
        def offline(self):
                """
                        A shortcut that indicates if the system is running in offline mode.
                """
-               return self.config.get("offline", False)
+               return self.config.get("downloader", "offline", False)
 
        def check_root_user(self):
                if not os.getuid() == 0 or not os.getgid() == 0:
                        raise Exception, "You must run pakfire as the root user."
 
-       def check_build_mode(self):
-               """
-                       Check if we are running in build mode.
-                       Otherwise, raise an exception.
-               """
-               if not self.mode == "builder":
-                       raise BuildError, "Cannot build when not in build mode."
-
        def check_host_arch(self, arch):
                """
                        Check if we can build for arch.
@@ -170,14 +136,12 @@ class Pakfire(object):
                if not arch:
                        return True
 
-               if not self.config.host_supports_arch(arch):
+               if not system.host_supports_arch(arch):
                        raise BuildError, "Cannot build for the target architecture: %s" % arch
 
                raise BuildError, arch
 
        def check_is_ipfire(self):
-               return # XXX disabled for now
-
                ret = os.path.exists("/etc/ipfire-release")
 
                if not ret:
@@ -188,123 +152,98 @@ class Pakfire(object):
                # XXX just backwards compatibility
                return self.mode == "builder"
 
-       def resolvdep(self, requires):
-               # Create a new request.
-               request = self.create_request()
-               for req in requires:
-                       req = self.create_relation(req)
-                       request.install(req)
-
-               # Do the solving.
-               solver = self.create_solver()
-               t = solver.solve(request)
-
-               if t:
-                       t.dump()
-               else:
-                       logging.info(_("Nothing to do"))
+       def install(self, requires, interactive=True, logger=None, signatures_mode=None, **kwargs):
+               # Initialize this pakfire instance.
+               self.initialize()
 
-       def install(self, requires, interactive=True, logger=None, **kwargs):
                if not logger:
-                       logger = logging.getLogger()
-
-               # Create a new request.
-               request = self.create_request()
+                       logger = logging.getLogger("pakfire")
 
-               # Expand all groups.
-               for req in requires:
-                       if req.startswith("@"):
-                               reqs = self.grouplist(req[1:])
-                       else:
-                               reqs = [req,]
-
-                       for req in reqs:
-                               if not isinstance(req, packages.BinaryPackage):
-                                       req = self.create_relation(req)
-
-                               request.install(req)
+               # Pointer to temporary repository.
+               repo = None
 
-               # Do the solving.
-               solver = self.create_solver()
-               t = solver.solve(request, **kwargs)
+               # Sort out what we got...
+               download_packages = []
+               local_packages = []
+               relations = []
 
-               if not t:
-                       if not interactive:
-                               raise DependencyError
-
-                       logging.info(_("Nothing to do"))
-                       return
+               for req in requires:
+                       if isinstance(req, packages.Package):
+                               relations.append(req)
+                               continue
 
-               if interactive:
-                       # Ask if the user acknowledges the transaction.
-                       if not t.cli_yesno():
-                               return
+                       # This looks like a file.
+                       elif req.endswith(".%s" % PACKAGE_EXTENSION) and os.path.exists(req) and os.path.isfile(req):
+                               local_packages.append(req)
+                               continue
 
-               else:
-                       t.dump(logger=logger)
+                       # Remote files.
+                       elif req.startswith("http://") or req.startswith("https://") or req.startswith("ftp://"):
+                               download_packages.append(req)
+                               continue
 
-               # Run the transaction.
-               t.run()
+                       # We treat the rest as relations. The solver will return any errors.
+                       relations.append(req)
 
-       def localinstall(self, files, yes=None, allow_uninstall=False):
-               repo_name = repo_desc = "localinstall"
+               # Redefine requires, which will be the list that will be passed to the
+               # solver.
+               requires = relations
 
-               # Create a new repository that holds all packages we passed on
-               # the commandline.
-               repo = repository.RepositoryDir(self, repo_name, repo_desc,
-                       os.path.join(LOCAL_TMP_PATH, "repo_%s" % util.random_string()))
+               try:
+                       # If we have got files to install, we need to create a temporary repository
+                       # called 'localinstall'.
+                       # XXX FIX TMP PATH
+                       if local_packages or download_packages:
+                               repo = repository.RepositoryDir(self, "localinstall", _("Local install repository"),
+                                       os.path.join(LOCAL_TMP_PATH, "repo_%s" % util.random_string()))
 
-               # Register the repository.
-               self.repos.add_repo(repo)
+                               # Register the repository.
+                               self.repos.add_repo(repo)
 
-               try:
-                       # Add all packages to the repository index.
-                       for file in files:
-                               repo.collect_packages(file)
+                               # Download packages.
+                               for download_package in download_packages:
+                                       repo.download_package(download_package)
 
-                       # Break if no packages were added at all.
-                       if not len(repo):
-                               logging.critical(_("There are no packages to install."))
-                               return
+                               # Add all packages to the repository index.
+                               repo.add_packages(local_packages)
 
-                       # Create a new request that installs all solvables from the
-                       # repository.
-                       request = self.create_request()
-                       for solv in [p.solvable for p in repo]:
-                               request.install(solv)
+                               # Add all packages to the requires.
+                               requires += repo
 
-                       solver = self.create_solver()
-                       t = solver.solve(request, uninstall=allow_uninstall)
+                       # Do the solving.
+                       request = self.pool.create_request(install=requires)
+                       solver  = self.pool.solve(request, logger=logger, interactive=interactive, **kwargs)
 
-                       # If solving was not possible, we exit here.
-                       if not t:
-                               logging.info(_("Nothing to do"))
-                               return
+                       # Create the transaction.
+                       t = transaction.Transaction.from_solver(self, solver)
+                       t.dump(logger=logger)
 
-                       if yes is None:
-                               # Ask the user if this is okay.
-                               if not t.cli_yesno():
-                                       return
-                       elif yes:
-                               t.dump()
-                       else:
+                       # Ask if the user acknowledges the transaction.
+                       if interactive and not t.cli_yesno():
                                return
 
-                       # If okay, run the transcation.
-                       t.run()
+                       # Run the transaction.
+                       t.run(logger=logger, signatures_mode=signatures_mode)
 
                finally:
-                       # Remove the temporary copy of the repository we have created earlier.
-                       repo.remove()
-                       self.repos.rem_repo(repo)
+                       if repo:
+                               # Remove the temporary repository we have created earlier.
+                               repo.remove()
+                               self.repos.rem_repo(repo)
 
-       def reinstall(self, pkgs, strict=False):
+       def reinstall(self, pkgs, strict=False, logger=None):
                """
                        Reinstall one or more packages.
 
                        If strict is True, only a package with excatly the same UUID
                        will replace the currently installed one.
                """
+               # Initialize this pakfire instance.
+               self.initialize()
+
+               if logger is None:
+                       logger = logging.getLogger("pakfire")
+
                # XXX it is possible to install packages without fulfulling
                # all dependencies.
 
@@ -319,17 +258,17 @@ class Pakfire(object):
                                _pkgs.append(pkg)
 
                        if not _pkgs:
-                               logging.warning(_("Could not find any installed package providing \"%s\".") \
+                               logger.warning(_("Could not find any installed package providing \"%s\".") \
                                        % pattern)
                        elif len(_pkgs) == 1:
                                reinstall_pkgs.append(_pkgs[0])
                                #t.add("reinstall", _pkgs[0])
                        else:
-                               logging.warning(_("Multiple reinstall candidates for \"%s\": %s") \
-                                       % (pattern, ", ".join(p.friendly_name for p in sorted(_pkgs))))
+                               logger.warning(_("Multiple reinstall candidates for \"%(pattern)s\": %(pkgs)s") \
+                                       % { "pattern" : pattern, "pkgs" : ", ".join(p.friendly_name for p in sorted(_pkgs)) })
 
                if not reinstall_pkgs:
-                       logging.info(_("Nothing to do"))
+                       logger.info(_("Nothing to do"))
                        return
 
                # Packages we want to replace.
@@ -354,7 +293,7 @@ class Pakfire(object):
                                        _pkgs.append(_pkg)
 
                        if not _pkgs:
-                               logging.warning(_("Could not find package %s in a remote repository.") % \
+                               logger.warning(_("Could not find package %s in a remote repository.") % \
                                        pkg.friendly_name)
                        else:
                                # Sort packages to reflect repository priorities, etc...
@@ -374,15 +313,15 @@ class Pakfire(object):
                        else:
                                if request is None:
                                        # Create a new request.
-                                       request = self.create_request()
+                                       request = self.pool.create_request()
 
                                # Install the new package, the old will
                                # be cleaned up automatically.
                                request.install(new.solvable)
 
                if request:
-                       solver = self.create_solver()
-                       t = solver.solve(request)
+                       solver = self.pool.solve(request)
+                       t = transaction.Transaction.from_solver(self, solver)
                else:
                        # Create new transaction.
                        t = transaction.Transaction(self)
@@ -395,46 +334,54 @@ class Pakfire(object):
                t.sort()
 
                if not t:
-                       logging.info(_("Nothing to do"))
+                       logger.info(_("Nothing to do"))
                        return
 
+               t.dump(logger=logger)
+
                if not t.cli_yesno():
                        return
 
-               t.run()
+               t.run(logger=logger)
 
-       def update(self, pkgs, check=False, excludes=None, allow_vendorchange=False, allow_archchange=False):
+       def update(self, pkgs=None, check=False, excludes=None, interactive=True, logger=None, sync=False, **kwargs):
                """
                        check indicates, if the method should return after calculation
                        of the transaction.
                """
-               request = self.create_request()
+               # Initialize this pakfire instance.
+               self.initialize()
+
+               if logger is None:
+                       logger = logging.getLogger("pakfire")
 
                # If there are given any packets on the command line, we will
                # only update them. Otherwise, we update the whole system.
+               updateall = True
                if pkgs:
-                       update = False
-                       for pkg in pkgs:
-                               pkg = self.create_relation(pkg)
-                               request.update(pkg)
-               else:
-                       update = True
+                       updateall = False
+
+               request = self.pool.create_request(update=pkgs, updateall=updateall)
 
                # Exclude packages that should not be updated.
-               if excludes:
-                       for exclude in excludes:
-                               logging.info(_("Excluding %s.") % exclude)
+               for exclude in excludes or []:
+                       logger.info(_("Excluding %s.") % exclude)
 
-                               exclude = self.create_relation(exclude)
-                               request.lock(exclude)
+                       exclude = self.pool.create_relation(exclude)
+                       request.lock(exclude)
 
-               solver = self.create_solver()
-               t = solver.solve(request, update=update,
-                       allow_vendorchange=allow_vendorchange,
-                       allow_archchange=allow_archchange)
+               # Update or downgrade to the latest version of all packages
+               # in the enabled repositories.
+               if sync:
+                       kwargs.update({
+                               "allow_downgrade" : True,
+                               "allow_uninstall" : True,
+                       })
 
-               if not t:
-                       logging.info(_("Nothing to do"))
+               solver = self.pool.solve(request, logger=logger, **kwargs)
+
+               if not solver.status:
+                       logger.info(_("Nothing to do"))
 
                        # If we are running in check mode, we return a non-zero value to
                        # indicate, that there are no updates.
@@ -443,23 +390,32 @@ class Pakfire(object):
                        else:
                                return
 
+               # Create the transaction.
+               t = transaction.Transaction.from_solver(self, solver)
+               t.dump(logger=logger)
+
                # Just exit here, because we won't do the transaction in this mode.
                if check:
-                       t.dump()
                        return
 
                # Ask the user if the transaction is okay.
-               if not t.cli_yesno():
+               if interactive and not t.cli_yesno():
                        return
 
                # Run the transaction.
-               t.run()
+               t.run(logger=logger)
 
-       def downgrade(self, pkgs, allow_vendorchange=False, allow_archchange=False):
+       def downgrade(self, pkgs, logger=None, **kwargs):
                assert pkgs
 
+               if logger is None:
+                       logger = logging.getLogger("pakfire")
+
+               # Initialize this pakfire instance.
+               self.initialize()
+
                # Create a new request.
-               request = self.create_request()
+               request = self.pool.create_request()
 
                # Fill request.
                for pattern in pkgs:
@@ -475,19 +431,21 @@ class Pakfire(object):
                                        best = pkg
 
                        if best is None:
-                               logging.warning(_("\"%s\" package does not seem to be installed.") % pattern)
+                               logger.warning(_("\"%s\" package does not seem to be installed.") % pattern)
                        else:
-                               rel = self.create_relation("%s<%s" % (best.name, best.friendly_version))
+                               rel = self.pool.create_relation("%s < %s" % (best.name, best.friendly_version))
                                request.install(rel)
 
                # Solve the request.
-               solver = self.create_solver()
-               t = solver.solve(request, allow_downgrade=True,
-                       allow_vendorchange=allow_vendorchange,
-                       allow_archchange=allow_archchange)
+               solver = self.pool.solve(request, allow_downgrade=True, **kwargs)
+               assert solver.status is True
+
+               # Create the transaction.
+               t = transaction.Transaction.from_solver(self, solver)
+               t.dump(logger=logger)
 
                if not t:
-                       logging.info(_("Nothing to do"))
+                       logger.info(_("Nothing to do"))
                        return
 
                if not t.cli_yesno():
@@ -495,19 +453,26 @@ class Pakfire(object):
 
                t.run()
 
-       def remove(self, pkgs):
+       def remove(self, pkgs, logger=None):
+               if logger is None:
+                       logger = logging.getLogger("pakfire")
+
+               # Initialize this pakfire instance.
+               self.initialize()
+
                # Create a new request.
-               request = self.create_request()
-               for pkg in pkgs:
-                       pkg = self.create_relation(pkg)
-                       request.remove(pkg)
+               request = self.pool.create_request(remove=pkgs)
 
                # Solve the request.
-               solver = self.create_solver()
-               t = solver.solve(request, uninstall=True)
+               solver = self.pool.solve(request, allow_uninstall=True)
+               assert solver.status is True
+
+               # Create the transaction.
+               t = transaction.Transaction.from_solver(self, solver)
+               t.dump()
 
                if not t:
-                       logging.info(_("Nothing to do"))
+                       log.info(_("Nothing to do"))
                        return
 
                # Ask the user if okay.
@@ -518,6 +483,9 @@ class Pakfire(object):
                t.run()
 
        def info(self, patterns):
+               # Initialize this pakfire instance.
+               self.initialize()
+
                pkgs = []
 
                # For all patterns we run a single search which returns us a bunch
@@ -541,6 +509,9 @@ class Pakfire(object):
                return sorted(pkgs)
 
        def search(self, pattern):
+               # Initialize this pakfire instance.
+               self.initialize()
+
                # Do the search.
                pkgs = {}
                for solv in self.pool.search(pattern, satsolver.SEARCH_STRING|satsolver.SEARCH_FILES):
@@ -561,26 +532,139 @@ class Pakfire(object):
                self.install("@%s" % group, **kwargs)
 
        def grouplist(self, group):
+               # Initialize this pakfire instance.
+               self.initialize()
+
+               return self.pool.grouplist(group)
+
+       def provides(self, patterns):
+               # Initialize this pakfire instance.
+               self.initialize()
+
                pkgs = []
+               for pattern in patterns:
+                       for pkg in self.pool.whatprovides(self, pattern):
+                               if pkg in pkgs:
+                                       continue
 
-               for solv in self.pool.search(group, satsolver.SEARCH_SUBSTRING, "solvable:group"):
-                       pkg = packages.SolvPackage(self, solv)
+                               pkgs.append(pkg)
 
-                       if group in pkg.groups and not pkg.name in pkgs:
-                               pkgs.append(pkg.name)
+               # Sort output.
+               pkgs.sort()
 
-               return sorted(pkgs)
+               return pkgs
+
+       def resolvdep(self, pkg):
+               # Initialize this pakfire instance.
+               self.initialize()
+
+               return self.pool.resolvdep(self, pkg)
+
+       def repo_list(self):
+               # Initialize this pakfire instance.
+               self.initialize()
 
-       @staticmethod
-       def build(pkg, resultdirs=None, shell=False, install_test=True, after_shell=False, **kwargs):
-               if not resultdirs:
-                       resultdirs = []
+               return [r for r in self.repos]
+
+       def clean_all(self):
+               # Initialize this pakfire instance.
+               self.initialize()
 
-               b = builder.BuildEnviron(pkg, **kwargs)
-               p = b.pakfire
+               log.debug("Cleaning up everything...")
 
-               # Always include local repository.
-               resultdirs.append(p.repos.local_build.path)
+               # Clean up repository caches.
+               self.repos.clean()
+
+       def check(self, allow_downgrade=True, allow_uninstall=True):
+               """
+                       Try to fix any errors in the system.
+               """
+               # Initialize this pakfire instance.
+               self.initialize()
+
+               # Detect any errors in the dependency tree.
+               # For that we create an empty request and solver and try to solve
+               # something.
+               request = self.pool.create_request()
+               request.verify()
+
+               solver = self.pool.solve(
+                       request,
+                       allow_downgrade=allow_downgrade,
+                       allow_uninstall=allow_uninstall,
+               )
+
+               if solver.status is False:
+                       log.info(_("Everything is fine."))
+                       return
+
+               # Create the transaction.
+               t = transaction.Transaction.from_solver(self, solver)
+               t.dump()
+
+               # Ask the user if okay.
+               if not t.cli_yesno():
+                       return
+
+               # Process the transaction.
+               t.run()
+
+       def build(self, makefile, resultdir, stages=None, **kwargs):
+               b = builder.Builder(self, makefile, resultdir, **kwargs)
+
+               try:
+                       b.build(stages=stages)
+
+               except Error:
+                       raise BuildError, _("Build command has failed.")
+
+               else:
+                       # If the build was successful, cleanup all temporary files.
+                       b.cleanup()
+
+       def dist(self, pkg, resultdir):
+               pkg = packages.Makefile(self, pkg)
+
+               return pkg.dist(resultdir=resultdir)
+
+
+class PakfireBuilder(Pakfire):
+       mode = "builder"
+
+       def __init__(self, distro_name=None, *args, **kwargs):
+               self.distro_name = distro_name
+
+               kwargs.update({
+                       "path" : os.path.join(BUILD_ROOT, util.random_string()),
+               })
+
+               Pakfire.__init__(self, *args, **kwargs)
+
+               # Let's see what is our host distribution.
+               self.host_distro = distro.Distribution()
+
+       def _load_config(self, files=None):
+               c = config.ConfigBuilder(files=files)
+
+               if self.distro_name is None:
+                       self.distro_name = c.get("builder", "distro", None)
+
+               if self.distro_name:
+                       c.load_distro_config(self.distro_name)
+
+               if not c.has_distro_conf():
+                       log.error(_("You have not set the distribution for which you want to build."))
+                       log.error(_("Please do so in builder.conf or on the CLI."))
+                       raise ConfigError, _("Distribution configuration is missing.")
+
+               return c
+
+       def build(self, pkg, resultdirs=None, shell=False, install_test=True, after_shell=False, **kwargs):
+               # As the BuildEnviron is only able to handle source packages, we must package makefiles.
+               if pkg.endswith(".%s" % MAKEFILE_EXTENSION):
+                       pkg = self.dist(pkg, resultdir=LOCAL_TMP_PATH)
+
+               b = builder.BuildEnviron(self, pkg, **kwargs)
 
                try:
                        # Start to prepare the build environment by mounting
@@ -590,6 +674,7 @@ class Pakfire(object):
                        try:
                                # Build the package.
                                b.build(install_test=install_test)
+
                        except BuildError:
                                # Raise the error, if the user does not want to
                                # have a shell.
@@ -599,112 +684,73 @@ class Pakfire(object):
                                # Run a shell to debug the issue.
                                b.shell()
 
-                       # If the user requests a shell after a successful build,
-                       # we run it here.
-                       if after_shell:
-                               b.shell()
-
                        # Copy-out all resultfiles if the build was successful.
+                       if not resultdirs:
+                               resultdirs = []
+
+                       # Always include local repository.
+                       resultdirs.append(self.repos.local_build.path)
+
                        for resultdir in resultdirs:
                                if not resultdir:
                                        continue
 
                                b.copy_result(resultdir)
-               finally:
-                       b.stop()
 
-       def _build(self, pkg, resultdir, nodeps=False, **kwargs):
-               b = builder.Builder(self, pkg, resultdir, **kwargs)
+                       # If the user requests a shell after a successful build,
+                       # we run it here.
+                       if after_shell:
+                               b.shell()
 
-               try:
-                       b.build()
-               except Error:
-                       raise BuildError, _("Build command has failed.")
+               finally:
+                       b.stop()
 
-               # If the build was successful, cleanup all temporary files.
-               b.cleanup()
+       def shell(self, pkg, **kwargs):
+               # As the BuildEnviron is only able to handle source packages, we must package makefiles.
+               if pkg and pkg.endswith(".%s" % MAKEFILE_EXTENSION):
+                       pkg = self.dist(pkg, resultdir=LOCAL_TMP_PATH)
 
-       @staticmethod
-       def shell(pkg, **kwargs):
-               b = builder.BuildEnviron(pkg, **kwargs)
+               b = builder.BuildEnviron(self, pkg, **kwargs)
 
                try:
                        b.start()
+
+                       try:
+                               b.build(prepare=True)
+                       except BuildError:
+                               pass
+
                        b.shell()
                finally:
                        b.stop()
 
-       def dist(self, pkgs, resultdirs=None):
-               assert resultdirs
 
-               for pkg in pkgs:
-                       pkg = packages.Makefile(self, pkg)
+class PakfireClient(Pakfire):
+       mode = "client"
 
-                       pkg.dist(resultdirs)
 
-       def provides(self, patterns):
-               pkgs = []
-               for pattern in patterns:
-                       for pkg in self.repos.whatprovides(pattern):
-                               if pkg in pkgs:
-                                       continue
+class PakfireServer(Pakfire):
+       mode = "server"
 
-                               pkgs.append(pkg)
-
-               return sorted(pkgs)
-
-       def repo_create(self, path, input_paths, type="binary"):
+       def repo_create(self, path, input_paths, name=None, key_id=None, type="binary"):
                assert type in ("binary", "source",)
 
-               repo = repository.RepositoryDir(
-                       self,
-                       name="new",
-                       description="New repository.",
-                       path=path,
-                       type=type,
-               )
+               if not name:
+                       name = _("New repository")
+
+               # Create new repository.
+               repo = repository.RepositoryDir(self, name=name, description="New repository.",
+                       path=path, key_id=key_id)
 
-               for input_path in input_paths:
-                       repo.collect_packages(input_path)
+               # Add all packages.
+               repo.add_packages(input_paths)
 
+               # Write metadata to disk.
                repo.save()
 
+               # Return the new repository.
                return repo
 
-       def repo_list(self):
-               return [r for r in self.repos]
-
-       def clean_all(self):
-               logging.debug("Cleaning up everything...")
-
-               # Clean up repository caches.
-               self.repos.clean()
 
-       def check(self, downgrade=True, uninstall=True):
-               """
-                       Try to fix any errors in the system.
-               """
-               # Detect any errors in the dependency tree.
-               # For that we create an empty request and solver and try to solve
-               # something.
-               request = self.create_request()
-               solver = self.create_solver()
-
-               # XXX the solver does crash if we call it with fix_system=1,
-               # allow_downgrade=1 and uninstall=1. Need to fix this.
-               allow_downgrade = False
-               uninstall = False
-
-               t = solver.solve(request, fix_system=True, allow_downgrade=downgrade,
-                       uninstall=uninstall)
-
-               if not t:
-                       logging.info(_("Everything is fine."))
-                       return
-
-               # Ask the user if okay.
-               if not t.cli_yesno():
-                       return
-
-               # Process the transaction.
-               t.run()
+class PakfireKey(Pakfire):
+       mode = "key"