#!/usr/bin/python -tt
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Copyright 2004 Duke University
import rpmUtils
import rpmUtils.miscutils
import rpmUtils.arch
def _vertup_cmp(tup1, tup2):
return rpmUtils.miscutils.compareEVR(tup1, tup2)
class Updates:
"""
This class computes and keeps track of updates and obsoletes.
initialize, add installed packages, add available packages (both as
unique lists of name, arch, ver, rel, epoch tuples), add an optional dict
of obsoleting packages with obsoletes and what they obsolete ie::
foo, i386, 0, 1.1, 1: bar >= 1.1.
"""
def __init__(self, instlist, availlist):
self.installed = instlist # list of installed pkgs (n, a, e, v, r)
self.available = availlist # list of available pkgs (n, a, e, v, r)
self.rawobsoletes = {} # dict of obsoleting package->[what it obsoletes]
self._obsoletes_by_name = None
self.obsoleted_dict = {} # obsoleted pkgtup -> [ obsoleting pkgtups ]
self.obsoleting_dict = {} # obsoleting pkgtup -> [ obsoleted pkgtups ]
self.exactarch = 1 # don't change archs by default
self.exactarchlist = set(['kernel', 'kernel-smp', 'glibc',
'kernel-hugemem',
'kernel-enterprise', 'kernel-bigmem',
'kernel-BOOT'])
self.myarch = rpmUtils.arch.canonArch # set this if you want to
# test on some other arch
# otherwise leave it alone
self._is_multilib = rpmUtils.arch.isMultiLibArch(self.myarch)
self._archlist = rpmUtils.arch.getArchList(self.myarch)
self._multilib_compat_arches = rpmUtils.arch.getMultiArchInfo(self.myarch)
# make some dicts from installed and available
self.installdict = self.makeNADict(self.installed, 1)
self.availdict = self.makeNADict(self.available, 0, # Done in doUpdate
filter=self.installdict)
# holder for our updates dict
self.updatesdict = {}
self.updating_dict = {}
#debug, ignore me
self.debug = 0
self.obsoletes = {}
def _delFromDict(self, dict_, keys, value):
for key in keys:
if key not in dict_:
continue
dict_[key] = filter(value.__ne__, dict_[key])
if not dict_[key]:
del dict_[key]
def _delFromNADict(self, dict_, pkgtup):
(n, a, e, v, r) = pkgtup
for aa in (a, None):
if (n, aa) in dict_:
dict_[(n, aa)] = filter((e,v,r).__ne__, dict_[(n, aa)])
if not dict_[(n, aa)]:
del dict_[(n, aa)]
def delPackage(self, pkgtup):
"""remove available pkgtup that is no longer available"""
if pkgtup not in self.available:
return
self.available.remove(pkgtup)
self._delFromNADict(self.availdict, pkgtup)
self._delFromDict(self.updating_dict, self.updatesdict.get(pkgtup, []), pkgtup)
self._delFromDict(self.updatesdict, self.updating_dict.get(pkgtup, []), pkgtup)
if pkgtup in self.rawobsoletes:
if self._obsoletes_by_name:
for name, flag, version in self.rawobsoletes[pkgtup]:
self._delFromDict(self._obsoletes_by_name, [name], (flag, version, pkgtup))
del self.rawobsoletes[pkgtup]
self._delFromDict(self.obsoleted_dict, self.obsoleting_dict.get(pkgtup, []), pkgtup)
self._delFromDict(self.obsoleting_dict, self.obsoleted_dict.get(pkgtup, []), pkgtup)
def debugprint(self, msg):
if self.debug:
print msg
def makeNADict(self, pkglist, Nonelists, filter=None):
"""return lists of (e,v,r) tuples as value of a dict keyed on (n, a)
optionally will return a (n, None) entry with all the a for that
n in tuples of (a,e,v,r)"""
returndict = {}
for (n, a, e, v, r) in pkglist:
if filter and (n, None) not in filter:
continue
if (n, a) not in returndict:
returndict[(n, a)] = []
if (e,v,r) in returndict[(n, a)]:
continue
returndict[(n, a)].append((e,v,r))
if Nonelists:
if (n, None) not in returndict:
returndict[(n, None)] = []
if (a,e,v,r) in returndict[(n, None)]:
continue
returndict[(n, None)].append((a, e, v, r))
return returndict
def returnNewest(self, evrlist):
"""takes a list of (e, v, r) tuples and returns the newest one"""
if len(evrlist)==0:
raise rpmUtils.RpmUtilsError, "Zero Length List in returnNewest call"
if len(evrlist)==1:
return evrlist[0]
(new_e, new_v, new_r) = evrlist[0] # we'll call the first ones 'newest'
for (e, v, r) in evrlist[1:]:
rc = rpmUtils.miscutils.compareEVR((e, v, r), (new_e, new_v, new_r))
if rc > 0:
new_e = e
new_v = v
new_r = r
return (new_e, new_v, new_r)
def returnHighestVerFromAllArchsByName(self, name, archlist, pkglist):
"""returns a list of package tuples in a list (n, a, e, v, r)
takes a package name, a list of archs, and a list of pkgs in
(n, a, e, v, r) form."""
returnlist = []
high_vertup = None
for pkgtup in pkglist:
(n, a, e, v, r) = pkgtup
# FIXME: returnlist used to _possibly_ contain things not in
# archlist ... was that desired?
if name == n and a in archlist:
vertup = (e, v, r)
if (high_vertup is None or
(_vertup_cmp(high_vertup, vertup) < 0)):
high_vertup = vertup
returnlist = []
if vertup == high_vertup:
returnlist.append(pkgtup)
return returnlist
def condenseUpdates(self):
"""remove any accidental duplicates in updates"""
for tup in self.updatesdict:
if len(self.updatesdict[tup]) > 1:
mylist = self.updatesdict[tup]
self.updatesdict[tup] = rpmUtils.miscutils.unique(mylist)
def checkForObsolete(self, pkglist, newest=1):
"""accept a list of packages to check to see if anything obsoletes them
return an obsoleted_dict in the format of makeObsoletedDict"""
if self._obsoletes_by_name is None:
self._obsoletes_by_name = {}
for pkgtup, obsoletes in self.rawobsoletes.iteritems():
for name, flag, version in obsoletes:
self._obsoletes_by_name.setdefault(name, []).append(
(flag, version, pkgtup) )
obsdict = {} # obseleting package -> [obsoleted package]
for pkgtup in pkglist:
name = pkgtup[0]
for obs_flag, obs_version, obsoleting in self._obsoletes_by_name.get(name, []):
if obs_flag in [None, 0] and name == obsoleting[0]: continue
if rpmUtils.miscutils.rangeCheck( (name, obs_flag, obs_version), pkgtup):
obsdict.setdefault(obsoleting, []).append(pkgtup)
if not obsdict:
return {}
obslist = obsdict.keys()
if newest:
obslist = self._reduceListNewestByNameArch(obslist)
returndict = {}
for new in obslist:
for old in obsdict[new]:
if old not in returndict:
returndict[old] = []
returndict[old].append(new)
return returndict
def doObsoletes(self):
"""figures out what things available obsolete things installed, returns
them in a dict attribute of the class."""
obsdict = {} # obseleting package -> [obsoleted package]
# this needs to keep arch in mind
# if foo.i386 obsoletes bar
# it needs to obsoletes bar.i386 preferentially, not bar.x86_64
# if there is only one bar and only one foo then obsolete it, but try to
# match the arch.
# look through all the obsoleting packages look for multiple archs per name
# if you find it look for the packages they obsolete
#
obs_arches = {}
for (n, a, e, v, r) in self.rawobsoletes:
if n not in obs_arches:
obs_arches[n] = []
obs_arches[n].append(a)
for pkgtup in self.rawobsoletes:
(name, arch, epoch, ver, rel) = pkgtup
for (obs_n, flag, (obs_e, obs_v, obs_r)) in self.rawobsoletes[(pkgtup)]:
if (obs_n, None) in self.installdict:
for (rpm_a, rpm_e, rpm_v, rpm_r) in self.installdict[(obs_n, None)]:
if flag in [None, 0] or \
rpmUtils.miscutils.rangeCheck((obs_n, flag, (obs_e, obs_v, obs_r)),
(obs_n, rpm_a, rpm_e, rpm_v, rpm_r)):
# make sure the obsoleting pkg is not already installed
willInstall = 1
if (name, None) in self.installdict:
for (ins_a, ins_e, ins_v, ins_r) in self.installdict[(name, None)]:
pkgver = (epoch, ver, rel)
installedver = (ins_e, ins_v, ins_r)
if self.returnNewest((pkgver, installedver)) == installedver:
willInstall = 0
break
if rpm_a != arch and rpm_a in obs_arches[name]:
willInstall = 0
if willInstall:
if pkgtup not in obsdict:
obsdict[pkgtup] = []
obsdict[pkgtup].append((obs_n, rpm_a, rpm_e, rpm_v, rpm_r))
self.obsoletes = obsdict
self.makeObsoletedDict()
def makeObsoletedDict(self):
"""creates a dict of obsoleted packages -> [obsoleting package], this
is to make it easier to look up what package obsoletes what item in
the rpmdb"""
self.obsoleted_dict = {}
for new in self.obsoletes:
for old in self.obsoletes[new]:
if old not in self.obsoleted_dict:
self.obsoleted_dict[old] = []
self.obsoleted_dict[old].append(new)
self.obsoleting_dict = {}
for obsoleted, obsoletings in self.obsoleted_dict.iteritems():
for obsoleting in obsoletings:
self.obsoleting_dict.setdefault(obsoleting, []).append(obsoleted)
def doUpdates(self):
"""check for key lists as populated then commit acts of evil to
determine what is updated and/or obsoleted, populate self.updatesdict
"""
# best bet is to chew through the pkgs and throw out the new ones early
# then deal with the ones where there are a single pkg installed and a
# single pkg available
# then deal with the multiples
# we should take the whole list as a 'newlist' and remove those entries
# which are clearly:
# 1. updates
# 2. identical to the ones in ourdb
# 3. not in our archdict at all
simpleupdate = []
complexupdate = []
updatedict = {} # (old n, a, e, v, r) : [(new n, a, e, v, r)]
# make the new ones a list b/c while we _shouldn't_
# have multiple updaters, we might and well, it needs
# to be solved one way or the other