1
0
mirror of https://github.com/openSUSE/osc.git synced 2024-12-28 10:46:15 +01:00

Merge branch 'master' into python3_fetch_module

This commit is contained in:
Marco Strigl 2019-04-07 10:25:50 -05:00 committed by GitHub
commit b71deaa537
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 395 additions and 260 deletions

27
NEWS
View File

@ -1,5 +1,32 @@
0.165
-
0.164.2
- deleterequest for entire projects needs the --all option as additional protection
- rewrite packagequery to support python3
- rewrite oscerr module to support python3
- rewrite archqeury and debquery to support python3
- Export vc env vars when running a source service
0.164.1
- rewrite cpio handling to support python3
- rewrite ar module to support python3
- enable fetch module to support python3
- rework progressbar hanlding (if module is not present)
- improve os_path_samefile in core.py
0.164 0.164
- add support for approved requests (requires OBS 2.10) - add support for approved requests (requires OBS 2.10)
- fix various multibuild problems
- improved and fixed various help texts
- check constraints without local checkout
- check out deleted sources (osc co -D)
- replace urlgrabber module with own module
- use progressbar module instead of urlgrabber to draw
progress bars
- show buildinfo for alternative projects (--alternative-project)
- run release job immediately (osc release --no-delay)
- build results on project level can now be watched (osc prjresults --watch)
0.163 0.163
- add sendsysrq command (requires OBS 2.10) - add sendsysrq command (requires OBS 2.10)

View File

@ -33,10 +33,7 @@ except ImportError:
from .conf import config, cookiejar from .conf import config, cookiejar
try: from .meter import create_text_meter
from .meter import TextMeter
except:
TextMeter = None
change_personality = { change_personality = {
'i686': 'linux32', 'i686': 'linux32',
@ -308,10 +305,9 @@ def get_preinstall_image(apiurl, arch, cache_dir, img_info):
print('packagecachedir is not writable for you?', file=sys.stderr) print('packagecachedir is not writable for you?', file=sys.stderr)
print(e, file=sys.stderr) print(e, file=sys.stderr)
sys.exit(1) sys.exit(1)
if sys.stdout.isatty() and TextMeter: progress_obj = None
progress_obj = TextMeter() if sys.stdout.isatty():
else: progress_obj = create_text_meter(use_pb_fallback=False)
progress_obj = None
gr = OscFileGrabber(progress_obj=progress_obj) gr = OscFileGrabber(progress_obj=progress_obj)
try: try:
gr.urlgrab(url, filename=ifile_path_part, text='fetching image') gr.urlgrab(url, filename=ifile_path_part, text='fetching image')
@ -502,6 +498,22 @@ def check_trusted_projects(apiurl, projects):
config['api_host_options'][apiurl]['trusted_prj'] = trusted config['api_host_options'][apiurl]['trusted_prj'] = trusted
conf.config_set_option(apiurl, 'trusted_prj', ' '.join(trusted)) conf.config_set_option(apiurl, 'trusted_prj', ' '.join(trusted))
def get_kiwipath_from_buildinfo(apiurl, bi_filename, prj, repo):
bi = Buildinfo(bi_filename, apiurl, 'kiwi')
# If the project does not have a path defined we need to get the config
# via the repositories in the kiwi file. Unfortunately the buildinfo
# does not include a hint if this is the case, so we rely on a heuristic
# here: if the path list contains our own repo, it probably does not
# come from the kiwi file and thus a path is defined in the config.
# It is unlikely that our own repo is included in the kiwi file, as it
# contains no packages.
myprp = prj + '/' + repo
if myprp in bi.pathes:
return None
kiwipath = bi.pathes
kiwipath.insert(0, myprp)
return kiwipath
def main(apiurl, opts, argv): def main(apiurl, opts, argv):
repo = argv[0] repo = argv[0]
@ -783,8 +795,11 @@ def main(apiurl, opts, argv):
# maybe we should check for errors before saving the file # maybe we should check for errors before saving the file
bi_file.write(bi_text) bi_file.write(bi_text)
bi_file.flush() bi_file.flush()
kiwipath = None
if build_type == 'kiwi':
kiwipath = get_kiwipath_from_buildinfo(apiurl, bi_filename, prj, repo)
print('Getting buildconfig from server and store to %s' % bc_filename) print('Getting buildconfig from server and store to %s' % bc_filename)
bc = get_buildconfig(apiurl, prj, repo) bc = get_buildconfig(apiurl, prj, repo, kiwipath)
if not bc_file: if not bc_file:
bc_file = open(bc_filename, 'w') bc_file = open(bc_filename, 'w')
bc_file.write(bc) bc_file.write(bc)

View File

@ -166,8 +166,8 @@ class Osc(cmdln.Cmdln):
self.options.verbose = conf.config['verbose'] self.options.verbose = conf.config['verbose']
self.download_progress = None self.download_progress = None
if conf.config.get('show_download_progress', False): if conf.config.get('show_download_progress', False):
from .meter import TextMeter from .meter import create_text_meter
self.download_progress = TextMeter() self.download_progress = create_text_meter()
def get_cmd_help(self, cmdname): def get_cmd_help(self, cmdname):
@ -1939,6 +1939,8 @@ Please submit there instead, or use --nodevelproject to force direct submission.
help='specify message TEXT') help='specify message TEXT')
@cmdln.option('-r', '--repository', metavar='REPOSITORY', @cmdln.option('-r', '--repository', metavar='REPOSITORY',
help='specify repository') help='specify repository')
@cmdln.option('--all', action='store_true',
help='deletes entire project with packages inside')
@cmdln.option('--accept-in-hours', metavar='HOURS', @cmdln.option('--accept-in-hours', metavar='HOURS',
help='specify time when request shall get accepted automatically. Only works with write permissions in target.') help='specify time when request shall get accepted automatically. Only works with write permissions in target.')
@cmdln.alias("dr") @cmdln.alias("dr")
@ -1950,8 +1952,8 @@ Please submit there instead, or use --nodevelproject to force direct submission.
usage: usage:
osc deletereq [-m TEXT] # works in checked out project/package osc deletereq [-m TEXT] # works in checked out project/package
osc deletereq [-m TEXT] PROJECT [PACKAGE] osc deletereq [-m TEXT] PROJECT PACKAGE
osc deletereq [-m TEXT] PROJECT [--repository REPOSITORY] osc deletereq [-m TEXT] PROJECT [--all|--repository REPOSITORY]
${cmd_option_list} ${cmd_option_list}
""" """
import cgi import cgi
@ -1977,6 +1979,9 @@ Please submit there instead, or use --nodevelproject to force direct submission.
else: else:
raise oscerr.WrongArgs('Please specify at least a project.') raise oscerr.WrongArgs('Please specify at least a project.')
if not opts.all and package is None:
raise oscerr.WrongOptions('No package name has been provided. Use --all option, if you want to request to delete the entire project.')
if opts.repository: if opts.repository:
repository = opts.repository repository = opts.repository
@ -5203,8 +5208,7 @@ Please submit there instead, or use --nodevelproject to force direct submission.
opts.vertical = None opts.vertical = None
opts.show_non_building = None opts.show_non_building = None
opts.show_excluded = None opts.show_excluded = None
self.do_prjresults('prjresults', opts, *args) return self.do_prjresults('prjresults', opts, *args)
return
if opts.xml and opts.csv: if opts.xml and opts.csv:
raise oscerr.WrongOptions("--xml and --csv are mutual exclusive") raise oscerr.WrongOptions("--xml and --csv are mutual exclusive")
@ -6011,7 +6015,7 @@ Please submit there instead, or use --nodevelproject to force direct submission.
if (arg == osc.build.hostarch or arg in all_archs) and arg_arch is None: if (arg == osc.build.hostarch or arg in all_archs) and arg_arch is None:
# it seems to be an architecture in general # it seems to be an architecture in general
arg_arch = arg arg_arch = arg
if not (arg in osc.build.can_also_build.get(osc.build.hostarch) or arg == osc.build.hostarch): if not (arg == osc.build.hostarch or arg in osc.build.can_also_build.get(osc.build.hostarch, [])):
print("WARNING: native compile is not possible, an emulator must be configured!") print("WARNING: native compile is not possible, an emulator must be configured!")
elif not arg_repository: elif not arg_repository:
arg_repository = arg arg_repository = arg
@ -6287,14 +6291,16 @@ Please submit there instead, or use --nodevelproject to force direct submission.
args = self.parse_repoarchdescr(args, opts.noinit or opts.offline, opts.alternative_project, False, opts.vm_type, opts.multibuild_package) args = self.parse_repoarchdescr(args, opts.noinit or opts.offline, opts.alternative_project, False, opts.vm_type, opts.multibuild_package)
# check for source services # check for source services
r = None if not opts.offline and not opts.noservice:
try: p = Package('.')
if not opts.offline and not opts.noservice: r = p.run_source_services(verbose=True)
p = Package('.') if r:
r = p.run_source_services(verbose=True) print('Source service run failed!', file=sys.stderr)
except: sys.exit(1)
print("WARNING: package is not existing on server yet") else:
opts.local_package = True msg = ('WARNING: source services from package or project will not'
'be executed. This may not be the same build as on server!')
print(msg)
if not opts.local_package: if not opts.local_package:
try: try:
@ -6307,15 +6313,6 @@ Please submit there instead, or use --nodevelproject to force direct submission.
except oscerr.NoWorkingCopy: except oscerr.NoWorkingCopy:
pass pass
if opts.offline or opts.local_package or r == None:
print("WARNING: source service from package or project will not be executed. This may not be the same build as on server!")
elif (conf.config['local_service_run'] and not opts.noservice) and not opts.noinit:
if r != 0:
print('Source service run failed!', file=sys.stderr)
sys.exit(1)
# that is currently unreadable on cli, we should not have a backtrace on standard errors:
#raise oscerr.ServiceRuntimeError('Service run failed: \'%s\'', r)
if conf.config['no_verify']: if conf.config['no_verify']:
opts.no_verify = True opts.no_verify = True
@ -6335,7 +6332,7 @@ Please submit there instead, or use --nodevelproject to force direct submission.
if opts.preload: if opts.preload:
opts.nopreinstallimage = True opts.nopreinstallimage = True
print('Building %s for %s/%s' % (args[2], args[0], args[1])) print('Building %s for %s/%s' % (args[2], args[0], args[1]))
if not opts.host: if not opts.host:
return osc.build.main(self.get_api_url(), opts, args) return osc.build.main(self.get_api_url(), opts, args)
@ -8850,37 +8847,6 @@ Please submit there instead, or use --nodevelproject to force direct submission.
else: else:
apiurl = self.get_api_url() apiurl = self.get_api_url()
# try to set the env variables for the user's realname and email
# (the variables are used by the "vc" script)
tag2envs = {'realname': ['VC_REALNAME'],
'email': ['VC_MAILADDR', 'mailaddr']}
tag2val = {}
missing_tags = []
for (tag, envs) in tag2envs.items():
env_present = [env for env in envs if env in os.environ]
config_present = tag in conf.config['api_host_options'][apiurl]
if not env_present and not config_present:
missing_tags.append(tag)
elif config_present:
tag2val[tag] = conf.config['api_host_options'][apiurl][tag]
if missing_tags:
user = conf.get_apiurl_usr(apiurl)
data = get_user_data(apiurl, user, *missing_tags)
if data is not None:
for tag in missing_tags:
val = data.pop(0)
if val != '-':
tag2val[tag] = val
else:
msg = 'Try env %s=...' % tag2envs[tag][0]
print(msg, file=sys.stderr)
for (tag, val) in tag2val.items():
for env in tag2envs[tag]:
os.environ[env] = val
if meego_style: if meego_style:
if opts.message or opts.just_edit: if opts.message or opts.just_edit:
print('Warning: to edit MeeGo style changelog, opts will be ignored.', file=sys.stderr) print('Warning: to edit MeeGo style changelog, opts will be ignored.', file=sys.stderr)
@ -8899,6 +8865,7 @@ Please submit there instead, or use --nodevelproject to force direct submission.
cmd_list.extend(args) cmd_list.extend(args)
vc_export_env(apiurl)
vc = Popen(cmd_list) vc = Popen(cmd_list)
vc.wait() vc.wait()
sys.exit(vc.returncode) sys.exit(vc.returncode)

