debian-builder: add option handling
[moonshot.git] / debian-builder
1 #!/usr/bin/python
2
3 '''A script to build Debian packages using sbuild from a source
4 directory rather than a source package. Also, if multiple packages are
5 built, previous packages are available to satisfy
6 dependencies. Requires that the build-place be available from the
7 chroot.
8 '''
9
10 from contextlib import contextmanager
11 import os, subprocess, exceptions
12 import re
13 import sys
14 from optparse import OptionParser
15 from shutil import copy
16
17 import debian.changelog, debian.deb822
18
19
20 # These variables can be overridden by options. If packages is not
21 # set, then it is read from the source_packages file
22 packages = []  # Set of packages to build
23 distribution = "sid"
24 build_place = os.path.join(os.getcwd(), 'debian_build')
25
26 class CommandError(exceptions.StandardError):
27     pass
28
29 @contextmanager
30 def current_directory(dir):
31     "Change the current directory as a context manager; when the context exits, return."
32     cwd = os.getcwd()
33     os.chdir(dir)
34     yield
35     os.chdir(cwd)
36
37
38 def run_cmd(args, **kwords):
39     rcode =  subprocess.call( args, **kwords)
40     if rcode <> 0:
41         raise CommandError(args)
42
43 def command_output(args) :
44     p = subprocess.Popen(args, stdout=subprocess.PIPE)
45     output = p.communicate()
46     output = output[0]
47     if p.returncode != 0:
48         raise CommandError(args)
49     return output
50
51 def dsc_files(dsc) :
52     '''Describe all the files included in dsc, wich is a string name
53     of a dsc package. dsc itself is included'''
54     package = debian.deb822.Dsc(file(dsc))
55     internal_files = map( lambda(x): x['name'], package['Files'])
56     internal_files.append(dsc)
57     return internal_files
58
59 def build(package):
60     with current_directory(package):
61         cl = debian.changelog.Changelog(file('debian/changelog'))
62         package_name = cl.package
63         package_version = str(cl.version)
64         orig_tgz = package_name+'_'+ cl.upstream_version + ".orig.tar.gz"
65         dsc_name = package_name+"_"+package_version + ".dsc"
66         print "==> Package: ", package_name
67         source_format = command_output(('dpkg-source', '--print-format', '.'))
68         if "native" not in source_format:
69             run_cmd( ('pristine-tar', 'checkout', '../'+orig_tgz))
70     package_path = os.path.split(package)
71     with current_directory(os.path.join ('.', *package_path[0:len(package_path)-1])) :
72         package_path = package_path[len(package_path)-1]
73         try:
74             run_cmd(("dpkg-source", '-b', '-i', package_path))
75             for f in dsc_files(dsc_name):
76                 copy(f, build_place)
77         finally:
78             for f in dsc_files(dsc_name):
79                 try: os.unlink(f)
80                 except OSError: pass
81     with current_directory(build_place):
82         sb = ['sbuild', '-n', '-d', distribution, '--setup-hook',
83     build_place + '/add_source']
84         if sbuild_opts is not None: sb += sbuild_opts
85         sb.append(dsc_name)
86         print " ".join(sb)
87         sys.stdout.flush()
88         run_cmd(sb)
89
90 def gen_package_files() :
91     '''Generate package files in build_place and a script
92     build_place/add_source that can be used as a sbuild setup hook to
93     include the package files. Unfortunately, apt doesn't have a
94     mechanism for asserting that a package file is trusted when it is
95     local. We could generate a unique gpg key, generate signed
96     releases files and trust that key. It's easier to simply touch the
97     release.gpg in apt's lists directory, which turns out to do what
98     we need.'''
99     # Rather than substuting the release file directly, we create gpg
100     # release files for any  local package list. That's easier than
101     # encoding apt's ideas about what characters to escape.
102     script = '''#!/bin/sh
103     set -e
104     echo deb file:{build_place} ./ >>/etc/apt/sources.list
105     apt-get update
106     touch $(ls /var/lib/apt/lists/_*Packages \
107         |sed -e s:Packages\$:Release.gpg:)
108     '''.format (
109         build_place = build_place
110         )
111     f = file(build_place + "/add_source", "w")
112     f.write(script)
113     f.close()
114     with current_directory(build_place):
115         run_cmd(('chmod', 'a+x', 'add_source'))
116         run_cmd( 'dpkg-scanpackages . >Packages',
117                  shell = True)
118
119
120 def read_packages():
121     '''Read in the packages file from source_packages
122     '''
123     try: pf = file("source_packages")
124     except IOError:
125         print "Error: source_packages file not found"
126         exit(1)
127     def is_comment(line):
128         if re.match("^\\s*#", line): return False
129         if "#" in line: raise ValueError(
130             "Source package line contains a comment but not at beginning")
131         return True
132     return map(lambda(x): x.rstrip(),
133         filter(is_comment, pf.readlines()))
134
135
136 # main program
137 opt = OptionParser()
138 opt.add_option('-b', '--build-place',
139                dest='build_place', help="Write resulting packages to BUILD_PLACE",
140                default=build_place)
141 opt.add_option('-d', '--distribution',
142                help="Set the Debian distribution to DISTRIBUTION",
143                dest="distribution",
144                default=distribution
145                )
146 opt.add_option('-s', '--sbuild-opt',
147                action='append', dest='sbuild_opts',
148                help='Specify an option to be sent to sbuild')
149 opt.usage = "%prog [options] [packages]"
150 (options, packages) = opt.parse_args()
151 build_place = options.build_place
152 distribution = options.distribution
153 sbuild_opts = options.sbuild_opts
154
155 if len(packages) == 0: packages = read_packages()
156
157 try:
158     os.makedirs(build_place)
159 except OSError: pass
160
161 try:
162     for p in packages:
163         gen_package_files()
164         build(p)
165 except CommandError as c:
166     print "Error:" + str(c.args)
167     exit(1)
168
169