2013-04-09 12:51:28 +02:00
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
2013-04-09 11:35:53 +02:00
|
|
|
from . import ar
|
2010-01-18 16:12:10 +01:00
|
|
|
import os.path
|
2009-09-12 21:02:58 +02:00
|
|
|
import re
|
|
|
|
import tarfile
|
2017-10-23 10:39:56 +02:00
|
|
|
import StringIO
|
2013-04-09 11:35:53 +02:00
|
|
|
from . import packagequery
|
2009-09-12 21:02:58 +02:00
|
|
|
|
2017-10-23 10:39:56 +02:00
|
|
|
HAVE_LZMA = True
|
|
|
|
try:
|
|
|
|
import lzma
|
|
|
|
except ImportError:
|
|
|
|
HAVE_LZMA = False
|
|
|
|
|
2009-09-12 21:02:58 +02:00
|
|
|
class DebError(packagequery.PackageError):
|
|
|
|
pass
|
|
|
|
|
2015-06-23 17:52:37 +02:00
|
|
|
class DebQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
|
2010-01-14 19:51:09 +01:00
|
|
|
|
|
|
|
default_tags = ('package', 'version', 'release', 'epoch', 'architecture', 'description',
|
2014-12-05 18:36:44 +01:00
|
|
|
'provides', 'depends', 'pre_depends', 'conflicts', 'breaks')
|
2010-01-14 19:51:09 +01:00
|
|
|
|
2009-09-12 21:02:58 +02:00
|
|
|
def __init__(self, fh):
|
|
|
|
self.__file = fh
|
2010-01-18 16:12:10 +01:00
|
|
|
self.__path = os.path.abspath(fh.name)
|
2009-09-13 19:25:48 +02:00
|
|
|
self.filename_suffix = 'deb'
|
2009-09-12 21:02:58 +02:00
|
|
|
self.fields = {}
|
|
|
|
|
2014-04-03 17:41:41 +02:00
|
|
|
def read(self, all_tags=False, self_provides=True, *extra_tags):
|
2009-09-12 21:02:58 +02:00
|
|
|
arfile = ar.Ar(fh = self.__file)
|
|
|
|
arfile.read()
|
|
|
|
debbin = arfile.get_file('debian-binary')
|
|
|
|
if debbin is None:
|
2010-10-21 21:46:08 +02:00
|
|
|
raise DebError(self.__path, 'no debian binary')
|
2009-09-12 21:02:58 +02:00
|
|
|
if debbin.read() != '2.0\n':
|
2010-10-21 21:46:08 +02:00
|
|
|
raise DebError(self.__path, 'invalid debian binary format')
|
2009-09-12 21:02:58 +02:00
|
|
|
control = arfile.get_file('control.tar.gz')
|
2017-10-23 10:39:56 +02:00
|
|
|
if control is not None:
|
|
|
|
# XXX: python2.4 relies on a name
|
|
|
|
tar = tarfile.open(name='control.tar.gz', fileobj=control)
|
|
|
|
else:
|
|
|
|
control = arfile.get_file('control.tar.xz')
|
|
|
|
if control is None:
|
|
|
|
raise DebError(self.__path, 'missing control.tar')
|
|
|
|
if not HAVE_LZMA:
|
|
|
|
raise DebError(self.__path, 'can\'t open control.tar.xz without python-lzma')
|
|
|
|
decompressed = lzma.decompress(control.read())
|
|
|
|
tar = tarfile.open(name="control.tar.xz",
|
|
|
|
fileobj=StringIO.StringIO(decompressed))
|
2009-09-12 21:02:58 +02:00
|
|
|
try:
|
2010-12-04 12:11:34 +01:00
|
|
|
name = './control'
|
|
|
|
# workaround for python2.4's tarfile module
|
|
|
|
if 'control' in tar.getnames():
|
|
|
|
name = 'control'
|
|
|
|
control = tar.extractfile(name)
|
2009-09-12 21:02:58 +02:00
|
|
|
except KeyError:
|
2017-10-23 10:39:56 +02:00
|
|
|
raise DebError(self.__path,
|
|
|
|
'missing \'control\' file in control.tar')
|
2014-04-03 17:41:41 +02:00
|
|
|
self.__parse_control(control, all_tags, self_provides, *extra_tags)
|
2015-06-23 17:52:37 +02:00
|
|
|
return self
|
2009-09-12 21:02:58 +02:00
|
|
|
|
2014-04-03 17:41:41 +02:00
|
|
|
def __parse_control(self, control, all_tags=False, self_provides=True, *extra_tags):
|
2009-09-12 21:02:58 +02:00
|
|
|
data = control.readline().strip()
|
|
|
|
while data:
|
|
|
|
field, val = re.split(':\s*', data.strip(), 1)
|
|
|
|
data = control.readline()
|
|
|
|
while data and re.match('\s+', data):
|
|
|
|
val += '\n' + data.strip()
|
|
|
|
data = control.readline().rstrip()
|
2010-01-14 19:51:09 +01:00
|
|
|
field = field.replace('-', '_').lower()
|
|
|
|
if field in self.default_tags + extra_tags or all_tags:
|
|
|
|
# a hyphen is not allowed in dict keys
|
|
|
|
self.fields[field] = val
|
2009-09-12 21:02:58 +02:00
|
|
|
versrel = self.fields['version'].rsplit('-', 1)
|
|
|
|
if len(versrel) == 2:
|
|
|
|
self.fields['version'] = versrel[0]
|
|
|
|
self.fields['release'] = versrel[1]
|
|
|
|
else:
|
2009-11-06 23:24:23 +01:00
|
|
|
self.fields['release'] = None
|
2009-09-29 14:32:26 +02:00
|
|
|
verep = self.fields['version'].split(':', 1)
|
|
|
|
if len(verep) == 2:
|
|
|
|
self.fields['epoch'] = verep[0]
|
|
|
|
self.fields['version'] = verep[1]
|
|
|
|
else:
|
|
|
|
self.fields['epoch'] = '0'
|
2019-01-27 17:35:47 +01:00
|
|
|
self.fields['provides'] = self._split_field_value('provides')
|
|
|
|
self.fields['depends'] = self._split_field_value('depends')
|
|
|
|
self.fields['pre_depends'] = self._split_field_value('pre_depends')
|
|
|
|
self.fields['conflicts'] = self._split_field_value('conflicts')
|
|
|
|
self.fields['breaks'] = self._split_field_value('breaks')
|
|
|
|
self.fields['recommends'] = self._split_field_value('recommends')
|
|
|
|
self.fields['suggests'] = self._split_field_value('suggests')
|
|
|
|
self.fields['enhances'] = self._split_field_value('enhances')
|
2014-04-03 17:41:41 +02:00
|
|
|
if self_provides:
|
|
|
|
# add self provides entry
|
2014-12-05 18:36:44 +01:00
|
|
|
self.fields['provides'].append('%s (= %s)' % (self.name(), '-'.join(versrel)))
|
2009-09-12 21:02:58 +02:00
|
|
|
|
2019-01-27 17:35:47 +01:00
|
|
|
def _split_field_value(self, field, delimeter=b',\s*'):
|
|
|
|
return [i.strip()
|
|
|
|
for i in re.split(delimeter, self.fields.get(field, b'')) if i]
|
|
|
|
|
2009-09-21 18:47:54 +02:00
|
|
|
def vercmp(self, debq):
|
2009-09-29 14:32:26 +02:00
|
|
|
res = cmp(int(self.epoch()), int(debq.epoch()))
|
|
|
|
if res != 0:
|
|
|
|
return res
|
|
|
|
res = DebQuery.debvercmp(self.version(), debq.version())
|
2009-11-06 23:24:23 +01:00
|
|
|
if res != None:
|
2009-09-29 14:32:26 +02:00
|
|
|
return res
|
|
|
|
res = DebQuery.debvercmp(self.release(), debq.release())
|
|
|
|
return res
|
2009-09-21 18:47:54 +02:00
|
|
|
|
2009-09-12 21:02:58 +02:00
|
|
|
def name(self):
|
|
|
|
return self.fields['package']
|
|
|
|
|
|
|
|
def version(self):
|
|
|
|
return self.fields['version']
|
|
|
|
|
|
|
|
def release(self):
|
|
|
|
return self.fields['release']
|
|
|
|
|
2009-09-29 14:32:26 +02:00
|
|
|
def epoch(self):
|
|
|
|
return self.fields['epoch']
|
|
|
|
|
2009-09-12 21:02:58 +02:00
|
|
|
def arch(self):
|
|
|
|
return self.fields['architecture']
|
|
|
|
|
|
|
|
def description(self):
|
|
|
|
return self.fields['description']
|
|
|
|
|
2010-01-18 16:12:10 +01:00
|
|
|
def path(self):
|
|
|
|
return self.__path
|
|
|
|
|
2009-09-12 21:02:58 +02:00
|
|
|
def provides(self):
|
|
|
|
return self.fields['provides']
|
|
|
|
|
|
|
|
def requires(self):
|
2014-12-05 18:36:44 +01:00
|
|
|
return self.fields['depends'] + self.fields['pre_depends']
|
|
|
|
|
|
|
|
def conflicts(self):
|
|
|
|
return self.fields['conflicts'] + self.fields['breaks']
|
|
|
|
|
|
|
|
def obsoletes(self):
|
|
|
|
return []
|
2009-09-12 21:02:58 +02:00
|
|
|
|
2017-12-26 23:14:47 +01:00
|
|
|
def recommends(self):
|
|
|
|
return self.fields['recommends']
|
|
|
|
|
|
|
|
def suggests(self):
|
|
|
|
return self.fields['suggests']
|
|
|
|
|
|
|
|
def supplements(self):
|
|
|
|
# a control file has no notion of "supplements"
|
|
|
|
return []
|
|
|
|
|
|
|
|
def enhances(self):
|
|
|
|
return self.fields['enhances']
|
|
|
|
|
2010-02-27 20:11:15 +01:00
|
|
|
def gettag(self, num):
|
2009-09-12 21:02:58 +02:00
|
|
|
return self.fields.get(num, None)
|
|
|
|
|
2010-03-24 13:20:09 +01:00
|
|
|
def canonname(self):
|
2014-05-13 13:28:19 +02:00
|
|
|
return DebQuery.filename(self.name(), self.epoch(), self.version(), self.release(), self.arch())
|
2010-03-24 13:20:09 +01:00
|
|
|
|
2009-09-12 21:02:58 +02:00
|
|
|
@staticmethod
|
2010-01-14 19:51:09 +01:00
|
|
|
def query(filename, all_tags = False, *extra_tags):
|
2009-09-12 21:02:58 +02:00
|
|
|
f = open(filename, 'rb')
|
|
|
|
debq = DebQuery(f)
|
2010-01-14 19:51:09 +01:00
|
|
|
debq.read(all_tags, *extra_tags)
|
2009-09-12 21:02:58 +02:00
|
|
|
f.close()
|
|
|
|
return debq
|
|
|
|
|
2009-09-29 14:32:26 +02:00
|
|
|
@staticmethod
|
|
|
|
def debvercmp(ver1, ver2):
|
|
|
|
"""
|
|
|
|
implementation of dpkg's version comparison algorithm
|
|
|
|
"""
|
|
|
|
# 32 is arbitrary - it is needed for the "longer digit string wins" handling
|
|
|
|
# (found this nice approach in Build/Deb.pm (build package))
|
|
|
|
ver1 = re.sub('(\d+)', lambda m: (32 * '0' + m.group(1))[-32:], ver1)
|
|
|
|
ver2 = re.sub('(\d+)', lambda m: (32 * '0' + m.group(1))[-32:], ver2)
|
|
|
|
vers = map(lambda x, y: (x or '', y or ''), ver1, ver2)
|
|
|
|
for v1, v2 in vers:
|
|
|
|
if v1 == v2:
|
|
|
|
continue
|
|
|
|
if (v1.isalpha() and v2.isalpha()) or (v1.isdigit() and v2.isdigit()):
|
|
|
|
res = cmp(v1, v2)
|
|
|
|
if res != 0:
|
|
|
|
return res
|
|
|
|
else:
|
|
|
|
if v1 == '~' or not v1:
|
|
|
|
return -1
|
|
|
|
elif v2 == '~' or not v2:
|
|
|
|
return 1
|
|
|
|
ord1 = ord(v1)
|
|
|
|
if not (v1.isalpha() or v1.isdigit()):
|
|
|
|
ord1 += 256
|
|
|
|
ord2 = ord(v2)
|
|
|
|
if not (v2.isalpha() or v2.isdigit()):
|
|
|
|
ord2 += 256
|
|
|
|
if ord1 > ord2:
|
|
|
|
return 1
|
|
|
|
else:
|
|
|
|
return -1
|
|
|
|
return 0
|
|
|
|
|
2010-03-24 13:20:09 +01:00
|
|
|
@staticmethod
|
2014-05-13 13:28:19 +02:00
|
|
|
def filename(name, epoch, version, release, arch):
|
2010-03-24 13:20:09 +01:00
|
|
|
if release:
|
|
|
|
return '%s_%s-%s_%s.deb' % (name, version, release, arch)
|
|
|
|
else:
|
|
|
|
return '%s_%s_%s.deb' % (name, version, arch)
|
|
|
|
|
2009-09-12 21:02:58 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
import sys
|
|
|
|
try:
|
|
|
|
debq = DebQuery.query(sys.argv[1])
|
2013-04-09 11:27:02 +02:00
|
|
|
except DebError as e:
|
2013-04-09 12:51:28 +02:00
|
|
|
print(e.msg)
|
2009-09-12 21:02:58 +02:00
|
|
|
sys.exit(2)
|
2013-04-09 12:51:28 +02:00
|
|
|
print(debq.name(), debq.version(), debq.release(), debq.arch())
|
|
|
|
print(debq.description())
|
|
|
|
print('##########')
|
|
|
|
print('\n'.join(debq.provides()))
|
|
|
|
print('##########')
|
|
|
|
print('\n'.join(debq.requires()))
|