View File

@ -5,7 +5,7 @@
from __future__ import print_function from __future__ import print_function
__version__ = '0.164.git' __version__ = '0.165.git'
# __store_version__ is to be incremented when the format of the working copy # __store_version__ is to be incremented when the format of the working copy
# "store" changes in an incompatible way. Please add any needed migration # "store" changes in an incompatible way. Please add any needed migration
@ -247,7 +247,7 @@ buildstatus_symbols = {'succeeded': '.',
def os_path_samefile(path1, path2): def os_path_samefile(path1, path2):
try: try:
return os.path.samefile(path1, path2) return os.path.samefile(path1, path2)
except: except AttributeError:
return os.path.realpath(path1) == os.path.realpath(path2) return os.path.realpath(path1) == os.path.realpath(path2)
class File: class File:
@ -406,6 +406,12 @@ class Serviceinfo:
data = { 'name' : singleservice, 'command' : [ singleservice ], 'mode' : '' } data = { 'name' : singleservice, 'command' : [ singleservice ], 'mode' : '' }
allservices = [data] allservices = [data]
if not allservices:
# short-circuit to avoid a potential http request in vc_export_env
# (if there are no services to execute this http request is
# useless)
return 0
# services can detect that they run via osc this way # services can detect that they run via osc this way
os.putenv("OSC_VERSION", get_osc_version()) os.putenv("OSC_VERSION", get_osc_version())
@ -415,6 +421,8 @@ class Serviceinfo:
os.putenv("OBS_SERVICE_APIURL", self.apiurl) os.putenv("OBS_SERVICE_APIURL", self.apiurl)
os.putenv("OBS_SERVICE_PROJECT", self.project) os.putenv("OBS_SERVICE_PROJECT", self.project)
os.putenv("OBS_SERVICE_PACKAGE", self.package) os.putenv("OBS_SERVICE_PACKAGE", self.package)
# also export vc env vars (some services (like obs_scm) use them)
vc_export_env(self.apiurl)
# recreate files # recreate files
ret = 0 ret = 0
@ -2971,7 +2979,7 @@ class Request:
lines.append(' *** This request will get automatically accepted after '+self.accept_at+' ! ***\n') lines.append(' *** This request will get automatically accepted after '+self.accept_at+' ! ***\n')
if self.priority in [ 'critical', 'important' ] and self.state.name in [ 'new', 'review' ]: if self.priority in [ 'critical', 'important' ] and self.state.name in [ 'new', 'review' ]:
lines.append(' *** This request has classified as '+self.priority+' ! ***\n') lines.append(' *** This request has classified as '+self.priority+' ! ***\n')
if self.state.approver and self.state.name == 'review': if self.state and self.state.approver and self.state.name == 'review':
lines.append(' *** This request got approved by '+self.state.approver+'. It will get automatically accepted after last review got accepted! ***\n') lines.append(' *** This request got approved by '+self.state.approver+'. It will get automatically accepted after last review got accepted! ***\n')
for action in self.actions: for action in self.actions:
@ -4617,8 +4625,8 @@ def get_binary_file(apiurl, prj, repo, arch,
progress_meter = False): progress_meter = False):
progress_obj = None progress_obj = None
if progress_meter: if progress_meter:
from .meter import TextMeter from .meter import create_text_meter
progress_obj = TextMeter() progress_obj = create_text_meter()
target_filename = target_filename or filename target_filename = target_filename or filename
@ -6097,8 +6105,12 @@ def get_buildinfo(apiurl, prj, package, repository, arch, specfile=None, addlist
return f.read() return f.read()
def get_buildconfig(apiurl, prj, repository): def get_buildconfig(apiurl, prj, repository, path=None):
u = makeurl(apiurl, ['build', prj, repository, '_buildconfig']) query = []
if path:
for prp in path:
query.append('path=%s' % quote_plus(prp))
u = makeurl(apiurl, ['build', prj, repository, '_buildconfig'], query=query)
f = http_GET(u) f = http_GET(u)
return f.read() return f.read()
@ -7786,4 +7798,37 @@ def checkout_deleted_package(apiurl, proj, pkg, dst):
f.write(data) f.write(data)
print('done.') print('done.')
def vc_export_env(apiurl, quiet=False):
# try to set the env variables for the user's realname and email
# (the variables are used by the "vc" script or some source service)
tag2envs = {'realname': ['VC_REALNAME'],
'email': ['VC_MAILADDR', 'mailaddr']}
tag2val = {}
missing_tags = []
for (tag, envs) in tag2envs.items():
env_present = [env for env in envs if env in os.environ]
config_present = tag in conf.config['api_host_options'][apiurl]
if not env_present and not config_present:
missing_tags.append(tag)
elif config_present:
tag2val[tag] = conf.config['api_host_options'][apiurl][tag]
if missing_tags:
user = conf.get_apiurl_usr(apiurl)
data = get_user_data(apiurl, user, *missing_tags)
if data is not None:
for tag in missing_tags:
val = data.pop(0)
if val != '-':
tag2val[tag] = val
elif not quiet:
msg = 'Try env %s=...' % tag2envs[tag][0]
print(msg, file=sys.stderr)
for (tag, val) in tag2val.items():
for env in tag2envs[tag]:
os.environ[env] = val
# vim: sw=4 et # vim: sw=4 et

View File

@ -3,18 +3,25 @@
# and distributed under the terms of the GNU General Public Licence, # and distributed under the terms of the GNU General Public Licence,
# either version 2, or (at your option) any later version. # either version 2, or (at your option) any later version.
import progressbar as pb try:
import progressbar as pb
have_pb_module = True
except ImportError:
have_pb_module = False
class TextMeter(object): class PBTextMeter(object):
def start(self, basename, size=None): def start(self, basename, size=None):
if size is None: if size is None:
widgets = [basename + ': ', pb.AnimatedMarker(), ' ', pb.Timer()] widgets = [basename + ': ', pb.AnimatedMarker(), ' ', pb.Timer()]
self.bar = pb.ProgressBar(widgets=widgets, maxval=pb.UnknownLength) self.bar = pb.ProgressBar(widgets=widgets, maxval=pb.UnknownLength)
else: else:
widgets = [basename + ': ', pb.Percentage(), pb.Bar(), ' ', widgets = [basename + ': ', pb.Bar(), ' ', pb.ETA()]
pb.ETA()] if size:
# if size is 0, using pb.Percentage will result in
# a ZeroDivisionException
widgets.insert(1, pb.Percentage())
self.bar = pb.ProgressBar(widgets=widgets, maxval=size) self.bar = pb.ProgressBar(widgets=widgets, maxval=size)
self.bar.start() self.bar.start()
@ -24,4 +31,31 @@ class TextMeter(object):
def end(self): def end(self):
self.bar.finish() self.bar.finish()
class NoPBTextMeter(object):
_complained = False
def start(self, basename, size=None):
if not self._complained:
print('Please install the progressbar module')
NoPBTextMeter._complained = True
print('Processing: %s' % basename)
def update(self, *args, **kwargs):
pass
def end(self, *args, **kwargs):
pass
def create_text_meter(*args, **kwargs):
use_pb_fallback = kwargs.pop('use_pb_fallback', True)
if have_pb_module or use_pb_fallback:
return TextMeter(*args, **kwargs)
return None
if have_pb_module:
TextMeter = PBTextMeter
else:
TextMeter = NoPBTextMeter
# vim: sw=4 et # vim: sw=4 et

View File

@ -82,7 +82,7 @@ class WorkingCopyOutdated(OscBaseError):
def __str__(self): def __str__(self):
return ('Working copy \'%s\' is out of date (rev %s vs rev %s).\n' return ('Working copy \'%s\' is out of date (rev %s vs rev %s).\n'
'Looks as if you need to update it first.' \ 'Looks as if you need to update it first.' \
% (self[0], self[1], self[2])) % (self.args[0], self.args[1], self.args[2]))
class PackageError(OscBaseError): class PackageError(OscBaseError):
"""Base class for all Package related exceptions""" """Base class for all Package related exceptions"""

View File

@ -20,12 +20,8 @@ import re
import sys import sys
import stat import stat
#XXX: python 2.7 contains io.StringIO, which needs unicode instead of str from io import BytesIO
#therefor try to import old stuff before new one here
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
# workaround for python24 # workaround for python24
if not hasattr(os, 'SEEK_SET'): if not hasattr(os, 'SEEK_SET'):
@ -48,6 +44,9 @@ class ArHdr:
self.date = date.strip() self.date = date.strip()
self.uid = uid.strip() self.uid = uid.strip()
self.gid = gid.strip() self.gid = gid.strip()
if not mode.strip():
# provide a dummy mode for the ext_fn hdr
mode = '0'
self.mode = stat.S_IMODE(int(mode, 8)) self.mode = stat.S_IMODE(int(mode, 8))
self.size = int(size) self.size = int(size)
self.fmag = fmag self.fmag = fmag
@ -57,10 +56,10 @@ class ArHdr:
def __str__(self): def __str__(self):
return '%16s %d' % (self.file, self.size) return '%16s %d' % (self.file, self.size)
class ArFile(StringIO): class ArFile(BytesIO):
"""Represents a file which resides in the archive""" """Represents a file which resides in the archive"""
def __init__(self, fn, uid, gid, mode, buf): def __init__(self, fn, uid, gid, mode, buf):
StringIO.__init__(self, buf) BytesIO.__init__(self, buf)
self.name = fn self.name = fn
self.uid = uid self.uid = uid
self.gid = gid self.gid = gid
@ -75,9 +74,8 @@ class ArFile(StringIO):
if not dir: if not dir:
dir = os.getcwd() dir = os.getcwd()
fn = os.path.join(dir, self.name) fn = os.path.join(dir, self.name)
f = open(fn, 'wb') with open(fn, 'wb') as f:
f.write(self.getvalue()) f.write(self.getvalue())
f.close()
os.chmod(fn, self.mode) os.chmod(fn, self.mode)
uid = self.uid uid = self.uid
if uid != os.geteuid() or os.geteuid() != 0: if uid != os.geteuid() or os.geteuid() != 0:
@ -97,11 +95,12 @@ class Ar:
Readonly access. Readonly access.
""" """
hdr_len = 60 hdr_len = 60
hdr_pat = re.compile('^(.{16})(.{12})(.{6})(.{6})(.{8})(.{10})(.{2})', re.DOTALL) hdr_pat = re.compile(b'^(.{16})(.{12})(.{6})(.{6})(.{8})(.{10})(.{2})',
re.DOTALL)
def __init__(self, fn = None, fh = None): def __init__(self, fn = None, fh = None):
if fn == None and fh == None: if fn == None and fh == None:
raise ArError('either \'fn\' or \'fh\' must be != None') raise ValueError('either \'fn\' or \'fh\' must be != None')
if fh != None: if fh != None:
self.__file = fh self.__file = fh
self.__closefile = False self.__closefile = False
@ -123,7 +122,7 @@ class Ar:
def _appendHdr(self, hdr): def _appendHdr(self, hdr):
# GNU uses an internal '//' file to store very long filenames # GNU uses an internal '//' file to store very long filenames
if hdr.file.startswith('//'): if hdr.file.startswith(b'//'):
self.ext_fnhdr = hdr self.ext_fnhdr = hdr
else: else:
self.hdrs.append(hdr) self.hdrs.append(hdr)
@ -137,11 +136,11 @@ class Ar:
Another special file is the '/' which contains the symbol lookup table. Another special file is the '/' which contains the symbol lookup table.
""" """
for h in self.hdrs: for h in self.hdrs:
if h.file == '/': if h.file == b'/':
continue continue
# remove slashes which are appended by ar # remove slashes which are appended by ar
h.file = h.file.rstrip('/') h.file = h.file.rstrip(b'/')
if not h.file.startswith('/'): if not h.file.startswith(b'/'):
continue continue
# handle long filename # handle long filename
off = int(h.file[1:len(h.file)]) off = int(h.file[1:len(h.file)])
@ -150,11 +149,11 @@ class Ar:
# XXX: is it safe to read all the data in one chunk? I assume the '//' data section # XXX: is it safe to read all the data in one chunk? I assume the '//' data section
# won't be too large # won't be too large
data = self.__file.read(self.ext_fnhdr.size) data = self.__file.read(self.ext_fnhdr.size)
end = data.find('/') end = data.find(b'/')
if end != -1: if end != -1:
h.file = data[0:end] h.file = data[0:end]
else: else:
raise ArError('//', 'invalid data section - trailing slash (off: %d)' % start) raise ArError(b'//', 'invalid data section - trailing slash (off: %d)' % start)
def _get_file(self, hdr): def _get_file(self, hdr):
self.__file.seek(hdr.dataoff, os.SEEK_SET) self.__file.seek(hdr.dataoff, os.SEEK_SET)
@ -162,25 +161,14 @@ class Ar:
self.__file.read(hdr.size)) self.__file.read(hdr.size))
def read(self): def read(self):
"""reads in the archive. It tries to use mmap due to performance reasons (in case of large files)""" """reads in the archive."""
if not self.__file: if not self.__file:
import mmap
self.__file = open(self.filename, 'rb') self.__file = open(self.filename, 'rb')
try:
if sys.platform[:3] != 'win':
self.__file = mmap.mmap(self.__file.fileno(), os.path.getsize(self.__file.name), prot=mmap.PROT_READ)
else:
self.__file = mmap.mmap(self.__file.fileno(), os.path.getsize(self.__file.name))
except EnvironmentError as e:
if e.errno == 19 or ( hasattr(e, 'winerror') and e.winerror == 5 ):
print('cannot use mmap to read the file, falling back to the default io', file=sys.stderr)
else:
raise e
else: else:
self.__file.seek(0, os.SEEK_SET) self.__file.seek(0, os.SEEK_SET)
self._init_datastructs() self._init_datastructs()
data = self.__file.read(7) data = self.__file.read(7)
if data != '!<arch>': if data != b'!<arch>':
raise ArError(self.filename, 'no ar archive') raise ArError(self.filename, 'no ar archive')
pos = 8 pos = 8
while (len(data) != 0): while (len(data) != 0):
@ -208,7 +196,19 @@ class Ar:
def __iter__(self): def __iter__(self):
for h in self.hdrs: for h in self.hdrs:
if h.file == '/': if h.file == b'/':
continue continue
yield self._get_file(h) yield self._get_file(h)
raise StopIteration()
if __name__ == '__main__':
if len(sys.argv) != 2:
print('usage: %s <arfile>' % sys.argv[0])
sys.exit(1)
# a potential user might want to pass a bytes instead of a str
# to make sure that the ArError's file attribute is always a
# bytes
ar = Ar(fn=sys.argv[1])
ar.read()
for hdr in ar.hdrs:
print(hdr)

