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 solv import Pool, Repo, Dataiterator, Job, Solver, Transaction, Selection
42 from iniparse import INIConfig
43 from optparse import OptionParser
46 #gc.set_debug(gc.DEBUG_LEAK)
48 def calc_cookie_file(filename):
49 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
51 chksum.add_stat(filename)
54 def calc_cookie_fp(fp):
55 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
59 class repo_generic(dict):
60 def __init__(self, name, type, attribs = {}):
66 def cachepath(self, ext = None):
67 path = re.sub(r'^\.', '_', self.name)
69 path += "_" + ext + ".solvx"
72 return "/var/cache/solv/" + re.sub(r'[/]', '_', path)
75 self.handle = pool.add_repo(self.name)
76 self.handle.appdata = self
77 self.handle.priority = 99 - self['priority']
78 dorefresh = bool(int(self['autorefresh']))
81 st = os.stat(self.cachepath())
82 if self['metadata_expire'] == -1 or time.time() - st[ST_MTIME] < self['metadata_expire']:
87 if not dorefresh and self.usecachedrepo(None):
88 print "repo: '%s': cached" % self.name
90 return self.load_if_changed()
92 def load_if_changed(self):
95 def load_ext(self, repodata):
98 def setfromurls(self, urls):
102 print "[using mirror %s]" % re.sub(r'^(.*?/...*?)/.*$', r'\1', url)
103 self['baseurl'] = url
105 def setfrommetalink(self, metalink):
106 nf = self.download(metalink, False, None)
109 f = os.fdopen(os.dup(solv.xfileno(nf)), 'r')
113 for l in f.readlines():
115 m = re.match(r'^<hash type="sha256">([0-9a-fA-F]{64})</hash>', l)
117 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256, m.group(1))
118 m = re.match(r'^<url.*>(https?://.+)repodata/repomd.xml</url>', l)
120 urls.append(m.group(1))
122 chksum = None # in case the metalink is about a different file
124 self.setfromurls(urls)
127 def setfrommirrorlist(self, mirrorlist):
128 nf = self.download(mirrorlist, False, None)
131 f = os.fdopen(os.dup(solv.xfileno(nf)), 'r')
134 for l in f.readline():
136 if l[0:6] == 'http://' or l[0:7] == 'https://':
138 self.setfromurls(urls)
141 def download(self, file, uncompress, chksum, markincomplete=False):
143 if 'baseurl' not in self:
144 if 'metalink' in self:
145 if file != self['metalink']:
146 metalinkchksum = self.setfrommetalink(self['metalink'])
147 if file == 'repodata/repomd.xml' and metalinkchksum and not chksum:
148 chksum = metalinkchksum
151 elif 'mirrorlist' in self:
152 if file != self['mirrorlist']:
153 self.setfrommirrorlist(self['mirrorlist'])
157 if 'baseurl' not in self:
158 print "%s: no baseurl" % self.name
160 url = re.sub(r'/$', '', self['baseurl']) + '/' + file
161 f = tempfile.TemporaryFile()
162 st = subprocess.call(['curl', '-f', '-s', '-L', url], stdout=f.fileno())
163 if os.lseek(f.fileno(), 0, os.SEEK_CUR) == 0 and (st == 0 or not chksum):
165 os.lseek(f.fileno(), 0, os.SEEK_SET)
167 print "%s: download error %d" % (file, st)
169 self['incomplete'] = True
172 fchksum = solv.Chksum(chksum.type)
174 print "%s: unknown checksum type" % file
176 self['incomplete'] = True
178 fchksum.add_fd(f.fileno())
179 if fchksum != chksum:
180 print "%s: checksum mismatch" % file
182 self['incomplete'] = True
185 return solv.xfopen_fd(file, os.dup(f.fileno()))
186 return solv.xfopen_fd(None, os.dup(f.fileno()))
188 def usecachedrepo(self, ext, mark=False):
190 cookie = self['cookie']
192 cookie = self['extcookie']
194 repopath = self.cachepath(ext)
195 f = open(repopath, 'r')
196 f.seek(-32, os.SEEK_END)
198 if len(fcookie) != 32:
200 if cookie and fcookie != cookie:
202 if self.type != 'system' and not ext:
203 f.seek(-32 * 2, os.SEEK_END)
204 fextcookie = f.read(32)
205 if len(fextcookie) != 32:
210 flags = Repo.REPO_USE_LOADING|Repo.REPO_EXTEND_SOLVABLES
212 flags |= Repo.REPO_LOCALPOOL
213 if not self.handle.add_solv(f, flags):
215 if self.type != 'system' and not ext:
216 self['cookie'] = fcookie
217 self['extcookie'] = fextcookie
219 # no futimes in python?
221 os.utime(repopath, None)
228 def genextcookie(self, f):
229 chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256)
230 chksum.add(self['cookie'])
232 stat = os.fstat(f.fileno())
233 chksum.add(str(stat[ST_DEV]))
234 chksum.add(str(stat[ST_INO]))
235 chksum.add(str(stat[ST_SIZE]))
236 chksum.add(str(stat[ST_MTIME]))
237 extcookie = chksum.raw()
238 # compatibility to c code
239 if ord(extcookie[0]) == 0:
240 extcookie[0] = chr(1)
241 self['extcookie'] = extcookie
243 def writecachedrepo(self, ext, info=None):
245 if not os.path.isdir("/var/cache/solv"):
246 os.mkdir("/var/cache/solv", 0755)
247 (fd, tmpname) = tempfile.mkstemp(prefix='.newsolv-', dir='/var/cache/solv')
249 f = os.fdopen(fd, 'w+')
254 else: # rewrite_repos case
255 self.handle.write_first_repodata(f)
256 if self.type != 'system' and not ext:
257 if 'extcookie' not in self:
259 f.write(self['extcookie'])
261 f.write(self['cookie'])
263 f.write(self['extcookie'])
265 if self.handle.iscontiguous():
266 # switch to saved repo to activate paging and save memory
267 nf = solv.xfopen(tmpname)
271 if not self.handle.add_solv(nf, Repo.SOLV_ADD_NO_STUBS):
272 sys.exit("internal error, cannot reload solv file")
275 # need to extend to repo boundaries, as this is how
276 # info.write() has written the data
277 info.extend_to_repo()
278 # LOCALPOOL does not help as pool already contains all ids
279 info.add_solv(nf, Repo.REPO_EXTEND_SOLVABLES)
281 os.rename(tmpname, self.cachepath(ext))
286 def updateaddedprovides(self, addedprovides):
287 if 'incomplete' in self:
289 if not hasattr(self, 'handle'):
291 if self.handle.isempty():
293 # make sure there's just one real repodata with extensions
294 repodata = self.handle.first_repodata()
297 oldaddedprovides = repodata.lookup_idarray(solv.SOLVID_META, solv.REPOSITORY_ADDEDFILEPROVIDES)
298 if not set(addedprovides) <= set(oldaddedprovides):
299 for id in addedprovides:
300 repodata.add_idarray(solv.SOLVID_META, solv.REPOSITORY_ADDEDFILEPROVIDES, id)
301 repodata.internalize()
302 self.writecachedrepo(None, repodata)
304 class repo_repomd(repo_generic):
305 def load_if_changed(self):
306 print "rpmmd repo '%s':" % self.name,
308 f = self.download("repodata/repomd.xml", False, None, None)
310 print "no repomd.xml file, skipped"
311 self.handle.free(True)
314 self['cookie'] = calc_cookie_fp(f)
315 if self.usecachedrepo(None, True):
319 self.handle.add_repomdxml(f, 0)
322 (filename, filechksum) = self.find('primary')
324 f = self.download(filename, True, filechksum, True)
326 self.handle.add_rpmmd(f, None, 0)
328 if 'incomplete' in self:
329 return False # hopeless, need good primary
330 (filename, filechksum) = self.find('updateinfo')
332 f = self.download(filename, True, filechksum, True)
334 self.handle.add_updateinfoxml(f, 0)
337 if 'incomplete' not in self:
338 self.writecachedrepo(None)
339 # must be called after writing the repo
340 self.handle.create_stubs()
343 def find(self, what):
344 di = self.handle.Dataiterator(solv.SOLVID_META, solv.REPOSITORY_REPOMD_TYPE, what, Dataiterator.SEARCH_STRING)
345 di.prepend_keyname(solv.REPOSITORY_REPOMD)
348 filename = dp.lookup_str(solv.REPOSITORY_REPOMD_LOCATION)
349 chksum = dp.lookup_checksum(solv.REPOSITORY_REPOMD_CHECKSUM)
350 if filename and not chksum:
351 print "no %s file checksum!" % filename
355 return (filename, chksum)
358 def add_ext(self, repodata, what, ext):
359 filename, chksum = self.find(what)
360 if not filename and what == 'deltainfo':
361 filename, chksum = self.find('prestodelta')
364 handle = repodata.new_handle()
365 repodata.set_poolstr(handle, solv.REPOSITORY_REPOMD_TYPE, what)
366 repodata.set_str(handle, solv.REPOSITORY_REPOMD_LOCATION, filename)
367 repodata.set_checksum(handle, solv.REPOSITORY_REPOMD_CHECKSUM, chksum)
369 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOSITORY_DELTAINFO)
370 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_FLEXARRAY)
372 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_FILELIST)
373 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRSTRARRAY)
374 repodata.add_flexarray(solv.SOLVID_META, solv.REPOSITORY_EXTERNAL, handle)
377 repodata = self.handle.add_repodata(0)
378 self.add_ext(repodata, 'deltainfo', 'DL')
379 self.add_ext(repodata, 'filelists', 'FL')
380 repodata.internalize()
382 def load_ext(self, repodata):
383 repomdtype = repodata.lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_TYPE)
384 if repomdtype == 'filelists':
386 elif repomdtype == 'deltainfo':
390 sys.stdout.write("[%s:%s: " % (self.name, ext))
391 if self.usecachedrepo(ext):
392 sys.stdout.write("cached]\n")
395 sys.stdout.write("fetching]\n")
397 filename = repodata.lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_LOCATION)
398 filechksum = repodata.lookup_checksum(solv.SOLVID_META, solv.REPOSITORY_REPOMD_CHECKSUM)
399 f = self.download(filename, True, filechksum)
403 self.handle.add_rpmmd(f, 'FL', Repo.REPO_USE_LOADING|Repo.REPO_EXTEND_SOLVABLES)
405 self.handle.add_deltainfoxml(f, Repo.REPO_USE_LOADING)
407 self.writecachedrepo(ext, repodata)
410 class repo_susetags(repo_generic):
411 def load_if_changed(self):
412 print "susetags repo '%s':" % self.name,
414 f = self.download("content", False, None, None)
416 print "no content file, skipped"
417 self.handle.free(True)
420 self['cookie'] = calc_cookie_fp(f)
421 if self.usecachedrepo(None, True):
425 self.handle.add_content(f, 0)
428 defvendorid = self.handle.lookup_id(solv.SOLVID_META, solv.SUSETAGS_DEFAULTVENDOR)
429 descrdir = self.handle.lookup_str(solv.SOLVID_META, solv.SUSETAGS_DESCRDIR)
431 descrdir = "suse/setup/descr"
432 (filename, filechksum) = self.find('packages.gz')
434 (filename, filechksum) = self.find('packages')
436 f = self.download(descrdir + '/' + filename, True, filechksum, True)
438 self.handle.add_susetags(f, defvendorid, None, Repo.REPO_NO_INTERNALIZE|Repo.SUSETAGS_RECORD_SHARES)
440 (filename, filechksum) = self.find('packages.en.gz')
442 (filename, filechksum) = self.find('packages.en')
444 f = self.download(descrdir + '/' + filename, True, filechksum, True)
446 self.handle.add_susetags(f, defvendorid, None, Repo.REPO_NO_INTERNALIZE|Repo.REPO_REUSE_REPODATA|Repo.REPO_EXTEND_SOLVABLES)
448 self.handle.internalize()
450 if 'incomplete' not in self:
451 self.writecachedrepo(None)
452 # must be called after writing the repo
453 self.handle.create_stubs()
456 def find(self, what):
457 di = self.handle.Dataiterator(solv.SOLVID_META, solv.SUSETAGS_FILE_NAME, what, Dataiterator.SEARCH_STRING)
458 di.prepend_keyname(solv.SUSETAGS_FILE)
461 chksum = dp.lookup_checksum(solv.SUSETAGS_FILE_CHECKSUM)
462 return (what, chksum)
465 def add_ext(self, repodata, what, ext):
466 (filename, chksum) = self.find(what)
469 handle = repodata.new_handle()
470 repodata.set_str(handle, solv.SUSETAGS_FILE_NAME, filename)
472 repodata.set_checksum(handle, solv.SUSETAGS_FILE_CHECKSUM, chksum)
474 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_DISKUSAGE)
475 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRNUMNUMARRAY)
477 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_FILELIST)
478 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRSTRARRAY)
480 for langtag, langtagtype in [
481 (solv.SOLVABLE_SUMMARY, solv.REPOKEY_TYPE_STR),
482 (solv.SOLVABLE_DESCRIPTION, solv.REPOKEY_TYPE_STR),
483 (solv.SOLVABLE_EULA, solv.REPOKEY_TYPE_STR),
484 (solv.SOLVABLE_MESSAGEINS, solv.REPOKEY_TYPE_STR),
485 (solv.SOLVABLE_MESSAGEDEL, solv.REPOKEY_TYPE_STR),
486 (solv.SOLVABLE_CATEGORY, solv.REPOKEY_TYPE_ID)
488 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, self.handle.pool.id2langid(langtag, ext, 1))
489 repodata.add_idarray(handle, solv.REPOSITORY_KEYS, langtagtype)
490 repodata.add_flexarray(solv.SOLVID_META, solv.REPOSITORY_EXTERNAL, handle)
493 repodata = self.handle.add_repodata(0)
494 di = self.handle.Dataiterator(solv.SOLVID_META, solv.SUSETAGS_FILE_NAME, None, 0)
495 di.prepend_keyname(solv.SUSETAGS_FILE)
500 if filename[0:9] != "packages.":
502 if len(filename) == 11 and filename != "packages.gz":
504 elif filename[11:12] == ".":
510 self.add_ext(repodata, filename, ext)
511 repodata.internalize()
513 def load_ext(self, repodata):
514 filename = repodata.lookup_str(solv.SOLVID_META, solv.SUSETAGS_FILE_NAME)
516 sys.stdout.write("[%s:%s: " % (self.name, ext))
517 if self.usecachedrepo(ext):
518 sys.stdout.write("cached]\n")
521 sys.stdout.write("fetching]\n")
523 defvendorid = self.handle.lookup_id(solv.SOLVID_META, solv.SUSETAGS_DEFAULTVENDOR)
524 descrdir = self.handle.lookup_str(solv.SOLVID_META, solv.SUSETAGS_DESCRDIR)
526 descrdir = "suse/setup/descr"
527 filechksum = repodata.lookup_checksum(solv.SOLVID_META, solv.SUSETAGS_FILE_CHECKSUM)
528 f = self.download(descrdir + '/' + filename, True, filechksum)
531 self.handle.add_susetags(f, defvendorid, ext, Repo.REPO_USE_LOADING|Repo.REPO_EXTEND_SOLVABLES)
533 self.writecachedrepo(ext, repodata)
536 class repo_unknown(repo_generic):
537 def load(self, pool):
538 print "unsupported repo '%s': skipped" % self.name
541 class repo_system(repo_generic):
542 def load(self, pool):
543 self.handle = pool.add_repo(self.name)
544 self.handle.appdata = self
545 pool.installed = self.handle
546 print "rpm database:",
547 self['cookie'] = calc_cookie_file("/var/lib/rpm/Packages")
548 if self.usecachedrepo(None):
552 if hasattr(self.handle.__class__, 'add_products'):
553 self.handle.add_products("/etc/products.d", Repo.REPO_NO_INTERNALIZE)
554 self.handle.add_rpmdb(None, Repo.REPO_REUSE_REPODATA)
555 self.writecachedrepo(None)
558 class repo_cmdline(repo_generic):
559 def load(self, pool):
560 self.handle = pool.add_repo(self.name)
561 self.handle.appdata = self
564 def load_stub(repodata):
565 repo = repodata.repo.appdata
567 return repo.load_ext(repodata)
571 parser = OptionParser(usage="usage: solv.py [options] COMMAND")
572 parser.add_option('-r', '--repo', action="append", type="string", dest="repos")
573 (options, args) = parser.parse_args()
575 parser.print_help(sys.stderr)
592 # read all repo configs
595 if os.path.isdir("/etc/zypp/repos.d"):
596 reposdirs = [ "/etc/zypp/repos.d" ]
598 reposdirs = [ "/etc/yum/repos.d" ]
600 for reposdir in ["/etc/zypp/repos.d"]:
601 if not os.path.isdir(reposdir):
603 for reponame in sorted(glob.glob('%s/*.repo' % reposdir)):
604 cfg = INIConfig(open(reponame))
606 repoattr = {'enabled': 0, 'priority': 99, 'autorefresh': 1, 'type': 'rpm-md', 'metadata_expire': 900}
608 repoattr[k] = cfg[alias][k]
609 if 'mirrorlist' in repoattr and 'metalink' not in repoattr:
610 if repoattr['mirrorlist'].find('/metalink'):
611 repoattr['metalink'] = repoattr['mirrorlist']
612 del repoattr['mirrorlist']
613 if repoattr['type'] == 'rpm-md':
614 repo = repo_repomd(alias, 'repomd', repoattr)
615 elif repoattr['type'] == 'yast2':
616 repo = repo_susetags(alias, 'susetags', repoattr)
618 repo = repo_unknown(alias, 'unknown', repoattr)
622 pool.setarch(os.uname()[4])
623 pool.set_loadcallback(load_stub)
625 # now load all enabled repos into the pool
626 sysrepo = repo_system('@System', 'system')
629 if int(repo['enabled']):
634 for reponame in options.repos:
635 mrepos = [ repo for repo in repos if repo.name == reponame ]
637 print "no repository matches '%s'" % reponame
640 if hasattr(repo, 'handle'):
642 repolimiter = pool.Selection()
643 repolimiter.addsimple(Job.SOLVER_SOLVABLE_REPO|Job.SOLVER_SETREPO|Job.SOLVER_SETVENDOR, repo.handle.id)
647 di = pool.Dataiterator(0, solv.SOLVABLE_NAME, args[0], Dataiterator.SEARCH_SUBSTRING|Dataiterator.SEARCH_NOCASE)
649 matches[d.solvid] = True
650 for solvid in sorted(matches.keys()):
651 print " - %s [%s]: %s" % (pool.solvid2str(solvid), pool.solvables[solvid].repo.name, pool.lookup_str(solvid, solv.SOLVABLE_SUMMARY))
655 if cmd == 'list' or cmd == 'info' or cmd == 'install':
657 if arg.endswith(".rpm") and os.access(arg, os.R_OK):
659 cmdlinerepo = repo_cmdline('@commandline', 'cmdline')
660 cmdlinerepo.load(pool)
661 cmdlinerepo['packages'] = {}
662 cmdlinerepo['packages'][arg] = cmdlinerepo.handle.add_rpm(arg, Repo.REPO_REUSE_REPODATA|Repo.REPO_NO_INTERNALIZE)
664 cmdlinerepo.handle.internalize()
666 addedprovides = pool.addfileprovides_queue()
668 sysrepo.updateaddedprovides(addedprovides)
670 repo.updateaddedprovides(addedprovides)
672 pool.createwhatprovides()
674 # convert arguments into jobs
677 if cmdlinerepo and arg in cmdlinerepo['packages']:
678 jobs.append(pool.Job(Job.SOLVER_SOLVABLE, cmdlinerepo['packages'][arg]))
680 flags = Selection.SELECTION_NAME|Selection.SELECTION_PROVIDES|Selection.SELECTION_GLOB
681 if len(arg) and arg[0] == '/':
682 flags |= Selection.SELECTION_FILELIST
684 flags |= Selection.SELECTION_INSTALLED_ONLY
685 sel = pool.select(arg, flags)
687 sel.limit(repolimiter)
689 sel = pool.select(arg, flags | Selection.SELECTION_NOCASE)
691 sel.limit(repolimiter)
692 if not sel.isempty():
693 print "[ignoring case for '%s']" % arg
695 print "nothing matches '%s'" % arg
697 if sel.flags() & Selection.SELECTION_FILELIST:
698 print "[using file list match for '%s']" % arg
699 if sel.flags() & Selection.SELECTION_PROVIDES:
700 print "[using capability match for '%s']" % arg
703 if not jobs and (cmd == 'up' or cmd == 'dup' or cmd == 'verify' or repolimiter):
704 sel = pool.Selection()
705 sel.addsimple(Job.SOLVER_SOLVABLE_ALL, 0)
707 sel.limit(repolimiter)
710 if cmd == 'list' or cmd == 'info':
712 print "no package matched."
715 for s in job.solvables():
718 print "Repo: %s" % s.repo
719 print "Summary: %s" % s.lookup_str(solv.SOLVABLE_SUMMARY)
720 str = s.lookup_str(solv.SOLVABLE_URL)
722 print "Url: %s" % str
723 str = s.lookup_str(solv.SOLVABLE_LICENSE)
725 print "License: %s" % str
726 print "Description:\n%s" % s.lookup_str(solv.SOLVABLE_DESCRIPTION)
729 print " - %s [%s]" % (s, s.repo)
730 print " %s" % s.lookup_str(solv.SOLVABLE_SUMMARY)
733 if cmd == 'install' or cmd == 'erase' or cmd == 'up' or cmd == 'dup' or cmd == 'verify':
735 print "no package matched."
739 # up magic: use install instead of update if no installed package matches
740 if job.how == Job.SOLVER_SOLVABLE_ALL or filter(lambda s: s.isinstalled(), job.solvables()):
741 job.how |= Job.SOLVER_UPDATE
743 job.how |= Job.SOLVER_INSTALL
744 elif cmd == 'install':
745 job.how |= Job.SOLVER_INSTALL
747 job.how |= Job.SOLVER_ERASE
749 job.how |= Job.SOLVER_DISTUPGRADE
750 elif cmd == 'verify':
751 job.how |= Job.SOLVER_VERIFY
753 #pool.set_debuglevel(2)
756 solver = pool.Solver()
757 solver.set_flag(Solver.SOLVER_FLAG_SPLITPROVIDES, 1);
759 solver.set_flag(Solver.SOLVER_FLAG_ALLOW_UNINSTALL, 1);
760 problems = solver.solve(jobs)
763 for problem in problems:
764 print "Problem %d:" % problem.id
765 r = problem.findproblemrule()
767 print ri.problemstr()
768 solutions = problem.solutions()
769 for solution in solutions:
770 print " Solution %d:" % solution.id
771 elements = solution.elements(True)
772 for element in elements:
773 print " - %s" % element.str()
776 while not (sol == 's' or sol == 'q' or (sol.isdigit() and int(sol) >= 1 and int(sol) <= len(solutions))):
777 sys.stdout.write("Please choose a solution: ")
779 sol = sys.stdin.readline().strip()
781 continue # skip problem
784 solution = solutions[int(sol) - 1]
785 for element in solution.elements():
786 newjob = element.Job()
787 if element.type == Solver.SOLVER_SOLUTION_JOB:
788 jobs[element.jobidx] = newjob
790 if newjob and newjob not in jobs:
793 # no problems, show transaction
794 trans = solver.transaction()
797 print "Nothing to do."
800 print "Transaction summary:"
802 for cl in trans.classify():
803 if cl.type == Transaction.SOLVER_TRANSACTION_ERASE:
804 print "%d erased packages:" % cl.count
805 elif cl.type == Transaction.SOLVER_TRANSACTION_INSTALL:
806 print "%d installed packages:" % cl.count
807 elif cl.type == Transaction.SOLVER_TRANSACTION_REINSTALLED:
808 print "%d reinstalled packages:" % cl.count
809 elif cl.type == Transaction.SOLVER_TRANSACTION_DOWNGRADED:
810 print "%d downgraded packages:" % cl.count
811 elif cl.type == Transaction.SOLVER_TRANSACTION_CHANGED:
812 print "%d changed packages:" % cl.count
813 elif cl.type == Transaction.SOLVER_TRANSACTION_UPGRADED:
814 print "%d upgraded packages:" % cl.count
815 elif cl.type == Transaction.SOLVER_TRANSACTION_VENDORCHANGE:
816 print "%d vendor changes from '%s' to '%s':" % (cl.count, pool.id2str(cl.fromid), pool.id2str(cl.toid))
817 elif cl.type == Transaction.SOLVER_TRANSACTION_ARCHCHANGE:
818 print "%d arch changes from '%s' to '%s':" % (cl.count, pool.id2str(cl.fromid), pool.id2str(cl.toid))
821 for p in cl.solvables():
822 if cl.type == Transaction.SOLVER_TRANSACTION_UPGRADED or cl.type == Transaction.SOLVER_TRANSACTION_DOWNGRADED:
823 op = trans.othersolvable(p)
824 print " - %s -> %s" % (p, op)
828 print "install size change: %d K" % trans.calc_installsizechange()
832 sys.stdout.write("OK to continue (y/n)? ")
834 yn = sys.stdin.readline().strip()
836 if yn == 'n': sys.exit(1)
837 newpkgs = trans.newpackages()
842 downloadsize += p.lookup_num(solv.SOLVABLE_DOWNLOADSIZE)
843 print "Downloading %d packages, %d K" % (len(newpkgs), downloadsize)
845 repo = p.repo.appdata
846 location, medianr = p.lookup_location()
849 if repo.type == 'commandline':
850 f = solv.xfopen(location)
852 sys.exit("\n%s: %s not found" % location)
855 if not sysrepo.handle.isempty() and os.access('/usr/bin/applydeltarpm', os.X_OK):
857 di = p.repo.Dataiterator(solv.SOLVID_META, solv.DELTA_PACKAGE_NAME, pname, Dataiterator.SEARCH_STRING)
858 di.prepend_keyname(solv.REPOSITORY_DELTAINFO)
861 if dp.lookup_id(solv.DELTA_PACKAGE_EVR) != p.evrid or dp.lookup_id(solv.DELTA_PACKAGE_ARCH) != p.archid:
863 baseevrid = dp.lookup_id(solv.DELTA_BASE_EVR)
865 for installedp in pool.whatprovides(p.nameid):
866 if installedp.isinstalled() and installedp.nameid == p.nameid and installedp.archid == p.archid and installedp.evrid == baseevrid:
867 candidate = installedp
870 seq = dp.lookup_str(solv.DELTA_SEQ_NAME) + '-' + dp.lookup_str(solv.DELTA_SEQ_EVR) + '-' + dp.lookup_str(solv.DELTA_SEQ_NUM)
871 st = subprocess.call(['/usr/bin/applydeltarpm', '-a', p.arch, '-c', '-s', seq])
874 chksum = dp.lookup_checksum(solv.DELTA_CHECKSUM)
877 dloc = dp.lookup_str(solv.DELTA_LOCATION_DIR) + '/' + dp.lookup_str(solv.DELTA_LOCATION_NAME) + '-' + dp.lookup_str(solv.DELTA_LOCATION_EVR) + '.' + dp.lookup_str(solv.DELTA_LOCATION_SUFFIX)
878 f = repo.download(dloc, False, chksum)
881 nf = tempfile.TemporaryFile()
882 nf = os.dup(nf.fileno())
883 st = subprocess.call(['/usr/bin/applydeltarpm', '-a', p.arch, "/dev/fd/%d" % solv.xfileno(f), "/dev/fd/%d" % nf])
885 os.lseek(nf, 0, os.SEEK_SET)
886 newpkgsfp[p.id] = solv.xfopen_fd("", nf)
888 if p.id in newpkgsfp:
889 sys.stdout.write("d")
893 if repo.type == 'susetags':
894 datadir = repo.handle.lookup_str(solv.SOLVID_META, solv.SUSETAGS_DATADIR)
897 location = datadir + '/' + location
898 chksum = p.lookup_checksum(solv.SOLVABLE_CHECKSUM)
899 f = repo.download(location, False, chksum)
901 sys.exit("\n%s: %s not found in repository" % (repo.name, location))
903 sys.stdout.write(".")
906 print "Committing transaction:"
908 ts = rpm.TransactionSet('/')
909 ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
911 for p in trans.steps():
912 type = trans.steptype(p, Transaction.SOLVER_TRANSACTION_RPM_ONLY)
913 if type == Transaction.SOLVER_TRANSACTION_ERASE:
914 rpmdbid = p.lookup_num(solv.RPM_RPMDBID)
915 erasenamehelper[p.name] = p
917 sys.exit("\ninternal error: installed package %s has no rpmdbid\n" % p)
919 elif type == Transaction.SOLVER_TRANSACTION_INSTALL:
921 h = ts.hdrFromFdno(solv.xfileno(f))
922 os.lseek(solv.xfileno(f), 0, os.SEEK_SET)
923 ts.addInstall(h, p, 'u')
924 elif type == Transaction.SOLVER_TRANSACTION_MULTIINSTALL:
926 h = ts.hdrFromFdno(solv.xfileno(f))
927 os.lseek(solv.xfileno(f), 0, os.SEEK_SET)
928 ts.addInstall(h, p, 'i')
929 checkproblems = ts.check()
934 def runCallback(reason, amount, total, p, d):
935 if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
936 return solv.xfileno(newpkgsfp[p.id])
937 if reason == rpm.RPMCALLBACK_INST_START:
939 if reason == rpm.RPMCALLBACK_UNINST_START:
940 # argh, p is just the name of the package
941 if p in erasenamehelper:
942 p = erasenamehelper[p]
944 runproblems = ts.run(runCallback, '')
950 print "unknown command", cmd