1 # vim: set fileencoding=utf-8 :
3 # (C) 2006,2007 Guido Guenther <agx@sigxcpu.org>
4 # (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 """Common functionality of the Debian/RPM package helpers"""
27 import gbp.command_wrappers as gbpc
28 from gbp.errors import GbpError
30 # compression types, extra options and extensions
31 compressor_opts = { 'gzip' : [ ['-n'], 'gz' ],
32 'bzip2' : [ [], 'bz2' ],
33 'lzma' : [ [], 'lzma' ],
36 # Map frequently used names of compression types to the internal ones:
37 compressor_aliases = { 'bz2' : 'bzip2',
40 # Supported archive formats
41 archive_formats = [ 'tar', 'zip' ]
43 # Map combined file extensions to archive and compression format
44 archive_ext_aliases = { 'tgz' : ('tar', 'gzip'),
45 'tbz2' : ('tar', 'bzip2'),
46 'tlz' : ('tar', 'lzma'),
47 'txz' : ('tar', 'xz')}
49 def parse_archive_filename(filename):
51 Given an filename return the basename (i.e. filename without the
52 archive and compression extensions), archive format and compression
55 @param filename: the name of the file
56 @type filename: string
57 @return: tuple containing basename, archive format and compression method
58 @rtype: C{tuple} of C{str}
60 >>> parse_archive_filename("abc.tar.gz")
61 ('abc', 'tar', 'gzip')
62 >>> parse_archive_filename("abc.tar.bz2")
63 ('abc', 'tar', 'bzip2')
64 >>> parse_archive_filename("abc.def.tbz2")
65 ('abc.def', 'tar', 'bzip2')
66 >>> parse_archive_filename("abc.def.tar.xz")
67 ('abc.def', 'tar', 'xz')
68 >>> parse_archive_filename("abc.zip")
70 >>> parse_archive_filename("abc.lzma")
72 >>> parse_archive_filename("abc.tar.foo")
73 ('abc.tar.foo', None, None)
74 >>> parse_archive_filename("abc")
77 (base_name, archive_fmt, compression) = (filename, None, None)
79 # Split filename to pieces
80 split = filename.split(".")
82 if split[-1] in archive_ext_aliases:
83 base_name = ".".join(split[:-1])
84 (archive_fmt, compression) = archive_ext_aliases[split[-1]]
85 elif split[-1] in archive_formats:
86 base_name = ".".join(split[:-1])
87 (archive_fmt, compression) = (split[-1], None)
89 for (c, o) in compressor_opts.iteritems():
91 base_name = ".".join(split[:-1])
93 if len(split) > 2 and split[-2] in archive_formats:
94 base_name = ".".join(split[:-2])
95 archive_fmt = split[-2]
97 return (base_name, archive_fmt, compression)
100 class PkgPolicy(object):
102 Common helpers for packaging policy.
104 packagename_re = None
105 packagename_msg = None
106 upstreamversion_re = None
107 upstreamversion_msg = None
110 def is_valid_packagename(cls, name):
112 Is this a valid package name?
114 >>> PkgPolicy.is_valid_packagename('doesnotmatter')
115 Traceback (most recent call last):
117 NotImplementedError: Class needs to provide packagename_re
119 if cls.packagename_re is None:
120 raise NotImplementedError("Class needs to provide packagename_re")
121 return True if cls.packagename_re.match(name) else False
124 def is_valid_upstreamversion(cls, version):
126 Is this a valid upstream version number?
128 >>> PkgPolicy.is_valid_upstreamversion('doesnotmatter')
129 Traceback (most recent call last):
131 NotImplementedError: Class needs to provide upstreamversion_re
133 if cls.upstreamversion_re is None:
134 raise NotImplementedError("Class needs to provide upstreamversion_re")
135 return True if cls.upstreamversion_re.match(version) else False
138 def is_valid_orig_archive(cls, filename):
139 "Is this a valid orig source archive"
140 (base, arch_fmt, compression) = parse_archive_filename(filename)
141 if arch_fmt == 'tar' and compression:
146 def guess_upstream_src_version(cls, filename, extra_regex=r''):
148 Guess the package name and version from the filename of an upstream
151 @param filename: filename (archive or directory) from which to guess
152 @type filename: C{string}
153 @param extra_regex: additional regex to apply, needs a 'package' and a
155 @return: (package name, version) or ('', '')
158 >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.gz')
160 >>> PkgPolicy.guess_upstream_src_version('foo-Bar_0.2.orig.tar.gz')
161 ('foo-Bar', '0.2.orig')
162 >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2.tar.gz')
164 >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2-rc1.tar.gz')
165 ('git-bar', '0.2-rc1')
166 >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2:~-rc1.tar.gz')
167 ('git-bar', '0.2:~-rc1')
168 >>> PkgPolicy.guess_upstream_src_version('git-Bar-0A2d:rc1.tar.bz2')
169 ('git-Bar', '0A2d:rc1')
170 >>> PkgPolicy.guess_upstream_src_version('git-1.tar.bz2')
172 >>> PkgPolicy.guess_upstream_src_version('kvm_87+dfsg.orig.tar.gz')
174 >>> PkgPolicy.guess_upstream_src_version('foo-Bar-a.b.tar.gz')
176 >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.xz')
178 >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.tar.gz')
180 >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.lzma')
182 >>> PkgPolicy.guess_upstream_src_version('foo-bar-0.2.zip')
184 >>> PkgPolicy.guess_upstream_src_version('foo-bar-0.2.tlz')
187 version_chars = r'[a-zA-Z\d\.\~\-\:\+]'
188 basename = parse_archive_filename(os.path.basename(filename))[0]
190 version_filters = map ( lambda x: x % version_chars,
191 ( # Debian upstream tarball: package_'<version>.orig.tar.gz'
192 r'^(?P<package>[a-z\d\.\+\-]+)_(?P<version>%s+)\.orig',
193 # Upstream 'package-<version>.tar.gz'
194 # or Debian native 'package_<version>.tar.gz'
195 # or directory 'package-<version>':
196 r'^(?P<package>[a-zA-Z\d\.\+\-]+)(-|_)(?P<version>[0-9]%s*)'))
198 version_filters = extra_regex + version_filters
200 for filter in version_filters:
201 m = re.match(filter, basename)
203 return (m.group('package'), m.group('version'))
207 def guess_upstream_src_version(filename, extra_regex=r''):
209 Guess the package name and version from the filename of an upstream
212 @param filename: filename (archive or directory) from which to guess
213 @type filename: C{string}
214 @param extra_regex: additional regex to apply, needs a 'package' and a
216 @return: (package name, version) or ('', '')
219 >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.gz')
221 >>> PkgPolicy.guess_upstream_src_version('foo-Bar_0.2.orig.tar.gz')
223 >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2.tar.gz')
225 >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2-rc1.tar.gz')
226 ('git-bar', '0.2-rc1')
227 >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2:~-rc1.tar.gz')
228 ('git-bar', '0.2:~-rc1')
229 >>> PkgPolicy.guess_upstream_src_version('git-Bar-0A2d:rc1.tar.bz2')
230 ('git-Bar', '0A2d:rc1')
231 >>> PkgPolicy.guess_upstream_src_version('git-1.tar.bz2')
233 >>> PkgPolicy.guess_upstream_src_version('kvm_87+dfsg.orig.tar.gz')
235 >>> PkgPolicy.guess_upstream_src_version('foo-Bar-a.b.tar.gz')
237 >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.xz')
239 >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.lzma')
241 >>> PkgPolicy.guess_upstream_src_version('foo-bar-0.2.zip')
243 >>> PkgPolicy.guess_upstream_src_version('foo-bar-0.2.tlz')
245 >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.tar.gz')
248 version_chars = r'[a-zA-Z\d\.\~\-\:\+]'
249 basename = parse_archive_filename(os.path.basename(filename))[0]
251 version_filters = map ( lambda x: x % version_chars,
252 ( # Debian upstream tarball: package_'<version>.orig.tar.gz'
253 r'^(?P<package>[a-z\d\.\+\-]+)_(?P<version>%s+)\.orig',
254 # Debian native: 'package_<version>.tar.gz'
255 r'^(?P<package>[a-z\d\.\+\-]+)_(?P<version>%s+)',
256 # Upstream 'package-<version>.tar.gz'
257 # or directory 'package-<version>':
258 r'^(?P<package>[a-zA-Z\d\.\+\-]+)(-)(?P<version>[0-9]%s*)'))
260 version_filters = extra_regex + version_filters
262 for filter in version_filters:
263 m = re.match(filter, basename)
265 return (m.group('package'), m.group('version'))
269 def has_orig(orig_file, dir):
270 "Check if orig tarball exists in dir"
272 os.stat( os.path.join(dir, orig_file) )
278 def symlink_orig(orig_file, orig_dir, output_dir, force=False):
280 symlink orig tarball from orig_dir to output_dir
281 @return: True if link was created or src == dst
282 False in case of error or src doesn't exist
284 orig_dir = os.path.abspath(orig_dir)
285 output_dir = os.path.abspath(output_dir)
287 if orig_dir == output_dir:
290 src = os.path.join(orig_dir, orig_file)
291 dst = os.path.join(output_dir, orig_file)
292 if not os.access(src, os.F_OK):
295 if os.access(dst, os.F_OK) and force:
303 class UpstreamSource(object):
305 Upstream source. Can be either an unpacked dir, a tarball or another type
308 @cvar _orig: are the upstream sources already suitable as an upstream
311 @cvar _path: path to the upstream sources
313 @cvar _unpacked: path to the unpacked source tree
314 @type _unpacked: string
316 def __init__(self, name, unpacked=None, pkg_policy=PkgPolicy, prefix=None):
318 self._tarball = False
319 self._pkg_policy = pkg_policy
320 self._path = os.path.abspath(name)
321 if not os.path.exists(self._path):
322 raise GbpError('UpstreamSource: unable to find %s' % self._path)
323 self.unpacked = unpacked
324 self._filename_base, \
326 self._compression = parse_archive_filename(os.path.basename(self.path))
327 self._prefix = prefix
328 if self._prefix is None:
329 self._determine_prefix()
333 self.unpacked = self.path
335 def _check_orig(self):
337 Check if upstream source format can be used as orig tarball.
338 This doesn't imply that the tarball is correctly named.
340 @return: C{True} if upstream source format is suitable
341 as upstream tarball, C{False} otherwise.
346 self._tarball = False
349 self._tarball = True if self.archive_fmt == 'tar' else False
350 self._orig = self._pkg_policy.is_valid_orig_archive(os.path.basename(self.path))
354 @return: C{True} if sources are suitable as upstream source,
360 def is_tarball(self):
362 @return: C{True} if source is a tarball, C{False} otherwise
369 @return: C{True} if if upstream sources are an unpacked directory,
373 return True if os.path.isdir(self._path) else False
377 return self._path.rstrip('/')
381 def _get_topdir_files(file_list):
382 """Parse content of the top directory from a file list
384 >>> UpstreamSource._get_topdir_files([])
386 >>> UpstreamSource._get_topdir_files([('-', 'foo/bar')])
388 >>> UpstreamSource._get_topdir_files([('d', 'foo/'), ('-', 'foo/bar')])
390 >>> UpstreamSource._get_topdir_files([('d', 'foo'), ('-', 'foo/bar')])
392 >>> UpstreamSource._get_topdir_files([('-', 'fob'), ('d', 'foo'), ('d', 'foo/bar'), ('-', 'foo/bar/baz')])
393 set([('-', 'fob'), ('d', 'foo')])
394 >>> UpstreamSource._get_topdir_files([('-', './foo/bar')])
396 >>> UpstreamSource._get_topdir_files([('-', 'foo/bar'), ('-', '.foo/bar')])
397 set([('d', '.foo'), ('d', 'foo')])
400 for typ, path in file_list:
401 split = re.sub('^(?:./|../)*', '', path).split('/')
403 topdir_files.add((typ, path))
405 topdir_files.add(('d', split[0]))
408 def _determine_prefix(self):
409 """Determine the prefix, i.e. the "leading directory name"""
412 # For directories we presume that the prefix is just the dirname
413 self._prefix = os.path.basename(self.path.rstrip('/'))
416 if self._archive_fmt == 'zip':
417 archive = zipfile.ZipFile(self.path)
418 for info in archive.infolist():
419 typ = 'd' if stat.S_ISDIR(info.external_attr >> 16) else '?'
420 files.append((typ, info.filename))
421 elif self._archive_fmt == 'tar':
422 popen = subprocess.Popen(['tar', '-t', '-v', '-f', self.path],
423 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
424 out, _err = popen.communicate()
426 raise GbpError("Listing tar archive content failed")
427 for line in out.splitlines():
428 fields = line.split(None, 5)
429 files.append((fields[0][0], fields[-1]))
431 raise GbpError("Unsupported archive format %s, unable to "
432 "determine prefix for '%s'" %
433 (self._archive_fmt, self.path))
434 # Determine prefix from the archive content
435 topdir_files = self._get_topdir_files(files)
436 if len(topdir_files) == 1:
437 typ, name = topdir_files.pop()
442 def archive_fmt(self):
443 """Archive format of the sources, e.g. 'tar'"""
444 return self._archive_fmt
447 def compression(self):
448 """Compression format of the sources, e.g. 'gzip'"""
449 return self._compression
453 """Prefix, i.e. the 'leading directory name' of the sources"""
456 def unpack(self, dir, filters=[]):
458 Unpack packed upstream sources into a given directory
459 and determine the toplevel of the source tree.
462 raise GbpError("Cannot unpack directory %s" % self.path)
467 if type(filters) != type([]):
468 raise GbpError("Filters must be a list")
470 if self._unpack_archive(dir, filters):
471 ret = type(self)(dir, prefix=self._prefix)
474 src_dir = os.path.join(dir, self._prefix)
475 ret.unpacked = src_dir if os.path.isdir(src_dir) else dir
478 def _unpack_archive(self, dir, filters):
480 Unpack packed upstream sources into a given directory. Return True if
481 the output was filtered, otherwise False.
483 ext = os.path.splitext(self.path)[1]
484 if ext in [ ".zip", ".xpi" ]:
485 self._unpack_zip(dir)
487 self._unpack_tar(dir, filters)
492 def _unpack_zip(self, dir):
494 gbpc.UnpackZipArchive(self.path, dir)()
495 except gbpc.CommandExecFailed:
496 raise GbpError("Unpacking of %s failed" % self.path)
498 def _unpack_tar(self, dir, filters):
500 Unpack a tarball to I{dir} applying a list of I{filters}. Leave the
501 cleanup to the caller in case of an error.
504 unpackArchive = gbpc.UnpackTarArchive(self.path, dir, filters)
506 except gbpc.CommandExecFailed:
507 # unpackArchive already printed an error message
510 def pack(self, newarchive, filters=[], newprefix=None):
512 Recreate a new archive from the current one
514 @param newarchive: the name of the new archive
515 @type newarchive: string
516 @param filters: tar filters to apply
517 @type filters: array of strings
518 @param newprefix: new prefix, None implies that prefix is not mangled
519 @type newprefix: string or None
520 @return: the new upstream source
521 @rtype: UpstreamSource
523 if not self.unpacked:
524 raise GbpError("Need an unpacked source tree to pack")
529 if type(filters) != type([]):
530 raise GbpError("Filters must be a list")
532 run_dir = os.path.dirname(self.unpacked.rstrip('/'))
533 pack_this = os.path.basename(self.unpacked.rstrip('/'))
535 if newprefix is not None:
536 newprefix = newprefix.strip('/.')
538 transform = 's!%s!%s!' % (pack_this, newprefix)
540 transform = 's!%s!%s!' % (pack_this, '.')
542 repackArchive = gbpc.PackTarArchive(newarchive,
548 except gbpc.CommandExecFailed:
549 # repackArchive already printed an error
551 new = type(self)(newarchive)
552 # Reuse the same unpacked dir if the content matches
554 new.unpacked = self.unpacked
558 def known_compressions():
559 return [ args[1][-1] for args in compressor_opts.items() ]
561 def guess_version(self, extra_regex=r''):
562 return self._pkg_policy.guess_upstream_src_version(self.path,