#!/usr/bin/python '''A script to build Debian packages using sbuild from a source directory rather than a source package. Also, if multiple packages are built, previous packages are available to satisfy dependencies. Requires that the build-place be available from the chroot. ''' from contextlib import contextmanager import os, subprocess, exceptions import re import sys from optparse import OptionParser from shutil import copy import debian.changelog, debian.deb822 # These variables can be overridden by options. If packages is not # set, then it is read from the source_packages file packages = [] # Set of packages to build distribution = "sid" build_place = os.path.join(os.getcwd(), 'debian_build') class CommandError(exceptions.StandardError): pass @contextmanager def current_directory(dir): "Change the current directory as a context manager; when the context exits, return." cwd = os.getcwd() os.chdir(dir) yield os.chdir(cwd) def run_cmd(args, **kwords): rcode = subprocess.call( args, **kwords) if rcode <> 0: raise CommandError(args) def command_output(args) : p = subprocess.Popen(args, stdout=subprocess.PIPE) output = p.communicate() output = output[0] if p.returncode != 0: raise CommandError(args) return output def dsc_files(dsc) : '''Describe all the files included in dsc, wich is a string name of a dsc package. dsc itself is included''' package = debian.deb822.Dsc(file(dsc)) internal_files = map( lambda(x): x['name'], package['Files']) internal_files.append(dsc) return internal_files def build(package): with current_directory(package): cl = debian.changelog.Changelog(file('debian/changelog')) package_name = cl.package package_version = str(cl.version) orig_tgz = package_name+'_'+ cl.upstream_version + ".orig.tar.gz" dsc_name = package_name+"_"+package_version + ".dsc" print "==> Package: ", package_name source_format = command_output(('dpkg-source', '--print-format', '.')) if "native" not in source_format: run_cmd( ('pristine-tar', 'checkout', '../'+orig_tgz)) package_path = os.path.split(package) with current_directory(os.path.join ('.', *package_path[0:len(package_path)-1])) : package_path = package_path[len(package_path)-1] try: run_cmd(("dpkg-source", '-b', '-i', package_path)) for f in dsc_files(dsc_name): copy(f, build_place) finally: for f in dsc_files(dsc_name): try: os.unlink(f) except OSError: pass with current_directory(build_place): sb = ['sbuild', '-n', '-d', distribution, '--setup-hook', build_place + '/add_source'] if sbuild_opts is not None: sb += sbuild_opts sb.append(dsc_name) print " ".join(sb) sys.stdout.flush() run_cmd(sb) def gen_package_files() : '''Generate package files in build_place and a script build_place/add_source that can be used as a sbuild setup hook to include the package files. Unfortunately, apt doesn't have a mechanism for asserting that a package file is trusted when it is local. We could generate a unique gpg key, generate signed releases files and trust that key. It's easier to simply touch the release.gpg in apt's lists directory, which turns out to do what we need.''' # Rather than substuting the release file directly, we create gpg # release files for any local package list. That's easier than # encoding apt's ideas about what characters to escape. script = '''#!/bin/sh set -e echo deb file:{build_place} ./ >>/etc/apt/sources.list apt-get update touch $(ls /var/lib/apt/lists/_*Packages \ |sed -e s:Packages\$:Release.gpg:) '''.format ( build_place = build_place ) f = file(build_place + "/add_source", "w") f.write(script) f.close() with current_directory(build_place): run_cmd(('chmod', 'a+x', 'add_source')) run_cmd( 'dpkg-scanpackages . >Packages', shell = True) def read_packages(): '''Read in the packages file from source_packages ''' try: pf = file("source_packages") except IOError: print "Error: source_packages file not found" exit(1) def is_comment(line): if re.match("^\\s*#", line): return False if "#" in line: raise ValueError( "Source package line contains a comment but not at beginning") return True return map(lambda(x): x.rstrip(), filter(is_comment, pf.readlines())) # main program opt = OptionParser() opt.add_option('-b', '--build-place', dest='build_place', help="Write resulting packages to BUILD_PLACE", default=build_place) opt.add_option('-d', '--distribution', help="Set the Debian distribution to DISTRIBUTION", dest="distribution", default=distribution ) opt.add_option('-s', '--sbuild-opt', action='append', dest='sbuild_opts', help='Specify an option to be sent to sbuild') opt.usage = "%prog [options] [packages]" (options, packages) = opt.parse_args() build_place = options.build_place distribution = options.distribution sbuild_opts = options.sbuild_opts if len(packages) == 0: packages = read_packages() try: os.makedirs(build_place) except OSError: pass try: for p in packages: gen_package_files() build(p) except CommandError as c: print "Error:" + str(c.args) exit(1)