bh@134: # Copyright (C) 2007, 2008 by Intevation GmbH
bh@134: # Authors:
bh@134: # Bernhard Herzog <bh@intevation.de>
bh@134: #
bh@134: # This program is free software under the GPL (>=v2)
bh@134: # Read the file COPYING coming with the software for details.
bh@134: 
bh@134: """Base classes for all gnupg packagers"""
bh@134: 
bh@134: import os
bh@134: import re
bh@134: import inspect
bh@134: import new
bh@134: 
bh@134: import treepkg.util
bh@134: import treepkg.packager
bh@134: import treepkg.run as run
bh@134: from treepkg.cmdexpand import cmdexpand
bh@134: 
bh@134: 
bh@134: class BaseSourcePackager(treepkg.packager.SourcePackager):
bh@134: 
bh@134:     def orig_source_version(self, directory):
bh@134:         """Determines the version from the configure.ac file in directory"""
bh@134:         filename = os.path.join(directory, "configure.ac")
bh@134:         for line in open(filename):
bh@134: 
bh@134:             # Matches lines like
bh@134:             # m4_define(my_version, [1.1.7])
bh@134:             # used by most of the gnupg packages
bh@197:             match = re.match(r"m4_define\(\[?my_version\]?, \[([^]]+)\]\)",
bh@134:                              line)
bh@134:             if match:
bh@134:                 return match.group(1)
bh@134: 
bh@134:             # Matches lines like.
bh@134:             # AC_INIT(pinentry, 0.7.6-cvs, [gnupg-devel@gnupg.org])
bh@134:             # pinentry is the GnuPG package that actually needs this
bh@134:             match = re.match(r"AC_INIT\([a-zA-Z_]+, ([0-9.]+)", line)
bh@134:             if match:
bh@134:                 return match.group(1)
bh@134: 
bh@134:         raise RuntimeError("Could not determine the version number from %s"
bh@134:                            % filename)
bh@134: 
bh@134:     def determine_package_version(self, directory):
aheinecke@327:         return "%s+svn%s" % (self.orig_source_version(directory), self.revision)
bh@134: 
bh@134:     def do_package(self):
bh@134:         pkgbaseversion, pkgbasedir = self.export_sources()
bh@134: 
bh@134:         run.call(cmdexpand("/bin/sh autogen.sh"), cwd=pkgbasedir,
bh@134:                  suppress_output=True)
bh@134:         orig_version = self.orig_source_version(pkgbasedir)
bh@134: 
bh@134:         # patch the version number in the newly generated configure
bh@134:         # file.  autogen.sh normally determines it from svn, but here it
bh@134:         # ran on a copy that did not include the .svn subdirectories and
bh@134:         # thus could not find the svn revision.
bh@134:         treepkg.util.replace_in_file(os.path.join(pkgbasedir, "configure"),
bh@134:                                      re.escape(orig_version) + "-svn0",
aheinecke@327:                                      orig_version + "-svn%s" % self.revision)
bh@134: 
bh@134:         pkgbasename = self.pkg_basename + "_" + pkgbaseversion
bh@134:         origtargz = os.path.join(self.work_dir,
bh@134:                                  pkgbasename + ".orig.tar.gz")
bh@134:         self.create_tarball(origtargz, self.work_dir,
bh@134:                             os.path.basename(pkgbasedir))
bh@134: 
aheinecke@327:         changemsg = ("Update to SVN rev. %s" % (self.revision,))
bh@134:         self.copy_debian_directory(pkgbasedir, pkgbaseversion,
bh@134:                                    changemsg)
bh@134: 
bh@134:         self.create_source_package(pkgbasedir, origtargz)
bh@134:         self.move_source_package(pkgbasename)
bh@134: 
bh@134: 
bh@134: class SmartSourcePackager(BaseSourcePackager):
bh@134: 
bh@134:     """SourcePackager that uses pbuilder to create the source tarball.
bh@134: 
bh@134:     We try to create source tarballs that are as close to the tarballs
bh@206:     created by the upstream maintainers as possible.  For the gnupg
bh@206:     software this means we need to run 'make dist' in a configured SVN
bh@206:     working copy with some additional software installed like autoconf
bh@206:     and texinfo.  We want to avoid running code from a working copy
bh@206:     outside of the pbuilder environment and having to install recipe
bh@206:     specific additional software packages in the treepkg host system.
bh@206:     Therefore we create the source tarball using 'pbuilder execute' with
bh@206:     a script.
bh@134:     """
bh@134: 
bh@134:     createtarball_script = """\
bh@134: #! /bin/bash
bh@134: set -e
bh@134: 
bh@134: apt-get --assume-yes --force-yes install %(builddeps)s
bh@134: 
bh@134: # copy the source tree to a directory that's under pbuilder control so
bh@134: # that it gets removed along with the build environment.  Otherwise we
bh@134: # end up with a directory containing files that cannot be removed by
bh@134: # treepkg
bh@134: workdir=/tmp/work
bh@134: cp -a %(basedir)s $workdir
bh@134: cd $workdir
bh@134: 
bh@134: # tweak autoconf settings so that make dist produces a tar.gz, not a
bh@134: # tar.bz2. Removing all options whose names contain 'dist' should
bh@134: # achieve that.
bh@134: cp Makefile.am Makefile.am.orig
bh@134: sed -e '/AUTOMAKE_OPTIONS/ s/[a-zA-Z0-9-]*dist[a-zA-Z0-9-]*//g' \
bh@134:     Makefile.am.orig > Makefile.am
bh@134: 
bh@134: ./autogen.sh
bh@134: ./configure --enable-maintainer-mode
bh@134: 
bh@134: # revert autoconf changes, so that the original Makefile.am ends up in
bh@134: # the tarball
bh@134: mv Makefile.am.orig Makefile.am
bh@134: 
bh@134: %(make_dist_command)s
bh@134: 
bh@134: mv *.tar.gz %(origtargz)s
bh@134: """
bh@134: 
bh@134:     make_dist_command = "make dist"
bh@134: 
bh@134:     def __init__(self, *args):
bh@134:         super(SmartSourcePackager, self).__init__(*args)
bh@134:         self.pkgbasename = None
bh@134:         self.pkgbaseversion = None
bh@134:         self.origtargz = None
bh@134: 
bh@134:     def copy_workingcopy(self, dest):
bh@134:         treepkg.util.copytree(self.track.checkout_dir, dest)
bh@134: 
bh@134:     def create_original_tarball(self):
bh@134:         copied_working_copy = os.path.join(self.work_dir, "copied_working_copy")
bh@134:         self.copy_workingcopy(copied_working_copy)
bh@134: 
bh@134:         self.pkgbaseversion = \
bh@134:                             self.determine_package_version(copied_working_copy)
bh@134:         self.pkgbasename = self.pkg_basename + "_" + self.pkgbaseversion
bh@134:         self.origtargz = os.path.join(self.work_dir,
bh@134:                                       self.pkgbasename + ".orig.tar.gz")
bh@134: 
bh@134:         script = (self.createtarball_script
bh@134:                   % dict(builddeps=" ".join(self.track.dependencies_required()
bh@134:                                             | self.tarball_dependencies),
bh@134:                          basedir=copied_working_copy,
bh@134:                          origtargz=self.origtargz,
bh@134:                          make_dist_command=self.make_dist_command))
bh@134:         script_name = os.path.join(self.work_dir, "createtarball")
bh@134:         treepkg.util.writefile(script_name, script, 0755)
bh@134: 
bh@134:         treepkg.util.ensure_directory(self.src_dir)
bh@137:         treepkg.util.ensure_directory(self.log_dir)
bh@181:         self.track.builder.run_script([script_name],
bh@137:                                       logfile=os.path.join(self.log_dir,
bh@137:                                                            "tarball_log.txt"),
bh@134:                                       bindmounts=[self.work_dir, self.src_dir])
bh@134: 
bh@134:     def create_orig_dir(self):
bh@134:         """Unpacks the tarball created by create_original_tarball into work_dir
bh@134:         """
bh@134:         unpack_dir = os.path.join(self.work_dir, "unpack")
bh@134:         treepkg.util.ensure_directory(unpack_dir)
bh@134:         run.call(cmdexpand("tar xzf $origtargz -C $unpack_dir",
bh@134:                            unpack_dir=unpack_dir, origtargz=self.origtargz))
bh@134:         unpacked_files = treepkg.util.listdir_abs(unpack_dir)
bh@134:         if len(unpacked_files) != 1:
bh@134:             raise RuntimeError("%s should have extracted to a single directory",
bh@134:                                origtargz)
bh@134:         unpacked_dir = unpacked_files[0]
bh@134: 
bh@134:         orig_dir = os.path.join(self.work_dir, os.path.basename(unpacked_dir))
bh@134:         os.rename(unpacked_dir, orig_dir)
bh@134:         return orig_dir
bh@134: 
bh@134:     def do_package(self):
bh@134:         self.create_original_tarball()
bh@134:         orig_dir = self.create_orig_dir()
bh@134: 
aheinecke@327:         changemsg = ("Update to SVN rev. %s" % (self.revision,))
bh@134:         self.copy_debian_directory(orig_dir, self.pkgbaseversion, changemsg)
bh@134: 
bh@134:         self.create_source_package(orig_dir, self.origtargz)
bh@134:         self.move_source_package(self.pkgbasename)
bh@134: 
bh@134: 
bh@134: def define_gnupg_packager(pkg_basename,
bh@134:                           tarball_dependencies=("autoconf", "automake",
bh@134:                                                 "texinfo", "subversion"),
bh@134:                           make_dist_command=None):
bh@134:     """Create a SourcePackager for a GnuPG package in the caller's globals().
bh@134:     This is a helper function for the modules in the recipe.gnupg package.
bh@134:     """
bh@134:     base_class = BaseSourcePackager
bh@134:     class_attributes = dict(pkg_basename=pkg_basename)
bh@134:     if tarball_dependencies is not None:
bh@134:         base_class = SmartSourcePackager
bh@134:         class_attributes["tarball_dependencies"] = set(tarball_dependencies)
bh@134:     if make_dist_command is not None:
bh@134:         base_class = SmartSourcePackager
bh@134:         class_attributes["make_dist_command"] = make_dist_command
bh@134: 
bh@134:     caller_globals = inspect.currentframe().f_back.f_globals
bh@134:     caller_globals["SourcePackager"] = new.classobj("SourcePackager",
bh@134:                                                     (base_class,),
bh@134:                                                     class_attributes)