View File

@ -17,7 +17,7 @@ class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
self.fields = {} self.fields = {}
#self.magic = None #self.magic = None
#self.pkgsuffix = 'pkg.tar.gz' #self.pkgsuffix = 'pkg.tar.gz'
self.pkgsuffix = 'arch' self.pkgsuffix = b'arch'
def read(self, all_tags=True, self_provides=True, *extra_tags): def read(self, all_tags=True, self_provides=True, *extra_tags):
# all_tags and *extra_tags are currently ignored # all_tags and *extra_tags are currently ignored
@ -28,22 +28,21 @@ class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
fn = open('/dev/null', 'wb') fn = open('/dev/null', 'wb')
pipe = subprocess.Popen(['tar', '-O', '-xf', self.__path, '.PKGINFO'], stdout=subprocess.PIPE, stderr=fn).stdout pipe = subprocess.Popen(['tar', '-O', '-xf', self.__path, '.PKGINFO'], stdout=subprocess.PIPE, stderr=fn).stdout
for line in pipe.readlines(): for line in pipe.readlines():
line = line.rstrip().split(' = ', 2) line = line.rstrip().split(b' = ', 2)
if len(line) == 2: if len(line) == 2:
if not line[0] in self.fields: field, value = line[0].decode('ascii'), line[1]
self.fields[line[0]] = [] self.fields.setdefault(field, []).append(value)
self.fields[line[0]].append(line[1])
if self_provides: if self_provides:
prv = '%s = %s' % (self.name(), self.fields['pkgver'][0]) prv = b'%s = %s' % (self.name(), self.fields['pkgver'][0])
self.fields.setdefault('provides', []).append(prv) self.fields.setdefault('provides', []).append(prv)
return self return self
def vercmp(self, archq): def vercmp(self, archq):
res = cmp(int(self.epoch()), int(archq.epoch())) res = packagequery.cmp(int(self.epoch()), int(archq.epoch()))
if res != 0: if res != 0:
return res return res
res = ArchQuery.rpmvercmp(self.version(), archq.version()) res = ArchQuery.rpmvercmp(self.version(), archq.version())
if res != None: if res != 0:
return res return res
res = ArchQuery.rpmvercmp(self.release(), archq.release()) res = ArchQuery.rpmvercmp(self.release(), archq.release())
return res return res
@ -54,25 +53,31 @@ class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
def version(self): def version(self):
pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None
if pkgver != None: if pkgver != None:
pkgver = re.sub(r'[0-9]+:', '', pkgver, 1) pkgver = re.sub(br'[0-9]+:', b'', pkgver, 1)
pkgver = re.sub(r'-[^-]*$', '', pkgver) pkgver = re.sub(br'-[^-]*$', b'', pkgver)
return pkgver return pkgver
def release(self): def release(self):
pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None
if pkgver != None: if pkgver != None:
m = re.search(r'-([^-])*$', pkgver) m = re.search(br'-([^-])*$', pkgver)
if m: if m:
return m.group(1) return m.group(1)
return None return None
def epoch(self): def _epoch(self):
pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None pkgver = self.fields.get('pkgver', [b''])[0]
if pkgver != None: if pkgver:
m = re.match(r'([0-9])+:', pkgver) m = re.match(br'([0-9])+:', pkgver)
if m: if m:
return m.group(1) return m.group(1)
return None return b''
def epoch(self):
epoch = self._epoch()
if epoch:
return epoch
return b'0'
def arch(self): def arch(self):
return self.fields['arch'][0] if 'arch' in self.fields else None return self.fields['arch'][0] if 'arch' in self.fields else None
@ -103,7 +108,7 @@ class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
# libsolv treats an optdepend as a "suggests", hence we do the same # libsolv treats an optdepend as a "suggests", hence we do the same
if 'optdepend' not in self.fields: if 'optdepend' not in self.fields:
return [] return []
return [re.sub(':.*', '', entry) for entry in self.fields['optdepend']] return [re.sub(b':.*', b'', entry) for entry in self.fields['optdepend']]
def supplements(self): def supplements(self):
# a .PKGINFO has no notion of "recommends" # a .PKGINFO has no notion of "recommends"
@ -114,8 +119,17 @@ class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
return [] return []
def canonname(self): def canonname(self):
pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None name = self.name()
return self.name() + '-' + pkgver + '-' + self.arch() + '.' + self.pkgsuffix if name is None:
raise ArchError(self.path(), 'package has no name')
version = self.version()
if version is None:
raise ArchError(self.path(), 'package has no version')
arch = self.arch()
if arch is None:
raise ArchError(self.path(), 'package has no arch')
return ArchQuery.filename(name, self._epoch(), version, self.release(),
arch)
def gettag(self, tag): def gettag(self, tag):
# implement me, if needed # implement me, if needed
@ -137,20 +151,24 @@ class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
""" """
if ver1 == ver2: if ver1 == ver2:
return 0 return 0
elif ver1 is None:
return -1
elif ver2 is None:
return 1
res = 0 res = 0
while res == 0: while res == 0:
# remove all leading non alphanumeric chars # remove all leading non alphanumeric chars
ver1 = re.sub('^[^a-zA-Z0-9]*', '', ver1) ver1 = re.sub(b'^[^a-zA-Z0-9]*', b'', ver1)
ver2 = re.sub('^[^a-zA-Z0-9]*', '', ver2) ver2 = re.sub(b'^[^a-zA-Z0-9]*', b'', ver2)
if not (len(ver1) and len(ver2)): if not (len(ver1) and len(ver2)):
break break
# check if we have a digits segment # check if we have a digits segment
mo1 = re.match('(\d+)', ver1) mo1 = re.match(b'(\d+)', ver1)
mo2 = re.match('(\d+)', ver2) mo2 = re.match(b'(\d+)', ver2)
numeric = True numeric = True
if mo1 is None: if mo1 is None:
mo1 = re.match('([a-zA-Z]+)', ver1) mo1 = re.match(b'([a-zA-Z]+)', ver1)
mo2 = re.match('([a-zA-Z]+)', ver2) mo2 = re.match(b'([a-zA-Z]+)', ver2)
numeric = False numeric = False
# check for different types: alpha and numeric # check for different types: alpha and numeric
if mo2 is None: if mo2 is None:
@ -163,43 +181,42 @@ class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
ver2 = ver2[mo2.end(1):] ver2 = ver2[mo2.end(1):]
if numeric: if numeric:
# remove leading zeros # remove leading zeros
seg1 = re.sub('^0+', '', seg1) seg1 = re.sub(b'^0+', b'', seg1)
seg2 = re.sub('^0+', '', seg2) seg2 = re.sub(b'^0+', b'', seg2)
# longer digit segment wins - if both have the same length # longer digit segment wins - if both have the same length
# a simple ascii compare decides # a simple ascii compare decides
res = len(seg1) - len(seg2) or cmp(seg1, seg2) res = len(seg1) - len(seg2) or packagequery.cmp(seg1, seg2)
else: else:
res = cmp(seg1, seg2) res = packagequery.cmp(seg1, seg2)
if res > 0: if res > 0:
return 1 return 1
elif res < 0: elif res < 0:
return -1 return -1
return cmp(ver1, ver2) return packagequery.cmp(ver1, ver2)
@staticmethod @staticmethod
def filename(name, epoch, version, release, arch): def filename(name, epoch, version, release, arch):
if epoch: if epoch:
if release: if release:
return '%s-%s:%s-%s-%s.arch' % (name, epoch, version, release, arch) return b'%s-%s:%s-%s-%s.arch' % (name, epoch, version, release, arch)
else: else:
return '%s-%s:%s-%s.arch' % (name, epoch, version, arch) return b'%s-%s:%s-%s.arch' % (name, epoch, version, arch)
if release: if release:
return '%s-%s-%s-%s.arch' % (name, version, release, arch) return b'%s-%s-%s-%s.arch' % (name, version, release, arch)
else: else:
return '%s-%s-%s.arch' % (name, version, arch) return b'%s-%s-%s.arch' % (name, version, arch)
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
archq = ArchQuery.query(sys.argv[1])
print(archq.name(), archq.version(), archq.release(), archq.arch())
try: try:
archq = ArchQuery.query(sys.argv[1]) print(archq.canonname())
except ArchError as e: except ArchError as e:
print(e.msg) print(e.msg)
sys.exit(2)
print(archq.name(), archq.version(), archq.release(), archq.arch())
print(archq.canonname())
print(archq.description()) print(archq.description())
print('##########') print('##########')
print('\n'.join(archq.provides())) print(b'\n'.join(archq.provides()))
print('##########') print('##########')
print('\n'.join(archq.requires())) print(b'\n'.join(archq.requires()))

