# # comps.py: header list and component set (package groups) management # # Erik Troan # Matt Wilson # # Copyright 1999-2002 Red Hat, Inc. # # This software may be freely redistributed under the terms of the GNU # library public license. # # You should have received a copy of the GNU Library Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # import rpm404 as rpm import os from string import * import types import urllib import time from rhpl.log import log from rhpl.translate import _, N_ import rhpl.comps ExcludePackages = { 'XFree86-3DLabs' : None, 'XFree86-8514' : None, 'XFree86-AGX' : None, 'XFree86-I128' : None, 'XFree86-Mach32' : None, 'XFree86-Mach64' : None, 'XFree86-Mach8' : None, 'XFree86-Mono' : None, 'XFree86-P9000' : None, 'XFree86-S3' : None, 'XFree86-S3V' : None, 'XFree86-SVGA' : None, 'XFree86-VGA16' : None, 'XFree86-W32' : None, 'kernel' : None, 'kernel-BOOT' : None, 'kernel-smp' : None, 'kernel-bigmem' : None, 'kernel-vrdr' : None, 'kernel-tape' : None, 'kernel-BOOTtape' : None, 'kernel-BOOTvrdr' : None, 'kernel-summit' : None, 'kinput2-canna' : None, 'kinput-canna-wnn4' : None, 'kinput2-wnn4' : None, 'kinput2-wnn6' : None } # Package selection is complicated. Here are the rules: # # Calling package.select() forces the package on. No other rules apply. # Calling package.unselect() forces the package off. No other rules apply. # # Else: # # Each package contains a list of components that include it. Each # registered component is checked to see if it and all its parent # components are on (this is done by recursive checking of # parent.isSelected()). Some subcomps are keyed on the state of # another toplevel component. If a component, all of its ancestors, # and conditional components are selected, the package is selected. # Otherwise it is not. # CHECK_COMPS = 0 FORCE_SELECT = 1 FORCE_UNSELECT = 2 class Package: def __getitem__(self, item): return self.h[item] def __repr__(self): return "%s" % self.name def select(self): self.state = FORCE_SELECT self.selected = 1 def unselect(self): self.state = FORCE_UNSELECT self.selected = 0 def isSelected(self): return self.selected def wasForcedOff(self): if self.state == FORCE_UNSELECT and not self.selected: return 1 else: return 0 def updateSelectionCache(self): if self.state == FORCE_SELECT or self.state == FORCE_UNSELECT: return self.selected = 0 for comp in self.comps: on = 1 # if this component is selected for any reason at all, # the package is not selected. if not comp.isSelected(justManual = 0): on = 0 else: # if the component is on, check to see if this package # was listed with an expression conditional in the comps # file. If it did, we'll find a list of expressions # in the component's package dictionary. If any of them # evaluates to be true, the package is selected. if comp.pkgDict[self] != None: on = 0 for expr in comp.pkgDict[self]: if comp.set.exprMatch (expr): on = 1 # one is sufficient break if on: self.selected = 1 # one component is sufficient in the "package is selected" # case, stop looking to save time. break def getState(self): return (self.state, self.selected) def setState(self, state): (self.state, self.selected) = state def registerComponent(self, comp): self.comps.append(comp) def __init__(self, header): self.h = header self.comps = [] self.selected = 0 self.state = CHECK_COMPS self.name = header[rpm.RPMTAG_NAME] self.size = header[rpm.RPMTAG_SIZE] class HeaderList: def selected(self): l = [] keys = self.packages.keys() keys.sort() for name in keys: if self.packages[name].selected: l.append(self.packages[name]) return l def has_key(self, item): return self.packages.has_key(item) def keys(self): return self.packages.keys() def values(self): return self.packages.values() def __getitem__(self, item): return self.packages[item] def list(self): return self.packages.values() def mergeFullHeaders(self, file): if self.hasFullHeaders: return fd = os.open(file, os.O_RDONLY) rpm.mergeHeaderListFromFD(self.hdlist, fd, 1000004) os.close(fd) self.hasFullHeaders = 1 def preordered(self): preordered = 1 for h in self.selected(): if h[1000003] == None: preordered = 0 return preordered def __init__(self, hdlist, compatPackages = None, noscore = 0): self.hdlist = hdlist self.packages = {} newCompat = [] self.hasFullHeaders = 0 for h in hdlist: name = h[rpm.RPMTAG_NAME] if noscore: self.packages[name] = Package(h) continue score1 = rpm.archscore(h['arch']) if (score1): if self.packages.has_key(name): score2 = rpm.archscore(self.packages[name].h['arch']) if (score1 < score2): newCompat.append(self.packages[name]) self.packages[name] = Package(h) else: newCompat.append(Package(h)) else: self.packages[name] = Package(h) if hdlist and not self.packages: raise RuntimeError, ("the header list was read, but no packages " "matching architecture '%s' were found." % os.uname()[4]) if compatPackages != None: compatPackages.extend(newCompat) class HeaderListFromFile (HeaderList): def __init__(self, path, compatPackages = None, noscore = 0): hdlist = rpm.readHeaderListFromFile(path) HeaderList.__init__(self, hdlist, compatPackages = compatPackages, noscore = noscore) class HeaderListFD (HeaderList): def __init__(self, fd): hdlist = rpm.readHeaderListFromFD (fd) HeaderList.__init__(self, hdlist) # A component has a name, a selection state, a list of included components, # and a list of packages whose selection depends in some way on this component # being selected. Selection and deselection recurses through included # components. # # When the component file is parsed, the comp lists that include each # package are built up. Component selection is used by the packages to # determine whether or not they are selected. # # The selection state consists of a manually selected flag and an included # selection count. They are separate to make UI coding easier. class Component: def __len__(self): return len(self.pkgs) def __repr__(self): return "comp %s" % (self.name) def packages(self): return self.pkgs def includesPackage(self, pkg): if not self.pkgDict.has_key(pkg): return 0 if self.pkgDict[pkg] == None: return 1 # if this package is the component with a condition, # check to see if the condition is met before saying that # the package is included in this component for expr in self.pkgDict[pkg]: if self.set.exprMatch (expr): return 1 return 0 def select(self, forInclude = 0, toplevel = 1): if forInclude: self.selectionCount = self.selectionCount + 1 else: self.manuallySelected = 1 for name in self.includes: if not self.set.has_key(name): log ("warning, unknown toplevel component %s " "included by component %s", name, self.name) self.set[name].select(forInclude = 1, toplevel = 0) if toplevel: self.set.updateSelections() def isSelected(self, justManual = 0): if self.conditionalKey: if not self.set.has_key(self.conditionalKey): log ("warning, unknown conditional trigger %s wanted by %s", self.conditionalKey, self.name) return 0 else: if (self.set[self.conditionalKey].isSelected() and self.parent.isSelected()): return 1 return 0 # don't admit to selection-by-inclusion if justManual: return self.manuallySelected return self.manuallySelected or (self.selectionCount > 0) def unselect(self, forInclude = 0, toplevel = 1): if forInclude: self.selectionCount = self.selectionCount - 1 if self.selectionCount < 0: self.selectionCount = 0 else: self.manuallySelected = 0 for name in self.includes: if not self.set.has_key(name): log ("warning, unknown toplevel component %s " "included by component %s", name, self.name) self.set[name].unselect(forInclude = 1, toplevel = 0) if toplevel: self.set.updateSelections() def addInclude(self, comp): self.includes.append(comp) def addPackage(self, p): self.pkgs.append(p) p.registerComponent(self) self.pkgDict[p] = None def addPackageWithExpression(self, expr, p): if not self.pkgDict.has_key (p): self.pkgDict[p] = [ expr ] self.pkgs.append(p) p.registerComponent(self) else: if type (self.pkgDict[p]) == type ([]): self.pkgDict[p].append (expr) else: self.pkgDict[p] = [ expr ] def setDefault(self, default): self.default = default def setDefaultSelection(self): if self.default: self.select() def getState(self): return (self.manuallySelected, self.selectionCount) def setState(self, state): (self.manuallySelected, self.selectionCount) = state def __init__(self, set, compgroup, packages, conditionalKey = "", parent=None): self.set = set self.name = compgroup.name self.hidden = not compgroup.user_visible self.default = compgroup.default self.comp = compgroup self.id = compgroup.id # do we use these anymore? self.conditionalKey = conditionalKey self.parent = parent self.pkgs = [] self.pkgDict = {} self.includes = [] self.manuallySelected = 0 self.selectionCount = 0 # FIXME: we need to properly go through and use the dependency # lists for pkg in compgroup.packages.keys(): if not packages.has_key(pkg): log("%s references package %s which doesn't exist" %(self.name, pkg)) continue self.addPackage(packages[pkg]) class ComponentSet: def __len__(self): return len(self.comps) def __getitem__(self, key): if (type(key) == types.IntType): return self.comps[key] return self.compsDict[key] def has_key(self, key): return self.compsDict.has_key(key) def getSelectionState(self): compsState = [] for comp in self.comps: compsState.append((comp, comp.getState())) pkgsState = [] for pkg in self.packages.list(): pkgsState.append((pkg, pkg.getState())) return (compsState, pkgsState) def setSelectionState(self, pickle): (compsState, pkgsState) = pickle for (comp, state) in compsState: comp.setState(state) for (pkg, state) in pkgsState: pkg.setState(state) def sizeStr(self): megs = self.size() if (megs >= 1000): big = megs / 1000 little = megs % 1000 return "%d,%03dM" % (big, little) return "%dM" % (megs) def totalSize(self): total = 0 for pkg in self.packages.list(): total = total + (pkg[rpm.RPMTAG_SIZE] / 1024) return total def size(self): size = 0 for pkg in self.packages.list(): if pkg.isSelected(): size = size + (pkg[rpm.RPMTAG_SIZE] / 1024) return size / 1024 def keys(self): return self.compsDict.keys() def exprMatch(self, expr, tags = [ "lang", "arch" ]): # FIXME: okay, we don't have this nonsense right now at least... # always assume true return 1 theTags = [] for tag in tags: theTags.append(tag) # no expression == true if not expr: return 1 # XXX preserve backwards compatible behavior if self.allLangs and "lang" in theTags: theTags.remove ("lang") if "lang" in theTags: if os.environ.has_key('LINGUAS'): langs = split (os.environ['LINGUAS'], ':') if len (langs) == 1 and not langs[0]: langs = None else: if os.environ.has_key('LANG'): langs = [ os.environ['LANG'] ] else: langs = None if langs == None: # no languages specified, install them all theTags.remove ("lang") if expr[0] != '(': raise ValueError, "leading ( expected" expr = expr[1:] if expr[len(expr) - 1] != ')': raise ValueError, "bad comps file [missing )]" expr = expr[:len(expr) - 1] exprList = split(expr, 'and') truth = 1 for expr in exprList: l = split(expr) if l[0] == "lang": if theTags and "lang" not in theTags: newTruth = 1 else: if len(l) != 2: raise ValueError, "too many arguments for lang" if l[1] and l[1][0] == "!": newTruth = l[1][1:] not in langs else: newTruth = l[1] in langs elif l[0] == "arch": if theTags and "arch" not in theTags: newTruth = 1 if len(l) != 2: raise ValueError, "too many arguments for arch" if l[1] and l[1][0] == "!": newTruth = l[1][1:] not in self.archList else: newTruth = l[1] in self.archList else: s = "unknown condition type %s" % (l[0],) raise ValueError, s truth = truth and newTruth return truth def readCompsFile(self, filename, packages): connected = 0 while not connected: try: file = urllib.urlopen(filename) except IOError, (errnum, msg): log("IOError %s occured getting %s: %s", filename, errnum, str(msg)) time.sleep(5) else: connected = 1 self.compsxml = rhpl.comps.Comps(file) file.close() self.comps = [] self.compsDict = {} self.compsById = {} groups = self.compsxml.groups.keys() groups.sort() # be leet and construct an everything group everything = rhpl.comps.Group(self.compsxml) everything.name = N_("Everything") everything.id = "everything" for pkg in packages.keys(): if ExcludePackages.has_key(packages[pkg]['name']): continue everything.packages[pkg] = (None, pkg) self.compsxml.groups['Everything'] = everything groups.append('Everything') # we have to go through first and make Comp objects for all # of the groups. then we can go through and set up the includes for group in groups: group = self.compsxml.groups[group] comp = Component(self, group, packages) self.comps.append(comp) self.compsDict[comp.name] = comp self.compsById[comp.id] = comp for group in groups: group = self.compsxml.groups[group] comp = self.compsDict[group.name] for id in group.groups.keys(): if not self.compsById.has_key(id): log("%s references component %s which doesn't exist" %(group.name, id)) continue comp.addInclude(self.compsById[id].name) # now, let's set up all of the dependencies for comp in self.comps: # kind of pointless for everything since it's all packages if comp.name == "Everything": continue # print "looking at %s" %(comp.name) pkgs = comp.packages() # print "packages is", pkgs while len(pkgs) > 0: tocheck = pkgs pkgs = [] for pkg in tocheck: pkg = pkg.name # make sure the package is in the package list if not self.compsxml.packages.has_key(pkg): log("Component %s needs package %s which doesn't exist" %(comp.name, pkg)) continue deps = self.compsxml.packages[pkg].dependencies for dep in deps: # really needs to be in the hdlist if not packages.has_key(dep): log("Package %s requires %s which we don't have" %(tocheck, dep)) continue # if the package is already in this group, don't # worry about it if comp.includesPackage(packages[dep]): continue # print "adding %s as depedency of %s in %s" % (dep, pkg, comp.name) comp.addPackage(packages[dep]) pkgs.append(packages[dep]) ## everything = Component(self, N_("Everything"), 0, 0) ## for package in packages.keys (): ## if ExcludePackages.has_key(packages[package][rpm.RPMTAG_NAME]): ## continue ## if self.expressions.has_key (packages[package]): ## expressions = self.expressions[packages[package]] ## if expressions == None: ## everything.addPackageWithExpression (None, ## packages[package]) ## else: ## for expression in expressions: ## everything.addPackageWithExpression (expression, ## packages[package]) ## else: ## everything.addPackage (packages[package]) ## self.comps.append (everything) ## self.compsDict["Everything"] = everything for comp in self.comps: comp.setDefaultSelection() def updateSelections(self): if not self.frozen: for pkg in self.packages.values(): pkg.updateSelectionCache() def freeze(self): self.frozen = self.frozen + 1 def thaw(self): self.frozen = self.frozen - 1 if not self.frozen: self.updateSelections() def __repr__(self): s = "" for n in self.comps: s = s + "{ " + n.name + " ["; for include in n.includes: s = s + " @" + include.name for package in n: s = s + " " + str(package) s = s + " ] } " return s def verifyDeps (self, instPath, upgrade): def formatRequire (name, version, flags): string = name if flags: if flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER | rpm.RPMSENSE_EQUAL): string = string + " " if flags & rpm.RPMSENSE_LESS: string = string + "<" if flags & rpm.RPMSENSE_GREATER: string = string + ">" if flags & rpm.RPMSENSE_EQUAL: string = string + "=" string = string + " %s" % version return string # if we still have the same packages selected, bail - we don't need to # do this again. if self.verifiedState == self.getSelectionState()[1]: return [] self.verifiedState = None if upgrade: db = rpm.opendb (0, instPath) how = 'u' else: db = None ts = rpm.TransactionSet() how = 'i' checkDeps = 1 rc = [] extras = {} while checkDeps: if upgrade: ts = rpm.TransactionSet(instPath, db) how = 'u' else: ts = rpm.TransactionSet() how = 'i' for p in self.packages.values(): if p.selected: ts.add(p.h, (p.h, p.h[rpm.RPMTAG_NAME]), how) else: if extras.has_key(p.h): ts.add(p.h, (p.h, p.h[rpm.RPMTAG_NAME]), how) else: ts.add(p.h, (p.h, p.h[rpm.RPMTAG_NAME]), "a") deps = ts.depcheck() checkDeps = 0 if not deps: break for ((name, version, release), (reqname, reqversion), flags, suggest, sense) in deps: if sense == rpm.RPMDEP_SENSE_REQUIRES: if suggest: (header, sugname) = suggest log ("depcheck: package %s needs %s (provided by %s)", name, formatRequire(reqname, reqversion, flags), sugname) extras[header] = None checkDeps = 1 else: log ("depcheck: package %s needs %s (not provided)", name, formatRequire(reqname, reqversion, flags)) sugname = _("no suggestion") if not (name, sugname) in rc: rc.append ((name, sugname)) elif sense == rpm.RPMDEP_SENSE_CONFLICTS: # We need to check if the one we are going to # install is ok. conflicts = 1 if reqversion: fields = split(reqversion, '-') if (len (fields) == 2): needed = ("", fields [0], fields [1]) else: needed = ("", fields [0], "") try: h = self.packages[reqname].h except KeyError: # we don't actually have the conflicting package # in our available packages, the conflict is # on the system. Continue on. continue installed = ("", h[rpm.RPMTAG_VERSION], h [rpm.RPMTAG_RELEASE]) if rpm.labelCompare (installed, needed) >= 0: conflicts = 0 if conflicts: log ("%s-%s-%s conflicts with to-be-installed " "package %s-%s, removing %s from set", name, version, release, reqname, reqversion, reqname) if self.packages.packages.has_key (reqname): self.packages.packages[reqname].selected = 0 log ("... removed") del ts if db: del db if not rc: self.verifiedState = self.getSelectionState()[1] return rc def selectDepCause (self, deps): for (who, dep) in deps: if self.packages.has_key(who): self.packages[who].select () def unselectDepCause (self, deps): for (who, dep) in deps: if self.packages.has_key(who): self.packages[who].unselect () def selectDeps (self, deps): for (who, dep) in deps: if self.packages.has_key(dep): self.packages[dep].select () def unselectDeps (self, deps): for (who, dep) in deps: if self.packages.has_key(dep): self.packages[dep].unselect () def canResolveDeps (self, deps): canresolve = 0 if deps: for (who, dep) in deps: if dep != _("no suggestion"): canresolve = 1 return canresolve def kernelVersionList(self): kernelVersions = [] # nick is used to generate the lilo name for (ktag, nick) in [ ('kernel-bigmem', 'bigmem'), ('kernel-smp', 'smp'), ('kernel-tape', 'tape') ]: tag = split(ktag, '-')[1] if (self.packages.has_key(ktag) and self.packages[ktag].selected): version = (self.packages[ktag][rpm.RPMTAG_VERSION] + "-" + self.packages[ktag][rpm.RPMTAG_RELEASE] + tag) kernelVersions.append((version, nick)) if (self.packages.has_key('kernel') and self.packages['kernel'].selected): version = (self.packages['kernel'][rpm.RPMTAG_VERSION] + "-" + self.packages['kernel'][rpm.RPMTAG_RELEASE]) kernelVersions.append((version, 'up')) return kernelVersions def __init__(self, file, hdlist, arch = None, matchAllLang = 0): self.frozen = 0 self.allLangs = matchAllLang self.archList = [] self.verifiedState = None if not arch: import iutil arch = iutil.getArch() self.archList.append(arch) # always set since with can have i386 arch with i686 arch2, # for example: # arch2 = None # if arch == "sparc" and os.uname ()[4] == "sparc64": # arch2 = "sparc64" # arch2 = os.uname ()[4] if not arch2 in self.archList: self.archList.append (arch2) else: self.archList.append(arch) self.packages = hdlist self.readCompsFile(file, self.packages) # this is a temporary way to set order of packages def orderPackageGroups(curgroups): compsOrder = [ "Base X Support", "Printing Support", "GNOME Desktop Environment", "KDE Desktop Environment", "Messaging and Web Tools", "GNOME Messaging and Web Tools", "KDE Messaging and Web Tools", "Multimedia Software", "GNOME Multimedia Software", "KDE Multimedia Software", "Office/Productivity Software", "GNOME Office/Productivity Software", "KDE Office/Productivity Software", "Authoring and Publishing", "Games and Entertainment", "GNOME Games and Entertainment", "KDE Games and Entertainment", "X Based Games and Entertainment", "Emacs", "Kernel Development", "Software Development", "GNOME Software Development", "KDE Software Development", "X Software Development", "Multimedia Software Development", "Utilities", "Workstation Tools", "Dialup Networking Support", "NFS File Server", "Web Server", "FTP Server", "Windows File Server", "DNS Name Server", "SQL Database Server", "Network Servers", "News Server", ] ignorelst = [] retval = [] while 1: bestfit = len(compsOrder)+1 bestgrp = None for grp in curgroups: if grp.name in ignorelst: continue if grp.name in compsOrder: if compsOrder.index(grp.name) < bestfit: bestfit = compsOrder.index(grp.name) bestgrp = grp if bestgrp is None: for grp in curgroups: if grp.name not in ignorelst: bestgrp = grp break if bestgrp is None: break ignorelst.append(bestgrp.name) retval.append(bestgrp) return retval