With the python shebang remaining as Python 2, switch the scripts to using Python 3. This also allows us to clean up some imports.
375 lines
14 KiB
Python
Executable File
375 lines
14 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
from pprint import pprint
|
|
import os
|
|
import sys
|
|
import re
|
|
import logging
|
|
import cmdln
|
|
|
|
from fnmatch import fnmatch
|
|
from ConfigParser import SafeConfigParser
|
|
import solv
|
|
import rpm
|
|
|
|
logger = None
|
|
|
|
REASONS = dict([(getattr(solv.Solver, i), i[14:]) for i in dir(solv.Solver) if i.startswith('SOLVER_REASON_')])
|
|
|
|
|
|
class DepTool(cmdln.Cmdln):
|
|
def __init__(self, *args, **kwargs):
|
|
cmdln.Cmdln.__init__(self, args, kwargs)
|
|
|
|
def get_optparser(self):
|
|
parser = cmdln.CmdlnOptionParser(self)
|
|
parser.add_option("--dry", action="store_true", help="dry run")
|
|
parser.add_option("--debug", action="store_true", help="debug output")
|
|
parser.add_option("--verbose", action="store_true", help="verbose")
|
|
parser.add_option("--system", action="store_true", help="with system repo")
|
|
parser.add_option("--arch", dest="arch", help="architecture", default='x86_64')
|
|
return parser
|
|
|
|
def postoptparse(self):
|
|
level = None
|
|
if self.options.debug:
|
|
level = logging.DEBUG
|
|
elif self.options.verbose:
|
|
level = logging.INFO
|
|
|
|
logging.basicConfig(level=level)
|
|
|
|
global logger
|
|
logger = logging.getLogger()
|
|
|
|
def prepare_pool(self, repos):
|
|
|
|
self.pool = solv.Pool()
|
|
self.pool.setarch(self.options.arch)
|
|
|
|
self._read_repos(repos)
|
|
|
|
if self.options.system:
|
|
self._add_system_repo()
|
|
|
|
self.pool.addfileprovides()
|
|
self.pool.createwhatprovides()
|
|
|
|
def _read_repos(self, repos):
|
|
repodir = '/etc/zypp/repos.d'
|
|
solvfile = '/var/cache/zypp/solv/%s/solv'
|
|
onlyenabled = False
|
|
|
|
parser = SafeConfigParser()
|
|
|
|
if not repos:
|
|
repos = [f for f in os.listdir(repodir) if fnmatch(f, '*.repo')]
|
|
onlyenabled = True
|
|
|
|
for r in repos:
|
|
if '/' in r or r.endswith('.solv'):
|
|
name = os.path.basename(os.path.splitext(r)[0])
|
|
repo = self.pool.add_repo(name)
|
|
repo.add_solv(r)
|
|
logger.debug("add repo %s" % name)
|
|
else:
|
|
try:
|
|
if r.endswith('.repo'):
|
|
name = os.path.splitext(r)[0]
|
|
else:
|
|
name = r
|
|
r += '.repo'
|
|
parser.read('/'.join((repodir, r)))
|
|
if onlyenabled == False or parser.get(name, 'enabled') == '1':
|
|
repo = self.pool.add_repo(name)
|
|
repo.add_solv(solvfile % name)
|
|
if parser.has_option(name, 'priority'):
|
|
repo.priority = parser.getint(name, 'priority')
|
|
logger.debug("add repo %s" % name)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
|
|
def _add_system_repo(self):
|
|
solvfile = '/var/cache/zypp/solv/@System/solv'
|
|
repo = self.pool.add_repo('system')
|
|
repo.add_solv(solvfile)
|
|
|
|
@cmdln.option("-s", "--single", action="store_true",
|
|
help="single step all requires/recommends")
|
|
@cmdln.option("--size", action="store_true",
|
|
help="print installed size")
|
|
@cmdln.option("-l", "--lock", dest="lock", action="append",
|
|
help="packages to lock")
|
|
@cmdln.option("-r", "--repo", dest="repo", action="append",
|
|
help="repo to use")
|
|
@cmdln.option("--explain", dest="explain", action="append",
|
|
help="rule to explain")
|
|
@cmdln.option("--solver-debug", action="store_true",
|
|
help="debug solver")
|
|
@cmdln.option("--ignore-recommended", action="store_true",
|
|
help="ignore recommended")
|
|
def do_install(self, subcmd, opts, *args):
|
|
"""${cmd_name}: generate pot file for patterns
|
|
|
|
${cmd_usage}
|
|
${cmd_option_list}
|
|
"""
|
|
|
|
locked = []
|
|
if opts.lock:
|
|
for l in opts.lock:
|
|
for i in l.split(','):
|
|
locked.append(i)
|
|
|
|
good = True
|
|
|
|
self.prepare_pool(opts.repo)
|
|
if opts.solver_debug:
|
|
self.pool.set_debuglevel(3)
|
|
|
|
def solveit(packages):
|
|
jobs = []
|
|
for l in locked:
|
|
sel = self.pool.select(str(l), solv.Selection.SELECTION_NAME)
|
|
if sel.isempty():
|
|
# if we can't find it, it probably is not as important
|
|
logger.debug('locked package {} not found'.format(l))
|
|
else:
|
|
jobs += sel.jobs(solv.Job.SOLVER_LOCK)
|
|
|
|
for n in packages:
|
|
sel = self.pool.select(str(n), solv.Selection.SELECTION_NAME)
|
|
if sel.isempty():
|
|
logger.error('package {} not found'.format(n))
|
|
jobs += sel.jobs(solv.Job.SOLVER_INSTALL)
|
|
|
|
solver = self.pool.Solver()
|
|
|
|
if opts.ignore_recommended:
|
|
solver.set_flag(solver.SOLVER_FLAG_IGNORE_RECOMMENDED, 1)
|
|
|
|
problems = solver.solve(jobs)
|
|
if problems:
|
|
for problem in problems:
|
|
logger.error('%s', problem)
|
|
return False
|
|
|
|
trans = solver.transaction()
|
|
if trans.isempty():
|
|
logger.error('nothing to do')
|
|
return False
|
|
|
|
for s in trans.newsolvables():
|
|
print(','.join(packages), s.name)
|
|
if opts.explain and s.name in opts.explain:
|
|
reason, rule = solver.describe_decision(s)
|
|
ruleinfo = None
|
|
if rule:
|
|
ruleinfo = rule.info().problemstr()
|
|
if reason == solv.Solver.SOLVER_REASON_WEAKDEP:
|
|
for v in solver.describe_weakdep_decision(s):
|
|
reason2, s2, dep = v
|
|
print("-> %s %s %s" % (s2.name, REASONS[reason2], dep))
|
|
else:
|
|
print("-> %s %s %s" % (s.name, REASONS[reason], ruleinfo))
|
|
|
|
if opts.size:
|
|
size = trans.calc_installsizechange()
|
|
print("SIZE %s" % (size))
|
|
|
|
return True
|
|
|
|
if opts.single:
|
|
for n in args:
|
|
sel = self.pool.select(str(n), solv.Selection.SELECTION_NAME)
|
|
for s in sel.solvables():
|
|
deps = s.lookup_deparray(solv.SOLVABLE_RECOMMENDS)
|
|
deps += s.lookup_deparray(solv.SOLVABLE_REQUIRES)
|
|
for dep in deps:
|
|
# only add recommends that exist as packages
|
|
rec = self.pool.select(dep.str(), solv.Selection.SELECTION_NAME)
|
|
if not rec.isempty():
|
|
if not solveit([dep.str()]):
|
|
good = False
|
|
else:
|
|
if not solveit(args):
|
|
good = False
|
|
|
|
if not good:
|
|
logger.error("solver errors encountered")
|
|
return 1
|
|
|
|
@cmdln.option("-r", "--repo", dest="repo", action="append",
|
|
help="repo to use")
|
|
def do_deps(self, subcmd, opts, *packages):
|
|
"""${cmd_name}: show package deps
|
|
|
|
${cmd_usage}
|
|
${cmd_option_list}
|
|
"""
|
|
|
|
self.prepare_pool(opts.repo)
|
|
|
|
for n in packages:
|
|
sel = self.pool.select(n, solv.Selection.SELECTION_NAME)
|
|
if sel.isempty():
|
|
logger.error("%s not found", n)
|
|
for s in sel.solvables():
|
|
print('- {}-{}@{}:'.format(s.name, s.evr, s.arch))
|
|
for kind in ('RECOMMENDS', 'REQUIRES', 'SUPPLEMENTS', 'ENHANCES', 'PROVIDES', 'SUGGESTS'):
|
|
deps = s.lookup_deparray(getattr(solv, 'SOLVABLE_' + kind), 0)
|
|
if deps:
|
|
print(' {}:'.format(kind))
|
|
for dep in deps:
|
|
print(' - {}'.format(dep))
|
|
|
|
@cmdln.option("-r", "--repo", dest="repo", action="append",
|
|
help="repo to use")
|
|
def do_whatprovides(self, subcmd, opts, *relation):
|
|
"""${cmd_name}: list packages providing given relations
|
|
|
|
${cmd_usage}
|
|
${cmd_option_list}
|
|
"""
|
|
|
|
self.prepare_pool(opts.repo)
|
|
|
|
for r in relation:
|
|
i = self.pool.str2id(r)
|
|
for s in self.pool.whatprovides(i):
|
|
print('- {}-{}@{}:'.format(s.name, s.evr, s.arch))
|
|
|
|
@cmdln.option("-r", "--repo", dest="repo", action="append",
|
|
help="repo to use")
|
|
def do_patterns(self, subcmd, opts, *relation):
|
|
"""${cmd_name}: list patterns
|
|
|
|
${cmd_usage}
|
|
${cmd_option_list}
|
|
"""
|
|
|
|
self.prepare_pool(opts.repo)
|
|
|
|
patternid = self.pool.str2id('pattern()')
|
|
for s in self.pool.whatprovides(patternid):
|
|
deps = s.lookup_deparray(solv.SOLVABLE_PROVIDES)
|
|
order = 0
|
|
for dep in deps:
|
|
name = str(dep)
|
|
if name.startswith('pattern-order()'):
|
|
# XXX: no function in bindings to do that properly
|
|
order = name[name.find('= ') + 2:]
|
|
print("{} {}".format(order, s.name))
|
|
|
|
@cmdln.option("--providers", action="store_true",
|
|
help="also show other providers")
|
|
@cmdln.option("--relation", action="store_true",
|
|
help="arguments are relations rather than package names")
|
|
@cmdln.option("-r", "--repo", dest="repo", action="append",
|
|
help="repo to use")
|
|
def do_rdeps(self, subcmd, opts, *args):
|
|
"""${cmd_name}: list packages that require, recommend etc the given packages
|
|
|
|
${cmd_usage}
|
|
${cmd_option_list}
|
|
"""
|
|
|
|
self.prepare_pool(opts.repo)
|
|
|
|
kinds = ['RECOMMENDS', 'REQUIRES', 'SUPPLEMENTS', 'ENHANCES', 'SUGGESTS']
|
|
if opts.providers:
|
|
kinds.append('PROVIDES')
|
|
|
|
for kind in kinds:
|
|
kindid = getattr(solv, 'SOLVABLE_' + kind, 0)
|
|
kindprinted = False
|
|
if opts.relation:
|
|
# FIXME: doesnt work
|
|
for r in args:
|
|
sel = self.pool.matchdeps(r, solv.Selection.SELECTION_REL | solv.Selection.SELECTION_FLAT, kindid)
|
|
if sel.isempty():
|
|
logger.info('nothing %s %s', kind.lower(), r)
|
|
continue
|
|
for s in sel.solvables():
|
|
print(' {}: {}-{}@{}'.format(r, s.name, s.evr, s.arch))
|
|
else:
|
|
for n in args:
|
|
sel = self.pool.select(n, solv.Selection.SELECTION_NAME)
|
|
if sel.isempty():
|
|
logger.error("%s not found", n)
|
|
continue
|
|
for s in sel.solvables():
|
|
prov = s.lookup_deparray(solv.SOLVABLE_PROVIDES, 0)
|
|
if not prov:
|
|
logger.error("%s doesn't provide anything")
|
|
continue
|
|
for p in prov:
|
|
sel = self.pool.matchdepid(p, solv.Selection.SELECTION_REL | solv.Selection.SELECTION_FLAT, kindid)
|
|
if sel.isempty():
|
|
logger.debug('nothing %s %s', kind.lower(), p)
|
|
continue
|
|
for r in sel.solvables():
|
|
if kindid == solv.SOLVABLE_PROVIDES and r == s:
|
|
continue
|
|
if not kindprinted:
|
|
print(kind)
|
|
kindprinted = True
|
|
print(' {}: {}-{}@{}'.format(p, r.name, r.evr, r.arch))
|
|
|
|
@cmdln.option("-r", "--repo", dest="repo", action="append",
|
|
help="repo to use")
|
|
def do_what(self, subcmd, opts, *relation):
|
|
"""${cmd_name}: list packages that have dependencies on given relation
|
|
|
|
${cmd_usage}
|
|
${cmd_option_list}
|
|
"""
|
|
|
|
self.prepare_pool(opts.repo)
|
|
|
|
kinds = ['PROVIDES', 'RECOMMENDS', 'REQUIRES', 'SUPPLEMENTS', 'ENHANCES', 'SUGGESTS']
|
|
|
|
for r in relation:
|
|
p = self.pool.str2id(r)
|
|
for kind in kinds:
|
|
kindprinted = False
|
|
kindid = getattr(solv, 'SOLVABLE_' + kind, 0)
|
|
sel = self.pool.matchdepid(p, solv.Selection.SELECTION_REL | solv.Selection.SELECTION_FLAT, kindid)
|
|
if sel.isempty():
|
|
logger.debug('nothing %s %s', kind.lower(), p)
|
|
continue
|
|
for r in sel.solvables():
|
|
if not kindprinted:
|
|
print(kind)
|
|
kindprinted = True
|
|
print(' {}-{}@{}'.format(r.name, r.evr, r.arch))
|
|
|
|
@cmdln.option("-r", "--repo", dest="repo", action="append",
|
|
help="repo to use")
|
|
def do_info(self, subcmd, opts, *args):
|
|
"""${cmd_name}: show some info about a package
|
|
|
|
${cmd_usage}
|
|
${cmd_option_list}
|
|
"""
|
|
|
|
self.prepare_pool(opts.repo)
|
|
|
|
sattrs = [s for s in dir(solv) if s.startswith("SOLVABLE_")]
|
|
for n in args:
|
|
sel = self.pool.select(str(n), solv.Selection.SELECTION_NAME)
|
|
for s in sel.solvables():
|
|
for attr in sattrs:
|
|
sid = getattr(solv, attr, 0)
|
|
# pretty stupid, just lookup strings
|
|
value = s.lookup_str(sid)
|
|
if value:
|
|
print('{}: {}'.format(attr[len('SOLVABLE_'):], value))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = DepTool()
|
|
sys.exit(app.main())
|
|
else:
|
|
logger = logging.getLogger()
|