__init__.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. # vim: set fileencoding=utf-8 :
  2. #
  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.
  9. #
  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.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, please see
  17. # <http://www.gnu.org/licenses/>
  18. """Common functionality of the Debian/RPM package helpers"""
  19. import os
  20. import re
  21. import glob
  22. import six
  23. import gbp.command_wrappers as gbpc
  24. from gbp.errors import GbpError
  25. # compression types, extra options and extensions
  26. compressor_opts = { 'gzip' : [ '-n', 'gz' ],
  27. 'bzip2' : [ '', 'bz2' ],
  28. 'lzma' : [ '', 'lzma' ],
  29. 'xz' : [ '', 'xz' ] }
  30. # Map frequently used names of compression types to the internal ones:
  31. compressor_aliases = { 'bz2' : 'bzip2',
  32. 'gz' : 'gzip', }
  33. # Supported archive formats
  34. archive_formats = [ 'tar', 'zip' ]
  35. # Map combined file extensions to archive and compression format
  36. archive_ext_aliases = { 'tgz' : ('tar', 'gzip'),
  37. 'tbz2' : ('tar', 'bzip2'),
  38. 'tlz' : ('tar', 'lzma'),
  39. 'txz' : ('tar', 'xz')}
  40. def parse_archive_filename(filename):
  41. """
  42. Given an filename return the basename (i.e. filename without the
  43. archive and compression extensions), archive format and compression
  44. method used.
  45. @param filename: the name of the file
  46. @type filename: string
  47. @return: tuple containing basename, archive format and compression method
  48. @rtype: C{tuple} of C{str}
  49. >>> parse_archive_filename("abc.tar.gz")
  50. ('abc', 'tar', 'gzip')
  51. >>> parse_archive_filename("abc.tar.bz2")
  52. ('abc', 'tar', 'bzip2')
  53. >>> parse_archive_filename("abc.def.tbz2")
  54. ('abc.def', 'tar', 'bzip2')
  55. >>> parse_archive_filename("abc.def.tar.xz")
  56. ('abc.def', 'tar', 'xz')
  57. >>> parse_archive_filename("abc.zip")
  58. ('abc', 'zip', None)
  59. >>> parse_archive_filename("abc.lzma")
  60. ('abc', None, 'lzma')
  61. >>> parse_archive_filename("abc.tar.foo")
  62. ('abc.tar.foo', None, None)
  63. >>> parse_archive_filename("abc")
  64. ('abc', None, None)
  65. """
  66. (base_name, archive_fmt, compression) = (filename, None, None)
  67. # Split filename to pieces
  68. split = filename.split(".")
  69. if len(split) > 1:
  70. if split[-1] in archive_ext_aliases:
  71. base_name = ".".join(split[:-1])
  72. (archive_fmt, compression) = archive_ext_aliases[split[-1]]
  73. elif split[-1] in archive_formats:
  74. base_name = ".".join(split[:-1])
  75. (archive_fmt, compression) = (split[-1], None)
  76. else:
  77. for (c, o) in six.iteritems(compressor_opts):
  78. if o[1] == split[-1]:
  79. base_name = ".".join(split[:-1])
  80. compression = c
  81. if len(split) > 2 and split[-2] in archive_formats:
  82. base_name = ".".join(split[:-2])
  83. archive_fmt = split[-2]
  84. return (base_name, archive_fmt, compression)
  85. class PkgPolicy(object):
  86. """
  87. Common helpers for packaging policy.
  88. """
  89. packagename_re = None
  90. packagename_msg = None
  91. upstreamversion_re = None
  92. upstreamversion_msg = None
  93. @classmethod
  94. def is_valid_packagename(cls, name):
  95. """
  96. Is this a valid package name?
  97. >>> PkgPolicy.is_valid_packagename('doesnotmatter')
  98. Traceback (most recent call last):
  99. ...
  100. NotImplementedError: Class needs to provide packagename_re
  101. """
  102. if cls.packagename_re is None:
  103. raise NotImplementedError("Class needs to provide packagename_re")
  104. return True if cls.packagename_re.match(name) else False
  105. @classmethod
  106. def is_valid_upstreamversion(cls, version):
  107. """
  108. Is this a valid upstream version number?
  109. >>> PkgPolicy.is_valid_upstreamversion('doesnotmatter')
  110. Traceback (most recent call last):
  111. ...
  112. NotImplementedError: Class needs to provide upstreamversion_re
  113. """
  114. if cls.upstreamversion_re is None:
  115. raise NotImplementedError("Class needs to provide upstreamversion_re")
  116. return True if cls.upstreamversion_re.match(version) else False
  117. @staticmethod
  118. def guess_upstream_src_version(filename, extra_regex=r''):
  119. """
  120. Guess the package name and version from the filename of an upstream
  121. archive.
  122. @param filename: filename (archive or directory) from which to guess
  123. @type filename: C{string}
  124. @param extra_regex: additional regex to apply, needs a 'package' and a
  125. 'version' group
  126. @return: (package name, version) or ('', '')
  127. @rtype: tuple
  128. >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.gz')
  129. ('foo-bar', '0.2')
  130. >>> PkgPolicy.guess_upstream_src_version('foo-Bar_0.2.orig.tar.gz')
  131. ('', '')
  132. >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2.tar.gz')
  133. ('git-bar', '0.2')
  134. >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2-rc1.tar.gz')
  135. ('git-bar', '0.2-rc1')
  136. >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2:~-rc1.tar.gz')
  137. ('git-bar', '0.2:~-rc1')
  138. >>> PkgPolicy.guess_upstream_src_version('git-Bar-0A2d:rc1.tar.bz2')
  139. ('git-Bar', '0A2d:rc1')
  140. >>> PkgPolicy.guess_upstream_src_version('git-1.tar.bz2')
  141. ('git', '1')
  142. >>> PkgPolicy.guess_upstream_src_version('kvm_87+dfsg.orig.tar.gz')
  143. ('kvm', '87+dfsg')
  144. >>> PkgPolicy.guess_upstream_src_version('foo-Bar-a.b.tar.gz')
  145. ('', '')
  146. >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.xz')
  147. ('foo-bar', '0.2')
  148. >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.lzma')
  149. ('foo-bar', '0.2')
  150. >>> PkgPolicy.guess_upstream_src_version('foo-bar-0.2.zip')
  151. ('foo-bar', '0.2')
  152. >>> PkgPolicy.guess_upstream_src_version('foo-bar-0.2.tlz')
  153. ('foo-bar', '0.2')
  154. >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.tar.gz')
  155. ('foo-bar', '0.2')
  156. """
  157. version_chars = r'[a-zA-Z\d\.\~\-\:\+]'
  158. basename = parse_archive_filename(os.path.basename(filename))[0]
  159. version_filters = map(lambda x: x % version_chars,
  160. ( # Debian upstream tarball: package_'<version>.orig.tar.gz'
  161. r'^(?P<package>[a-z\d\.\+\-]+)_(?P<version>%s+)\.orig',
  162. # Debian native: 'package_<version>.tar.gz'
  163. r'^(?P<package>[a-z\d\.\+\-]+)_(?P<version>%s+)',
  164. # Upstream 'package-<version>.tar.gz'
  165. # or directory 'package-<version>':
  166. r'^(?P<package>[a-zA-Z\d\.\+\-]+)(-)(?P<version>[0-9]%s*)'))
  167. if extra_regex:
  168. version_filters = extra_regex + version_filters
  169. for filter in version_filters:
  170. m = re.match(filter, basename)
  171. if m:
  172. return (m.group('package'), m.group('version'))
  173. return ('', '')
  174. @staticmethod
  175. def has_orig(orig_file, dir):
  176. "Check if orig tarball exists in dir"
  177. try:
  178. os.stat( os.path.join(dir, orig_file) )
  179. except OSError:
  180. return False
  181. return True
  182. @staticmethod
  183. def symlink_orig(orig_file, orig_dir, output_dir, force=False):
  184. """
  185. symlink orig tarball from orig_dir to output_dir
  186. @return: True if link was created or src == dst
  187. False in case of error or src doesn't exist
  188. """
  189. orig_dir = os.path.abspath(orig_dir)
  190. output_dir = os.path.abspath(output_dir)
  191. if orig_dir == output_dir:
  192. return True
  193. src = os.path.join(orig_dir, orig_file)
  194. dst = os.path.join(output_dir, orig_file)
  195. if not os.access(src, os.F_OK):
  196. return False
  197. try:
  198. if os.access(dst, os.F_OK) and force:
  199. os.unlink(dst)
  200. os.symlink(src, dst)
  201. except OSError:
  202. return False
  203. return True
  204. class UpstreamSource(object):
  205. """
  206. Upstream source. Can be either an unpacked dir, a tarball or another type
  207. of archive
  208. @cvar _orig: are the upstream sources already suitable as an upstream
  209. tarball
  210. @type _orig: boolean
  211. @cvar _path: path to the upstream sources
  212. @type _path: string
  213. @cvar _unpacked: path to the unpacked source tree
  214. @type _unpacked: string
  215. """
  216. def __init__(self, name, unpacked=None, pkg_policy=PkgPolicy):
  217. self._orig = False
  218. self._pkg_policy = pkg_policy
  219. self._path = name
  220. self.unpacked = unpacked
  221. self._check_orig()
  222. if self.is_dir():
  223. self.unpacked = self.path
  224. def _check_orig(self):
  225. """
  226. Check if upstream source format can be used as orig tarball.
  227. This doesn't imply that the tarball is correctly named.
  228. @return: C{True} if upstream source format is suitable
  229. as upstream tarball, C{False} otherwise.
  230. @rtype: C{bool}
  231. """
  232. if self.is_dir():
  233. self._orig = False
  234. return
  235. parts = self._path.split('.')
  236. try:
  237. if parts[-1] == 'tgz':
  238. self._orig = True
  239. elif parts[-2] == 'tar':
  240. if (parts[-1] in compressor_opts or
  241. parts[-1] in compressor_aliases):
  242. self._orig = True
  243. except IndexError:
  244. self._orig = False
  245. def is_orig(self):
  246. """
  247. @return: C{True} if sources are suitable as upstream source,
  248. C{False} otherwise
  249. @rtype: C{bool}
  250. """
  251. return self._orig
  252. def is_dir(self):
  253. """
  254. @return: C{True} if if upstream sources are an unpacked directory,
  255. C{False} otherwise
  256. @rtype: C{bool}
  257. """
  258. return True if os.path.isdir(self._path) else False
  259. @property
  260. def path(self):
  261. return self._path.rstrip('/')
  262. def unpack(self, dir, filters=[]):
  263. """
  264. Unpack packed upstream sources into a given directory
  265. and determine the toplevel of the source tree filtering
  266. out files specified by filters.
  267. """
  268. if self.is_dir():
  269. raise GbpError("Cannot unpack directory %s" % self.path)
  270. if not filters:
  271. filters = []
  272. if not isinstance(filters, list):
  273. raise GbpError("Filters must be a list")
  274. self._unpack_archive(dir, filters)
  275. self.unpacked = self._unpacked_toplevel(dir)
  276. def _unpack_archive(self, dir, filters):
  277. """
  278. Unpack packed upstream sources into a given directory
  279. allowing to filter out files in case of tar archives.
  280. """
  281. ext = os.path.splitext(self.path)[1]
  282. if ext in [".zip", ".xpi"]:
  283. if filters:
  284. raise GbpError("Can only filter tar archives: %s", (ext, self.path))
  285. self._unpack_zip(dir)
  286. else:
  287. self._unpack_tar(dir, filters)
  288. def _unpack_zip(self, dir):
  289. try:
  290. gbpc.UnpackZipArchive(self.path, dir)()
  291. except gbpc.CommandExecFailed:
  292. raise GbpError("Unpacking of %s failed" % self.path)
  293. def _unpacked_toplevel(self, dir):
  294. """unpacked archives can contain a leading directory or not"""
  295. unpacked = glob.glob('%s/*' % dir)
  296. unpacked.extend(glob.glob("%s/.*" % dir)) # include hidden files and folders
  297. # Check that dir contains nothing but a single folder:
  298. if len(unpacked) == 1 and os.path.isdir(unpacked[0]):
  299. return unpacked[0]
  300. else:
  301. return dir
  302. def _unpack_tar(self, dir, filters):
  303. """
  304. Unpack a tarball to I{dir} applying a list of I{filters}. Leave the
  305. cleanup to the caller in case of an error.
  306. """
  307. try:
  308. unpackArchive = gbpc.UnpackTarArchive(self.path, dir, filters)
  309. unpackArchive()
  310. except gbpc.CommandExecFailed:
  311. # unpackArchive already printed an error message
  312. raise GbpError
  313. def pack(self, newarchive, filters=[]):
  314. """
  315. Recreate a new archive from the current one
  316. @param newarchive: the name of the new archive
  317. @type newarchive: string
  318. @param filters: tar filters to apply
  319. @type filters: array of strings
  320. @return: the new upstream source
  321. @rtype: UpstreamSource
  322. """
  323. if not self.unpacked:
  324. raise GbpError("Need an unpacked source tree to pack")
  325. if not filters:
  326. filters = []
  327. if not isinstance(filters, list):
  328. raise GbpError("Filters must be a list")
  329. try:
  330. unpacked = self.unpacked.rstrip('/')
  331. repackArchive = gbpc.PackTarArchive(newarchive,
  332. os.path.dirname(unpacked),
  333. os.path.basename(unpacked),
  334. filters)
  335. repackArchive()
  336. except gbpc.CommandExecFailed:
  337. # repackArchive already printed an error
  338. raise GbpError
  339. return type(self)(newarchive)
  340. @staticmethod
  341. def known_compressions():
  342. return [args[1][-1] for args in compressor_opts.items()]
  343. def guess_version(self, extra_regex=r''):
  344. return self._pkg_policy.guess_upstream_src_version(self.path,
  345. extra_regex)