~ubuntu-branches/ubuntu/quantal/bzr-builder/quantal

« back to all changes in this revision

Viewing changes to deb_util.py

  • Committer: Package Import Robot
  • Author(s): Logan Rosen
  • Date: 2012-06-10 02:45:03 UTC
  • mfrom: (1.1.6) (12.1.2 precise)
  • Revision ID: package-import@ubuntu.com-20120610024503-4syoetggxq6qr1lp
Tags: 0.7.3-0ubuntu1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# bzr-builder: a bzr plugin to constuct trees based on recipes
 
2
# Copyright 2009-2011 Canonical Ltd.
 
3
 
 
4
# This program is free software: you can redistribute it and/or modify it 
 
5
# under the terms of the GNU General Public License version 3, as published 
 
6
# by the Free Software Foundation.
 
7
 
 
8
# This program is distributed in the hope that it will be useful, but 
 
9
# WITHOUT ANY WARRANTY; without even the implied warranties of 
 
10
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
 
11
# PURPOSE.  See the GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License along 
 
14
# with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 
 
16
"""Debian-specific utility functions."""
 
17
 
 
18
from base64 import standard_b64decode
 
19
from email import utils
 
20
import errno
 
21
import os
 
22
import shutil
 
23
import signal
 
24
import subprocess
 
25
 
 
26
from bzrlib import (
 
27
    errors,
 
28
    export as _mod_export,
 
29
    osutils,
 
30
    trace,
 
31
    )
 
32
 
 
33
from bzrlib.plugins.builder.deb_version import substitute_changelog_vars
 
34
from bzrlib.plugins.builder.recipe import (
 
35
    SubstitutionUnavailable,
 
36
    )
 
37
 
 
38
try:
 
39
    from debian import changelog, deb822
 
40
except ImportError:
 
41
    # In older versions of python-debian the main package was named 
 
42
    # debian_bundle
 
43
    from debian_bundle import changelog, deb822
 
44
 
 
45
 
 
46
try:
 
47
    get_maintainer = changelog.get_maintainer
 
48
except AttributeError:
 
49
    # Implementation of get_maintainer was added after 0.1.18 so import same
 
50
    # function from backports module if python-debian doesn't have it.
 
51
    from bzrlib.plugins.builder.backports import get_maintainer
 
52
 
 
53
# The default distribution used by add_autobuild_changelog_entry()
 
54
DEFAULT_UBUNTU_DISTRIBUTION = "lucid"
 
55
 
 
56
 
 
57
class MissingDependency(errors.BzrError):
 
58
    pass
 
59
 
 
60
 
 
61
def target_from_dput(dput):
 
62
    """Convert a dput specification to a LP API specification.
 
63
 
 
64
    :param dput: A dput command spec like ppa:team-name.
 
65
    :return: A LP API target like team-name/ppa.
 
66
    """
 
67
    ppa_prefix = 'ppa:'
 
68
    if not dput.startswith(ppa_prefix):
 
69
        raise errors.BzrCommandError('%r does not appear to be a PPA. '
 
70
            'A dput target like \'%suser[/name]\' must be used.'
 
71
            % (dput, ppa_prefix))
 
72
    base, _, suffix = dput[len(ppa_prefix):].partition('/')
 
73
    if not suffix:
 
74
        suffix = 'ppa'
 
75
    return base, suffix
 
76
 
 
77
 
 
78
def debian_source_package_name(control_path):
 
79
    """Open a debian control file and extract the package name.
 
80
 
 
81
    """
 
82
    f = open(control_path, 'r')
 
83
    try:
 
84
        control = deb822.Deb822(f)
 
85
        # Debian policy states package names are [a-z0-9][a-z0-9.+-]+ so ascii
 
86
        return control["Source"].encode("ascii")
 
87
    finally:
 
88
        f.close()
 
89
 
 
90
 
 
91
def reconstruct_pristine_tar(dest, delta, dest_filename):
 
92
    """Reconstruct a pristine tarball from a directory and a delta.
 
93
 
 
94
    :param dest: Directory to pack
 
95
    :param delta: pristine-tar delta
 
96
    :param dest_filename: Destination filename
 
97
    """
 
98
    command = ["pristine-tar", "gentar", "-",
 
99
               os.path.abspath(dest_filename)]
 
100
    _run_command(command, dest,
 
101
        "Reconstructing pristine tarball",
 
102
        "Generating tar from delta failed",
 
103
        not_installed_msg="pristine-tar is not installed",
 
104
        indata=delta)
 
105
 
 
106
 
 
107
def extract_upstream_tarball(branch, package, version, dest_dir):
 
108
    """Extract the upstream tarball from a branch.
 
109
 
 
110
    :param branch: Branch with the upstream pristine tar data
 
111
    :param package: Package name
 
112
    :param version: Package version
 
113
    :param dest_dir: Destination directory
 
114
    """
 
115
    tag_names = ["upstream-%s" % version, "upstream/%s" % version]
 
116
    for tag_name in tag_names:
 
117
        try:
 
118
            revid = branch.tags.lookup_tag(tag_name)
 
119
        except errors.NoSuchTag:
 
120
            pass
 
121
        else:
 
122
            break
 
123
    else:
 
124
        raise errors.NoSuchTag(tag_names[0])
 
125
    tree = branch.repository.revision_tree(revid)
 
126
    rev = branch.repository.get_revision(revid)
 
127
    if 'deb-pristine-delta' in rev.properties:
 
128
        uuencoded = rev.properties['deb-pristine-delta']
 
129
        dest_filename = "%s_%s.orig.tar.gz" % (package, version)
 
130
    elif 'deb-pristine-delta-bz2' in rev.properties:
 
131
        uuencoded = rev.properties['deb-pristine-delta-bz2']
 
132
        dest_filename = "%s_%s.orig.tar.bz2" % (package, version)
 
133
    else:
 
134
        uuencoded = None
 
135
    if uuencoded is not None:
 
136
        delta = standard_b64decode(uuencoded)
 
137
        dest = os.path.join(dest_dir, "orig")
 
138
        try:
 
139
            _mod_export.export(tree, dest, format='dir')
 
140
            reconstruct_pristine_tar(dest, delta,
 
141
                os.path.join(dest_dir, dest_filename))
 
142
        finally:
 
143
            if os.path.exists(dest):
 
144
                shutil.rmtree(dest)
 
145
    else:
 
146
        # Default to .tar.gz
 
147
        dest_filename = "%s_%s.orig.tar.gz" % (package, version)
 
148
        _mod_export.export(tree, os.path.join(dest_dir, dest_filename),
 
149
                per_file_timestamps=True)
 
150
 
 
151
 
 
152
def add_autobuild_changelog_entry(base_branch, basedir, package,
 
153
        distribution=None, author_name=None, author_email=None,
 
154
        append_version=None):
 
155
    """Add a new changelog entry for an autobuild.
 
156
 
 
157
    :param base_branch: Recipe base branch
 
158
    :param basedir: Base working directory
 
159
    :param package: package name
 
160
    :param distribution: Optional distribution (defaults to last entry
 
161
        distribution)
 
162
    :param author_name: Name of the build requester
 
163
    :param author_email: Email of the build requester
 
164
    :param append_version: Optional version suffix to add
 
165
    """
 
166
    debian_dir = os.path.join(basedir, "debian")
 
167
    if not os.path.exists(debian_dir):
 
168
        os.makedirs(debian_dir)
 
169
    cl_path = os.path.join(debian_dir, "changelog")
 
170
    file_found = False
 
171
    if os.path.exists(cl_path):
 
172
        file_found = True
 
173
        cl_f = open(cl_path)
 
174
        try:
 
175
            contents = cl_f.read()
 
176
        finally:
 
177
            cl_f.close()
 
178
        cl = changelog.Changelog(file=contents)
 
179
    else:
 
180
        cl = changelog.Changelog()
 
181
    if len(cl._blocks) > 0:
 
182
        if distribution is None:
 
183
            distribution = cl._blocks[0].distributions.split()[0]
 
184
    else:
 
185
        if file_found:
 
186
            if len(contents.strip()) > 0:
 
187
                reason = ("debian/changelog didn't contain any "
 
188
                         "parseable stanzas")
 
189
            else:
 
190
                reason = "debian/changelog was empty"
 
191
        else:
 