View File

@ -42,7 +42,7 @@ class CpioHdr:
""" """
def __init__(self, mgc, ino, mode, uid, gid, nlink, mtime, filesize, def __init__(self, mgc, ino, mode, uid, gid, nlink, mtime, filesize,
dev_maj, dev_min, rdev_maj, rdev_min, namesize, checksum, dev_maj, dev_min, rdev_maj, rdev_min, namesize, checksum,
off = -1, filename = ''): off=-1, filename=b''):
""" """
All passed parameters are hexadecimal strings (not NUL terminated) except All passed parameters are hexadecimal strings (not NUL terminated) except
off and filename. They will be converted into normal ints. off and filename. They will be converted into normal ints.
@ -82,7 +82,7 @@ class CpioRead:
# supported formats - use name -> mgc mapping to increase readabilty # supported formats - use name -> mgc mapping to increase readabilty
sfmt = { sfmt = {
'newascii': '070701', 'newascii': b'070701',
} }
# header format # header format
@ -124,11 +124,10 @@ class CpioRead:
if not stat.S_ISREG(stat.S_IFMT(hdr.mode)): if not stat.S_ISREG(stat.S_IFMT(hdr.mode)):
msg = '\'%s\' is no regular file - only regular files are supported atm' % hdr.filename msg = '\'%s\' is no regular file - only regular files are supported atm' % hdr.filename
raise NotImplementedError(msg) raise NotImplementedError(msg)
fn = os.path.join(dest, fn)
f = open(fn, 'wb')
self.__file.seek(hdr.dataoff, os.SEEK_SET) self.__file.seek(hdr.dataoff, os.SEEK_SET)
f.write(self.__file.read(hdr.filesize)) fn = os.path.join(dest, fn)
f.close() with open(fn, 'wb') as f:
f.write(self.__file.read(hdr.filesize))
os.chmod(fn, hdr.mode) os.chmod(fn, hdr.mode)
uid = hdr.uid uid = hdr.uid
if uid != os.geteuid() or os.geteuid() != 1: if uid != os.geteuid() or os.geteuid() != 1:
@ -147,16 +146,6 @@ class CpioRead:
def read(self): def read(self):
if not self.__file: if not self.__file:
self.__file = open(self.filename, 'rb') self.__file = open(self.filename, 'rb')
try:
if sys.platform[:3] != 'win':
self.__file = mmap.mmap(self.__file.fileno(), os.path.getsize(self.__file.name), prot = mmap.PROT_READ)
else:
self.__file = mmap.mmap(self.__file.fileno(), os.path.getsize(self.__file.name))
except EnvironmentError as e:
if e.errno == 19 or ( hasattr(e, 'winerror') and e.winerror == 5 ):
print('cannot use mmap to read the file, failing back to default', file=sys.stderr)
else:
raise e
else: else:
self.__file.seek(0, os.SEEK_SET) self.__file.seek(0, os.SEEK_SET)
self._init_datastructs() self._init_datastructs()
@ -174,7 +163,7 @@ class CpioRead:
data = struct.unpack(self.hdr_fmt, data) data = struct.unpack(self.hdr_fmt, data)
hdr = CpioHdr(*data) hdr = CpioHdr(*data)
hdr.filename = self.__file.read(hdr.namesize - 1) hdr.filename = self.__file.read(hdr.namesize - 1)
if hdr.filename == 'TRAILER!!!': if hdr.filename == b'TRAILER!!!':
break break
pos += hdr.namesize pos += hdr.namesize
if self._is_format('newascii'): if self._is_format('newascii'):
@ -210,47 +199,59 @@ class CpioWrite:
"""cpio archive small files in memory, using new style portable header format""" """cpio archive small files in memory, using new style portable header format"""
def __init__(self): def __init__(self):
self.cpio = '' self.cpio = bytearray()
def add(self, name=None, content=None, perms=0x1a4, type=0x8000): def add(self, name=None, content=None, perms=0x1a4, type=0x8000):
namesize = len(name) + 1 namesize = len(name) + 1
if namesize % 2: if namesize % 2:
name += '\0' name += b'\0'
filesize = len(content) filesize = len(content)
mode = perms | type mode = perms | type
c = [] c = bytearray()
c.append('070701') # magic c.extend(b'070701') # magic
c.append('%08X' % 0) # inode c.extend(b'%08X' % 0) # inode
c.append('%08X' % mode) # mode c.extend(b'%08X' % mode) # mode
c.append('%08X' % 0) # uid c.extend(b'%08X' % 0) # uid
c.append('%08X' % 0) # gid c.extend(b'%08X' % 0) # gid
c.append('%08X' % 0) # nlink c.extend(b'%08X' % 0) # nlink
c.append('%08X' % 0) # mtime c.extend(b'%08X' % 0) # mtime
c.append('%08X' % filesize) c.extend(b'%08X' % filesize)
c.append('%08X' % 0) # major c.extend(b'%08X' % 0) # major
c.append('%08X' % 0) # minor c.extend(b'%08X' % 0) # minor
c.append('%08X' % 0) # rmajor c.extend(b'%08X' % 0) # rmajor
c.append('%08X' % 0) # rminor c.extend(b'%08X' % 0) # rminor
c.append('%08X' % namesize) c.extend(b'%08X' % namesize)
c.append('%08X' % 0) # checksum c.extend(b'%08X' % 0) # checksum
c.append(name + '\0') c.extend(name + b'\0')
c.append('\0' * (len(''.join(c)) % 4)) c.extend(b'\0' * (len(c) % 4))
c.append(content) c.extend(content)
c = ''.join(c)
if len(c) % 4: if len(c) % 4:
c += '\0' * (4 - len(c) % 4) c.extend(b'\0' * (4 - len(c) % 4))
self.cpio += c self.cpio.extend(c)
def add_padding(self): def add_padding(self):
if len(self.cpio) % 512: if len(self.cpio) % 512:
self.cpio += '\0' * (512 - len(self.cpio) % 512) self.cpio.extend(b'\0' * (512 - len(self.cpio) % 512))
def get(self): def get(self):
self.add('TRAILER!!!', '') self.add(b'TRAILER!!!', b'')
self.add_padding() self.add_padding()
return ''.join(self.cpio) return bytes(self.cpio)
if __name__ == '__main__':
if len(sys.argv) != 2:
print('usage: %s /path/to/file.cpio' % sys.argv[0])
sys.exit(1)
# a potential user might want to pass a bytes instead of a str
# to make sure that the CpioError's file attribute is always a
# bytes
cpio = CpioRead(sys.argv[1])
cpio.read()
for hdr in cpio:
print(hdr)

