4 # Copyright (c) 2011, Novell Inc.
6 # This program is licensed under the BSD license, read LICENSE.BSD
7 # for further information
10 # pysolv a little software installer demoing the sat solver library/bindings
13 # - understands globs for package names / dependencies
14 # - understands .arch suffix
15 # - repository data caching
16 # - on demand loading of secondary repository data
17 # - checksum verification
19 # - installation of commandline packages
21 # things not yet ported:
24 # - fastestmirror implementation
26 # things available in the library but missing from pysolv:
27 # - vendor policy loading
28 # - soft locks file handling
29 # - multi version handling
41 from iniparse import INIConfig
42 from optparse import OptionParser
45 #gc.set_debug(gc.DEBUG_LEAK)
46 #rpm.setVerbosity(rpm.RPMLOG_DEBUG)
48 class repo_generic(dict):
49 def __init__(self, name, type, attribs = {}):
55 def calc_cookie_file(self, filename):
56 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
58 chksum.add_stat(filename)
61 def calc_cookie_fp(self, fp):
62 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
67 def calc_cookie_ext(self, f, cookie):
68 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
71 chksum.add_fstat(f.fileno())
74 def cachepath(self, ext = None):
75 path = re.sub(r'^\.', '_', self.name)
77 path += "_" + ext + ".solvx"
80 return "/var/cache/solv/" + re.sub(r'[/]', '_', path)
83 self.handle = pool.add_repo(self.name)
84 self.handle.appdata = self
85 self.handle.priority = 99 - self['priority']
86 dorefresh = bool(int(self['autorefresh']))
89 st = os.stat(self.cachepath())
90 if self['metadata_expire'] == -1 or time.time() - st[ST_MTIME] < self['metadata_expire']:
95 self['extcookie'] = ''
96 if not dorefresh and self.usecachedrepo(None):
97 print("repo: '%s': cached" % self.name)
101 def load_ext(self, repodata):
104 def setfromurls(self, urls):
108 print("[using mirror %s]" % re.sub(r'^(.*?/...*?)/.*$', r'\1', url))
109 self['baseurl'] = url
111 def setfrommetalink(self, metalink):
112 f = self.download(metalink, False, None)
115 f = os.fdopen(f.dup(), 'r')
118 for l in f.readlines():
120 m = re.match(r'^<hash type="sha256">([0-9a-fA-F]{64})</hash>', l)
122 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256, m.group(1))
123 m = re.match(r'^<url.*>(https?://.+)repodata/repomd.xml</url>', l)
125 urls.append(m.group(1))
127 chksum = None # in case the metalink is about a different file
129 self.setfromurls(urls)
132 def setfrommirrorlist(self, mirrorlist):
133 f = self.download(mirrorlist, False, None)
136 f = os.fdopen(f.dup(), 'r')
138 for l in f.readline():
140 if l[0:6] == 'http://' or l[0:7] == 'https://':
142 self.setfromurls(urls)
145 def download(self, file, uncompress, chksum, markincomplete=False):
147 if 'baseurl' not in self:
148 if 'metalink' in self:
149 if file != self['metalink']:
150 metalinkchksum = self.setfrommetalink(self['metalink'])
151 if file == 'repodata/repomd.xml' and metalinkchksum and not chksum:
152 chksum = metalinkchksum
155 elif 'mirrorlist' in self:
156 if file != self['mirrorlist']:
157 self.setfrommirrorlist(self['mirrorlist'])
161 if 'baseurl' not in self:
162 print("%s: no baseurl" % self.name)
164 url = re.sub(r'/$', '', self['baseurl']) + '/' + file
165 f = tempfile.TemporaryFile()
166 st = subprocess.call(['curl', '-f', '-s', '-L', url], stdout=f.fileno())
167 if os.lseek(f.fileno(), 0, os.SEEK_CUR) == 0 and (st == 0 or not chksum):
169 os.lseek(f.fileno(), 0, os.SEEK_SET)
171 print("%s: download error %d" % (file, st))
173 self['incomplete'] = True
176 fchksum = solv.Chksum(chksum.type)
178 print("%s: unknown checksum type" % file)
180 self['incomplete'] = True
182 fchksum.add_fd(f.fileno())
183 if fchksum != chksum:
184 print("%s: checksum mismatch" % file)
186 self['incomplete'] = True
189 return solv.xfopen_fd(file, f.fileno())
190 return solv.xfopen_fd(None, f.fileno())
192 def usecachedrepo(self, ext, mark=False):
194 repopath = self.cachepath(ext)
195 f = open(repopath, 'rb')
196 f.seek(-32, os.SEEK_END)
198 if len(fcookie) != 32:
201 cookie = self['cookie']
203 cookie = self['extcookie']
204 if cookie and fcookie != cookie:
206 if self.type != 'system' and not ext:
207 f.seek(-32 * 2, os.SEEK_END)
208 fextcookie = f.read(32)
209 if len(fextcookie) != 32:
212 f = solv.xfopen_fd('', f.fileno())
215 flags = solv.Repo.REPO_USE_LOADING|solv.Repo.REPO_EXTEND_SOLVABLES
217 flags |= solv.Repo.REPO_LOCALPOOL
218 if not self.handle.add_solv(f, flags):
220 if self.type != 'system' and not ext:
221 self['cookie'] = fcookie
222 self['extcookie'] = fextcookie
224 # no futimes in python?
226 os.utime(repopath, None)
233 def writecachedrepo(self, ext, repodata=None):
234 if 'incomplete' in self:
238 if not os.path.isdir("/var/cache/solv"):
239 os.mkdir("/var/cache/solv", 0o755)
240 (fd, tmpname) = tempfile.mkstemp(prefix='.newsolv-', dir='/var/cache/solv')
242 f = os.fdopen(fd, 'wb+')
243 f = solv.xfopen_fd(None, f.fileno())
248 else: # rewrite_repos case, do not write stubs
249 self.handle.write_first_repodata(f)
251 if self.type != 'system' and not ext:
252 if not self['extcookie']:
253 self['extcookie'] = self.calc_cookie_ext(f, self['cookie'])
254 f.write(self['extcookie'])
256 f.write(self['cookie'])
258 f.write(self['extcookie'])
260 if self.handle.iscontiguous():
261 # switch to saved repo to activate paging and save memory
262 nf = solv.xfopen(tmpname)
266 flags = solv.Repo.SOLV_ADD_NO_STUBS
268 flags = 0 # rewrite repos case, recreate stubs
269 if not self.handle.add_solv(nf, flags):
270 sys.exit("internal error, cannot reload solv file")
273 # need to extend to repo boundaries, as this is how
274 # repodata.write() has written the data
275 repodata.extend_to_repo()
276 flags = solv.Repo.REPO_EXTEND_SOLVABLES
278 flags |= solv.Repo.REPO_LOCALPOOL
279 repodata.add_solv(nf, flags)
280 os.rename(tmpname, self.cachepath(ext))
281 except (OSError, IOError):
285 def updateaddedprovides(self, addedprovides):
286 if 'incomplete' in self:
288 if not hasattr(self, 'handle'):
290 if self.handle.isempty():
292 # make sure there's just one real repodata with extensions
293 repodata = self.handle.first_repodata()
296 oldaddedprovides = repodata.lookup_idarray(solv.SOLVID_META, solv.REPOSITORY_ADDEDFILEPROVIDES)
297 if not set(addedprovides) <= set(oldaddedprovides):
298 for id in addedprovides:
299 repodata.add_idarray(solv.SOLVID_META, solv.REPOSITORY_ADDEDFILEPROVIDES, id)
300 repodata.internalize()
301 self.writecachedrepo(None, repodata)
303 def packagespath(self):
306 def add_ext_keys(self, ext, repodata, handle):
308 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOSITORY_DELTAINFO)
309 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_FLEXARRAY)
311 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_DISKUSAGE)
312 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRNUMNUMARRAY)
314 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_FILELIST)
315 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRSTRARRAY)
317 for langtag, langtagtype in [
318 (solv.SOLVABLE_SUMMARY, solv.REPOKEY_TYPE_STR),
319 (solv.SOLVABLE_DESCRIPTION, solv.REPOKEY_TYPE_STR),
320 (solv.SOLVABLE_EULA, solv.REPOKEY_TYPE_STR),
321 (solv.SOLVABLE_MESSAGEINS, solv.REPOKEY_TYPE_STR),
322 (solv.SOLVABLE_MESSAGEDEL, solv.REPOKEY_TYPE_STR),
323 (solv.SOLVABLE_CATEGORY, solv.REPOKEY_TYPE_ID)
325 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, self.handle.pool.id2langid(langtag, ext, 1))
326 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, langtagtype)
329 class repo_repomd(repo_generic):
330 def load(self, pool):
331 if super(repo_repomd, self).load(pool):
333 sys.stdout.write("rpmmd repo '%s': " % self.name)
335 f = self.download("repodata/repomd.xml", False, None, None)
337 print("no repomd.xml file, skipped")
338 self.handle.free(True)
341 self['cookie'] = self.calc_cookie_fp(f)
342 if self.usecachedrepo(None, True):
345 self.handle.add_repomdxml(f, 0)
347 (filename, filechksum) = self.find('primary')
349 f = self.download(filename, True, filechksum, True)
351 self.handle.add_rpmmd(f, None, 0)
352 if 'incomplete' in self:
353 return False # hopeless, need good primary
354 (filename, filechksum) = self.find('updateinfo')
356 f = self.download(filename, True, filechksum, True)
358 self.handle.add_updateinfoxml(f, 0)
360 self.writecachedrepo(None)
361 # must be called after writing the repo
362 self.handle.create_stubs()
365 def find(self, what):
366 di = self.handle.Dataiterator_meta(solv.REPOSITORY_REPOMD_TYPE, what, solv.Dataiterator.SEARCH_STRING)
367 di.prepend_keyname(solv.REPOSITORY_REPOMD)
370 filename = dp.lookup_str(solv.REPOSITORY_REPOMD_LOCATION)
371 chksum = dp.lookup_checksum(solv.REPOSITORY_REPOMD_CHECKSUM)
372 if filename and not chksum:
373 print("no %s file checksum!" % filename)
377 return (filename, chksum)
380 def add_ext(self, repodata, what, ext):
381 filename, chksum = self.find(what)
382 if not filename and what == 'deltainfo':
383 filename, chksum = self.find('prestodelta')
386 handle = repodata.new_handle()
387 repodata.set_poolstr(handle, solv.REPOSITORY_REPOMD_TYPE, what)
388 repodata.set_str(handle, solv.REPOSITORY_REPOMD_LOCATION, filename)
389 repodata.set_checksum(handle, solv.REPOSITORY_REPOMD_CHECKSUM, chksum)
390 self.add_ext_keys(ext, repodata, handle)
391 repodata.add_flexarray(solv.SOLVID_META, solv.REPOSITORY_EXTERNAL, handle)
394 repodata = self.handle.add_repodata(0)
395 repodata.extend_to_repo()
396 self.add_ext(repodata, 'deltainfo', 'DL')
397 self.add_ext(repodata, 'filelists', 'FL')
398 repodata.internalize()
400 def load_ext(self, repodata):
401 repomdtype = repodata.lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_TYPE)
402 if repomdtype == 'filelists':
404 elif repomdtype == 'deltainfo':
408 sys.stdout.write("[%s:%s: " % (self.name, ext))
409 if self.usecachedrepo(ext):
410 sys.stdout.write("cached]\n")
413 sys.stdout.write("fetching]\n")
415 filename = repodata.lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_LOCATION)
416 filechksum = repodata.lookup_checksum(solv.SOLVID_META, solv.REPOSITORY_REPOMD_CHECKSUM)
417 f = self.download(filename, True, filechksum)
421 self.handle.add_rpmmd(f, 'FL', solv.Repo.REPO_USE_LOADING|solv.Repo.REPO_EXTEND_SOLVABLES|solv.Repo.REPO_LOCALPOOL)
423 self.handle.add_deltainfoxml(f, solv.Repo.REPO_USE_LOADING)
424 self.writecachedrepo(ext, repodata)
427 class repo_susetags(repo_generic):
428 def load(self, pool):
429 if super(repo_susetags, self).load(pool):
431 sys.stdout.write("susetags repo '%s': " % self.name)
433 f = self.download("content", False, None, None)
435 print("no content file, skipped")
436 self.handle.free(True)
439 self['cookie'] = self.calc_cookie_fp(f)
440 if self.usecachedrepo(None, True):
443 self.handle.add_content(f, 0)
445 defvendorid = self.handle.meta.lookup_id(solv.SUSETAGS_DEFAULTVENDOR)
446 descrdir = self.handle.meta.lookup_str(solv.SUSETAGS_DESCRDIR)
448 descrdir = "suse/setup/descr"
449 (filename, filechksum) = self.find('packages.gz')
451 (filename, filechksum) = self.find('packages')
453 f = self.download(descrdir + '/' + filename, True, filechksum, True)
455 self.handle.add_susetags(f, defvendorid, None, solv.Repo.REPO_NO_INTERNALIZE|solv.Repo.SUSETAGS_RECORD_SHARES)
456 (filename, filechksum) = self.find('packages.en.gz')
458 (filename, filechksum) = self.find('packages.en')
460 f = self.download(descrdir + '/' + filename, True, filechksum, True)
462 self.handle.add_susetags(f, defvendorid, None, solv.Repo.REPO_NO_INTERNALIZE|solv.Repo.REPO_REUSE_REPODATA|solv.Repo.REPO_EXTEND_SOLVABLES)
463 self.handle.internalize()
465 self.writecachedrepo(None)
466 # must be called after writing the repo
467 self.handle.create_stubs()
470 def find(self, what):
471 di = self.handle.Dataiterator_meta(solv.SUSETAGS_FILE_NAME, what, solv.Dataiterator.SEARCH_STRING)
472 di.prepend_keyname(solv.SUSETAGS_FILE)
475 chksum = dp.lookup_checksum(solv.SUSETAGS_FILE_CHECKSUM)
476 return (what, chksum)
479 def add_ext(self, repodata, what, ext):
480 (filename, chksum) = self.find(what)
483 handle = repodata.new_handle()
484 repodata.set_str(handle, solv.SUSETAGS_FILE_NAME, filename)
486 repodata.set_checksum(handle, solv.SUSETAGS_FILE_CHECKSUM, chksum)
487 self.add_ext_keys(ext, repodata, handle)
488 repodata.add_flexarray(solv.SOLVID_META, solv.REPOSITORY_EXTERNAL, handle)
491 repodata = self.handle.add_repodata(0)
492 di = self.handle.Dataiterator_meta(solv.SUSETAGS_FILE_NAME, None, 0)
493 di.prepend_keyname(solv.SUSETAGS_FILE)
498 if filename[0:9] != "packages.":
500 if len(filename) == 11 and filename != "packages.gz":
502 elif filename[11:12] == ".":
508 self.add_ext(repodata, filename, ext)
509 repodata.internalize()
511 def load_ext(self, repodata):
512 filename = repodata.lookup_str(solv.SOLVID_META, solv.SUSETAGS_FILE_NAME)
514 sys.stdout.write("[%s:%s: " % (self.name, ext))
515 if self.usecachedrepo(ext):
516 sys.stdout.write("cached]\n")
519 sys.stdout.write("fetching]\n")
521 defvendorid = self.handle.meta.lookup_id(solv.SUSETAGS_DEFAULTVENDOR)
522 descrdir = self.handle.meta.lookup_str(solv.SUSETAGS_DESCRDIR)
524 descrdir = "suse/setup/descr"
525 filechksum = repodata.lookup_checksum(solv.SOLVID_META, solv.SUSETAGS_FILE_CHECKSUM)
526 f = self.download(descrdir + '/' + filename, True, filechksum)
529 flags = solv.Repo.REPO_USE_LOADING|solv.Repo.REPO_EXTEND_SOLVABLES
531 flags |= solv.Repo.REPO_LOCALPOOL
532 self.handle.add_susetags(f, defvendorid, ext, flags)
533 self.writecachedrepo(ext, repodata)
536 def packagespath(self):
537 datadir = repo.handle.meta.lookup_str(solv.SUSETAGS_DATADIR)
542 class repo_unknown(repo_generic):
543 def load(self, pool):
544 print("unsupported repo '%s': skipped" % self.name)
547 class repo_system(repo_generic):
548 def load(self, pool):
549 self.handle = pool.add_repo(self.name)
550 self.handle.appdata = self
551 pool.installed = self.handle
552 sys.stdout.write("rpm database: ")
553 self['cookie'] = self.calc_cookie_file("/var/lib/rpm/Packages")
554 if self.usecachedrepo(None):
558 if hasattr(self.handle.__class__, 'add_products'):
559 self.handle.add_products("/etc/products.d", solv.Repo.REPO_NO_INTERNALIZE)
560 f = solv.xfopen(self.cachepath())
561 self.handle.add_rpmdb_reffp(f, solv.Repo.REPO_REUSE_REPODATA)
562 self.writecachedrepo(None)
565 class repo_cmdline(repo_generic):
566 def load(self, pool):
567 self.handle = pool.add_repo(self.name)
568 self.handle.appdata = self
571 def load_stub(repodata):
572 repo = repodata.repo.appdata
574 return repo.load_ext(repodata)
578 parser = OptionParser(usage="usage: solv.py [options] COMMAND")
579 parser.add_option('-r', '--repo', action="append", type="string", dest="repos", help="limit to specified repositories")
580 parser.add_option('--best', action="store_true", dest="best", help="force installation/update to best packages")
581 parser.add_option('--clean', action="store_true", dest="clean", help="delete no longer needed packages")
582 (options, args) = parser.parse_args()
584 parser.print_help(sys.stderr)
590 cmdabbrev = {'ls': 'list', 'in': 'install', 'rm': 'erase', 've': 'verify', 'se': 'search'}
595 'install': solv.Job.SOLVER_INSTALL,
596 'erase': solv.Job.SOLVER_ERASE,
597 'up': solv.Job.SOLVER_UPDATE,
598 'dup': solv.Job.SOLVER_DISTUPGRADE,
599 'verify': solv.Job.SOLVER_VERIFY,
604 # read all repo configs
607 if os.path.isdir("/etc/zypp/repos.d"):
608 reposdirs = [ "/etc/zypp/repos.d" ]
610 reposdirs = [ "/etc/yum/repos.d" ]
612 for reposdir in reposdirs:
613 if not os.path.isdir(reposdir):
615 for reponame in sorted(glob.glob('%s/*.repo' % reposdir)):
616 cfg = INIConfig(open(reponame))
618 repoattr = {'enabled': 0, 'priority': 99, 'autorefresh': 1, 'type': 'rpm-md', 'metadata_expire': 900}
620 repoattr[k] = cfg[alias][k]
621 if 'mirrorlist' in repoattr and 'metalink' not in repoattr:
622 if repoattr['mirrorlist'].find('/metalink'):
623 repoattr['metalink'] = repoattr['mirrorlist']
624 del repoattr['mirrorlist']
625 if repoattr['type'] == 'rpm-md':
626 repo = repo_repomd(alias, 'repomd', repoattr)
627 elif repoattr['type'] == 'yast2':
628 repo = repo_susetags(alias, 'susetags', repoattr)
630 repo = repo_unknown(alias, 'unknown', repoattr)
635 pool.set_loadcallback(load_stub)
637 # now load all enabled repos into the pool
638 sysrepo = repo_system('@System', 'system')
641 if int(repo['enabled']):
646 for reponame in options.repos:
647 mrepos = [ repo for repo in repos if repo.name == reponame ]
649 print("no repository matches '%s'" % reponame)
652 if hasattr(repo, 'handle'):
654 repofilter = pool.Selection()
655 repofilter.add(repo.handle.Selection(solv.Job.SOLVER_SETVENDOR))
658 pool.createwhatprovides()
659 sel = pool.Selection()
660 di = pool.Dataiterator(solv.SOLVABLE_NAME, args[0], solv.Dataiterator.SEARCH_SUBSTRING|solv.Dataiterator.SEARCH_NOCASE)
662 sel.add_raw(solv.Job.SOLVER_SOLVABLE, d.solvid)
664 sel.filter(repofilter)
665 for s in sel.solvables():
666 print(" - %s [%s]: %s" % (s, s.repo.name, s.lookup_str(solv.SOLVABLE_SUMMARY)))
669 if cmd not in cmdactionmap:
670 print("unknown command %s" % cmd)
674 if cmd == 'list' or cmd == 'info' or cmd == 'install':
676 if arg.endswith(".rpm") and os.access(arg, os.R_OK):
678 cmdlinerepo = repo_cmdline('@commandline', 'cmdline')
679 cmdlinerepo.load(pool)
680 cmdlinerepo['packages'] = {}
681 s = cmdlinerepo.handle.add_rpm(arg, solv.Repo.REPO_REUSE_REPODATA|solv.Repo.REPO_NO_INTERNALIZE)
685 cmdlinerepo['packages'][arg] = s
687 cmdlinerepo.handle.internalize()
689 addedprovides = pool.addfileprovides_queue()
691 sysrepo.updateaddedprovides(addedprovides)
693 repo.updateaddedprovides(addedprovides)
695 pool.createwhatprovides()
697 # convert arguments into jobs
700 if cmdlinerepo and arg in cmdlinerepo['packages']:
701 jobs.append(pool.Job(solv.Job.SOLVER_SOLVABLE, cmdlinerepo['packages'][arg].id))
703 flags = solv.Selection.SELECTION_NAME|solv.Selection.SELECTION_PROVIDES|solv.Selection.SELECTION_GLOB
704 flags |= solv.Selection.SELECTION_CANON|solv.Selection.SELECTION_DOTARCH|solv.Selection.SELECTION_REL
705 if len(arg) and arg[0] == '/':
706 flags |= solv.Selection.SELECTION_FILELIST
708 flags |= solv.Selection.SELECTION_INSTALLED_ONLY
709 sel = pool.select(arg, flags)
711 sel.filter(repofilter)
713 sel = pool.select(arg, flags | solv.Selection.SELECTION_NOCASE)
715 sel.filter(repofilter)
716 if not sel.isempty():
717 print("[ignoring case for '%s']" % arg)
719 print("nothing matches '%s'" % arg)
721 if sel.flags & solv.Selection.SELECTION_FILELIST:
722 print("[using file list match for '%s']" % arg)
723 if sel.flags & solv.Selection.SELECTION_PROVIDES:
724 print("[using capability match for '%s']" % arg)
725 jobs += sel.jobs(cmdactionmap[cmd])
727 if not jobs and (cmd == 'up' or cmd == 'dup' or cmd == 'verify' or repofilter):
728 sel = pool.Selection_all()
730 sel.filter(repofilter)
731 jobs += sel.jobs(cmdactionmap[cmd])
734 print("no package matched.")
737 if cmd == 'list' or cmd == 'info':
739 for s in job.solvables():
741 print("Name: %s" % s)
742 print("Repo: %s" % s.repo)
743 print("Summary: %s" % s.lookup_str(solv.SOLVABLE_SUMMARY))
744 str = s.lookup_str(solv.SOLVABLE_URL)
746 print("Url: %s" % str)
747 str = s.lookup_str(solv.SOLVABLE_LICENSE)
749 print("License: %s" % str)
750 print("Description:\n%s" % s.lookup_str(solv.SOLVABLE_DESCRIPTION))
753 print(" - %s [%s]" % (s, s.repo))
754 print(" %s" % s.lookup_str(solv.SOLVABLE_SUMMARY))
757 # up magic: use install instead of update if no installed package matches
759 if cmd == 'up' and job.isemptyupdate():
760 job.how ^= solv.Job.SOLVER_UPDATE ^ solv.Job.SOLVER_INSTALL
762 job.how |= solv.Job.SOLVER_FORCEBEST
764 job.how |= solv.Job.SOLVER_CLEANDEPS
766 #pool.set_debuglevel(2)
767 solver = pool.Solver()
768 solver.set_flag(solv.Solver.SOLVER_FLAG_SPLITPROVIDES, 1);
770 solver.set_flag(solv.Solver.SOLVER_FLAG_ALLOW_UNINSTALL, 1);
773 problems = solver.solve(jobs)
776 for problem in problems:
777 print("Problem %d/%d:" % (problem.id, len(problems)))
779 solutions = problem.solutions()
780 for solution in solutions:
781 print(" Solution %d:" % solution.id)
782 elements = solution.elements(True)
783 for element in elements:
784 print(" - %s" % element.str())
787 while not (sol == 's' or sol == 'q' or (sol.isdigit() and int(sol) >= 1 and int(sol) <= len(solutions))):
788 sys.stdout.write("Please choose a solution: ")
790 sol = sys.stdin.readline().strip()
793 for decisionset in problem.get_decisionsetlist():
794 print("%s: %s" % (decisionset, decisionset.reasonstr()))
797 continue # skip problem
800 solution = solutions[int(sol) - 1]
801 for element in solution.elements():
802 newjob = element.Job()
803 if element.type == solv.Solver.SOLVER_SOLUTION_JOB:
804 jobs[element.jobidx] = newjob
806 if newjob and newjob not in jobs:
809 # no problems, show transaction
810 trans = solver.transaction()
812 print("Nothing to do.")
815 print("Transaction summary:")
817 for cl in trans.classify(solv.Transaction.SOLVER_TRANSACTION_SHOW_OBSOLETES | solv.Transaction.SOLVER_TRANSACTION_OBSOLETE_IS_UPGRADE):
818 if cl.type == solv.Transaction.SOLVER_TRANSACTION_ERASE:
819 print("%d erased packages:" % cl.count)
820 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_INSTALL:
821 print("%d installed packages:" % cl.count)
822 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_REINSTALLED:
823 print("%d reinstalled packages:" % cl.count)
824 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_DOWNGRADED:
825 print("%d downgraded packages:" % cl.count)
826 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_CHANGED:
827 print("%d changed packages:" % cl.count)
828 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_UPGRADED:
829 print("%d upgraded packages:" % cl.count)
830 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_VENDORCHANGE:
831 print("%d vendor changes from '%s' to '%s':" % (cl.count, cl.fromstr, cl.tostr))
832 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_ARCHCHANGE:
833 print("%d arch changes from '%s' to '%s':" % (cl.count, cl.fromstr, cl.tostr))
836 for p in cl.solvables():
837 if cl.type == solv.Transaction.SOLVER_TRANSACTION_UPGRADED or cl.type == solv.Transaction.SOLVER_TRANSACTION_DOWNGRADED:
838 op = trans.othersolvable(p)
839 print(" - %s -> %s" % (p, op))
843 print("install size change: %d K" % trans.calc_installsizechange())
846 alternatives = solver.alternatives()
848 print('Alternatives:')
849 for a in alternatives:
853 for ac in a.choices():
854 print("%6d: %s" % (aidx, ac))
861 sys.stdout.write("OK to continue (y/n)? ")
863 yn = sys.stdin.readline().strip()
865 if yn == 'n' or yn == 'q': sys.exit(1)
866 newpkgs = trans.newsolvables()
871 downloadsize += p.lookup_num(solv.SOLVABLE_DOWNLOADSIZE)
872 print("Downloading %d packages, %d K" % (len(newpkgs), downloadsize / 1024))
874 repo = p.repo.appdata
875 location, medianr = p.lookup_location()
878 if repo.type == 'commandline':
879 f = solv.xfopen(location)
881 sys.exit("\n%s: %s not found" % location)
884 if not sysrepo.handle.isempty() and os.access('/usr/bin/applydeltarpm', os.X_OK):
886 di = p.repo.Dataiterator_meta(solv.DELTA_PACKAGE_NAME, pname, solv.Dataiterator.SEARCH_STRING)
887 di.prepend_keyname(solv.REPOSITORY_DELTAINFO)
890 if dp.lookup_id(solv.DELTA_PACKAGE_EVR) != p.evrid or dp.lookup_id(solv.DELTA_PACKAGE_ARCH) != p.archid:
892 baseevrid = dp.lookup_id(solv.DELTA_BASE_EVR)
894 for installedp in pool.whatprovides(p.nameid):
895 if installedp.isinstalled() and installedp.nameid == p.nameid and installedp.archid == p.archid and installedp.evrid == baseevrid:
896 candidate = installedp
899 seq = dp.lookup_deltaseq()
900 st = subprocess.call(['/usr/bin/applydeltarpm', '-a', p.arch, '-c', '-s', seq])
903 chksum = dp.lookup_checksum(solv.DELTA_CHECKSUM)
906 dloc, dmedianr = dp.lookup_deltalocation()
907 dloc = repo.packagespath() + dloc
908 f = repo.download(dloc, False, chksum)
911 nf = tempfile.TemporaryFile()
912 nf = os.dup(nf.fileno()) # get rid of CLOEXEC
914 st = subprocess.call(['/usr/bin/applydeltarpm', '-a', p.arch, "/dev/fd/%d" % f.fileno(), "/dev/fd/%d" % nf])
918 os.lseek(nf, 0, os.SEEK_SET)
919 newpkgsfp[p.id] = solv.xfopen_fd("", nf)
922 if p.id in newpkgsfp:
923 sys.stdout.write("d")
927 chksum = p.lookup_checksum(solv.SOLVABLE_CHECKSUM)
928 location = repo.packagespath() + location
929 f = repo.download(location, False, chksum)
931 sys.exit("\n%s: %s not found in repository" % (repo.name, location))
933 sys.stdout.write(".")
936 print("Committing transaction:")
938 ts = rpm.TransactionSet('/')
939 ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
941 for p in trans.steps():
942 type = trans.steptype(p, solv.Transaction.SOLVER_TRANSACTION_RPM_ONLY)
943 if type == solv.Transaction.SOLVER_TRANSACTION_ERASE:
944 rpmdbid = p.lookup_num(solv.RPM_RPMDBID)
945 erasenamehelper[p.name] = p
947 sys.exit("\ninternal error: installed package %s has no rpmdbid\n" % p)
949 elif type == solv.Transaction.SOLVER_TRANSACTION_INSTALL:
951 h = ts.hdrFromFdno(f.fileno())
952 os.lseek(f.fileno(), 0, os.SEEK_SET)
953 ts.addInstall(h, p, 'u')
954 elif type == solv.Transaction.SOLVER_TRANSACTION_MULTIINSTALL:
956 h = ts.hdrFromFdno(f.fileno())
957 os.lseek(f.fileno(), 0, os.SEEK_SET)
958 ts.addInstall(h, p, 'i')
959 checkproblems = ts.check()
964 def runCallback(reason, amount, total, p, d):
965 if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
967 os.lseek(f.fileno(), 0, os.SEEK_SET)
969 if reason == rpm.RPMCALLBACK_INST_START:
970 print("install %s" % p)
971 if reason == rpm.RPMCALLBACK_UNINST_START:
972 # argh, p is just the name of the package
973 if p in erasenamehelper:
974 p = erasenamehelper[p]
975 print("erase %s" % p)
976 runproblems = ts.run(runCallback, '')