373 lines
14 KiB
Python
Executable File
373 lines
14 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import os
|
|
import sys
|
|
import logging
|
|
import cmdln
|
|
|
|
from fnmatch import fnmatch
|
|
from configparser import SafeConfigParser
|
|
import solv
|
|
|
|
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(f"add repo {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 is 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(f"add repo {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 list in opts.lock:
|
|
for lock in list.split(','):
|
|
locked.append(lock)
|
|
|
|
good = True
|
|
|
|
self.prepare_pool(opts.repo)
|
|
if opts.solver_debug:
|
|
self.pool.set_debuglevel(3)
|
|
|
|
def solveit(packages):
|
|
jobs = []
|
|
for lock in locked:
|
|
sel = self.pool.select(str(lock), solv.Selection.SELECTION_NAME)
|
|
if sel.isempty():
|
|
# if we can't find it, it probably is not as important
|
|
logger.debug(f'locked package {lock} not found')
|
|
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(f'package {n} not found')
|
|
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(f"-> {s2.name} {REASONS[reason2]} {dep}")
|
|
else:
|
|
print(f"-> {s.name} {REASONS[reason]} {ruleinfo}")
|
|
|
|
if opts.size:
|
|
size = trans.calc_installsizechange()
|
|
print(f"SIZE {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(f'- {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(f' {kind}:')
|
|
for dep in deps:
|
|
print(f' - {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(f'- {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(f"{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(f' {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(f' {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(f' {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(f"{attr[len('SOLVABLE_'):]}: {value}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = DepTool()
|
|
sys.exit(app.main())
|
|
else:
|
|
logger = logging.getLogger()
|