View File

@ -5,8 +5,10 @@ from . import ar
import os.path import os.path
import re import re
import tarfile import tarfile
import StringIO from io import BytesIO
from . import packagequery from . import packagequery
import itertools
HAVE_LZMA = True HAVE_LZMA = True
try: try:
@ -14,13 +16,21 @@ try:
except ImportError: except ImportError:
HAVE_LZMA = False HAVE_LZMA = False
if (not hasattr(itertools, 'zip_longest')
and hasattr(itertools, 'izip_longest')):
# python2 case
itertools.zip_longest = itertools.izip_longest
class DebError(packagequery.PackageError): class DebError(packagequery.PackageError):
pass pass
class DebQuery(packagequery.PackageQuery, packagequery.PackageQueryResult): class DebQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
default_tags = ('package', 'version', 'release', 'epoch', 'architecture', 'description', default_tags = (b'package', b'version', b'release', b'epoch',
'provides', 'depends', 'pre_depends', 'conflicts', 'breaks') b'architecture', b'description', b'provides', b'depends',
b'pre_depends', b'conflicts', b'breaks')
def __init__(self, fh): def __init__(self, fh):
self.__file = fh self.__file = fh
@ -31,24 +41,24 @@ class DebQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
def read(self, all_tags=False, self_provides=True, *extra_tags): def read(self, all_tags=False, self_provides=True, *extra_tags):
arfile = ar.Ar(fh = self.__file) arfile = ar.Ar(fh = self.__file)
arfile.read() arfile.read()
debbin = arfile.get_file('debian-binary') debbin = arfile.get_file(b'debian-binary')
if debbin is None: if debbin is None:
raise DebError(self.__path, 'no debian binary') raise DebError(self.__path, 'no debian binary')
if debbin.read() != '2.0\n': if debbin.read() != b'2.0\n':
raise DebError(self.__path, 'invalid debian binary format') raise DebError(self.__path, 'invalid debian binary format')
control = arfile.get_file('control.tar.gz') control = arfile.get_file(b'control.tar.gz')
if control is not None: if control is not None:
# XXX: python2.4 relies on a name # XXX: python2.4 relies on a name
tar = tarfile.open(name='control.tar.gz', fileobj=control) tar = tarfile.open(name='control.tar.gz', fileobj=control)
else: else:
control = arfile.get_file('control.tar.xz') control = arfile.get_file(b'control.tar.xz')
if control is None: if control is None:
raise DebError(self.__path, 'missing control.tar') raise DebError(self.__path, 'missing control.tar')
if not HAVE_LZMA: if not HAVE_LZMA:
raise DebError(self.__path, 'can\'t open control.tar.xz without python-lzma') raise DebError(self.__path, 'can\'t open control.tar.xz without python-lzma')
decompressed = lzma.decompress(control.read()) decompressed = lzma.decompress(control.read())
tar = tarfile.open(name="control.tar.xz", tar = tarfile.open(name="control.tar.xz",
fileobj=StringIO.StringIO(decompressed)) fileobj=BytesIO(decompressed))
try: try:
name = './control' name = './control'
# workaround for python2.4's tarfile module # workaround for python2.4's tarfile module
@ -64,94 +74,98 @@ class DebQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
def __parse_control(self, control, all_tags=False, self_provides=True, *extra_tags): def __parse_control(self, control, all_tags=False, self_provides=True, *extra_tags):
data = control.readline().strip() data = control.readline().strip()
while data: while data:
field, val = re.split(':\s*', data.strip(), 1) field, val = re.split(b':\s*', data.strip(), 1)
data = control.readline() data = control.readline()
while data and re.match('\s+', data): while data and re.match(b'\s+', data):
val += '\n' + data.strip() val += b'\n' + data.strip()
data = control.readline().rstrip() data = control.readline().rstrip()
field = field.replace('-', '_').lower() field = field.replace(b'-', b'_').lower()
if field in self.default_tags + extra_tags or all_tags: if field in self.default_tags + extra_tags or all_tags:
# a hyphen is not allowed in dict keys # a hyphen is not allowed in dict keys
self.fields[field] = val self.fields[field] = val
versrel = self.fields['version'].rsplit('-', 1) versrel = self.fields[b'version'].rsplit(b'-', 1)
if len(versrel) == 2: if len(versrel) == 2:
self.fields['version'] = versrel[0] self.fields[b'version'] = versrel[0]
self.fields['release'] = versrel[1] self.fields[b'release'] = versrel[1]
else: else:
self.fields['release'] = None self.fields[b'release'] = None
verep = self.fields['version'].split(':', 1) verep = self.fields[b'version'].split(b':', 1)
if len(verep) == 2: if len(verep) == 2:
self.fields['epoch'] = verep[0] self.fields[b'epoch'] = verep[0]
self.fields['version'] = verep[1] self.fields[b'version'] = verep[1]
else: else:
self.fields['epoch'] = '0' self.fields[b'epoch'] = b'0'
self.fields['provides'] = [ i.strip() for i in re.split(',\s*', self.fields.get('provides', '')) if i ] self.fields[b'provides'] = self._split_field_value(b'provides')
self.fields['depends'] = [ i.strip() for i in re.split(',\s*', self.fields.get('depends', '')) if i ] self.fields[b'depends'] = self._split_field_value(b'depends')
self.fields['pre_depends'] = [ i.strip() for i in re.split(',\s*', self.fields.get('pre_depends', '')) if i ] self.fields[b'pre_depends'] = self._split_field_value(b'pre_depends')
self.fields['conflicts'] = [ i.strip() for i in re.split(',\s*', self.fields.get('conflicts', '')) if i ] self.fields[b'conflicts'] = self._split_field_value(b'conflicts')
self.fields['breaks'] = [ i.strip() for i in re.split(',\s*', self.fields.get('breaks', '')) if i ] self.fields[b'breaks'] = self._split_field_value(b'breaks')
self.fields['recommends'] = [ i.strip() for i in re.split(',\s*', self.fields.get('recommends', '')) if i ] self.fields[b'recommends'] = self._split_field_value(b'recommends')
self.fields['suggests'] = [ i.strip() for i in re.split(',\s*', self.fields.get('suggests', '')) if i ] self.fields[b'suggests'] = self._split_field_value(b'suggests')
self.fields['enhances'] = [ i.strip() for i in re.split(',\s*', self.fields.get('enhances', '')) if i ] self.fields[b'enhances'] = self._split_field_value(b'enhances')
if self_provides: if self_provides:
# add self provides entry # add self provides entry
self.fields['provides'].append('%s (= %s)' % (self.name(), '-'.join(versrel))) self.fields[b'provides'].append(b'%s (= %s)' % (self.name(), b'-'.join(versrel)))
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]
def vercmp(self, debq): def vercmp(self, debq):
res = cmp(int(self.epoch()), int(debq.epoch())) res = packagequery.cmp(int(self.epoch()), int(debq.epoch()))
if res != 0: if res != 0:
return res return res
res = DebQuery.debvercmp(self.version(), debq.version()) res = DebQuery.debvercmp(self.version(), debq.version())
if res != None: if res != 0:
return res return res
res = DebQuery.debvercmp(self.release(), debq.release()) res = DebQuery.debvercmp(self.release(), debq.release())
return res return res
def name(self): def name(self):
return self.fields['package'] return self.fields[b'package']
def version(self): def version(self):
return self.fields['version'] return self.fields[b'version']
def release(self): def release(self):
return self.fields['release'] return self.fields[b'release']
def epoch(self): def epoch(self):
return self.fields['epoch'] return self.fields[b'epoch']
def arch(self): def arch(self):
return self.fields['architecture'] return self.fields[b'architecture']
def description(self): def description(self):
return self.fields['description'] return self.fields[b'description']
def path(self): def path(self):
return self.__path return self.__path
def provides(self): def provides(self):
return self.fields['provides'] return self.fields[b'provides']
def requires(self): def requires(self):
return self.fields['depends'] + self.fields['pre_depends'] return self.fields[b'depends'] + self.fields[b'pre_depends']
def conflicts(self): def conflicts(self):
return self.fields['conflicts'] + self.fields['breaks'] return self.fields[b'conflicts'] + self.fields[b'breaks']
def obsoletes(self): def obsoletes(self):
return [] return []
def recommends(self): def recommends(self):
return self.fields['recommends'] return self.fields[b'recommends']
def suggests(self): def suggests(self):
return self.fields['suggests'] return self.fields[b'suggests']
def supplements(self): def supplements(self):
# a control file has no notion of "supplements" # a control file has no notion of "supplements"
return [] return []
def enhances(self): def enhances(self):
return self.fields['enhances'] return self.fields[b'enhances']
def gettag(self, num): def gettag(self, num):
return self.fields.get(num, None) return self.fields.get(num, None)
@ -174,20 +188,31 @@ class DebQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
""" """
# 32 is arbitrary - it is needed for the "longer digit string wins" handling # 32 is arbitrary - it is needed for the "longer digit string wins" handling
# (found this nice approach in Build/Deb.pm (build package)) # (found this nice approach in Build/Deb.pm (build package))
ver1 = re.sub('(\d+)', lambda m: (32 * '0' + m.group(1))[-32:], ver1) ver1 = re.sub(b'(\d+)', lambda m: (32 * b'0' + m.group(1))[-32:], ver1)
ver2 = re.sub('(\d+)', lambda m: (32 * '0' + m.group(1))[-32:], ver2) ver2 = re.sub(b'(\d+)', lambda m: (32 * b'0' + m.group(1))[-32:], ver2)
vers = map(lambda x, y: (x or '', y or ''), ver1, ver2) vers = itertools.zip_longest(ver1, ver2, fillvalue=b'')
for v1, v2 in vers: for v1, v2 in vers:
if v1 == v2: if v1 == v2:
continue continue
if not v1:
# this makes the corresponding condition in the following
# else part superfluous - keep the superfluous condition for
# now (just to ease a (hopefully) upcoming refactoring (this
# method really deserves a cleanup...))
return -1
if not v2:
# see above
return 1
v1 = bytes(bytearray([v1]))
v2 = bytes(bytearray([v2]))
if (v1.isalpha() and v2.isalpha()) or (v1.isdigit() and v2.isdigit()): if (v1.isalpha() and v2.isalpha()) or (v1.isdigit() and v2.isdigit()):
res = cmp(v1, v2) res = packagequery.cmp(v1, v2)
if res != 0: if res != 0:
return res return res
else: else:
if v1 == '~' or not v1: if v1 == b'~' or not v1:
return -1 return -1
elif v2 == '~' or not v2: elif v2 == b'~' or not v2:
return 1 return 1
ord1 = ord(v1) ord1 = ord(v1)
if not (v1.isalpha() or v1.isdigit()): if not (v1.isalpha() or v1.isdigit()):
@ -204,9 +229,9 @@ class DebQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
@staticmethod @staticmethod
def filename(name, epoch, version, release, arch): def filename(name, epoch, version, release, arch):
if release: if release:
return '%s_%s-%s_%s.deb' % (name, version, release, arch) return b'%s_%s-%s_%s.deb' % (name, version, release, arch)
else: else:
return '%s_%s_%s.deb' % (name, version, arch) return b'%s_%s_%s.deb' % (name, version, arch)
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
@ -218,6 +243,6 @@ if __name__ == '__main__':
print(debq.name(), debq.version(), debq.release(), debq.arch()) print(debq.name(), debq.version(), debq.release(), debq.arch())
print(debq.description()) print(debq.description())
print('##########') print('##########')
print('\n'.join(debq.provides())) print(b'\n'.join(debq.provides()))
print('##########') print('##########')
print('\n'.join(debq.requires())) print(b'\n'.join(debq.requires()))

