view treepkg/builder.py @ 579:97a5e09c84dc tip

Fix: pass url to command expand to be able to checkout a new git repository
author Bjoern Ricks <bricks@intevation.de>
date Sat, 03 Sep 2011 12:32:32 +0000
parents 058856954e2d
children
line wrap: on
line source
# Copyright (C) 2007, 2008, 2009 by Intevation GmbH
# Authors:
# Bernhard Herzog <bh@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with the software for details.

"""Build binary packages from source packages"""

import sys
import os
import shutil
import logging
import tempfile

import util
import run
from cmdexpand import cmdexpand

class Builder:
    
    basetgz_dir = util.filenameproperty("base")
    build_dir = util.filenameproperty("build")
    result_dir = util.filenameproperty("result")
    aptcache_dir = util.filenameproperty("aptcache")
    extra_pkg_dir = util.filenameproperty("extra-pkg")
    
    """ Parent class for all Builders """
    def __init__(self):
        pass

    def update_extra_pkg_dir(self):
        run.call(cmdexpand("apt-ftparchive packages ."),
                 stdout=open(os.path.join(self.extra_pkg_dir, "Packages"), "w"),
                 cwd=self.extra_pkg_dir)
        release_filename = os.path.join(self.extra_pkg_dir, "Release")
        run.call(cmdexpand("apt-ftparchive release ."),
                 stdout=open(release_filename, "w"), cwd=self.extra_pkg_dir)
        # handle signatures.  remove any existing signature because it
        # will be invalid now.
        signature = release_filename + ".gpg"
        try:
            os.remove(signature)
        except OSError:
            pass
        if self.release_signing_keyid:
            run.call(cmdexpand("gpg --detach-sign --armor --local-user=$keyid"
                               " -o $sig $release",
                               keyid=self.release_signing_keyid,
                               sig=release_filename + ".gpg",
                               release=release_filename))

    def add_binaries_to_extra_pkg(self, filenames, subdirectory="auto"):
        """Adds binary packages to the extra-pkg directory.
        The filenames parameter should be sequence of absolute
        filenames.  The files named will be copied to a subdirectory of
        the extra-pkg directory which is assumed to reside in the same
        directory as the pbuilderrc.  The subdirectory is specified with
        the subdirectory parameter and defaults to 'auto'.  Afterwards,
        the method generates a Packages file in the directory and runs
        pbuilder update.  All of this assumes that pbuilder was set up
        the way bin/initpbuilder.py does.
        """
        target_dir = os.path.join(self.extra_pkg_dir, subdirectory)
        util.ensure_directory(target_dir)
        for filename in filenames:
            logging.info("Copying %s into %s", filename, target_dir)
            shutil.copy(filename, target_dir)

        logging.info("Running apt-ftparchive in %s", self.extra_pkg_dir)
        self.update_extra_pkg_dir()

        self.update(suppress_output=True, log_info=True)

