~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/canonical/buildd/buildrecipe

  • Committer: mbp at canonical
  • Date: 2011-11-20 23:37:23 UTC
  • mto: This revision was merged to the branch mainline in revision 14344.
  • Revision ID: mbp@canonical.com-20111120233723-370p96db2crru5tm
Delete canonical.buildd again

Show diffs side-by-side

added added

removed removed

Lines of Context:
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).
4
 
 
5
 
"""A script that builds a package from a recipe and a chroot."""
6
 
 
7
 
__metaclass__ = type
8
 
 
9
 
 
10
 
import os
11
 
import os.path
12
 
import pwd
13
 
from resource import RLIMIT_AS, setrlimit
14
 
import socket
15
 
from subprocess import (
16
 
    Popen,
17
 
    call,
18
 
    )
19
 
import sys
20
 
 
21
 
 
22
 
RETCODE_SUCCESS = 0
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
27
 
 
28
 
 
29
 
class NotVirtualized(Exception):
30
 
    """Exception raised when not running in a virtualized environment."""
31
 
 
32
 
    def __init__(self):
33
 
        Exception.__init__(self, 'Not running under Xen.')
34
 
 
35
 
 
36
 
def call_report_rusage(args):
37
 
    """Run a subprocess.
38
 
 
39
 
    Report that it was run, and the resources used, and complain if it fails.
40
 
 
41
 
    :return: The process wait status.
42
 
    """
43
 
    print 'RUN %r' % args
44
 
    proc = Popen(args)
45
 
    pid, status, rusage = os.wait4(proc.pid, 0)
46
 
    print(rusage)
47
 
    return status
48
 
 
49
 
 
50
 
class RecipeBuilder:
51
 
    """Builds a package from a recipe."""
52
 
 
53
 
    def __init__(self, build_id, author_name, author_email,
54
 
                 suite, distroseries_name, component, archive_purpose):
55
 
        """Constructor.
56
 
 
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).
61
 
        """
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
68
 
        self.suite = suite
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]
76
 
 
77
 
    def install(self):
78
 
        """Install all the requirements for building recipes.
79
 
 
80
 
        :return: A retcode from apt.
81
 
        """
82
 
        # XXX: AaronBentley 2010-07-07 bug=602463: pbuilder uses aptitude but
83
 
        # does not depend on it.
84
 
        return self.chroot([
85
 
            'apt-get', 'install', '-y', 'pbuilder', 'aptitude'])
86
 
 
87
 
    def buildTree(self):
88
 
        """Build the recipe into a source tree.
89
 
 
90
 
        As a side-effect, sets self.source_dir_relative.
91
 
        :return: a retcode from `bzr dailydeb`.
92
 
        """
93
 
        try:
94
 
            ensure_virtualized()
95
 
        except NotVirtualized, e:
96
 
            sys.stderr.write('Aborting on failed virtualization check:\n')
97
 
            sys.stderr.write(str(e))
98
 
            return 1
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')
103
 
        try:
104
 
            recipe = recipe_file.read()
105
 
        finally:
106
 
            recipe_file.close()
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
111
 
 
112
 
        print 'Bazaar versions:'
113
 
        check_call(['bzr', 'version'])
114
 
        check_call(['bzr', 'plugins'])
115
 
 
116
 
        print 'Building recipe:'
117
 
        print recipe
118
 
        sys.stdout.flush()
119
 
        env = {
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)
128
 
        if retcode != 0:
129
 
            return retcode
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)
134
 
        return retcode
135
 
 
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]
141
 
 
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 = (
148
 
            'Package: %s\n'
149
 
            'Suite: %s\n'
150
 
            'Component: %s\n'
151
 
            'Purpose: %s\n'
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])
160
 
 
161
 
    def chroot(self, args, echo=False):
162
 
        """Run a command in the chroot.
163
 
 
164
 
        :param args: the command and arguments to run.
165
 
        :return: the status code.
166
 
        """
167
 
        if echo:
168
 
            print "Running in chroot: %s" % ' '.join(
169
 
                "'%s'" % arg for arg in args)
170
 
            sys.stdout.flush()
171
 
        return call([
172
 
            '/usr/bin/sudo', '/usr/sbin/chroot', self.chroot_path] + args)
173
 
 
174
 
    def buildSourcePackage(self):
175
 
        """Build the source package.
176
 
 
177
 
        :return: a retcode from dpkg-buildpackage.
178
 
        """
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))
186
 
        return retcode
187
 
 
188
 
 
189
 
def get_build_path(build_id, *extra):
190
 
    """Generate a path within the build directory.
191
 
 
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.
195
 
    """
196
 
    return os.path.join(
197
 
        os.environ["HOME"], "build-" + build_id, *extra)
198
 
 
199
 
 
200
 
def ensure_virtualized():
201
 
    """Raise an exception if not running in a virtualized environment.
202
 
 
203
 
    Raises if not running under Xen.
204
 
    """
205
 
    if not os.path.isdir('/proc/xen') or os.path.exists('/proc/xen/xsd_kva'):
206
 
        raise NotVirtualized()
207
 
 
208
 
 
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)