Include suffix in dsc name
[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 = "unstable"
23 build_place = os.path.join(os.getcwd(), 'debian_build')
24
25 suffix_map = {
26     'wheezy': 'deb70',
27     'jessie': 'deb80',
28     'unstable': 'sid',
29     'sid': 'sid'}
30
31 class CommandError(exceptions.StandardError):
32     pass
33
34 @contextmanager
35 def current_directory(dir):
36     "Change the current directory as a context manager; when the context exits, return."
37     cwd = os.getcwd()
38     os.chdir(dir)
39     yield
40     os.chdir(cwd)
41
42
43 def run_cmd(args, **kwords):
44     rcode =  subprocess.call( args, **kwords)
45     if rcode <> 0:
46         raise CommandError(args)
47
48 def command_output(args) :
49     p = subprocess.Popen(args, stdout=subprocess.PIPE)
50     output = p.communicate()
51     output = output[0]
52     if p.returncode != 0:
53         raise CommandError(args)
54     return output
55
56 def dsc_files(dsc) :
57     '''Describe all the files included in dsc, wich is a string name
58     of a dsc package. dsc itself is included'''
59     package = debian.deb822.Dsc(open(dsc))
60     internal_files = map( lambda(x): x['name'], package['Files'])
61     internal_files.append(dsc)
62     return internal_files
63
64 def build(package):
65     with current_directory(package):
66         cl = debian.changelog.Changelog(open('debian/changelog'))
67         package_name = cl.package
68         package_version = re.sub('^\d+:','',str(cl.version))
69         orig_tar = package_name+'_'+ cl.upstream_version + ".orig.tar"
70         dsc_name = package_name+"_"+package_version + '~' + version_suffix+ ".dsc"
71         run_cmd(['dch', '-b', '-v' +str(cl.version)+'~'+version_suffix, '-D'+distribution, 'Autobuilt package'])
72         print "==> Package: ", package_name
73         source_format = command_output(('dpkg-source', '--print-format', '.'))
74         if "native" not in source_format:
75             run_cmd( ('git', 'fetch', 'origin', 'pristine-tar:pristine-tar'))
76             for file in command_output(( 'pristine-tar', 'list')).split("\n"):
77                 if file.startswith(orig_tar): orig_tar = file
78             run_cmd( ('pristine-tar', 'checkout', '../'+orig_tar))
79     package_path = os.path.split(package)
80     with current_directory(os.path.join ('.', *package_path[0:len(package_path)-1])) :
81         package_path = package_path[len(package_path)-1]
82         try:
83             run_cmd(("dpkg-source", '-b', '-i', package_path))
84             for f in dsc_files(dsc_name):
85                 copy(f, build_place)
86         finally:
87             for f in dsc_files(dsc_name):
88                 try: os.unlink(f)
89                 except OSError: pass
90     with current_directory(build_place):
91         sb = ['sbuild', '-n', '-d', distribution, '--setup-hook',
92     build_place + '/add_source']
93         if sbuild_opts is not None: sb += sbuild_opts
94         sb.append(dsc_name)
95         print " ".join(sb)
96         sys.stdout.flush()
97         run_cmd(sb)
98
99 def gen_package_files() :
100     '''Generate package files in build_place and a script
101     build_place/add_source that can be used as a sbuild setup hook to
102     include the package files.  Use the sbuild key to sign our packages'''
103     script = '''#!/bin/sh
104     set -e
105     sudo -u root /usr/local/sbin/add-source {build_place}
106     '''.format (
107         build_place = build_place
108         )
109     f = open(build_place + "/add_source", "w")
110     f.write(script)
111     f.close()
112     with current_directory(build_place):
113         run_cmd(('chmod', 'a+x', 'add_source'))
114         run_cmd( 'dpkg-scanpackages . >Packages',
115                  shell = True)
116         run_cmd('apt-ftparchive   release . >Release', shell=True)
117         try: os.unlink('Release.gpg')
118         except OSError: pass
119         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)
120
121
122 def read_packages():
123     '''Read in the packages file from source_packages
124     '''
125     try: pf = open("source_packages")
126     except IOError:
127         print "Error: source_packages file not found"
128         exit(1)
129     def is_comment(line):
130         if re.match("^\\s*#", line): return False
131         if "#" in line: raise ValueError(
132             "Source package line contains a comment but not at beginning")
133         return True
134     return map(lambda(x): x.rstrip(),
135         filter(is_comment, pf.readlines()))
136
137
138 # main program
139 opt = OptionParser()
140 opt.add_option('-b', '--build-place',
141                dest='build_place', help="Write resulting packages to BUILD_PLACE",
142                default=build_place)
143 opt.add_option('--version-suffix', dest ='version_suffix')
144 opt.add_option('-d', '--distribution',
145                help="Set the Debian distribution to DISTRIBUTION",
146                dest="distribution",
147                default=distribution
148                )
149 opt.add_option('-s', '--sbuild-opt',
150                action='append', dest='sbuild_opts',
151                help='Specify an option to be sent to sbuild')
152 opt.add_option('--tar-file',
153                dest='tar_file',
154                help = 'Tar up resulting packages in given tar file',
155                default = None)
156 opt.usage = "%prog [options] [packages]"
157 (options, packages) = opt.parse_args()
158 build_place = options.build_place
159 distribution = options.distribution
160 version_suffix = options.version_suffix
161 if not version_suffix: version_suffix = suffix_map.get(distribution, '0')
162 sbuild_opts = options.sbuild_opts
163 tar_file = options.tar_file
164
165
166 if len(packages) == 0: packages = read_packages()
167 try:
168     os.makedirs(build_place)
169 except OSError: pass
170
171 code = 0
172 try:
173     for p in packages:
174         gen_package_files()
175         build(p)
176 except CommandError as c:
177     print "Error:" + str(c.args)
178     code = 1
179 finally:
180     if tar_file is not None:
181         with current_directory(build_place):
182             run_cmd('tar -cf '+tar_file+' .',
183                     shell=True)
184
185 sys.exit(code)