Update openssh package
[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( ('pristine-tar', 'checkout', '../'+orig_tgz))
69     package_path = os.path.split(package)
70     with current_directory(os.path.join ('.', *package_path[0:len(package_path)-1])) :
71         package_path = package_path[len(package_path)-1]
72         try:
73             run_cmd(("dpkg-source", '-b', '-i', package_path))
74             for f in dsc_files(dsc_name):
75                 copy(f, build_place)
76         finally:
77             for f in dsc_files(dsc_name):
78                 try: os.unlink(f)
79                 except OSError: pass
80     with current_directory(build_place):
81         sb = ['sbuild', '-n', '-d', distribution, '--setup-hook',
82     build_place + '/add_source']
83         if sbuild_opts is not None: sb += sbuild_opts
84         sb.append(dsc_name)
85         print " ".join(sb)
86         sys.stdout.flush()
87         run_cmd(sb)
88
89 def gen_package_files() :
90     '''Generate package files in build_place and a script
91     build_place/add_source that can be used as a sbuild setup hook to
92     include the package files. Unfortunately, apt doesn't have a
93     mechanism for asserting that a package file is trusted when it is
94     local. We could generate a unique gpg key, generate signed
95     releases files and trust that key. It's easier to simply touch the
96     release.gpg in apt's lists directory, which turns out to do what
97     we need.'''
98     # Rather than substuting the release file directly, we create gpg
99     # release files for any  local package list. That's easier than
100     # encoding apt's ideas about what characters to escape.
101     script = '''#!/bin/sh
102     set -e
103     echo deb file:{build_place} ./ >>/etc/apt/sources.list
104     apt-get update
105     touch $(ls /var/lib/apt/lists/_*Packages \
106         |sed -e s:Packages\$:Release.gpg:)
107     '''.format (
108         build_place = build_place
109         )
110     f = open(build_place + "/add_source", "w")
111     f.write(script)
112     f.close()
113     with current_directory(build_place):
114         run_cmd(('chmod', 'a+x', 'add_source'))
115         run_cmd( 'dpkg-scanpackages . >Packages',
116                  shell = True)
117
118
119 def read_packages():
120     '''Read in the packages file from source_packages
121     '''
122     try: pf = open("source_packages")
123     except IOError:
124         print "Error: source_packages file not found"
125         exit(1)
126     def is_comment(line):
127         if re.match("^\\s*#", line): return False
128         if "#" in line: raise ValueError(
129             "Source package line contains a comment but not at beginning")
130         return True
131     return map(lambda(x): x.rstrip(),
132         filter(is_comment, pf.readlines()))
133
134
135 # main program
136 opt = OptionParser()
137 opt.add_option('-b', '--build-place',
138                dest='build_place', help="Write resulting packages to BUILD_PLACE",
139                default=build_place)
140 opt.add_option('-d', '--distribution',
141                help="Set the Debian distribution to DISTRIBUTION",
142                dest="distribution",
143                default=distribution
144                )
145 opt.add_option('-s', '--sbuild-opt',
146                action='append', dest='sbuild_opts',
147                help='Specify an option to be sent to sbuild')
148 opt.add_option('--tar-file',
149                dest='tar_file',
150                help = 'Tar up resulting packages in given tar file',
151                default = None)
152 opt.usage = "%prog [options] [packages]"
153 (options, packages) = opt.parse_args()
154 build_place = options.build_place
155 distribution = options.distribution
156 sbuild_opts = options.sbuild_opts
157 tar_file = options.tar_file
158
159
160 if len(packages) == 0: packages = read_packages()
161 try:
162     os.makedirs(build_place)
163 except OSError: pass
164
165 code = 0
166 try:
167     for p in packages:
168         gen_package_files()
169         build(p)
170 except CommandError as c:
171     print "Error:" + str(c.args)
172     code = 1
173 finally:
174     if tar_file is not None:
175         with current_directory(build_place):
176             run_cmd('tar -cf '+tar_file+' .',
177                     shell=True)