class PBuilder(Builder):

    """Represents a way to run and manage a specific pbuilder instance"""

    pbuilderrc_template = '''\
# This file was automatically generated by initpbuilder.py.
# for the possible settings see "man pbuilderrc"

BASETGZ=%(basetgz_dir)s/base.tgz
BUILDPLACE=%(build_dir)s
USEPROC=yes
USEDEVPTS=yes
BUILDRESULT=%(result_dir)s
DISTRIBUTION=%(distribution)s
APTCACHE=%(aptcache_dir)s
APTCACHEHARDLINK=yes
REMOVEPACKAGES=lilo
MIRRORSITE="%(mirrorsite)s"
OTHERMIRROR="%(othermirror)s"
BINDMOUNTS="%(extra_pkg_dir)s"
PKGNAME_LOGFILE=yes
'''

    def __init__(self, pbuilderrc, root_cmd, release_signing_keyid=None):
        """Initialize the PBuilder instance with the configuration file.
        The root_cmd parameter should be a list with a command that can
        be used to get root permissions to run pbuilder.  It may be an
        empty list if no command is needed.  It's a list so that
        commands with several shell-words can be used without having to
        worry about quoting.
        """
        self.pbuilderrc = pbuilderrc
        self.root_cmd = root_cmd
        self.release_signing_keyid = release_signing_keyid
        self.base_dir = os.path.dirname(self.pbuilderrc)

    def init_builder(self, distribution, mirrorsite, extramirrors):
        """Initializes the pbuilder instance"""
        if not os.path.isabs(self.pbuilderrc):
            print >>sys.stderr, "pbuilderrc must be an absolute filename"
            sys.exit(1)

        if os.path.exists(self.pbuilderrc):
            print >>sys.stderr, ("pbuilderrc %r already exists."
                                 % self.pbuilderrc)
            sys.exit(1)

        basedir = os.path.dirname(self.pbuilderrc)
        replacements = dict(basedir=basedir,
                            distribution=distribution,
                            mirrorsite=mirrorsite)

        # create the pbuilder directories.  basedir is created implicitly by
        # creating its subdirectories.
        for attr in ["basetgz_dir", "build_dir", "result_dir", "aptcache_dir",
                     "extra_pkg_dir"]:
            directory = getattr(self, attr)
            replacements[attr] = directory
            print "creating directory:", repr(directory)
            util.ensure_directory(directory)

        # build OTHERMIRROR value.  We always include the extra-pkg dir.
        othermirror = "deb file://%(extra_pkg_dir)s ./" % replacements
        if extramirrors:
            othermirror += " | " + extramirrors
        replacements["othermirror"] = othermirror

        # create the pbuilderrcfile
        print "creating pbuilderrc:", repr(self.pbuilderrc)
        util.writefile(self.pbuilderrc, self.pbuilderrc_template % replacements)

        # turn the extra-pkg directory into a proper deb archive
        print "turning the extra-pkg dir into a debian archive"
        self.update_extra_pkg_dir()

        # create the base.tgz chroot
        print "running pbuilder create"
        run.call(cmdexpand("@root_cmd pbuilder create --configfile $pbuilderrc",
                           root_cmd=self.root_cmd, pbuilderrc=self.pbuilderrc))

    def update(self, suppress_output=True, log_info=True):
        """Runs pbuilder update on this pbuilder instance"""
        if log_info:
            logging.info("Running pbuilder update for %s", self.pbuilderrc)
        run.call(cmdexpand("@rootcmd /usr/sbin/pbuilder update"
                           " --configfile $pbuilderrc",
                           rootcmd=self.root_cmd, pbuilderrc=self.pbuilderrc),
                 suppress_output=suppress_output)

    def add_apt_key(self, keyid):
        """Runs apt-key add in the chroot"""
        # Creates a temporary file in extra_pkg_dir (because that's
        # bind-mounted by default) with a script that adds the desired
        # key.  The exported key is included in the script file so that
        # only one file has to be created
        script = tempfile.NamedTemporaryFile(dir=self.extra_pkg_dir)
        try:
            script.write("#! /bin/sh\n")
            script.write("apt-key add $0\n")
            script.write("exit\n\n")
            script.flush()
            run.call(cmdexpand("gpg --export --armor $keyid", **locals()),
                     stdout=script.fileno())
            self.run_script([script.name], logfile=None, save_after_exec=True)
        finally:
            script.close()

    def build(self, dsc_file, binary_dir=None, logfile=None, bindmounts=(),
              extra_packages=(), extra_env=None):
        """Build a binary packager from a source package
        Parameters:
           dsc_file -- name of the debian .dsc file of the source package
           binary_dir -- name of the directory to receive the binary packages
           logfile -- name of the logfile of the build
           bindmounts -- Sequence of directory names that should be
                         bind-mounted in the pbuilder chroot
                         environment
           extra_packages -- Extra packages to install
           extra_env -- mapping with extra environment variables to set
                        when runing the pbuilder process.  If pbuilder
                        is started via sudo, make sure that sudo does
                        not remove these variables when it starts
                        pbuilder
        """
        args = []
        if logfile is not None:
            args.extend(["--logfile", logfile])
        if binary_dir is not None:
            args.extend(["--buildresult", binary_dir])
            util.ensure_directory(binary_dir)
        for mount in bindmounts:
            args.extend(["--bindmounts", mount])
        for pkg in extra_packages:
            args.extend(["--extrapackages", pkg])
        run.call(cmdexpand("@rootcmd /usr/sbin/pbuilder build"
                           " --configfile $pbuilderrc @args"
                           " --debbuildopts -b $dsc",
                           rootcmd=self.root_cmd, pbuilderrc=self.pbuilderrc,
                           dsc=dsc_file, args=args),
                 suppress_output=True,
                 extra_env=extra_env)

        # remove the source package files put into the binary directory
        # by pbuilder
        if binary_dir is not None:
            for filename in os.listdir(binary_dir):
                if os.path.splitext(filename)[1] not in (".deb", ".changes"):
                    os.remove(os.path.join(binary_dir, filename))


    def run_script(self, script, logfile, bindmounts=(), save_after_exec=False):
        """Execute a script in pbuilder's chroot environment
        Parameters:
           script -- A list of strings with the command line to invoke the
                     script
           logfile -- name of the logfile of the build
           bindmounts -- Sequence of directory names that should be
                         bind-mounted in the pbuilder chroot
                         environment (optional)
           save_after_exec -- Boolean indicating whether the chroot
                              environment should be copied back so that
                              modifications are available in subsequent
                              uses of the pbuilder instance.
        """
        logging.info("Running pbuilder execute on %s", script)
        args = []
        if logfile:
            args.extend(["--logfile", logfile])
            # create the logfile.  This makes sure that it is owned by
            # the user the tree packager is running as and not root, as
            # would be the case when it is created indirectly by
            # pbuilder
            open(logfile, "w").close()
        for mount in bindmounts:
            args.extend(["--bindmounts", mount])
        if save_after_exec:
            args.append("--save-after-exec")

        run.call(cmdexpand("@rootcmd /usr/sbin/pbuilder execute"
                           " --configfile $pbuilderrc @args -- @script",
                           rootcmd=self.root_cmd, pbuilderrc=self.pbuilderrc,
                           args=args, script=script),
                 suppress_output=False)

    def login(self, bindmounts=(), save_after_login=False):
        """Start an interactive shell in the pbuilder environment"""
        args = []
        for mount in bindmounts:
            args.extend(["--bindmounts", mount])
        if save_after_login:
            args.extend(["--save-after-login"])
        run.call(cmdexpand("@rootcmd /usr/sbin/pbuilder login"
                           " --configfile $pbuilderrc @args",
                           rootcmd=self.root_cmd, pbuilderrc=self.pbuilderrc,
                           args=args),
                 suppress_output=False)
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)