1
#! /usr/bin/env python -u
2
# Copyright 2010, 2011 Canonical Ltd. This software is licensed under the
3
# GNU Affero General Public License version 3 (see the file LICENSE).
5
"""A script that builds a package from a recipe and a chroot."""
13
from resource import RLIMIT_AS, setrlimit
15
from subprocess import (
23
RETCODE_FAILURE_INSTALL = 200
24
RETCODE_FAILURE_BUILD_TREE = 201
25
RETCODE_FAILURE_INSTALL_BUILD_DEPS = 202
26
RETCODE_FAILURE_BUILD_SOURCE_PACKAGE = 203
29
class NotVirtualized(Exception):
30
"""Exception raised when not running in a virtualized environment."""
33
Exception.__init__(self, 'Not running under Xen.')
36
def call_report_rusage(args):
39
Report that it was run, and the resources used, and complain if it fails.
41
:return: The process wait status.
45
pid, status, rusage = os.wait4(proc.pid, 0)
51
"""Builds a package from a recipe."""
53
def __init__(self, build_id, author_name, author_email,
54
suite, distroseries_name, component, archive_purpose):
57
:param build_id: The id of the build (a str).
58
:param author_name: The name of the author (a str).
59
:param author_email: The email address of the author (a str).
60
:param suite: The suite the package should be built for (a str).
62
self.build_id = build_id
63
self.author_name = author_name.decode('utf-8')
64
self.author_email = author_email
65
self.archive_purpose = archive_purpose
66
self.component = component
67
self.distroseries_name = distroseries_name
69
self.base_branch = None
70
self.chroot_path = get_build_path(build_id, 'chroot-autobuild')
71
self.work_dir_relative = os.environ['HOME'] + '/work'
72
self.work_dir = os.path.join(self.chroot_path,
73
self.work_dir_relative[1:])
74
self.tree_path = os.path.join(self.work_dir, 'tree')
75
self.username = pwd.getpwuid(os.getuid())[0]
78
"""Install all the requirements for building recipes.
80
:return: A retcode from apt.
82
# XXX: AaronBentley 2010-07-07 bug=602463: pbuilder uses aptitude but
83
# does not depend on it.
85
'apt-get', 'install', '-y', 'pbuilder', 'aptitude'])
88
"""Build the recipe into a source tree.
90
As a side-effect, sets self.source_dir_relative.
91
:return: a retcode from `bzr dailydeb`.
95
except NotVirtualized, e:
96
sys.stderr.write('Aborting on failed virtualization check:\n')
97
sys.stderr.write(str(e))
99
assert not os.path.exists(self.tree_path)
100
recipe_path = os.path.join(self.work_dir, 'recipe')
101
manifest_path = os.path.join(self.tree_path, 'manifest')
102
recipe_file = open(recipe_path, 'rb')
104
recipe = recipe_file.read()
107
# As of bzr 2.2, a defined identity is needed. In this case, we're
108
# using buildd@<hostname>.
109
hostname = socket.gethostname()
110
bzr_email = 'buildd@%s' % hostname
112
print 'Bazaar versions:'
113
check_call(['bzr', 'version'])
114
check_call(['bzr', 'plugins'])
116
print 'Building recipe:'
120
'DEBEMAIL': self.author_email,
121
'DEBFULLNAME': self.author_name.encode('utf-8'),
122
'BZR_EMAIL': bzr_email}
123
retcode = call_report_rusage([
124
'bzr', 'dailydeb', '--safe', '--no-build', recipe_path,
125
self.tree_path, '--manifest', manifest_path,
126
'--allow-fallback-to-native', '--append-version',
127
'~%s1' % self.distroseries_name], env=env)
130
(source,) = [name for name in os.listdir(self.tree_path)
131
if name != 'manifest']
132
self.source_dir_relative = os.path.join(
133
self.work_dir_relative, 'tree', source)
136
def getPackageName(self):
137
source_dir = os.path.join(
138
self.chroot_path, self.source_dir_relative.lstrip('/'))
139
changelog = os.path.join(source_dir, 'debian/changelog')
140
return open(changelog, 'r').readline().split(' ')[0]
142
def installBuildDeps(self):
143
"""Install the build-depends of the source tree."""
144
package = self.getPackageName()
145
currently_building_path = os.path.join(
146
self.chroot_path, 'CurrentlyBuilding')
147
currently_building_contents = (
152
'Build-Debug-Symbols: no\n' %
153
(package, self.suite, self.component, self.archive_purpose))
154
currently_building = open(currently_building_path, 'w')
155
currently_building.write(currently_building_contents)
156
currently_building.close()
157
return self.chroot(['sh', '-c', 'cd %s &&'
158
'/usr/lib/pbuilder/pbuilder-satisfydepends'
159
% self.source_dir_relative])
161
def chroot(self, args, echo=False):
162
"""Run a command in the chroot.
164
:param args: the command and arguments to run.
165
:return: the status code.
168
print "Running in chroot: %s" % ' '.join(
169
"'%s'" % arg for arg in args)
172
'/usr/bin/sudo', '/usr/sbin/chroot', self.chroot_path] + args)
174
def buildSourcePackage(self):
175
"""Build the source package.
177
:return: a retcode from dpkg-buildpackage.
179
retcode = self.chroot([
180
'su', '-c', 'cd %s && /usr/bin/dpkg-buildpackage -i -I -us -uc -S'
181
% self.source_dir_relative, self.username])
182
for filename in os.listdir(self.tree_path):
183
path = os.path.join(self.tree_path, filename)
184
if os.path.isfile(path):
185
os.rename(path, get_build_path(self.build_id, filename))
189
def get_build_path(build_id, *extra):
190
"""Generate a path within the build directory.
192
:param build_id: the build id to use.
193
:param extra: the extra path segments within the build directory.
194
:return: the generated path.
197
os.environ["HOME"], "build-" + build_id, *extra)
200
def ensure_virtualized():
201
"""Raise an exception if not running in a virtualized environment.
203
Raises if not running under Xen.
205
if not os.path.isdir('/proc/xen') or os.path.exists('/proc/xen/xsd_kva'):
206
raise NotVirtualized()
209
if __name__ == '__main__':
210
setrlimit(RLIMIT_AS, (1000000000, -1))
211
builder = RecipeBuilder(*sys.argv[1:])
212
if builder.buildTree() != 0:
213
sys.exit(RETCODE_FAILURE_BUILD_TREE)
214
if builder.install() != 0:
215
sys.exit(RETCODE_FAILURE_INSTALL)
216
if builder.installBuildDeps() != 0:
217
sys.exit(RETCODE_FAILURE_INSTALL_BUILD_DEPS)
218
if builder.buildSourcePackage() != 0:
219
sys.exit(RETCODE_FAILURE_BUILD_SOURCE_PACKAGE)
220
sys.exit(RETCODE_SUCCESS)