192
            reason = "debian/changelog was not present"
 
193
        if distribution is None:
 
194
            distribution = DEFAULT_UBUNTU_DISTRIBUTION
 
195
    if base_branch.format in (0.1, 0.2, 0.3):
 
196
        try:
 
197
            substitute_changelog_vars(base_branch, None, cl)
 
198
        except SubstitutionUnavailable, e:
 
199
            raise errors.BzrCommandError("No previous changelog to "
 
200
                    "take the upstream version from as %s was "
 
201
                    "used: %s: %s." % (e.name, e.reason, reason))
 
202
    # Use debian packaging environment variables
 
203
    # or default values if they don't exist
 
204
    if author_name is None or author_email is None:
 
205
        author_name, author_email = get_maintainer()
 
206
        # The python-debian package breaks compatibility at version 0.1.20 by
 
207
        # switching to expecting (but not checking for) unicode rather than
 
208
        # bytestring inputs. Detect this and decode environment if needed.
 
209
        if getattr(changelog.Changelog, "__unicode__", None) is not None:
 
210
            enc = osutils.get_user_encoding()
 
211
            author_name = author_name.decode(enc)
 
212
            author_email = author_email.decode(enc)
 
213
    author = "%s <%s>" % (author_name, author_email)
 
214
 
 
215
    date = utils.formatdate(localtime=True)
 
216
    version = base_branch.deb_version
 
217
    if append_version is not None:
 
218
        version += append_version
 
219
    try:
 
220
        changelog.Version(version)
 
221
    except (changelog.VersionError, ValueError), e:
 
222
        raise errors.BzrCommandError("Invalid deb-version: %s: %s"
 
223
                % (version, e))
 
224
    cl.new_block(package=package, version=version,
 
225
            distributions=distribution, urgency="low",
 
226
            changes=['', '  * Auto build.', ''],
 
227
            author=author, date=date)
 
228
    cl_f = open(cl_path, 'wb')
 
229
    try:
 
230
        cl.write_to_open_file(cl_f)
 
231
    finally:
 
232
        cl_f.close()
 
233
 
 
234
 
 
235
def calculate_package_dir(package_name, package_version, working_basedir):
 
236
    """Calculate the directory name that should be used while debuilding.
 
237
 
 
238
    :param base_branch: Recipe base branch
 
239
    :param package_version: Version of the package
 
240
    :param package_name: Package name
 
241
    :param working_basedir: Base directory
 
242
    """
 
243
    package_basedir = "%s-%s" % (package_name, package_version.upstream_version)
 
244
    package_dir = os.path.join(working_basedir, package_basedir)
 
245
    return package_dir
 
246
 
 
247
 
 
248
def _run_command(command, basedir, msg, error_msg,
 
249
        not_installed_msg=None, env=None, success_exit_codes=None, indata=None):
 
250
    """ Run a command in a subprocess.
 
251
 
 
252
    :param command: list with command and parameters
 
253
    :param msg: message to display to the user
 
254
    :param error_msg: message to display if something fails.
 
255
    :param not_installed_msg: the message to display if the command
 
256
        isn't available.
 
257
    :param env: Optional environment to use rather than os.environ.
 
258
    :param success_exit_codes: Exit codes to consider succesfull, defaults to [0].
 
259
    :param indata: Data to write to standard input
 
260
    """
 
261
    def subprocess_setup():
 
262
        signal.signal(signal.SIGPIPE, signal.SIG_DFL)
 
263
    trace.note(msg)
 
264
    # Hide output if -q is in use.
 
265
    quiet = trace.is_quiet()
 
266
    if quiet:
 
267
        kwargs = {"stderr": subprocess.STDOUT, "stdout": subprocess.PIPE}
 
268
    else:
 
269
        kwargs = {}
 
270
    if env is not None:
 
271
        kwargs["env"] = env
 
272
    trace.mutter("running: %r", command)
 
273
    try:
 
274
        proc = subprocess.Popen(command, cwd=basedir,
 
275
                stdin=subprocess.PIPE, preexec_fn=subprocess_setup, **kwargs)
 
276
    except OSError, e:
 
277
        if e.errno != errno.ENOENT:
 
278
            raise
 
279
        if not_installed_msg is None:
 
