1
# bzr-builder: a bzr plugin to constuct trees based on recipes
2
# Copyright 2009-2011 Canonical Ltd.
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.
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.
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/>.
16
"""Debian-specific utility functions."""
18
from base64 import standard_b64decode
19
from email import utils
28
export as _mod_export,
33
from bzrlib.plugins.builder.deb_version import substitute_changelog_vars
34
from bzrlib.plugins.builder.recipe import (
35
SubstitutionUnavailable,
39
from debian import changelog, deb822
41
# In older versions of python-debian the main package was named
43
from debian_bundle import changelog, deb822
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
53
# The default distribution used by add_autobuild_changelog_entry()
54
DEFAULT_UBUNTU_DISTRIBUTION = "lucid"
57
class MissingDependency(errors.BzrError):
61
def target_from_dput(dput):
62
"""Convert a dput specification to a LP API specification.
64
:param dput: A dput command spec like ppa:team-name.
65
:return: A LP API target like team-name/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.'
72
base, _, suffix = dput[len(ppa_prefix):].partition('/')
78
def debian_source_package_name(control_path):
79
"""Open a debian control file and extract the package name.
82
f = open(control_path, 'r')
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")
91
def reconstruct_pristine_tar(dest, delta, dest_filename):
92
"""Reconstruct a pristine tarball from a directory and a delta.
94
:param dest: Directory to pack
95
:param delta: pristine-tar delta
96
:param dest_filename: Destination filename
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",
107
def extract_upstream_tarball(branch, package, version, dest_dir):
108
"""Extract the upstream tarball from a branch.
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
115
tag_names = ["upstream-%s" % version, "upstream/%s" % version]
116
for tag_name in tag_names:
118
revid = branch.tags.lookup_tag(tag_name)
119
except errors.NoSuchTag:
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)
135
if uuencoded is not None:
136
delta = standard_b64decode(uuencoded)
137
dest = os.path.join(dest_dir, "orig")
139
_mod_export.export(tree, dest, format='dir')
140
reconstruct_pristine_tar(dest, delta,
141
os.path.join(dest_dir, dest_filename))
143
if os.path.exists(dest):
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)
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.
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
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
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")
171
if os.path.exists(cl_path):
175
contents = cl_f.read()
178
cl = changelog.Changelog(file=contents)
180
cl = changelog.Changelog()
181
if len(cl._blocks) > 0:
182
if distribution is None:
183
distribution = cl._blocks[0].distributions.split()[0]
186
if len(contents.strip()) > 0:
187
reason = ("debian/changelog didn't contain any "
190
reason = "debian/changelog was empty"
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):
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)
215
date = utils.formatdate(localtime=True)
216
version = base_branch.deb_version
217
if append_version is not None:
218
version += append_version
220
changelog.Version(version)
221
except (changelog.VersionError, ValueError), e:
222
raise errors.BzrCommandError("Invalid deb-version: %s: %s"
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')
230
cl.write_to_open_file(cl_f)
235
def calculate_package_dir(package_name, package_version, working_basedir):
236
"""Calculate the directory name that should be used while debuilding.
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
243
package_basedir = "%s-%s" % (package_name, package_version.upstream_version)
244
package_dir = os.path.join(working_basedir, package_basedir)
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.
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
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
261
def subprocess_setup():
262
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
264
# Hide output if -q is in use.
265
quiet = trace.is_quiet()
267
kwargs = {"stderr": subprocess.STDOUT, "stdout": subprocess.PIPE}
272
trace.mutter("running: %r", command)
274
proc = subprocess.Popen(command, cwd=basedir,
275
stdin=subprocess.PIPE, preexec_fn=subprocess_setup, **kwargs)
277
if e.errno != errno.ENOENT:
279
if not_installed_msg is None:
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:
287
raise errors.BzrCommandError("%s: %s" % (error_msg, output))
289
raise errors.BzrCommandError(error_msg)
292
def build_source_package(basedir, tgz_check=True):
293
command = ["/usr/bin/debuild"]
295
command.append("--tgz-check")
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.")
306
def get_source_format(path):
307
"""Retrieve the source format name from a package.
309
:param path: Path to the package
310
:return: String with package format
312
source_format_path = os.path.join(path, "debian", "source", "format")
313
if not os.path.exists(source_format_path):
315
f = open(source_format_path, 'r')
317
return f.read().strip()
322
def convert_3_0_quilt_to_native(path):
323
"""Convert a package in 3.0 (quilt) format to 3.0 (native).
325
This applies all patches in the package and updates the
326
debian/source/format file.
328
:param path: Path to the package on disk
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')
344
f.write("3.0 (native)\n")
349
def force_native_format(working_tree_path, current_format):
350
"""Make sure a package is a format that supports native packages.
352
:param working_tree_path: Path to the package
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" %
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.")
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.")