Mercurial > treepkg > treepkg
diff treepkg/packager.py @ 0:f78a02e79c84
initial checkin
author | Bernhard Herzog <bh@intevation.de> |
---|---|
date | Tue, 06 Mar 2007 17:37:32 +0100 |
parents | |
children | e6a9f4037f68 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/treepkg/packager.py Tue Mar 06 17:37:32 2007 +0100 @@ -0,0 +1,340 @@ +# Copyright (C) 2007 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. + +"""Classes to automatically build debian packages from subversion checkouts""" + +import os +import time +import re +import logging +import shutil +import traceback + +import util +import subversion +import run + + +def _filenameproperty(relative_dir): + def get(self): + return os.path.join(self.base_dir, relative_dir) + return property(get) + + +class SourcePackager(object): + + def __init__(self, plant, status, work_dir, src_dir, revision): + self.plant = plant + self.status = status + self.work_dir = work_dir + self.src_dir = src_dir + self.revision = revision + self.enterprise_version = (time.strftime("%Y%m%d", time.localtime()) \ + + "." + str(self.revision)) + + def kdepim_version(self, directory): + """Determine the kdepim version. + + The version is taken from the kdepim.lsm file in the plants + checkout dir. + """ + return util.extract_lsm_version(os.path.join(directory, "kdepim.lsm")) + + def determine_package_version(self, directory): + enterprise_version = self.enterprise_version + kdepimversion = self.kdepim_version(directory) + version_template = "%(kdepimversion)s.enterprise.0.%(enterprise_version)s" + return version_template % locals() + + def export_sources(self): + temp_dir = os.path.join(self.work_dir, "temp") + self.plant.export_sources(temp_dir) + + pkgbaseversion = self.determine_package_version(temp_dir) + pkgbasedir = os.path.join(self.work_dir, "kdepim-" + pkgbaseversion) + + os.rename(temp_dir, pkgbasedir) + return pkgbaseversion, pkgbasedir + + + def update_version_numbers(self, pkgbasedir): + versionstring = "(enterprise %s)" % self.enterprise_version + for versionfile in ["kmail/kmversion.h", "kontact/src/main.cpp", + "korganizer/version.h"]: + filename = os.path.join(pkgbasedir, versionfile) + patched = re.sub("\(enterprise ([^)]*)\)", versionstring, + open(filename).read()) + f = open(filename, "w") + f.write(patched) + f.close() + + def create_tarball(self, tarballname, workdir, basedir): + logging.info("Creating tarball %r", tarballname) + run.call(["tar", "czf", tarballname, "-C", workdir, basedir]) + + def copy_debian_directory(self, pkgbasedir, pkgbaseversion, changemsg): + debian_dir = os.path.join(pkgbasedir, "debian") + changelog = os.path.join(debian_dir, "changelog") + + self.plant.copy_debian_directory(debian_dir) + + logging.info("Updating %r", changelog) + oldversion = util.debian_changelog_version(changelog) + if ":" in oldversion: + oldversionprefix = oldversion.split(":")[0] + ":" + else: + oldversionprefix = "" + run.call(["debchange", "-c", changelog, + "-v", oldversionprefix + pkgbaseversion + "-kk1", + changemsg], + env=self.plant.debian_environment()) + + + def create_source_package(self, pkgbasedir, origtargz): + logging.info("Creating new source package") + run.call(["dpkg-source", "-b", os.path.basename(pkgbasedir), + os.path.basename(origtargz)], + cwd=os.path.dirname(pkgbasedir), + suppress_output=True, + env=self.plant.debian_environment()) + + def package(self): + util.ensure_directory(self.work_dir) + try: + self.status.set("creating_source_package") + pkgbaseversion, pkgbasedir = self.export_sources() + self.update_version_numbers(pkgbasedir) + + pkgbasename = "kdepim_" + pkgbaseversion + origtargz = os.path.join(self.work_dir, + pkgbasename + ".orig.tar.gz") + self.create_tarball(origtargz, self.work_dir, + os.path.basename(pkgbasedir)) + + changemsg = ("Update to SVN enterprise branch rev. %d" + % (self.revision,)) + self.copy_debian_directory(pkgbasedir, pkgbaseversion, + changemsg) + + self.create_source_package(pkgbasedir, origtargz) + + logging.info("Moving source package to %r", self.src_dir) + util.ensure_directory(self.src_dir) + for filename in [filename for filename in os.listdir(self.work_dir) + if filename.startswith(pkgbasename)]: + os.rename(os.path.join(self.work_dir, filename), + os.path.join(self.src_dir, filename)) + self.status.set("source_package_created") + finally: + logging.info("Removing workdir %r", self.work_dir) + shutil.rmtree(self.work_dir) + + +class BinaryPackager(object): + + def __init__(self, plant, status, binary_dir, dsc_file, logfile): + self.plant = plant + self.status = status + self.binary_dir = binary_dir + self.dsc_file = dsc_file + self.logfile = logfile + + def package(self): + self.status.set("creating_binary_package") + util.ensure_directory(self.binary_dir) + logging.info("Building binary package; loging to %r", self.logfile) + cmd = [] + if self.plant.root_cmd: + cmd.append(self.plant.root_cmd) + run.call(cmd + ["/usr/sbin/pbuilder", "build", + "--logfile", self.logfile, + "--buildresult", self.binary_dir, + self.dsc_file], + suppress_output=True) + self.status.set("binary_package_created") + + +class RevisionPackager(object): + + def __init__(self, plant, revision): + self.plant = plant + self.revision = revision + self.base_dir = self.plant.pkg_dir_for_revision(self.revision, 1) + self.status = util.StatusFile(os.path.join(self.base_dir, "status")) + + work_dir = _filenameproperty("work") + binary_dir = _filenameproperty("binary") + src_dir = _filenameproperty("src") + + def find_dsc_file(self): + for filename in os.listdir(self.src_dir): + if filename.endswith(".dsc"): + return os.path.join(self.src_dir, filename) + return None + + def package(self): + try: + src_packager = SourcePackager(self.plant, self.status, + self.work_dir, self.src_dir, + self.revision) + src_packager.package() + + dsc_file = self.find_dsc_file() + if dsc_file is None: + raise RuntimeError("Cannot find dsc File in %r" % self.src_dir) + + bin_packager = BinaryPackager(self.plant, self.status, + self.binary_dir, dsc_file, + os.path.join(self.base_dir, + "build.log")) + bin_packager.package() + except: + self.status.set("failed", traceback.format_exc()) + raise + + def remove_package_dir(self): + logging.info("Removing pkgdir %r", self.base_dir) + shutil.rmtree(self.base_dir) + + +class AssemblyLine(object): + + def __init__(self, name, base_dir, svn_url, root_cmd, deb_email, + deb_fullname): + self.name = name + self.base_dir = base_dir + self.svn_url = svn_url + self.root_cmd = root_cmd + self.deb_email = deb_email + self.deb_fullname = deb_fullname + self.pkg_dir_template = "%(revision)d-%(increment)d" + self.pkg_dir_regex \ + = re.compile(r"(?P<revision>[0-9]+)-(?P<increment>[0-9]+)$") + + checkout_dir = _filenameproperty("checkout") + debian_dir = _filenameproperty("debian") + pkg_dir = _filenameproperty("pkg") + + def pkg_dir_for_revision(self, revision, increment): + return os.path.join(self.pkg_dir, + self.pkg_dir_template % locals()) + + def last_changed_revision(self): + rev1 = subversion.last_changed_revision(self.checkout_dir) + rev2 = subversion.last_changed_revision(os.path.join(self.checkout_dir, + "admin")) + return max(rev1, rev2) + + def last_packaged_revision(self): + """Returns the revision number of the highest packaged revision. + + If the revision cannot be determined because no already packaged + revisions can be found, the function returns -1. + """ + revisions = [-1] + if os.path.exists(self.pkg_dir): + for filename in os.listdir(self.pkg_dir): + match = self.pkg_dir_regex.match(filename) + if match: + revisions.append(int(match.group("revision"))) + return max(revisions) + + def debian_source(self): + return util.extract_value_for_key(open(os.path.join(self.debian_dir, + "control")), + "Source:") + + def update_checkout(self): + """Updates the working copy of self.svn_url in self.checkout_dir. + + If self.checkout_dir doesn't exist yet, self.svn_url is checked + out into that directory. + """ + localdir = self.checkout_dir + if os.path.exists(localdir): + logging.info("Updating the working copy in %r", localdir) + subversion.update(localdir) + else: + logging.info("The working copy in %r doesn't exist yet." + " Checking out fromo %r", localdir, + self.svn_url) + subversion.checkout(self.svn_url, localdir) + + def export_sources(self, to_dir): + logging.info("Exporting sources for tarball to %r", to_dir) + subversion.export(self.checkout_dir, to_dir) + # some versions of svn (notably version 1.4.2 shipped with etch) + # do export externals such as the admin subdirectory. We may + # have to do that in an extra step. + admindir = os.path.join(to_dir, "admin") + if not os.path.isdir(admindir): + subversion.export(os.path.join(self.checkout_dir, "admin"), + admindir) + + def copy_debian_directory(self, to_dir): + logging.info("Copying debian directory to %r", to_dir) + shutil.copytree(self.debian_dir, to_dir) + + def debian_environment(self): + """Returns the environment variables for the debian commands""" + env = os.environ.copy() + env["DEBFULLNAME"] = self.deb_fullname + env["DEBEMAIL"] = self.deb_email + return env + + def package_if_updated(self): + """Checks if the checkout changed and returns a new packager if so""" + self.update_checkout() + current_revision = self.last_changed_revision() + logging.info("New revision is %d", current_revision) + previous_revision = self.last_packaged_revision() + logging.info("Previously packaged revision was %d", previous_revision) + if current_revision > previous_revision: + logging.info("New revision is not packaged yet") + return RevisionPackager(self, current_revision) + else: + logging.info("New revision already packaged.") + + + +class Packager(object): + + def __init__(self, assembly_lines, check_interval): + self.assembly_lines = assembly_lines + self.check_interval = check_interval + + def run(self): + """Runs the plant indefinitely""" + logging.info("Packager start. Will check every %d seconds", + self.check_interval) + last_check = -1 + while 1: + now = time.time() + if now > last_check + self.check_interval: + self.check_assembly_lines() + last_check = now + next_check = now + self.check_interval + to_sleep = next_check - time.time() + if to_sleep > 0: + logging.info("Next check at %s", + time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(next_check))) + time.sleep(to_sleep) + else: + logging.info("Next check now") + + def check_assembly_lines(self): + logging.info("Checking assembly lines") + for line in self.assembly_lines: + try: + packager = line.package_if_updated() + if packager: + packager.package() + except: + logging.exception("An error occurred while" + " checking assembly line %r", line.name) + logging.info("Checked all assembly lines")