View File

@ -60,18 +60,18 @@ class PackageQuery:
f.seek(0) f.seek(0)
extra_tags = () extra_tags = ()
pkgquery = None pkgquery = None
if magic[:4] == '\xed\xab\xee\xdb': if magic[:4] == b'\xed\xab\xee\xdb':
from . import rpmquery from . import rpmquery
pkgquery = rpmquery.RpmQuery(f) pkgquery = rpmquery.RpmQuery(f)
extra_tags = extra_rpmtags extra_tags = extra_rpmtags
elif magic == '!<arch>': elif magic == b'!<arch>':
from . import debquery from . import debquery
pkgquery = debquery.DebQuery(f) pkgquery = debquery.DebQuery(f)
extra_tags = extra_debtags extra_tags = extra_debtags
elif magic[:5] == '<?xml': elif magic[:5] == b'<?xml':
f.close() f.close()
return None return None
elif magic[:5] == '\375\067zXZ' or magic[:2] == '\037\213': elif magic[:5] == b'\375\067zXZ' or magic[:2] == b'\037\213':
from . import archquery from . import archquery
pkgquery = archquery.ArchQuery(f) pkgquery = archquery.ArchQuery(f)
else: else:
@ -159,6 +159,11 @@ class PackageQueryResult:
evr = epoch + ":" + evr evr = epoch + ":" + evr
return evr return evr
def cmp(a, b):
return (a > b) - (a < b)
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
try: try:

View File

@ -370,16 +370,15 @@ class RpmQuery(packagequery.PackageQuery, packagequery.PackageQueryResult):
def filename(name, epoch, version, release, arch): def filename(name, epoch, version, release, arch):
return '%s-%s-%s.%s.rpm' % (name, version, release, arch) return '%s-%s-%s.%s.rpm' % (name, version, release, arch)
def unpack_string(data): def unpack_string(data, encoding=None):
"""unpack a '\\0' terminated string from data""" """unpack a '\\0' terminated string from data"""
val = '' idx = data.find(b'\0')
for c in data: if idx == -1:
c, = struct.unpack('!c', c) raise ValueError('illegal string: not \\0 terminated')
if c == '\0': data = data[:idx]
break if encoding is not None:
else: data = data.decode(encoding)
val += c return data
return val
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys