1
# pristinetar.py -- Providers of upstream source
2
# Copyright (C) 2009-2011 Canonical Ltd.
3
# Copyright (C) 2009 Jelmer Vernooij <jelmer@debian.org>
5
# This file is part of bzr-builddeb.
7
# bzr-builddeb is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 2 of the License, or
10
# (at your option) any later version.
12
# bzr-builddeb is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with bzr-builddeb; if not, write to the Free Software
19
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
31
from bzrlib.plugins.builddeb.errors import (
32
MultipleUpstreamTarballsNotSupported,
33
PackageVersionNotPresent,
34
PerFileTimestampsNotSupported,
36
from bzrlib.plugins.builddeb.upstream import UpstreamSource
37
from bzrlib.plugins.builddeb.util import (
42
from bzrlib import osutils
43
from bzrlib.errors import (
48
from bzrlib.trace import (
54
class PristineTarError(BzrError):
55
_fmt = 'There was an error using pristine-tar: %(error)s.'
57
def __init__(self, error):
58
BzrError.__init__(self, error=error)
61
def reconstruct_pristine_tar(dest, delta, dest_filename):
62
"""Reconstruct a pristine tarball from a directory and a delta.
64
:param dest: Directory to pack
65
:param delta: pristine-tar delta
66
:param dest_filename: Destination filename
68
command = ["pristine-tar", "gentar", "-",
69
os.path.abspath(dest_filename)]
71
proc = subprocess.Popen(command, stdin=subprocess.PIPE,
72
cwd=dest, preexec_fn=subprocess_setup,
73
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
75
if e.errno == errno.ENOENT:
76
raise PristineTarError("pristine-tar is not installed")
79
(stdout, stderr) = proc.communicate(delta)
80
if proc.returncode != 0:
81
raise PristineTarError("Generating tar from delta failed: %s" % stdout)
84
def make_pristine_tar_delta(dest, tarball_path):
85
"""Create a pristine-tar delta for a tarball.
87
:param dest: Directory to generate pristine tar delta for
88
:param tarball_path: Path to the tarball
89
:return: pristine-tarball
91
# If tarball_path is relative, the cwd=dest parameter to Popen will make
92
# pristine-tar faaaail. pristine-tar doesn't use the VFS either, so we
94
tarball_path = osutils.abspath(tarball_path)
95
command = ["pristine-tar", "gendelta", tarball_path, "-"]
97
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
98
cwd=dest, preexec_fn=subprocess_setup,
99
stderr=subprocess.PIPE)
101
if e.errno == errno.ENOENT:
102
raise PristineTarError("pristine-tar is not installed")
105
(stdout, stderr) = proc.communicate()
106
if proc.returncode != 0:
107
raise PristineTarError("Generating delta from tar failed: %s" % stderr)
111
class PristineTarSource(UpstreamSource):
112
"""Source that uses the pristine-tar revisions in the packaging branch."""
114
def __init__(self, tree, branch):
119
return "<%s at %s>" % (self.__class__.__name__, self.branch.base)
121
def tag_name(self, version, component=None, distro=None):
122
"""Gets the tag name for the upstream part of version.
124
:param version: the Version object to extract the upstream
125
part of the version number from.
126
:param component: Name of the component (None for base)
127
:param distro: Optional distribution name
128
:return: a String with the name of the tag.
130
assert isinstance(version, str)
132
name = "upstream-" + version
134
name = "upstream-%s-%s" % (distro, version)
135
if component is not None:
136
name += "/%s" % component
139
def tag_version(self, version, revid, component=None):
140
"""Tags the upstream branch's last revision with an upstream version.
142
Sets a tag on the last revision of the upstream branch and on the main
143
branch with a tag that refers to the upstream part of the version
146
:param version: the upstream part of the version number to derive the
148
:param component: name of the component that is being imported
150
:param revid: the revid to associate the tag with, or None for the
151
tip of self.pristine_upstream_branch.
152
:return The tag name, revid of the added tag.
154
assert isinstance(version, str)
155
tag_name = self.tag_name(version, component=component)
156
self.branch.tags.set_tag(tag_name, revid)
157
return tag_name, revid
159
def import_tarballs(self, package, version, tree, parent_ids, tarballs,
160
timestamp=None, author=None):
161
"""Import the upstream tarballs.
163
:param package: Package name
164
:param version: Package version
165
:param path: Path with tree to import
166
:param parent_ids: Parent revisions
167
:param tarballs: List of (path, component, md5)
168
:param timestamp: Optional timestamp for new commits
169
:param author: Optional author for new commits
170
:return: List of tuples with (component, tag, revid)
173
for (tarball, component, md5) in tarballs:
174
(tag, revid) = self.import_component_tarball(
175
package, version, tree, parent_ids, component,
176
md5, tarball, author=author, timestamp=timestamp)
177
ret.append((component, tag, revid))
180
def import_component_tarball(self, package, version, tree, parent_ids,
181
component=None, md5=None, tarball=None, author=None, timestamp=None):
184
:param package: Package name
185
:param version: Upstream version
186
:param component: Component name (None for base)
188
if component is not None:
189
raise BzrError("Importing non-base tarballs not yet supported")
190
tree.set_parent_ids(parent_ids)
193
revprops["deb-md5"] = md5
194
delta = self.make_pristine_tar_delta(tree, tarball)
195
uuencoded = standard_b64encode(delta)
196
if tarball.endswith(".tar.bz2"):
197
revprops["deb-pristine-delta-bz2"] = uuencoded
198
elif tarball.endswith(".tar.lzma"):
199
revprops["deb-pristine-delta-lzma"] = uuencoded
201
revprops["deb-pristine-delta"] = uuencoded
202
if author is not None:
203
revprops['authors'] = author
205
if timestamp is not None:
206
timezone = timestamp[1]
207
timestamp = timestamp[0]
208
message = "Import upstream version %s" % (version,)
209
if component is not None:
210
message += ", component %s" % component
211
revid = tree.commit(message, revprops=revprops, timestamp=timestamp,
213
tag_name, _ = self.tag_version(version, revid=revid)
214
return tag_name, revid
216
def fetch_component_tarball(self, package, version, component, target_dir):
217
revid = self.version_component_as_revision(package, version, component)
219
rev = self.branch.repository.get_revision(revid)
220
except NoSuchRevision:
221
raise PackageVersionNotPresent(package, version, self)
222
note("Using pristine-tar to reconstruct the needed tarball.")
223
if self.has_pristine_tar_delta(rev):
224
format = self.pristine_tar_format(rev)
227
target_filename = self._tarball_path(package, version, component,
228
target_dir, format=format)
230
self.reconstruct_pristine_tar(revid, package, version, target_filename)
231
except PristineTarError:
232
raise PackageVersionNotPresent(package, version, self)
233
except PerFileTimestampsNotSupported:
234
raise PackageVersionNotPresent(package, version, self)
235
return target_filename
237
def fetch_tarballs(self, package, version, target_dir):
238
return [self.fetch_component_tarball(package, version, None, target_dir)]
240
def _has_revision(self, revid, md5=None):
241
self.branch.lock_read()
243
graph = self.branch.repository.get_graph()
244
if not graph.is_ancestor(revid, self.branch.last_revision()):
250
rev = self.branch.repository.get_revision(revid)
252
return rev.properties['deb-md5'] == md5
254
warning("tag present in branch, but there is no "
255
"associated 'deb-md5' property in associated "
256
"revision %s", revid)
259
def version_as_revision(self, package, version, tarballs=None):
261
return self.version_component_as_revision(package, version, component=None)
262
elif len(tarballs) > 1:
263
raise MultipleUpstreamTarballsNotSupported()
265
return self.version_component_as_revision(package, version, tarballs[0][1],
268
def version_component_as_revision(self, package, version, component, md5=None):
269
assert isinstance(version, str)
270
for tag_name in self.possible_tag_names(version, component=component):
272
revid = self.branch.tags.lookup_tag(tag_name)
276
if self._has_revision(revid, md5=md5):
278
tag_name = self.tag_name(version, component=component)
280
return self.branch.tags.lookup_tag(tag_name)
282
raise PackageVersionNotPresent(package, version, self)
284
def has_version(self, package, version, tarballs=None):
286
return self.has_version_component(package, version, component=None)
287
elif len(tarballs) > 1:
288
raise MultipleUpstreamTarballsNotSupported()
290
return self.has_version_component(package, version, tarballs[0][1],
293
def has_version_component(self, package, version, component, md5=None):
294
assert isinstance(version, str), str(type(version))
295
for tag_name in self.possible_tag_names(version, component=component):
297
revid = self.branch.tags.lookup_tag(tag_name)
301
if self._has_revision(revid, md5=md5):
305
def possible_tag_names(self, version, component):
306
assert isinstance(version, str)
307
tags = [self.tag_name(version, component=component),
308
self.tag_name(version, component=component, distro="debian"),
309
self.tag_name(version, component=component, distro="ubuntu"),
311
if component is None:
312
# compatibility with git-buildpackage
313
tags += ["upstream/%s" % version]
316
def has_pristine_tar_delta(self, rev):
317
return ('deb-pristine-delta' in rev.properties
318
or 'deb-pristine-delta-bz2' in rev.properties
319
or 'deb-pristine-delta-lzma' in rev.properties)
321
def pristine_tar_format(self, rev):
322
if 'deb-pristine-delta' in rev.properties:
324
elif 'deb-pristine-delta-bz2' in rev.properties:
326
elif 'deb-pristine-delta-lzma' in rev.properties:
328
assert self.has_pristine_tar_delta(rev)
329
raise AssertionError("Not handled new delta type in "
330
"pristine_tar_format")
332
def pristine_tar_delta(self, rev):
333
if 'deb-pristine-delta' in rev.properties:
334
uuencoded = rev.properties['deb-pristine-delta']
335
elif 'deb-pristine-delta-bz2' in rev.properties:
336
uuencoded = rev.properties['deb-pristine-delta-bz2']
337
elif 'deb-pristine-delta-lzma' in rev.properties:
338
uuencoded = rev.properties['deb-pristine-delta-lzma']
340
assert self.has_pristine_tar_delta(rev)
341
raise AssertionError("Not handled new delta type in "
342
"pristine_tar_delta")
343
return standard_b64decode(uuencoded)
345
def reconstruct_pristine_tar(self, revid, package, version,
347
"""Reconstruct a pristine-tar tarball from a bzr revision."""
348
tree = self.branch.repository.revision_tree(revid)
349
tmpdir = tempfile.mkdtemp(prefix="builddeb-pristine-")
351
dest = os.path.join(tmpdir, "orig")
352
rev = self.branch.repository.get_revision(revid)
353
if self.has_pristine_tar_delta(rev):
354
export(tree, dest, format='dir')
355
delta = self.pristine_tar_delta(rev)
356
reconstruct_pristine_tar(dest, delta, dest_filename)
358
export(tree, dest_filename, require_per_file_timestamps=True)
360
shutil.rmtree(tmpdir)
362
def make_pristine_tar_delta(self, tree, tarball_path):
363
tmpdir = tempfile.mkdtemp(prefix="builddeb-pristine-")
365
dest = os.path.join(tmpdir, "orig")
368
for (dp, ie) in tree.inventory.iter_entries():
369
ie._read_tree_state(dp, tree)
370
export(tree, dest, format='dir')
373
return make_pristine_tar_delta(dest, tarball_path)
375
shutil.rmtree(tmpdir)
377
def iter_versions(self):
378
"""Iterate over all upstream versions.
380
:return: Iterator over (tag_name, version, revid) tuples
382
for tag_name, tag_revid in self.branch.tags.get_tag_dict().iteritems():
383
if not is_upstream_tag(tag_name):
385
yield (tag_name, upstream_tag_version(tag_name), tag_revid)
388
def is_upstream_tag(tag):
389
"""Return true if tag is an upstream tag.
391
:param tag: The string name of the tag.
392
:return: True if the tag name is one generated by upstream tag operations.
394
return tag.startswith('upstream-') or tag.startswith('upstream/')
397
def upstream_tag_version(tag):
398
"""Return the upstream version portion of an upstream tag name.
400
:param tag: The string name of the tag.
401
:return: The version portion of the tag.
403
assert is_upstream_tag(tag), "Not an upstream tag: %s" % tag
404
if tag.startswith('upstream/'):
405
tag = tag[len('upstream/'):]
406
elif tag.startswith('upstream-'):
407
tag = tag[len('upstream-'):]
408
if tag.startswith('debian-'):
409
tag = tag[len('debian-'):]
410
elif tag.startswith('ubuntu-'):
411
tag = tag[len('ubuntu-'):]