280
            raise
 
281
        raise MissingDependency(msg=not_installed_msg)
 
282
    output = proc.communicate(indata)
 
283
    if success_exit_codes is None:
 
284
        success_exit_codes = [0]
 
285
    if proc.returncode not in success_exit_codes:
 
286
        if quiet:
 
287
            raise errors.BzrCommandError("%s: %s" % (error_msg, output))
 
288
        else:
 
289
            raise errors.BzrCommandError(error_msg)
 
290
 
 
291
 
 
292
def build_source_package(basedir, tgz_check=True):
 
293
    command = ["/usr/bin/debuild"]
 
294
    if tgz_check:
 
295
        command.append("--tgz-check")
 
296
    else:
 
297
        command.append("--no-tgz-check")
 
298
    command.extend(["-i", "-I", "-S", "-uc", "-us"])
 
299
    _run_command(command, basedir,
 
300
        "Building the source package",
 
301
        "Failed to build the source package",
 
302
        not_installed_msg="debuild is not installed, please install "
 
303
            "the devscripts package.")
 
304
 
 
305
 
 
306
def get_source_format(path):
 
307
    """Retrieve the source format name from a package.
 
308
 
 
309
    :param path: Path to the package
 
310
    :return: String with package format
 
311
    """
 
312
    source_format_path = os.path.join(path, "debian", "source", "format")
 
313
    if not os.path.exists(source_format_path):
 
314
        return "1.0"
 
315
    f = open(source_format_path, 'r')
 
316
    try:
 
317
        return f.read().strip()
 
318
    finally:
 
319
        f.close()
 
320
 
 
321
 
 
322
def convert_3_0_quilt_to_native(path):
 
323
    """Convert a package in 3.0 (quilt) format to 3.0 (native).
 
324
 
 
325
    This applies all patches in the package and updates the 
 
326
    debian/source/format file.
 
327
 
 
328
    :param path: Path to the package on disk
 
329
    """
 
330
    path = os.path.abspath(path)
 
331
    patches_dir = os.path.join(path, "debian", "patches")
 
332
    series_file = os.path.join(patches_dir, "series")
 
333
    if os.path.exists(series_file):
 
334
        _run_command(["quilt", "push", "-a", "-v"], path,
 
335
            "Applying quilt patches",
 
336
            "Failed to apply quilt patches",
 
337
            not_installed_msg="quilt is not installed, please install it.",
 
338
            env={"QUILT_SERIES": series_file, "QUILT_PATCHES": patches_dir},
 
339
            success_exit_codes=(0, 2))
 
340
    if os.path.exists(patches_dir):
 
341
        shutil.rmtree(patches_dir)
 
342
    f = open(os.path.join(path, "debian", "source", "format"), 'w')
 
343
    try:
 
344
        f.write("3.0 (native)\n")
 
345
    finally:
 
346
        f.close()
 
347
 
 
348
 
 
349
def force_native_format(working_tree_path, current_format):
 
350
    """Make sure a package is a format that supports native packages.
 
351
 
 
352
    :param working_tree_path: Path to the package
 
353
    """
 
354
    if current_format == "3.0 (quilt)":
 
355
        convert_3_0_quilt_to_native(working_tree_path)
 
356
    elif current_format not in ("1.0", "3.0 (native)"):
 
357
        raise errors.BzrCommandError("Unknown source format %s" %
 
358
                                     current_format)
 
359
 
 
360
 
 
361
def sign_source_package(basedir, key_id):
 
362
    command = ["/usr/bin/debsign", "-S", "-k%s" % key_id]
 
363
    _run_command(command, basedir,
 
364
        "Signing the source package",
 
365
        "Signing the package failed",
 
366
        not_installed_msg="debsign is not installed, please install "
 
367
            "the devscripts package.")
 
368
 
 
369
 
 
370
def dput_source_package(basedir, target):
 
371
    command = ["/usr/bin/debrelease", "-S", "--dput", target]
 
372
    _run_command(command, basedir,
 
373
        "Uploading the source package",
 
374
        "Uploading the package failed",
 
375
        not_installed_msg="debrelease is not installed, please "
 
376
            "install the devscripts package.")
 
377
 
 
378
 
 
379