debian-builder: Update the pristine-tar branch when doing a pristine-tar checkout
[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 # These variables can be overridden by options. If packages is not
20 # set, then it is read from the source_packages file
21 packages = []  # Set of packages to build
22 distribution = "sid"
23 build_place = os.path.join(os.getcwd(), 'debian_build')
24
25 class CommandError(exceptions.StandardError):
26     pass
27
28 @contextmanager
29 def current_directory(dir):
30     "Change the current directory as a context manager; when the context exits, return."
31     cwd = os.getcwd()
32     os.chdir(dir)
33     yield
34     os.chdir(cwd)
35
36
37 def run_cmd(args, **kwords):
38     rcode =  subprocess.call( args, **kwords)
39     if rcode <> 0:
40         raise CommandError(args)
41
42 def command_output(args) :
43     p = subprocess.Popen(args, stdout=subprocess.PIPE)
44     output = p.communicate()
45     output = output[0]
46     if p.returncode != 0:
47         raise CommandError(args)
48     return output
49
50 def dsc_files(dsc) :
51     '''Describe all the files included in dsc, wich is a string name
52     of a dsc package. dsc itself is included'''
53     package = debian.deb822.Dsc(open(dsc))
54     internal_files = map( lambda(x): x['name'], package['Files'])
55     internal_files.append(dsc)
56     return internal_files
57
58 def build(package):
59     with current_directory(package):
60         cl = debian.changelog.Changelog(open('debian/changelog'))
61         package_name = cl.package
62         package_version = re.sub('^\d+:','',str(cl.version))
63         orig_tgz = package_name+'_'+ cl.upstream_version + ".orig.tar.gz"
64         dsc_name = package_name+"_"+package_version + ".dsc"
65         print "==> Package: ", package_name
66         source_format = command_output(('dpkg-source', '--print-format', '.'))
67         if "native" not in source_format:
68             run_cmd('git', 'fetch', 'origin', 'pristine-tar:pristine-tar')
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     sudo -u root /usr/local/sbin/add-source {build_place}
105     '''.format (
106         build_place = build_place
107         )
108     f = open(build_place + "/add_source", "w")
109     f.write(script)
110     f.close()
111     with current_directory(build_place):
112         run_cmd(('chmod', 'a+x', 'add_source'))
113         run_cmd( 'dpkg-scanpackages . >Packages',
114                  shell = True)
115
116
117 def read_packages():
118     '''Read in the packages file from source_packages
119     '''
120     try: pf = open("source_packages")
121     except IOError:
122         print "Error: source_packages file not found"
123         exit(1)
124     def is_comment(line):
125         if re.match("^\\s*#", line): return False
126         if "#" in line: raise ValueError(
127             "Source package line contains a comment but not at beginning")
128         return True
129     return map(lambda(x): x.rstrip(),
130         filter(is_comment, pf.readlines()))
131
132
133 # main program
134 opt = OptionParser()
135 opt.add_option('-b', '--build-place',
136                dest='build_place', help="Write resulting packages to BUILD_PLACE",
137                default=build_place)
138 opt.add_option('-d', '--distribution',
139                help="Set the Debian distribution to DISTRIBUTION",
140                dest="distribution",
141                default=distribution
142                )
143 opt.add_option('-s', '--sbuild-opt',
144                action='append', dest='sbuild_opts',
145                help='Specify an option to be sent to sbuild')
146 opt.add_option('--tar-file',
147                dest='tar_file',
148                help = 'Tar up resulting packages in given tar file',
149                default = None)
150 opt.usage = "%prog [options] [packages]"
151 (options, packages) = opt.parse_args()
152 build_place = options.build_place
153 distribution = options.distribution
154 sbuild_opts = options.sbuild_opts
155 tar_file = options.tar_file
156
157
158 if len(packages) == 0: packages = read_packages()
159 try:
160     os.makedirs(build_place)
161 except OSError: pass
162
163 code = 0
164 try:
165     for p in packages:
166         gen_package_files()
167         build(p)
168 except CommandError as c:
169     print "Error:" + str(c.args)
170     code = 1
171 finally:
172     if tar_file is not None:
173         with current_directory(build_place):
174             run_cmd('tar -cf '+tar_file+' .',
175                     shell=True)
176
177 sys.exit(code)