#!/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 = "unstable" build_place = os.path.join(os.getcwd(), 'debian_build') suffix_map = { 'wheezy': 'deb70', 'jessie': 'deb80', 'unstable': 'sid', 'sid': 'sid'} 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(open(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(open('debian/changelog')) package_name = cl.package package_version = re.sub('^\d+:','',str(cl.version)) orig_tar = package_name+'_'+ cl.upstream_version + ".orig.tar" dsc_name = package_name+"_"+package_version + '~' + version_suffix+ ".dsc" run_cmd(['dch', '-b', '-v' +str(cl.version)+'~'+version_suffix, '-D'+distribution, 'Autobuilt package']) print "==> Package: ", package_name source_format = command_output(('dpkg-source', '--print-format', '.')) if "native" not in source_format: run_cmd( ('git', 'fetch', 'origin', 'pristine-tar:pristine-tar')) for file in command_output(( 'pristine-tar', 'list')).split("\n"): if file.startswith(orig_tar): orig_tar = file run_cmd( ('pristine-tar', 'checkout', '../'+orig_tar)) 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(package_path): run_cmd('git checkout debian/changelog', shell=True) 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. Use the sbuild key to sign our packages''' script = '''#!/bin/sh set -e sudo -u root /usr/local/sbin/add-source {build_place} '''.format ( build_place = build_place ) f = open(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) run_cmd('apt-ftparchive release . >Release', shell=True) try: os.unlink('Release.gpg') except OSError: pass run_cmd( 'gpg -sabt -o Release.gpg --secret-keyring /var/lib/sbuild/apt-keys/sbuild-key.sec --keyring /var/lib/sbuild/apt-keys/sbuild-key.pub Release', shell=True) def read_packages(): '''Read in the packages file from source_packages ''' try: pf = open("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('--version-suffix', dest ='version_suffix') 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.add_option('--tar-file', dest='tar_file', help = 'Tar up resulting packages in given tar file', default = None) opt.usage = "%prog [options] [packages]" (options, packages) = opt.parse_args() build_place = options.build_place distribution = options.distribution version_suffix = options.version_suffix if not version_suffix: version_suffix = suffix_map.get(distribution, '0') sbuild_opts = options.sbuild_opts tar_file = options.tar_file if len(packages) == 0: packages = read_packages() try: os.makedirs(build_place) except OSError: pass code = 0 try: for p in packages: gen_package_files() build(p) except CommandError as c: print "Error:" + str(c.args) code = 1 finally: if tar_file is not None: with current_directory(build_place): run_cmd('tar -cf '+tar_file+' .', shell=True) sys.exit(code)