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)
47 class repo_generic(dict):
48 def __init__(self, name, type, attribs = {}):
54 def calc_cookie_file(self, filename):
55 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
57 chksum.add_stat(filename)
60 def calc_cookie_fp(self, fp):
61 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
66 def calc_cookie_ext(self, f, cookie):
67 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
70 chksum.add_fstat(f.fileno())
71 extcookie = chksum.raw()
72 # compatibility to c code
73 if ord(extcookie[0]) == 0:
77 def cachepath(self, ext = None):
78 path = re.sub(r'^\.', '_', self.name)
80 path += "_" + ext + ".solvx"
83 return "/var/cache/solv/" + re.sub(r'[/]', '_', path)
86 self.handle = pool.add_repo(self.name)
87 self.handle.appdata = self
88 self.handle.priority = 99 - self['priority']
89 dorefresh = bool(int(self['autorefresh']))
92 st = os.stat(self.cachepath())
93 if self['metadata_expire'] == -1 or time.time() - st[ST_MTIME] < self['metadata_expire']:
98 if not dorefresh and self.usecachedrepo(None):
99 print "repo: '%s': cached" % self.name
103 def load_ext(self, repodata):
106 def setfromurls(self, urls):
110 print "[using mirror %s]" % re.sub(r'^(.*?/...*?)/.*$', r'\1', url)
111 self['baseurl'] = url
113 def setfrommetalink(self, metalink):
114 f = self.download(metalink, False, None)
117 f = os.fdopen(f.dup(), 'r')
120 for l in f.readlines():
122 m = re.match(r'^<hash type="sha256">([0-9a-fA-F]{64})</hash>', l)
124 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256, m.group(1))
125 m = re.match(r'^<url.*>(https?://.+)repodata/repomd.xml</url>', l)
127 urls.append(m.group(1))
129 chksum = None # in case the metalink is about a different file
131 self.setfromurls(urls)
134 def setfrommirrorlist(self, mirrorlist):
135 f = self.download(mirrorlist, False, None)
138 f = os.fdopen(f.dup(), 'r')
140 for l in f.readline():
142 if l[0:6] == 'http://' or l[0:7] == 'https://':
144 self.setfromurls(urls)
147 def download(self, file, uncompress, chksum, markincomplete=False):
149 if 'baseurl' not in self:
150 if 'metalink' in self:
151 if file != self['metalink']:
152 metalinkchksum = self.setfrommetalink(self['metalink'])
153 if file == 'repodata/repomd.xml' and metalinkchksum and not chksum:
154 chksum = metalinkchksum
157 elif 'mirrorlist' in self:
158 if file != self['mirrorlist']:
159 self.setfrommirrorlist(self['mirrorlist'])
163 if 'baseurl' not in self:
164 print "%s: no baseurl" % self.name
166 url = re.sub(r'/$', '', self['baseurl']) + '/' + file
167 f = tempfile.TemporaryFile()
168 st = subprocess.call(['curl', '-f', '-s', '-L', url], stdout=f.fileno())
169 if os.lseek(f.fileno(), 0, os.SEEK_CUR) == 0 and (st == 0 or not chksum):
171 os.lseek(f.fileno(), 0, os.SEEK_SET)
173 print "%s: download error %d" % (file, st)
175 self['incomplete'] = True
178 fchksum = solv.Chksum(chksum.type)
180 print "%s: unknown checksum type" % file
182 self['incomplete'] = True
184 fchksum.add_fd(f.fileno())
185 if fchksum != chksum:
186 print "%s: checksum mismatch" % file
188 self['incomplete'] = True
191 return solv.xfopen_fd(file, f.fileno())
192 return solv.xfopen_fd(None, f.fileno())
194 def usecachedrepo(self, ext, mark=False):
196 repopath = self.cachepath(ext)
197 f = open(repopath, 'r')
198 f.seek(-32, os.SEEK_END)
200 if len(fcookie) != 32:
203 cookie = self['cookie']
205 cookie = self['extcookie']
206 if cookie and fcookie != cookie:
208 if self.type != 'system' and not ext:
209 f.seek(-32 * 2, os.SEEK_END)
210 fextcookie = f.read(32)
211 if len(fextcookie) != 32:
216 flags = solv.Repo.REPO_USE_LOADING|solv.Repo.REPO_EXTEND_SOLVABLES
218 flags |= solv.Repo.REPO_LOCALPOOL
219 if not self.handle.add_solv(f, flags):
221 if self.type != 'system' and not ext:
222 self['cookie'] = fcookie
223 self['extcookie'] = fextcookie
225 # no futimes in python?
227 os.utime(repopath, None)
234 def writecachedrepo(self, ext, info=None):
235 if 'incomplete' in self:
238 if not os.path.isdir("/var/cache/solv"):
239 os.mkdir("/var/cache/solv", 0755)
240 (fd, tmpname) = tempfile.mkstemp(prefix='.newsolv-', dir='/var/cache/solv')
242 f = os.fdopen(fd, 'w+')
247 else: # rewrite_repos case
248 self.handle.write_first_repodata(f)
249 if self.type != 'system' and not ext:
250 if 'extcookie' not in self:
251 self['extcookie'] = self.calc_cookie_ext(f, self['cookie'])
252 f.write(self['extcookie'])
254 f.write(self['cookie'])
256 f.write(self['extcookie'])
258 if self.handle.iscontiguous():
259 # switch to saved repo to activate paging and save memory
260 nf = solv.xfopen(tmpname)
264 if not self.handle.add_solv(nf, solv.Repo.SOLV_ADD_NO_STUBS):
265 sys.exit("internal error, cannot reload solv file")
268 # need to extend to repo boundaries, as this is how
269 # info.write() has written the data
270 info.extend_to_repo()
271 flags = solv.Repo.REPO_EXTEND_SOLVABLES
273 flags |= solv.Repo.REPO_LOCALPOOL
274 info.add_solv(nf, flags)
275 os.rename(tmpname, self.cachepath(ext))
280 def updateaddedprovides(self, addedprovides):
281 if 'incomplete' in self:
283 if not hasattr(self, 'handle'):
285 if self.handle.isempty():
287 # make sure there's just one real repodata with extensions
288 repodata = self.handle.first_repodata()
291 oldaddedprovides = repodata.lookup_idarray(solv.SOLVID_META, solv.REPOSITORY_ADDEDFILEPROVIDES)
292 if not set(addedprovides) <= set(oldaddedprovides):
293 for id in addedprovides:
294 repodata.add_idarray(solv.SOLVID_META, solv.REPOSITORY_ADDEDFILEPROVIDES, id)
295 repodata.internalize()
296 self.writecachedrepo(None, repodata)
298 def packagespath(self):
301 class repo_repomd(repo_generic):
302 def load(self, pool):
303 if super(repo_repomd, self).load(pool):
305 print "rpmmd repo '%s':" % self.name,
307 f = self.download("repodata/repomd.xml", False, None, None)
309 print "no repomd.xml file, skipped"
310 self.handle.free(True)
313 self['cookie'] = self.calc_cookie_fp(f)
314 if self.usecachedrepo(None, True):
317 self.handle.add_repomdxml(f, 0)
319 (filename, filechksum) = self.find('primary')
321 f = self.download(filename, True, filechksum, True)
323 self.handle.add_rpmmd(f, None, 0)
324 if 'incomplete' in self:
325 return False # hopeless, need good primary
326 (filename, filechksum) = self.find('updateinfo')
328 f = self.download(filename, True, filechksum, True)
330 self.handle.add_updateinfoxml(f, 0)
332 self.writecachedrepo(None)
333 # must be called after writing the repo
334 self.handle.create_stubs()
337 def find(self, what):
338 di = self.handle.Dataiterator(solv.SOLVID_META, solv.REPOSITORY_REPOMD_TYPE, what, solv.Dataiterator.SEARCH_STRING)
339 di.prepend_keyname(solv.REPOSITORY_REPOMD)
342 filename = dp.lookup_str(solv.REPOSITORY_REPOMD_LOCATION)
343 chksum = dp.lookup_checksum(solv.REPOSITORY_REPOMD_CHECKSUM)
344 if filename and not chksum:
345 print "no %s file checksum!" % filename
349 return (filename, chksum)
352 def add_ext(self, repodata, what, ext):
353 filename, chksum = self.find(what)
354 if not filename and what == 'deltainfo':
355 filename, chksum = self.find('prestodelta')
358 handle = repodata.new_handle()
359 repodata.set_poolstr(handle, solv.REPOSITORY_REPOMD_TYPE, what)
360 repodata.set_str(handle, solv.REPOSITORY_REPOMD_LOCATION, filename)
361 repodata.set_checksum(handle, solv.REPOSITORY_REPOMD_CHECKSUM, chksum)
363 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOSITORY_DELTAINFO)
364 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_FLEXARRAY)
366 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_FILELIST)
367 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRSTRARRAY)
368 repodata.add_flexarray(solv.SOLVID_META, solv.REPOSITORY_EXTERNAL, handle)
371 repodata = self.handle.add_repodata(0)
372 self.add_ext(repodata, 'deltainfo', 'DL')
373 self.add_ext(repodata, 'filelists', 'FL')
374 repodata.internalize()
376 def load_ext(self, repodata):
377 repomdtype = repodata.lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_TYPE)
378 if repomdtype == 'filelists':
380 elif repomdtype == 'deltainfo':
384 sys.stdout.write("[%s:%s: " % (self.name, ext))
385 if self.usecachedrepo(ext):
386 sys.stdout.write("cached]\n")
389 sys.stdout.write("fetching]\n")
391 filename = repodata.lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_LOCATION)
392 filechksum = repodata.lookup_checksum(solv.SOLVID_META, solv.REPOSITORY_REPOMD_CHECKSUM)
393 f = self.download(filename, True, filechksum)
397 self.handle.add_rpmmd(f, 'FL', solv.Repo.REPO_USE_LOADING|solv.Repo.REPO_EXTEND_SOLVABLES|solv.Repo.REPO_LOCALPOOL)
399 self.handle.add_deltainfoxml(f, solv.Repo.REPO_USE_LOADING)
400 self.writecachedrepo(ext, repodata)
403 class repo_susetags(repo_generic):
404 def load(self, pool):
405 if super(repo_susetags, self).load(pool):
407 print "susetags repo '%s':" % self.name,
409 f = self.download("content", False, None, None)
411 print "no content file, skipped"
412 self.handle.free(True)
415 self['cookie'] = self.calc_cookie_fp(f)
416 if self.usecachedrepo(None, True):
419 self.handle.add_content(f, 0)
421 defvendorid = self.handle.meta.lookup_id(solv.SUSETAGS_DEFAULTVENDOR)
422 descrdir = self.handle.meta.lookup_str(solv.SUSETAGS_DESCRDIR)
424 descrdir = "suse/setup/descr"
425 (filename, filechksum) = self.find('packages.gz')
427 (filename, filechksum) = self.find('packages')
429 f = self.download(descrdir + '/' + filename, True, filechksum, True)
431 self.handle.add_susetags(f, defvendorid, None, solv.Repo.REPO_NO_INTERNALIZE|solv.Repo.SUSETAGS_RECORD_SHARES)
432 (filename, filechksum) = self.find('packages.en.gz')
434 (filename, filechksum) = self.find('packages.en')
436 f = self.download(descrdir + '/' + filename, True, filechksum, True)
438 self.handle.add_susetags(f, defvendorid, None, solv.Repo.REPO_NO_INTERNALIZE|solv.Repo.REPO_REUSE_REPODATA|solv.Repo.REPO_EXTEND_SOLVABLES)
439 self.handle.internalize()
441 self.writecachedrepo(None)
442 # must be called after writing the repo
443 self.handle.create_stubs()
446 def find(self, what):
447 di = self.handle.Dataiterator(solv.SOLVID_META, solv.SUSETAGS_FILE_NAME, what, solv.Dataiterator.SEARCH_STRING)
448 di.prepend_keyname(solv.SUSETAGS_FILE)
451 chksum = dp.lookup_checksum(solv.SUSETAGS_FILE_CHECKSUM)
452 return (what, chksum)
455 def add_ext(self, repodata, what, ext):
456 (filename, chksum) = self.find(what)
459 handle = repodata.new_handle()
460 repodata.set_str(handle, solv.SUSETAGS_FILE_NAME, filename)
462 repodata.set_checksum(handle, solv.SUSETAGS_FILE_CHECKSUM, chksum)
464 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_DISKUSAGE)
465 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRNUMNUMARRAY)
467 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_FILELIST)
468 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRSTRARRAY)
470 for langtag, langtagtype in [
471 (solv.SOLVABLE_SUMMARY, solv.REPOKEY_TYPE_STR),
472 (solv.SOLVABLE_DESCRIPTION, solv.REPOKEY_TYPE_STR),
473 (solv.SOLVABLE_EULA, solv.REPOKEY_TYPE_STR),
474 (solv.SOLVABLE_MESSAGEINS, solv.REPOKEY_TYPE_STR),
475 (solv.SOLVABLE_MESSAGEDEL, solv.REPOKEY_TYPE_STR),
476 (solv.SOLVABLE_CATEGORY, solv.REPOKEY_TYPE_ID)
478 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, self.handle.pool.id2langid(langtag, ext, 1))
479 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, langtagtype)
480 repodata.add_flexarray(solv.SOLVID_META, solv.REPOSITORY_EXTERNAL, handle)
483 repodata = self.handle.add_repodata(0)
484 di = self.handle.Dataiterator(solv.SOLVID_META, solv.SUSETAGS_FILE_NAME, None, 0)
485 di.prepend_keyname(solv.SUSETAGS_FILE)
490 if filename[0:9] != "packages.":
492 if len(filename) == 11 and filename != "packages.gz":
494 elif filename[11:12] == ".":
500 self.add_ext(repodata, filename, ext)
501 repodata.internalize()
503 def load_ext(self, repodata):
504 filename = repodata.lookup_str(solv.SOLVID_META, solv.SUSETAGS_FILE_NAME)
506 sys.stdout.write("[%s:%s: " % (self.name, ext))
507 if self.usecachedrepo(ext):
508 sys.stdout.write("cached]\n")
511 sys.stdout.write("fetching]\n")
513 defvendorid = self.handle.meta.lookup_id(solv.SUSETAGS_DEFAULTVENDOR)
514 descrdir = self.handle.meta.lookup_str(solv.SUSETAGS_DESCRDIR)
516 descrdir = "suse/setup/descr"
517 filechksum = repodata.lookup_checksum(solv.SOLVID_META, solv.SUSETAGS_FILE_CHECKSUM)
518 f = self.download(descrdir + '/' + filename, True, filechksum)
521 flags = solv.Repo.REPO_USE_LOADING|solv.Repo.REPO_EXTEND_SOLVABLES
523 flags |= solv.Repo.REPO_LOCALPOOL
524 self.handle.add_susetags(f, defvendorid, ext, flags)
525 self.writecachedrepo(ext, repodata)
528 def packagespath(self):
529 datadir = repo.handle.meta.lookup_str(solv.SUSETAGS_DATADIR)
534 class repo_unknown(repo_generic):
535 def load(self, pool):
536 print "unsupported repo '%s': skipped" % self.name
539 class repo_system(repo_generic):
540 def load(self, pool):
541 self.handle = pool.add_repo(self.name)
542 self.handle.appdata = self
543 pool.installed = self.handle
544 print "rpm database:",
545 self['cookie'] = self.calc_cookie_file("/var/lib/rpm/Packages")
546 if self.usecachedrepo(None):
550 if hasattr(self.handle.__class__, 'add_products'):
551 self.handle.add_products("/etc/products.d", solv.Repo.REPO_NO_INTERNALIZE)
552 f = solv.xfopen(self.cachepath())
553 self.handle.add_rpmdb_reffp(f, solv.Repo.REPO_REUSE_REPODATA)
554 self.writecachedrepo(None)
557 class repo_cmdline(repo_generic):
558 def load(self, pool):
559 self.handle = pool.add_repo(self.name)
560 self.handle.appdata = self
563 def load_stub(repodata):
564 repo = repodata.repo.appdata
566 return repo.load_ext(repodata)
570 parser = OptionParser(usage="usage: solv.py [options] COMMAND")
571 parser.add_option('-r', '--repo', action="append", type="string", dest="repos", help="limit to specified repositories")
572 parser.add_option('--best', action="store_true", dest="best", help="force installation/update to best packages")
573 parser.add_option('--clean', action="store_true", dest="clean", help="delete no longer needed packages")
574 (options, args) = parser.parse_args()
576 parser.print_help(sys.stderr)
582 cmdabbrev = {'li': 'list', 'in': 'install', 'rm': 'erase', 've': 'verify', 'se': 'search'}
587 'install': solv.Job.SOLVER_INSTALL,
588 'erase': solv.Job.SOLVER_ERASE,
589 'up': solv.Job.SOLVER_UPDATE,
590 'dup': solv.Job.SOLVER_DISTUPGRADE,
591 'verify': solv.Job.SOLVER_VERIFY,
596 # read all repo configs
599 if os.path.isdir("/etc/zypp/repos.d"):
600 reposdirs = [ "/etc/zypp/repos.d" ]
602 reposdirs = [ "/etc/yum/repos.d" ]
604 for reposdir in reposdirs:
605 if not os.path.isdir(reposdir):
607 for reponame in sorted(glob.glob('%s/*.repo' % reposdir)):
608 cfg = INIConfig(open(reponame))
610 repoattr = {'enabled': 0, 'priority': 99, 'autorefresh': 1, 'type': 'rpm-md', 'metadata_expire': 900}
612 repoattr[k] = cfg[alias][k]
613 if 'mirrorlist' in repoattr and 'metalink' not in repoattr:
614 if repoattr['mirrorlist'].find('/metalink'):
615 repoattr['metalink'] = repoattr['mirrorlist']
616 del repoattr['mirrorlist']
617 if repoattr['type'] == 'rpm-md':
618 repo = repo_repomd(alias, 'repomd', repoattr)
619 elif repoattr['type'] == 'yast2':
620 repo = repo_susetags(alias, 'susetags', repoattr)
622 repo = repo_unknown(alias, 'unknown', repoattr)
627 pool.set_loadcallback(load_stub)
629 # now load all enabled repos into the pool
630 sysrepo = repo_system('@System', 'system')
633 if int(repo['enabled']):
638 for reponame in options.repos:
639 mrepos = [ repo for repo in repos if repo.name == reponame ]
641 print "no repository matches '%s'" % reponame
644 if hasattr(repo, 'handle'):
646 repofilter = pool.Selection()
647 repofilter.add(repo.handle.Selection(solv.Job.SOLVER_SETVENDOR))
650 pool.createwhatprovides()
651 sel = pool.Selection()
652 di = pool.Dataiterator(0, solv.SOLVABLE_NAME, args[0], solv.Dataiterator.SEARCH_SUBSTRING|solv.Dataiterator.SEARCH_NOCASE)
654 sel.add_raw(solv.Job.SOLVER_SOLVABLE, d.solvid)
656 sel.filter(repofilter)
657 for s in sel.solvables():
658 print " - %s [%s]: %s" % (s, s.repo.name, s.lookup_str(solv.SOLVABLE_SUMMARY))
661 if cmd not in cmdactionmap:
662 print "unknown command", cmd
666 if cmd == 'list' or cmd == 'info' or cmd == 'install':
668 if arg.endswith(".rpm") and os.access(arg, os.R_OK):
670 cmdlinerepo = repo_cmdline('@commandline', 'cmdline')
671 cmdlinerepo.load(pool)
672 cmdlinerepo['packages'] = {}
673 cmdlinerepo['packages'][arg] = cmdlinerepo.handle.add_rpm(arg, solv.Repo.REPO_REUSE_REPODATA|solv.Repo.REPO_NO_INTERNALIZE)
675 cmdlinerepo.handle.internalize()
677 addedprovides = pool.addfileprovides_queue()
679 sysrepo.updateaddedprovides(addedprovides)
681 repo.updateaddedprovides(addedprovides)
683 pool.createwhatprovides()
685 # convert arguments into jobs
688 if cmdlinerepo and arg in cmdlinerepo['packages']:
689 jobs.append(pool.Job(solv.Job.SOLVER_SOLVABLE, cmdlinerepo['packages'][arg]))
691 flags = solv.Selection.SELECTION_NAME|solv.Selection.SELECTION_PROVIDES|solv.Selection.SELECTION_GLOB
692 flags |= solv.Selection.SELECTION_CANON|solv.Selection.SELECTION_DOTARCH|solv.Selection.SELECTION_REL
693 if len(arg) and arg[0] == '/':
694 flags |= solv.Selection.SELECTION_FILELIST
696 flags |= solv.Selection.SELECTION_INSTALLED_ONLY
697 sel = pool.select(arg, flags)
699 sel.filter(repofilter)
701 sel = pool.select(arg, flags | solv.Selection.SELECTION_NOCASE)
703 sel.filter(repofilter)
704 if not sel.isempty():
705 print "[ignoring case for '%s']" % arg
707 print "nothing matches '%s'" % arg
709 if sel.flags() & solv.Selection.SELECTION_FILELIST:
710 print "[using file list match for '%s']" % arg
711 if sel.flags() & solv.Selection.SELECTION_PROVIDES:
712 print "[using capability match for '%s']" % arg
713 jobs += sel.jobs(cmdactionmap[cmd])
715 if not jobs and (cmd == 'up' or cmd == 'dup' or cmd == 'verify' or repofilter):
716 sel = pool.Selection_all()
718 sel.filter(repofilter)
719 jobs += sel.jobs(cmdactionmap[cmd])
722 print "no package matched."
725 if cmd == 'list' or cmd == 'info':
727 for s in job.solvables():
730 print "Repo: %s" % s.repo
731 print "Summary: %s" % s.lookup_str(solv.SOLVABLE_SUMMARY)
732 str = s.lookup_str(solv.SOLVABLE_URL)
734 print "Url: %s" % str
735 str = s.lookup_str(solv.SOLVABLE_LICENSE)
737 print "License: %s" % str
738 print "Description:\n%s" % s.lookup_str(solv.SOLVABLE_DESCRIPTION)
741 print " - %s [%s]" % (s, s.repo)
742 print " %s" % s.lookup_str(solv.SOLVABLE_SUMMARY)
745 # up magic: use install instead of update if no installed package matches
747 if cmd == 'up' and job.isemptyupdate():
748 job.how ^= solv.Job.SOLVER_UPDATE ^ solv.Job.SOLVER_INSTALL
750 job.how |= solv.Job.SOLVER_FORCEBEST
752 job.how |= solv.Job.SOLVER_CLEANDEPS
754 #pool.set_debuglevel(2)
755 solver = pool.Solver()
756 solver.set_flag(solv.Solver.SOLVER_FLAG_SPLITPROVIDES, 1);
758 solver.set_flag(solv.Solver.SOLVER_FLAG_ALLOW_UNINSTALL, 1);
761 problems = solver.solve(jobs)
764 for problem in problems:
765 print "Problem %d/%d:" % (problem.id, len(problems))
766 r = problem.findproblemrule()
768 print ri.problemstr()
769 solutions = problem.solutions()
770 for solution in solutions:
771 print " Solution %d:" % solution.id
772 elements = solution.elements(True)
773 for element in elements:
774 print " - %s" % element.str()
777 while not (sol == 's' or sol == 'q' or (sol.isdigit() and int(sol) >= 1 and int(sol) <= len(solutions))):
778 sys.stdout.write("Please choose a solution: ")
780 sol = sys.stdin.readline().strip()
782 continue # skip problem
785 solution = solutions[int(sol) - 1]
786 for element in solution.elements():
787 newjob = element.Job()
788 if element.type == solv.Solver.SOLVER_SOLUTION_JOB:
789 jobs[element.jobidx] = newjob
791 if newjob and newjob not in jobs:
794 # no problems, show transaction
795 trans = solver.transaction()
798 print "Nothing to do."
801 print "Transaction summary:"
803 for cl in trans.classify(solv.Transaction.SOLVER_TRANSACTION_SHOW_OBSOLETES | solv.Transaction.SOLVER_TRANSACTION_OBSOLETE_IS_UPGRADE):
804 if cl.type == solv.Transaction.SOLVER_TRANSACTION_ERASE:
805 print "%d erased packages:" % cl.count
806 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_INSTALL:
807 print "%d installed packages:" % cl.count
808 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_REINSTALLED:
809 print "%d reinstalled packages:" % cl.count
810 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_DOWNGRADED:
811 print "%d downgraded packages:" % cl.count
812 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_CHANGED:
813 print "%d changed packages:" % cl.count
814 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_UPGRADED:
815 print "%d upgraded packages:" % cl.count
816 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_VENDORCHANGE:
817 print "%d vendor changes from '%s' to '%s':" % (cl.count, cl.fromdep(), cl.todep())
818 elif cl.type == solv.Transaction.SOLVER_TRANSACTION_ARCHCHANGE:
819 print "%d arch changes from '%s' to '%s':" % (cl.count, cl.fromdep(), cl.todep())
822 for p in cl.solvables():
823 if cl.type == solv.Transaction.SOLVER_TRANSACTION_UPGRADED or cl.type == solv.Transaction.SOLVER_TRANSACTION_DOWNGRADED:
824 op = trans.othersolvable(p)
825 print " - %s -> %s" % (p, op)
829 print "install size change: %d K" % trans.calc_installsizechange()
833 sys.stdout.write("OK to continue (y/n)? ")
835 yn = sys.stdin.readline().strip()
837 if yn == 'n' or yn == 'q': sys.exit(1)
838 newpkgs = trans.newpackages()
843 downloadsize += p.lookup_num(solv.SOLVABLE_DOWNLOADSIZE)
844 print "Downloading %d packages, %d K" % (len(newpkgs), downloadsize)
846 repo = p.repo.appdata
847 location, medianr = p.lookup_location()
850 if repo.type == 'commandline':
851 f = solv.xfopen(location)
853 sys.exit("\n%s: %s not found" % location)
856 if not sysrepo.handle.isempty() and os.access('/usr/bin/applydeltarpm', os.X_OK):
858 di = p.repo.Dataiterator(solv.SOLVID_META, solv.DELTA_PACKAGE_NAME, pname, solv.Dataiterator.SEARCH_STRING)
859 di.prepend_keyname(solv.REPOSITORY_DELTAINFO)
862 if dp.lookup_id(solv.DELTA_PACKAGE_EVR) != p.evrid or dp.lookup_id(solv.DELTA_PACKAGE_ARCH) != p.archid:
864 baseevrid = dp.lookup_id(solv.DELTA_BASE_EVR)
866 for installedp in pool.whatprovides(p.nameid):
867 if installedp.isinstalled() and installedp.nameid == p.nameid and installedp.archid == p.archid and installedp.evrid == baseevrid:
868 candidate = installedp
871 seq = dp.lookup_deltaseq()
872 st = subprocess.call(['/usr/bin/applydeltarpm', '-a', p.arch, '-c', '-s', seq])
875 chksum = dp.lookup_checksum(solv.DELTA_CHECKSUM)
878 dloc, dmedianr = dp.lookup_deltalocation()
879 dloc = repo.packagespath() + dloc
880 f = repo.download(dloc, False, chksum)
883 nf = tempfile.TemporaryFile()
884 nf = os.dup(nf.fileno()) # get rid of CLOEXEC
885 st = subprocess.call(['/usr/bin/applydeltarpm', '-a', p.arch, "/dev/fd/%d" % f.fileno(), "/dev/fd/%d" % nf])
886 os.lseek(nf, 0, os.SEEK_SET)
887 newpkgsfp[p.id] = solv.xfopen_fd("", nf)
890 if p.id in newpkgsfp:
891 sys.stdout.write("d")
895 chksum = p.lookup_checksum(solv.SOLVABLE_CHECKSUM)
896 location = repo.packagespath() + location
897 f = repo.download(location, False, chksum)
899 sys.exit("\n%s: %s not found in repository" % (repo.name, location))
901 sys.stdout.write(".")
904 print "Committing transaction:"
906 ts = rpm.TransactionSet('/')
907 ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
909 for p in trans.steps():
910 type = trans.steptype(p, solv.Transaction.SOLVER_TRANSACTION_RPM_ONLY)
911 if type == solv.Transaction.SOLVER_TRANSACTION_ERASE:
912 rpmdbid = p.lookup_num(solv.RPM_RPMDBID)
913 erasenamehelper[p.name] = p
915 sys.exit("\ninternal error: installed package %s has no rpmdbid\n" % p)
917 elif type == solv.Transaction.SOLVER_TRANSACTION_INSTALL:
919 h = ts.hdrFromFdno(f.fileno())
920 os.lseek(f.fileno(), 0, os.SEEK_SET)
921 ts.addInstall(h, p, 'u')
922 elif type == solv.Transaction.SOLVER_TRANSACTION_MULTIINSTALL:
924 h = ts.hdrFromFdno(f.fileno())
925 os.lseek(f.fileno(), 0, os.SEEK_SET)
926 ts.addInstall(h, p, 'i')
927 checkproblems = ts.check()
932 def runCallback(reason, amount, total, p, d):
933 if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
934 return newpkgsfp[p.id].fileno()
935 if reason == rpm.RPMCALLBACK_INST_START:
937 if reason == rpm.RPMCALLBACK_UNINST_START:
938 # argh, p is just the name of the package
939 if p in erasenamehelper:
940 p = erasenamehelper[p]
942 runproblems = ts.run(runCallback, '')