Sign packages so modern sbuild works
[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_tar = package_name+'_'+ cl.upstream_version + ".orig.tar"
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             for file in command_output(( 'pristine-tar', 'list')).split("\n"):
70                 if file.startswith(orig_tar): orig_tar = file
71             run_cmd( ('pristine-tar', 'checkout', '../'+orig_tar))
72     package_path = os.path.split(package)
73     with current_directory(os.path.join ('.', *package_path[0:len(package_path)-1])) :
74         package_path = package_path[len(package_path)-1]
75         try:
76             run_cmd(("dpkg-source", '-b', '-i', package_path))
77             for f in dsc_files(dsc_name):
78                 copy(f, build_place)
79         finally:
80             for f in dsc_files(dsc_name):
81                 try: os.unlink(f)
82                 except OSError: pass
83     with current_directory(build_place):
84         sb = ['sbuild', '-n', '-d', distribution, '--setup-hook',
85     build_place + '/add_source']
86         if sbuild_opts is not None: sb += sbuild_opts
87         sb.append(dsc_name)
88         print " ".join(sb)
89         sys.stdout.flush()
90         run_cmd(sb)
91
92 def gen_package_files() :
93     '''Generate package files in build_place and a script
94     build_place/add_source that can be used as a sbuild setup hook to
95     include the package files.  Use the sbuild key to sign our packages'''
96     script = '''#!/bin/sh
97     set -e
98     sudo -u root /usr/local/sbin/add-source {build_place}
99     '''.format (
100         build_place = build_place
101         )
102     f = open(build_place + "/add_source", "w")
103     f.write(script)
104     f.close()
105     with current_directory(build_place):
106         run_cmd(('chmod', 'a+x', 'add_source'))
107         run_cmd( 'dpkg-scanpackages . >Packages',
108                  shell = True)
109         run_cmd('apt-ftparchive   release . >Release', shell=True)
110         try: os.unlink('Release.gpg')
111         except OSError: pass
112         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)
113
114
115 def read_packages():
116     '''Read in the packages file from source_packages
117     '''
118     try: pf = open("source_packages")
119     except IOError:
120         print "Error: source_packages file not found"
121         exit(1)
122     def is_comment(line):
123         if re.match("^\\s*#", line): return False
124         if "#" in line: raise ValueError(
125             "Source package line contains a comment but not at beginning")
126         return True
127     return map(lambda(x): x.rstrip(),
128         filter(is_comment, pf.readlines()))
129
130
131 # main program
132 opt = OptionParser()
133 opt.add_option('-b', '--build-place',
134                dest='build_place', help="Write resulting packages to BUILD_PLACE",
135                default=build_place)
136 opt.add_option('-d', '--distribution',
137                help="Set the Debian distribution to DISTRIBUTION",
138                dest="distribution",
139                default=distribution
140                )
141 opt.add_option('-s', '--sbuild-opt',
142                action='append', dest='sbuild_opts',
143                help='Specify an option to be sent to sbuild')
144 opt.add_option('--tar-file',
145                dest='tar_file',
146                help = 'Tar up resulting packages in given tar file',
147                default = None)
148 opt.usage = "%prog [options] [packages]"
149 (options, packages) = opt.parse_args()
150 build_place = options.build_place
151 distribution = options.distribution
152 sbuild_opts = options.sbuild_opts
153 tar_file = options.tar_file
154
155
156 if len(packages) == 0: packages = read_packages()
157 try:
158     os.makedirs(build_place)
159 except OSError: pass
160
161 code = 0
162 try:
163     for p in packages:
164         gen_package_files()
165         build(p)
166 except CommandError as c:
167     print "Error:" + str(c.args)
168     code = 1
169 finally:
170     if tar_file is not None:
171         with current_directory(build_place):
172             run_cmd('tar -cf '+tar_file+' .',
173                     shell=True)
174
175 sys.exit(code)