def getMinUpgrade(vulnerableList, unaffectedList, portdbapi, vardbapi, minimize=True): """ Checks if the systemstate is matching an atom in I{vulnerableList} and returns string describing the lowest version for the package that matches an atom in I{unaffectedList} and is greater than the currently installed version. It will return an empty list if the system is affected, and no upgrade is possible or None if the system is not affected. Both I{vulnerableList} and I{unaffectedList} should have the same base package. @type vulnerableList: List of Strings @param vulnerableList: atoms matching vulnerable package versions @type unaffectedList: List of Strings @param unaffectedList: atoms matching unaffected package versions @type portdbapi: portage.dbapi.porttree.portdbapi @param portdbapi: Ebuild repository @type vardbapi: portage.dbapi.vartree.vardbapi @param vardbapi: Installed package repository @type minimize: Boolean @param minimize: True for a least-change upgrade, False for emerge-like algorithm @rtype: String | None @return: the lowest unaffected version that is greater than the installed version. """ rValue = "" v_installed = reduce(operator.add, [match(v, vardbapi) for v in vulnerableList], []) u_installed = reduce(operator.add, [match(u, vardbapi) for u in unaffectedList], []) # remove all unaffected atoms from vulnerable list v_installed = list(set(v_installed).difference(set(u_installed))) if not v_installed: return None # this tuple holds all vulnerable atoms, and the related upgrade atom vuln_update = [] avail_updates = set() for u in unaffectedList: # TODO: This had match_type="match-all" before. I don't think it should # since we disregarded masked items later anyway (match(=rValue, "porttree")) avail_updates.update(match(u, portdbapi)) # if an atom is already installed, we should not consider it for upgrades avail_updates.difference_update(u_installed) for vuln in v_installed: update = "" for c in avail_updates: c_pv = portage.catpkgsplit(c) if vercmp(c.version, vuln.version) > 0 \ and (update == "" \ or (minimize ^ (vercmp(c.version, update.version) > 0))) \ and portdbapi._pkg_str(c, None).slot == vardbapi._pkg_str(vuln, None).slot: update = c_pv[0]+"/"+c_pv[1]+"-"+c_pv[2] if c_pv[3] != "r0": # we don't like -r0 for display update += "-"+c_pv[3] vuln_update.append([vuln, update]) return vuln_update
def isVulnerable(self): rValue = True v = getKernelVersion() c = getKernelOptions() for vul in self.vul_vers: value = vul[0] version = vul[1] match = False if (value == '<' or value == '<=') and vercmp(version, v) == 1: match = True if (value == '=' or value == '<=' or value == '>=') and vercmp(version, v) == 0: match = True if (value == '>' or value == '>=') and vercmp(version, v) == -1: match = True if (value == '!=') and vercmp(version, v) != 0: match = True if not match: rValue = False for conf in self.vul_configs: match = False exists = False for conf_current in c: if re.match(conf[0], conf_current[1]): exists = True if re.match(conf[1], conf_current[0]): match = True if not match and not(exists and conf[0] == ""): rValue = False return rValue
def getMinUpgrade(vulnerableList, unaffectedList, portdbapi, vardbapi, minimize=True): """ Checks if the systemstate is matching an atom in I{vulnerableList} and returns string describing the lowest version for the package that matches an atom in I{unaffectedList} and is greater than the currently installed version or None if the system is not affected. Both I{vulnerableList} and I{unaffectedList} should have the same base package. @type vulnerableList: List of Strings @param vulnerableList: atoms matching vulnerable package versions @type unaffectedList: List of Strings @param unaffectedList: atoms matching unaffected package versions @type portdbapi: portage.dbapi.porttree.portdbapi @param portdbapi: Ebuild repository @type vardbapi: portage.dbapi.vartree.vardbapi @param vardbapi: Installed package repository @type minimize: Boolean @param minimize: True for a least-change upgrade, False for emerge-like algorithm @rtype: String | None @return: the lowest unaffected version that is greater than the installed version. """ rValue = None v_installed = [] u_installed = [] for v in vulnerableList: v_installed += match(v, vardbapi) for u in unaffectedList: u_installed += match(u, vardbapi) install_unaffected = True for i in v_installed: if i not in u_installed: install_unaffected = False if install_unaffected: return rValue for u in unaffectedList: mylist = match(u, portdbapi, match_type="match-all") for c in mylist: i = best(v_installed) if vercmp(c.version, i.version) > 0 \ and (rValue == None \ or not match("="+rValue, portdbapi) \ or (minimize ^ (vercmp(c.version, rValue.version) > 0)) \ and match("="+c, portdbapi)) \ and portdbapi.aux_get(c, ["SLOT"]) == vardbapi.aux_get(best(v_installed), ["SLOT"]): rValue = c return rValue
def __resolve_dependencies(self): to_install = [] for pkg, comp, version in self.__desc.self_depends: # no version required, get the latest available if version == None: to_install.append('%s-%s' % (pkg, self.__dbtree.latest_version(pkg))) continue # here we need to calculate the better version to install versions = self.__dbtree.package_versions(pkg) allowed_versions = [] for _version in versions: comparation = vercmp(_version, version) if eval('%s %s 0' % (comparation, comp)): allowed_versions.append(_version) to_install.append('%s-%s' % (pkg, self.__dbtree.version_compare(allowed_versions))) if len(allowed_versions) == 0: raise EbuildException('Can\'t resolve a dependency: %s' % pkg) # creating the ebuilds for the dependencies, recursivelly for ebuild in to_install: Ebuild( ebuild, force = self.__force, conf = self.__conf, pkg_manager = self.__pkg_manager, scm = self.__scm ).create()
def testVerCmpLess(self): """ pre < alpha < beta < rc < p -> test each of these, they are inductive (or should be..) """ tests = [ ("4.0", "5.0"), ("5", "5.0"), ("1.0_pre2", "1.0_p2"), ("1.0_alpha2", "1.0_p2"), ("1.0_alpha1", "1.0_beta1"), ("1.0_beta3", "1.0_rc3"), ("1.001000000000000000001", "1.001000000000000000002"), ("1.00100000000", "1.0010000000000000001"), ("9999", "cvs.9999"), ("999999999999999999999999999998", "999999999999999999999999999999"), ("1.01", "1.1"), ("1.0-r0", "1.0-r1"), ("1.0", "1.0-r1"), ("1.0", "1.0.0"), ("1.0b", "1.0.0"), ("1_p1", "1b_p1"), ("1", "1b"), ("1.1", "1.1b"), ("12.2b", "12.2.5"), ] for test in tests: self.failIf(vercmp(test[0], test[1]) >= 0, msg="%s > %s? Wrong!" % (test[0], test[1]))
def testVerCmpEqual(self): tests = [("4.0", "4.0"), ("1.0", "1.0"), ("1.0-r0", "1.0"), ("1.0", "1.0-r0"), ("1.0-r0", "1.0-r0"), ("1.0-r1", "1.0-r1")] for test in tests: self.failIf(vercmp(test[0], test[1]) != 0, msg="%s != %s? Wrong!" % (test[0], test[1]))
def fill_world_ng(): global world, options print "Checking ALL installed packages..." if not read_smartworld(): # SmartWorld file doesn't exist or not valid, let's calculate world and create one if options.problematic_only: raw_emerge_pattern = re.compile('(?:\[ebuild.I.....\]|\[ebuild....F..\])\s+([^\s]+).*') else: raw_emerge_pattern = re.compile('\[ebuild.+\]\s+([^\s]+).*') if vercmp(portage.VERSION, "2.2") < 0: # Portage < 2.2 raw_pkglist = commands.getstatusoutput('emerge -ep --quiet --nospinner world system') else: # Portage >= 2.2 raw_pkglist = commands.getstatusoutput('emerge -ep --quiet --nospinner @world @system') if raw_pkglist[0] == 0: pkglist = raw_pkglist[1].split('\n') for pkg in pkglist: match = raw_emerge_pattern.match(pkg) if match: world.append(match.group(1)) else: raise EwoError('Oops! No world packages list...') write_smartworld()
def BestEBuild(ebuilds): """Returns the newest EBuild from a list of EBuild objects.""" from portage.versions import vercmp winner = ebuilds[0] for ebuild in ebuilds[1:]: if vercmp(winner.version, ebuild.version) < 0: winner = ebuild return winner
def testVerCmpEqual(self): tests = [ ("4.0", "4.0"), ("1.0", "1.0"), ("1.0-r0", "1.0"), ("1.0", "1.0-r0"), ("1.0-r0", "1.0-r0"), ("1.0-r1", "1.0-r1")] for test in tests: self.failIf( vercmp( test[0], test[1]) != 0, msg="%s != %s? Wrong!" % (test[0],test[1]))
def __lt__(self, other): if not isinstance(other, self.__class__): raise TypeError("other isn't of %s type, is %s" % (self.__class__, other.__class__)) if self.category != other.category: return self.category < other.category elif self.name != other.name: return self.name < other.name else: # FIXME: this cmp() hack is for vercmp not using -1,0,1 # See bug 266493; this was fixed in portage-2.2_rc31 # return vercmp(self.fullversion, other.fullversion) return vercmp(self.fullversion, other.fullversion) < 0
def _reduce(self, atomlist): mydict = {} for atom in atomlist[:]: cpv = self._portdbapi.xmatch("match-all", atom)[0] slot = self._portdbapi.aux_get(cpv, ["SLOT"])[0] cps = "%s:%s" % (cpv.cp, slot) if not cps in mydict: mydict[cps] = (atom, cpv) else: other_cpv = mydict[cps][1] if vercmp(cpv.version, other_cpv.version) > 0: atomlist.remove(mydict[cps][0]) mydict[cps] = (atom, cpv) return atomlist
def __init__(self, basedir, dbapi): def p(x): return os.path.join(basedir, x) pkw = [p('package.keywords')] if vercmp(portage_ver, '2.1.9') >= 0: pkw.append(p('package.accept_keywords')) self.files = { 'use': PackageFileSet(p('package.use')), 'kw': PackageKeywordsFileSet(pkw, dbapi), 'lic': PackageFileSet(p('package.license')), 'env': PackageEnvFileSet(p('package.env')) }
def _reduce(self, atomlist): mydict = {} for atom in atomlist[:]: cpv = self._portdbapi.xmatch("match-all", atom)[0] pkg = self._portdbapi._pkg_str(cpv, None) cps = "%s:%s" % (pkg.cp, pkg.slot) if not cps in mydict: mydict[cps] = (atom, cpv) else: other_cpv = mydict[cps][1] if vercmp(cpv.version, other_cpv.version) > 0: atomlist.remove(mydict[cps][0]) mydict[cps] = (atom, cpv) return atomlist
def testVerCmpEqual(self): tests = [ ("4.0", "4.0"), ("1.0", "1.0"), ("1.0-r0", "1.0"), ("1.0", "1.0-r0"), ("1.0-r0", "1.0-r0"), ("1.0-r1", "1.0-r1"), ] for test in tests: self.assertFalse( vercmp(test[0], test[1]) != 0, msg="%s != %s? Wrong!" % (test[0], test[1]), )
def testVerCmpGreater(self): tests = [ ("6.0", "5.0"), ("5.0", "5"), ("1.0-r1", "1.0-r0"), ("1.0-r1", "1.0"), ("999999999999999999999999999999", "999999999999999999999999999998"), ("1.0.0", "1.0"), ("1.0.0", "1.0b"), ("1b", "1"), ("1b_p1", "1_p1"), ("1.1b", "1.1"), ("12.2.5", "12.2b"), ] for test in tests: self.assertFalse(vercmp(test[0], test[1]) <= 0, msg="%s < %s? Wrong!" % (test[0], test[1]))
def load(self): atoms = [] xmatch = self._portdb.xmatch xmatch_level = "bestmatch-visible" cp_list = self._vardb.cp_list pkg_str = self._vardb._pkg_str for cp in self._vardb.cp_all(): for cpv in cp_list(cp): pkg = pkg_str(cpv, None) slot_atom = "%s:%s" % (pkg.cp, pkg.slot) ebuild = xmatch(xmatch_level, slot_atom) if not ebuild: continue if vercmp(cpv.version, ebuild.version) > 0: atoms.append(slot_atom) self._setAtoms(atoms)
def testVerNotEqual(self): tests = [ ("1", "2"), ("1.0_alpha", "1.0_pre"), ("1.0_beta", "1.0_alpha"), ("0", "0.0"), ("1.0-r0", "1.0-r1"), ("1.0-r1", "1.0-r0"), ("1.0", "1.0-r1"), ("1.0-r1", "1.0"), ("1.0", "1.0.0"), ("1_p1", "1b_p1"), ("1b", "1"), ("1.1b", "1.1"), ("12.2b", "12.2"), ] for test in tests: self.assertFalse(vercmp(test[0], test[1]) == 0, msg="%s == %s? Wrong!" % (test[0], test[1]))
def load(self): atoms = [] xmatch = self._portdb.xmatch xmatch_level = "bestmatch-visible" cp_list = self._vardb.cp_list aux_get = self._vardb.aux_get aux_keys = ["SLOT"] for cp in self._vardb.cp_all(): for cpv in cp_list(cp): slot, = aux_get(cpv, aux_keys) slot_atom = "%s:%s" % (cp, slot) ebuild = xmatch(xmatch_level, slot_atom) if not ebuild: continue if vercmp(cpv.version, ebuild.version) > 0: atoms.append(slot_atom) self._setAtoms(atoms)
def match(self, other): """See whether a passed in VersionMatch or CPV instance matches self. Example usage: >>> from gentoolkit.versionmatch import VersionMatch >>> from gentoolkit.cpv import CPV >>> VersionMatch(CPV('foo/bar-1.5'), op='>').match( ... VersionMatch(CPV('foo/bar-2.0'))) True @type other: gentoolkit.versionmatch.VersionMatch OR gentoolkit.cpv.CPV @param other: version to compare with self's version @rtype: bool """ if self.droprevision: ver1, ver2 = self.version, other.version else: ver1, ver2 = self.fullversion, other.fullversion return vercmp(ver2, ver1) in self.values
def testVerCmpLess(self): """ pre < alpha < beta < rc < p -> test each of these, they are inductive (or should be..) """ tests = [ ("4.0", "5.0"), ("5", "5.0"), ("1.0_pre2", "1.0_p2"), ("1.0_alpha2", "1.0_p2"), ("1.0_alpha1", "1.0_beta1"), ("1.0_beta3", "1.0_rc3"), ("1.001000000000000000001", "1.001000000000000000002"), ("1.00100000000", "1.0010000000000000001"), ("999999999999999999999999999998", "999999999999999999999999999999"), ("1.01", "1.1"), ("1.0-r0", "1.0-r1"), ("1.0", "1.0-r1"), ("1.0", "1.0.0"), ("1.0b", "1.0.0"), ("1_p1", "1b_p1"), ("1", "1b"), ("1.1", "1.1b"), ("12.2b", "12.2.5"), ] for test in tests: self.assertFalse(vercmp(test[0], test[1]) >= 0, msg="%s > %s? Wrong!" % (test[0], test[1]))
def __lt__(self, other): """ Compare different file names, first by file type and then for ebuilds by version and lexicographically for others. EBUILD < MISC < AUX < DIST < None """ if self.__class__ != other.__class__: raise NotImplementedError # Sort by file type as defined by _file_type_lt(). if self._file_type_lt(self, other): return True elif self._file_type_lt(other, self): return False # Files have the same type. if self.file_type == "EBUILD": # Sort by version. Lowest first. ver = "-".join(pkgsplit(self.file_name[:-7])[1:3]) other_ver = "-".join(pkgsplit(other.file_name[:-7])[1:3]) return vercmp(ver, other_ver) < 0 else: # Sort lexicographically. return self.file_name < other.file_name
def __automask(myroot): cfg = config(config_root=unicode(myroot), target_root=unicode(myroot)) for directory in cfg.profiles: mask_pkgs = Packages() unmask_pkgs = Packages() pf = PackagesFile(directory) for package in pf.list_pkgs().list(): if package.version: if package.operator == '=' and not package.removal: mask_pkgs += Package(package.name, version=package.version, operator='>') if package.operator == '=' and package.removal: lowest_version = str() for pkg in pf.list_pkgs().lookup(package.name): lowest_version = package.version if vercmp( package.version, pkg.version ) <= 0 else pkg.version if package.version == lowest_version: continue else: unmask_pkgs += Package(package.name, version=lowest_version, operator='<=') pm = PackageMaskFile(directory) pm.update(mask_pkgs) pu = PackageUnmaskFile(directory) pu.update(unmask_pkgs)
def __lt__(self, other): """ Compare different file names, first by file type and then for ebuilds by version and lexicographically for others. EBUILD < MISC < AUX < DIST < None """ if self.__class__ != other.__class__: raise NotImplementedError # Sort by file type as defined by _file_type_lt(). if self._file_type_lt(self, other): return True if self._file_type_lt(other, self): return False # Files have the same type. if self.file_type == "EBUILD": # Sort by version. Lowest first. ver = "-".join(pkgsplit(self.file_name[:-7])[1:3]) other_ver = "-".join(pkgsplit(other.file_name[:-7])[1:3]) return vercmp(ver, other_ver) < 0 # Sort lexicographically. return self.file_name < other.file_name
def __lt__(self, other): return vercmp(str(self), str(other)) < 0
def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, minimize_slots=False): """ Takes an unreduced and reduced deplist and removes satisfied dependencies. Returned deplist contains steps that must be taken to satisfy dependencies. """ if trees is None: trees = portage.db writemsg("ZapDeps -- %s\n" % (use_binaries), 2) if not reduced or unreduced == ["||"] or dep_eval(reduced): return [] if unreduced[0] != "||": unresolved = [] for x, satisfied in zip(unreduced, reduced): if isinstance(x, list): unresolved += dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees, minimize_slots=minimize_slots) elif not satisfied: unresolved.append(x) return unresolved # We're at a ( || atom ... ) type level and need to make a choice deps = unreduced[1:] satisfieds = reduced[1:] # Our preference order is for an the first item that: # a) contains all unmasked packages with the same key as installed packages # b) contains all unmasked packages # c) contains masked installed packages # d) is the first item preferred_installed = [] preferred_in_graph = [] preferred_any_slot = [] preferred_non_installed = [] unsat_use_in_graph = [] unsat_use_installed = [] unsat_use_non_installed = [] other_installed = [] other_installed_some = [] other_installed_any_slot = [] other = [] # unsat_use_* must come after preferred_non_installed # for correct ordering in cases like || ( foo[a] foo[b] ). choice_bins = ( preferred_in_graph, preferred_installed, preferred_any_slot, preferred_non_installed, unsat_use_in_graph, unsat_use_installed, unsat_use_non_installed, other_installed, other_installed_some, other_installed_any_slot, other, ) # Alias the trees we'll be checking availability against parent = trees[myroot].get("parent") priority = trees[myroot].get("priority") graph_db = trees[myroot].get("graph_db") graph = trees[myroot].get("graph") pkg_use_enabled = trees[myroot].get("pkg_use_enabled") want_update_pkg = trees[myroot].get("want_update_pkg") downgrade_probe = trees[myroot].get("downgrade_probe") vardb = None if "vartree" in trees[myroot]: vardb = trees[myroot]["vartree"].dbapi if use_binaries: mydbapi = trees[myroot]["bintree"].dbapi else: mydbapi = trees[myroot]["porttree"].dbapi try: mydbapi_match_pkgs = mydbapi.match_pkgs except AttributeError: def mydbapi_match_pkgs(atom): return [mydbapi._pkg_str(cpv, atom.repo) for cpv in mydbapi.match(atom)] # Sort the deps into installed, not installed but already # in the graph and other, not installed and not in the graph # and other, with values of [[required_atom], availablility] for x, satisfied in zip(deps, satisfieds): if isinstance(x, list): atoms = dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees, minimize_slots=minimize_slots) else: atoms = [x] if vardb is None: # When called by repoman, we can simply return the first choice # because dep_eval() handles preference selection. return atoms all_available = True all_use_satisfied = True all_use_unmasked = True conflict_downgrade = False installed_downgrade = False slot_atoms = collections.defaultdict(list) slot_map = {} cp_map = {} for atom in atoms: if atom.blocker: continue # Ignore USE dependencies here since we don't want USE # settings to adversely affect || preference evaluation. avail_pkg = mydbapi_match_pkgs(atom.without_use) if avail_pkg: avail_pkg = avail_pkg[-1] # highest (ascending order) avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) if not avail_pkg: all_available = False all_use_satisfied = False break if graph_db is not None and downgrade_probe is not None: slot_matches = graph_db.match_pkgs(avail_slot) if (len(slot_matches) > 1 and avail_pkg < slot_matches[-1] and not downgrade_probe(avail_pkg)): # If a downgrade is not desirable, then avoid a # choice that pulls in a lower version involved # in a slot conflict (bug #531656). conflict_downgrade = True if atom.use: avail_pkg_use = mydbapi_match_pkgs(atom) if not avail_pkg_use: all_use_satisfied = False if pkg_use_enabled is not None: # Check which USE flags cause the match to fail, # so we can prioritize choices that do not # require changes to use.mask or use.force # (see bug #515584). violated_atom = atom.violated_conditionals( pkg_use_enabled(avail_pkg), avail_pkg.iuse.is_valid_flag) # Note that violated_atom.use can be None here, # since evaluation can collapse conditional USE # deps that cause the match to fail due to # missing IUSE (match uses atom.unevaluated_atom # to detect such missing IUSE). if violated_atom.use is not None: for flag in violated_atom.use.enabled: if flag in avail_pkg.use.mask: all_use_unmasked = False break else: for flag in violated_atom.use.disabled: if flag in avail_pkg.use.force and \ flag not in avail_pkg.use.mask: all_use_unmasked = False break else: # highest (ascending order) avail_pkg_use = avail_pkg_use[-1] if avail_pkg_use != avail_pkg: avail_pkg = avail_pkg_use avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) if downgrade_probe is not None and graph is not None: highest_in_slot = mydbapi_match_pkgs(avail_slot) highest_in_slot = (highest_in_slot[-1] if highest_in_slot else None) if (avail_pkg and highest_in_slot and avail_pkg < highest_in_slot and not downgrade_probe(avail_pkg) and (highest_in_slot.installed or highest_in_slot in graph)): installed_downgrade = True slot_map[avail_slot] = avail_pkg slot_atoms[avail_slot].append(atom) highest_cpv = cp_map.get(avail_pkg.cp) all_match_current = None all_match_previous = None if (highest_cpv is not None and highest_cpv.slot == avail_pkg.slot): # If possible, make the package selection internally # consistent by choosing a package that satisfies all # atoms which match a package in the same slot. Later on, # the package version chosen here is used in the # has_upgrade/has_downgrade logic to prefer choices with # upgrades, and a package choice that is not internally # consistent will lead the has_upgrade/has_downgrade logic # to produce invalid results (see bug 600346). all_match_current = all(a.match(avail_pkg) for a in slot_atoms[avail_slot]) all_match_previous = all(a.match(highest_cpv) for a in slot_atoms[avail_slot]) if all_match_previous and not all_match_current: continue current_higher = (highest_cpv is None or vercmp(avail_pkg.version, highest_cpv.version) > 0) if current_higher or (all_match_current and not all_match_previous): cp_map[avail_pkg.cp] = avail_pkg new_slot_count = (len(slot_map) if graph_db is None else sum(not graph_db.match_pkgs(slot_atom) for slot_atom in slot_map if not slot_atom.cp.startswith("virtual/"))) this_choice = _dep_choice(atoms=atoms, slot_map=slot_map, cp_map=cp_map, all_available=all_available, all_installed_slots=False, new_slot_count=new_slot_count) if all_available: # The "all installed" criterion is not version or slot specific. # If any version of a package is already in the graph then we # assume that it is preferred over other possible packages choices. all_installed = True for atom in set(Atom(atom.cp) for atom in atoms \ if not atom.blocker): # New-style virtuals have zero cost to install. if not vardb.match(atom) and not atom.startswith("virtual/"): all_installed = False break all_installed_slots = False if all_installed: all_installed_slots = True for slot_atom in slot_map: # New-style virtuals have zero cost to install. if not vardb.match(slot_atom) and \ not slot_atom.startswith("virtual/"): all_installed_slots = False break this_choice.all_installed_slots = all_installed_slots if graph_db is None: if all_use_satisfied: if all_installed: if all_installed_slots: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if not all_use_unmasked: other.append(this_choice) elif all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) elif conflict_downgrade or installed_downgrade: other.append(this_choice) else: all_in_graph = True for atom in atoms: # New-style virtuals have zero cost to install. if atom.blocker or atom.cp.startswith("virtual/"): continue # We check if the matched package has actually been # added to the digraph, in order to distinguish between # those packages and installed packages that may need # to be uninstalled in order to resolve blockers. if not any(pkg in graph for pkg in graph_db.match_pkgs(atom)): all_in_graph = False break circular_atom = None if not (parent is None or priority is None) and \ (parent.onlydeps or (all_in_graph and priority.buildtime and not (priority.satisfied or priority.optional))): # Check if the atom would result in a direct circular # dependency and try to avoid that if it seems likely # to be unresolvable. This is only relevant for # buildtime deps that aren't already satisfied by an # installed package. cpv_slot_list = [parent] for atom in atoms: if atom.blocker: continue if vardb.match(atom): # If the atom is satisfied by an installed # version then it's not a circular dep. continue if atom.cp != parent.cp: continue if match_from_list(atom, cpv_slot_list): circular_atom = atom break if circular_atom is not None: other.append(this_choice) else: if all_use_satisfied: if all_in_graph: preferred_in_graph.append(this_choice) elif all_installed: if all_installed_slots: preferred_installed.append(this_choice) elif parent is None or want_update_pkg is None: preferred_any_slot.append(this_choice) else: # When appropriate, prefer a slot that is not # installed yet for bug #478188. want_update = True for slot_atom, avail_pkg in slot_map.items(): if avail_pkg in graph: continue # New-style virtuals have zero cost to install. if slot_atom.startswith("virtual/") or \ vardb.match(slot_atom): continue if not want_update_pkg(parent, avail_pkg): want_update = False break if want_update: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if not all_use_unmasked: other.append(this_choice) elif all_in_graph: unsat_use_in_graph.append(this_choice) elif all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) else: all_installed = True some_installed = False for atom in atoms: if not atom.blocker: if vardb.match(atom): some_installed = True else: all_installed = False if all_installed: this_choice.all_installed_slots = True other_installed.append(this_choice) elif some_installed: other_installed_some.append(this_choice) # Use Atom(atom.cp) for a somewhat "fuzzy" match, since # the whole atom may be too specific. For example, see # bug #522652, where using the whole atom leads to an # unsatisfiable choice. elif any(vardb.match(Atom(atom.cp)) for atom in atoms if not atom.blocker): other_installed_any_slot.append(this_choice) else: other.append(this_choice) # Prefer choices which contain upgrades to higher slots. This helps # for deps such as || ( foo:1 foo:2 ), where we want to prefer the # atom which matches the higher version rather than the atom furthest # to the left. Sorting is done separately for each of choice_bins, so # as not to interfere with the ordering of the bins. Because of the # bin separation, the main function of this code is to allow # --depclean to remove old slots (rather than to pull in new slots). for choices in choice_bins: if len(choices) < 2: continue sort_keys = [] # Prefer choices with all_installed_slots for bug #480736. sort_keys.append(lambda x: not x.all_installed_slots) if minimize_slots: # Prefer choices having fewer new slots. When used with DNF form, # this can eliminate unecessary packages that depclean would # ultimately eliminate (see bug 632026). Only use this behavior # when deemed necessary by the caller, since this will discard the # order specified in the ebuild, and the preferences specified # there can serve as a crucial sources of guidance (see bug 645002). # NOTE: Under some conditions, new_slot_count value may have some # variance from one calculation to the next because it depends on # the order that packages are added to the graph. This variance can # contribute to outcomes that appear to be random. Meanwhile, # the order specified in the ebuild is without variance, so it # does not have this problem. sort_keys.append(lambda x: x.new_slot_count) choices.sort(key=lambda x: tuple(f(x) for f in sort_keys)) for choice_1 in choices[1:]: cps = set(choice_1.cp_map) for choice_2 in choices: if choice_1 is choice_2: # choice_1 will not be promoted, so move on break intersecting_cps = cps.intersection(choice_2.cp_map) if not intersecting_cps: continue has_upgrade = False has_downgrade = False for cp in intersecting_cps: version_1 = choice_1.cp_map[cp] version_2 = choice_2.cp_map[cp] difference = vercmp(version_1.version, version_2.version) if difference != 0: if difference > 0: has_upgrade = True else: has_downgrade = True break if has_upgrade and not has_downgrade: # promote choice_1 in front of choice_2 choices.remove(choice_1) index_2 = choices.index(choice_2) choices.insert(index_2, choice_1) break for allow_masked in (False, True): for choices in choice_bins: for choice in choices: if choice.all_available or allow_masked: return choice.atoms assert(False) # This point should not be reachable
def _prepare_conflict_msg_and_check_for_specificity(self): """ Print all slot conflicts in a human readable way. """ _pkg_use_enabled = self.depgraph._pkg_use_enabled msg = self.conflict_msg indent = " " msg.append("\n!!! Multiple package instances within a single " + \ "package slot have been pulled\n") msg.append("!!! into the dependency graph, resulting" + \ " in a slot conflict:\n\n") for (slot_atom, root), pkgs \ in self.slot_collision_info.items(): msg.append(str(slot_atom)) if root != '/': msg.append(" for %s" % (root, )) msg.append("\n\n") for pkg in pkgs: msg.append(indent) msg.append(str(pkg)) parent_atoms = self.all_parents.get(pkg) if parent_atoms: #Create a list of collision reasons and map them to sets #of atoms. #Possible reasons: # ("version", "ge") for operator >=, > # ("version", "eq") for operator =, ~ # ("version", "le") for operator <=, < # ("use", "<some use flag>") for unmet use conditionals collision_reasons = {} num_all_specific_atoms = 0 for ppkg, atom in parent_atoms: atom_set = InternalPackageSet(initial_atoms=(atom, )) atom_without_use_set = InternalPackageSet( initial_atoms=(atom.without_use, )) for other_pkg in pkgs: if other_pkg == pkg: continue if not atom_without_use_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): #The version range does not match. sub_type = None if atom.operator in (">=", ">"): sub_type = "ge" elif atom.operator in ("=", "~"): sub_type = "eq" elif atom.operator in ("<=", "<"): sub_type = "le" atoms = collision_reasons.get( ("version", sub_type), set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[("version", sub_type)] = atoms elif not atom_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): #Use conditionals not met. violated_atom = atom.violated_conditionals(_pkg_use_enabled(other_pkg), \ other_pkg.iuse.is_valid_flag) for flag in violated_atom.use.enabled.union( violated_atom.use.disabled): atoms = collision_reasons.get( ("use", flag), set()) atoms.add((ppkg, atom, other_pkg)) collision_reasons[("use", flag)] = atoms num_all_specific_atoms += 1 msg.append(" pulled in by\n") selected_for_display = set() for (type, sub_type), parents in collision_reasons.items(): #From each (type, sub_type) pair select at least one atom. #Try to select as few atoms as possible if type == "version": #Find the atom with version that is as far away as possible. best_matches = {} for ppkg, atom, other_pkg in parents: if atom.cp in best_matches: cmp = vercmp( \ cpv_getversion(atom.cpv), \ cpv_getversion(best_matches[atom.cp][1].cpv)) if (sub_type == "ge" and cmp > 0) \ or (sub_type == "le" and cmp < 0) \ or (sub_type == "eq" and cmp > 0): best_matches[atom.cp] = (ppkg, atom) else: best_matches[atom.cp] = (ppkg, atom) selected_for_display.update(best_matches.values()) elif type == "use": #Prefer atoms with unconditional use deps over, because it's #not possible to change them on the parent, which means there #are fewer possible solutions. use = sub_type hard_matches = set() conditional_matches = set() for ppkg, atom, other_pkg in parents: parent_use = None if isinstance(ppkg, Package): parent_use = _pkg_use_enabled(ppkg) violated_atom = atom.unevaluated_atom.violated_conditionals( \ _pkg_use_enabled(other_pkg), other_pkg.iuse.is_valid_flag, parent_use=parent_use) if use in violated_atom.use.enabled.union( violated_atom.use.disabled): hard_matches.add((ppkg, atom)) else: conditional_matches.add((ppkg, atom)) if hard_matches: matches = hard_matches else: matches = conditional_matches if not selected_for_display.intersection(matches): selected_for_display.add(matches.pop()) def highlight_violations(atom, version, use=[]): """Colorize parts of an atom""" atom_str = str(atom) if version: op = atom.operator ver = cpv_getversion(atom.cpv) slot = atom.slot atom_str = atom_str.replace( op, colorize("BAD", op), 1) start = atom_str.rfind(ver) end = start + len(ver) atom_str = atom_str[:start] + \ colorize("BAD", ver) + \ atom_str[end+1:] if slot: atom_str = atom_str.replace( ":" + slot, colorize("BAD", ":" + slot)) if use and atom.use.tokens: use_part_start = atom_str.find("[") use_part_end = atom_str.find("]") new_tokens = [] for token in atom.use.tokens: if token.lstrip("-!").rstrip("=?") in use: new_tokens.append(colorize("BAD", token)) else: new_tokens.append(token) atom_str = atom_str[:use_part_start] \ + "[%s]" % (",".join(new_tokens),) + \ atom_str[use_part_end+1:] return atom_str for parent_atom in selected_for_display: parent, atom = parent_atom msg.append(2 * indent) if isinstance(parent, (PackageArg, AtomArg)): # For PackageArg and AtomArg types, it's # redundant to display the atom attribute. msg.append(str(parent)) else: # Display the specific atom from SetArg or # Package types. version_violated = False use = [] for type, sub_type in collision_reasons: if type == "version": for x in collision_reasons[(type, sub_type)]: if ppkg == x[0] and atom == x[1]: version_violated = True elif type == "use": use.append(sub_type) atom_str = highlight_violations( atom.unevaluated_atom, version_violated, use) if version_violated: self.is_a_version_conflict = True msg.append("%s required by %s" % (atom_str, parent)) msg.append("\n") if not selected_for_display: msg.append(2 * indent) msg.append( "(no parents that aren't satisfied by other packages in this slot)\n" ) self.conflict_is_unspecific = True omitted_parents = num_all_specific_atoms - len( selected_for_display) if omitted_parents: msg.append(2 * indent) if len(selected_for_display) > 1: msg.append( "(and %d more with the same problems)\n" % omitted_parents) else: msg.append( "(and %d more with the same problem)\n" % omitted_parents) else: msg.append(" (no parents)\n") msg.append("\n") msg.append("\n")
def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): """ Takes an unreduced and reduced deplist and removes satisfied dependencies. Returned deplist contains steps that must be taken to satisfy dependencies. """ if trees is None: trees = portage.db writemsg("ZapDeps -- %s\n" % (use_binaries), 2) if not reduced or unreduced == ["||"] or dep_eval(reduced): return [] if unreduced[0] != "||": unresolved = [] for x, satisfied in zip(unreduced, reduced): if isinstance(x, list): unresolved += dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees) elif not satisfied: unresolved.append(x) return unresolved # We're at a ( || atom ... ) type level and need to make a choice deps = unreduced[1:] satisfieds = reduced[1:] # Our preference order is for an the first item that: # a) contains all unmasked packages with the same key as installed packages # b) contains all unmasked packages # c) contains masked installed packages # d) is the first item preferred_installed = [] preferred_in_graph = [] preferred_any_slot = [] preferred_non_installed = [] unsat_use_in_graph = [] unsat_use_installed = [] unsat_use_non_installed = [] other_installed = [] other_installed_some = [] other = [] # unsat_use_* must come after preferred_non_installed # for correct ordering in cases like || ( foo[a] foo[b] ). choice_bins = ( preferred_in_graph, preferred_installed, preferred_any_slot, preferred_non_installed, unsat_use_in_graph, unsat_use_installed, unsat_use_non_installed, other_installed, other_installed_some, other, ) # Alias the trees we'll be checking availability against parent = trees[myroot].get("parent") priority = trees[myroot].get("priority") graph_db = trees[myroot].get("graph_db") graph = trees[myroot].get("graph") want_update_pkg = trees[myroot].get("want_update_pkg") vardb = None if "vartree" in trees[myroot]: vardb = trees[myroot]["vartree"].dbapi if use_binaries: mydbapi = trees[myroot]["bintree"].dbapi else: mydbapi = trees[myroot]["porttree"].dbapi try: mydbapi_match_pkgs = mydbapi.match_pkgs except AttributeError: def mydbapi_match_pkgs(atom): return [mydbapi._pkg_str(cpv, atom.repo) for cpv in mydbapi.match(atom)] # Sort the deps into installed, not installed but already # in the graph and other, not installed and not in the graph # and other, with values of [[required_atom], availablility] for x, satisfied in zip(deps, satisfieds): if isinstance(x, list): atoms = dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees) else: atoms = [x] if vardb is None: # When called by repoman, we can simply return the first choice # because dep_eval() handles preference selection. return atoms all_available = True all_use_satisfied = True slot_map = {} cp_map = {} for atom in atoms: if atom.blocker: continue # Ignore USE dependencies here since we don't want USE # settings to adversely affect || preference evaluation. avail_pkg = mydbapi_match_pkgs(atom.without_use) if avail_pkg: avail_pkg = avail_pkg[-1] # highest (ascending order) avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) if not avail_pkg: all_available = False all_use_satisfied = False break if atom.use: avail_pkg_use = mydbapi_match_pkgs(atom) if not avail_pkg_use: all_use_satisfied = False else: # highest (ascending order) avail_pkg_use = avail_pkg_use[-1] if avail_pkg_use != avail_pkg: avail_pkg = avail_pkg_use avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) slot_map[avail_slot] = avail_pkg highest_cpv = cp_map.get(avail_pkg.cp) if highest_cpv is None or vercmp(avail_pkg.version, highest_cpv.version) > 0: cp_map[avail_pkg.cp] = avail_pkg this_choice = _dep_choice( atoms=atoms, slot_map=slot_map, cp_map=cp_map, all_available=all_available, all_installed_slots=False ) if all_available: # The "all installed" criterion is not version or slot specific. # If any version of a package is already in the graph then we # assume that it is preferred over other possible packages choices. all_installed = True for atom in set(Atom(atom.cp) for atom in atoms if not atom.blocker): # New-style virtuals have zero cost to install. if not vardb.match(atom) and not atom.startswith("virtual/"): all_installed = False break all_installed_slots = False if all_installed: all_installed_slots = True for slot_atom in slot_map: # New-style virtuals have zero cost to install. if not vardb.match(slot_atom) and not slot_atom.startswith("virtual/"): all_installed_slots = False break this_choice.all_installed_slots = all_installed_slots if graph_db is None: if all_use_satisfied: if all_installed: if all_installed_slots: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) else: all_in_graph = True for atom in atoms: # New-style virtuals have zero cost to install. if atom.blocker or atom.cp.startswith("virtual/"): continue # We check if the matched package has actually been # added to the digraph, in order to distinguish between # those packages and installed packages that may need # to be uninstalled in order to resolve blockers. if not any(pkg in graph for pkg in graph_db.match_pkgs(atom)): all_in_graph = False break circular_atom = None if all_in_graph: if parent is None or priority is None: pass elif priority.buildtime and not (priority.satisfied or priority.optional): # Check if the atom would result in a direct circular # dependency and try to avoid that if it seems likely # to be unresolvable. This is only relevant for # buildtime deps that aren't already satisfied by an # installed package. cpv_slot_list = [parent] for atom in atoms: if atom.blocker: continue if vardb.match(atom): # If the atom is satisfied by an installed # version then it's not a circular dep. continue if atom.cp != parent.cp: continue if match_from_list(atom, cpv_slot_list): circular_atom = atom break if circular_atom is not None: other.append(this_choice) else: if all_use_satisfied: if all_in_graph: preferred_in_graph.append(this_choice) elif all_installed: if all_installed_slots: preferred_installed.append(this_choice) elif parent is None or want_update_pkg is None: preferred_any_slot.append(this_choice) else: # When appropriate, prefer a slot that is not # installed yet for bug #478188. want_update = True for slot_atom, avail_pkg in slot_map.items(): if avail_pkg in graph: continue # New-style virtuals have zero cost to install. if slot_atom.startswith("virtual/") or vardb.match(slot_atom): continue if not want_update_pkg(parent, avail_pkg): want_update = False break if want_update: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if all_in_graph: unsat_use_in_graph.append(this_choice) elif all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) else: all_installed = True some_installed = False for atom in atoms: if not atom.blocker: if vardb.match(atom): some_installed = True else: all_installed = False if all_installed: this_choice.all_installed_slots = True other_installed.append(this_choice) elif some_installed: other_installed_some.append(this_choice) else: other.append(this_choice) # Prefer choices which contain upgrades to higher slots. This helps # for deps such as || ( foo:1 foo:2 ), where we want to prefer the # atom which matches the higher version rather than the atom furthest # to the left. Sorting is done separately for each of choice_bins, so # as not to interfere with the ordering of the bins. Because of the # bin separation, the main function of this code is to allow # --depclean to remove old slots (rather than to pull in new slots). for choices in choice_bins: if len(choices) < 2: continue # Prefer choices with all_installed_slots for bug #480736. choices.sort(key=operator.attrgetter("all_installed_slots"), reverse=True) for choice_1 in choices[1:]: cps = set(choice_1.cp_map) for choice_2 in choices: if choice_1 is choice_2: # choice_1 will not be promoted, so move on break intersecting_cps = cps.intersection(choice_2.cp_map) if not intersecting_cps: continue has_upgrade = False has_downgrade = False for cp in intersecting_cps: version_1 = choice_1.cp_map[cp] version_2 = choice_2.cp_map[cp] difference = vercmp(version_1.version, version_2.version) if difference != 0: if difference > 0: has_upgrade = True else: has_downgrade = True break if has_upgrade and not has_downgrade: # promote choice_1 in front of choice_2 choices.remove(choice_1) index_2 = choices.index(choice_2) choices.insert(index_2, choice_1) break for allow_masked in (False, True): for choices in choice_bins: for choice in choices: if choice.all_available or allow_masked: return choice.atoms assert False # This point should not be reachable
def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): """ Takes an unreduced and reduced deplist and removes satisfied dependencies. Returned deplist contains steps that must be taken to satisfy dependencies. """ if trees is None: trees = portage.db writemsg("ZapDeps -- %s\n" % (use_binaries), 2) if not reduced or unreduced == ["||"] or dep_eval(reduced): return [] if unreduced[0] != "||": unresolved = [] for x, satisfied in zip(unreduced, reduced): if isinstance(x, list): unresolved += dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees) elif not satisfied: unresolved.append(x) return unresolved # We're at a ( || atom ... ) type level and need to make a choice deps = unreduced[1:] satisfieds = reduced[1:] # Our preference order is for an the first item that: # a) contains all unmasked packages with the same key as installed packages # b) contains all unmasked packages # c) contains masked installed packages # d) is the first item preferred_installed = [] preferred_in_graph = [] preferred_any_slot = [] preferred_non_installed = [] unsat_use_in_graph = [] unsat_use_installed = [] unsat_use_non_installed = [] other_installed = [] other_installed_some = [] other_installed_any_slot = [] other = [] # unsat_use_* must come after preferred_non_installed # for correct ordering in cases like || ( foo[a] foo[b] ). choice_bins = ( preferred_in_graph, preferred_installed, preferred_any_slot, preferred_non_installed, unsat_use_in_graph, unsat_use_installed, unsat_use_non_installed, other_installed, other_installed_some, other_installed_any_slot, other, ) # Alias the trees we'll be checking availability against parent = trees[myroot].get("parent") priority = trees[myroot].get("priority") graph_db = trees[myroot].get("graph_db") graph = trees[myroot].get("graph") pkg_use_enabled = trees[myroot].get("pkg_use_enabled") want_update_pkg = trees[myroot].get("want_update_pkg") downgrade_probe = trees[myroot].get("downgrade_probe") vardb = None if "vartree" in trees[myroot]: vardb = trees[myroot]["vartree"].dbapi if use_binaries: mydbapi = trees[myroot]["bintree"].dbapi else: mydbapi = trees[myroot]["porttree"].dbapi try: mydbapi_match_pkgs = mydbapi.match_pkgs except AttributeError: def mydbapi_match_pkgs(atom): return [ mydbapi._pkg_str(cpv, atom.repo) for cpv in mydbapi.match(atom) ] # Sort the deps into installed, not installed but already # in the graph and other, not installed and not in the graph # and other, with values of [[required_atom], availablility] for x, satisfied in zip(deps, satisfieds): if isinstance(x, list): atoms = dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees) else: atoms = [x] if vardb is None: # When called by repoman, we can simply return the first choice # because dep_eval() handles preference selection. return atoms all_available = True all_use_satisfied = True all_use_unmasked = True conflict_downgrade = False slot_map = {} cp_map = {} for atom in atoms: if atom.blocker: continue # Ignore USE dependencies here since we don't want USE # settings to adversely affect || preference evaluation. avail_pkg = mydbapi_match_pkgs(atom.without_use) if avail_pkg: avail_pkg = avail_pkg[-1] # highest (ascending order) avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) if not avail_pkg: all_available = False all_use_satisfied = False break if graph_db is not None and downgrade_probe is not None: slot_matches = graph_db.match_pkgs(avail_slot) if (len(slot_matches) > 1 and avail_pkg < slot_matches[-1] and not downgrade_probe(avail_pkg)): # If a downgrade is not desirable, then avoid a # choice that pulls in a lower version involved # in a slot conflict (bug #531656). conflict_downgrade = True if atom.use: avail_pkg_use = mydbapi_match_pkgs(atom) if not avail_pkg_use: all_use_satisfied = False if pkg_use_enabled is not None: # Check which USE flags cause the match to fail, # so we can prioritize choices that do not # require changes to use.mask or use.force # (see bug #515584). violated_atom = atom.violated_conditionals( pkg_use_enabled(avail_pkg), avail_pkg.iuse.is_valid_flag) # Note that violated_atom.use can be None here, # since evaluation can collapse conditional USE # deps that cause the match to fail due to # missing IUSE (match uses atom.unevaluated_atom # to detect such missing IUSE). if violated_atom.use is not None: for flag in violated_atom.use.enabled: if flag in avail_pkg.use.mask: all_use_unmasked = False break else: for flag in violated_atom.use.disabled: if flag in avail_pkg.use.force and \ flag not in avail_pkg.use.mask: all_use_unmasked = False break else: # highest (ascending order) avail_pkg_use = avail_pkg_use[-1] if avail_pkg_use != avail_pkg: avail_pkg = avail_pkg_use avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) slot_map[avail_slot] = avail_pkg highest_cpv = cp_map.get(avail_pkg.cp) if highest_cpv is None or \ vercmp(avail_pkg.version, highest_cpv.version) > 0: cp_map[avail_pkg.cp] = avail_pkg this_choice = _dep_choice(atoms=atoms, slot_map=slot_map, cp_map=cp_map, all_available=all_available, all_installed_slots=False) if all_available: # The "all installed" criterion is not version or slot specific. # If any version of a package is already in the graph then we # assume that it is preferred over other possible packages choices. all_installed = True for atom in set(Atom(atom.cp) for atom in atoms \ if not atom.blocker): # New-style virtuals have zero cost to install. if not vardb.match(atom) and not atom.startswith("virtual/"): all_installed = False break all_installed_slots = False if all_installed: all_installed_slots = True for slot_atom in slot_map: # New-style virtuals have zero cost to install. if not vardb.match(slot_atom) and \ not slot_atom.startswith("virtual/"): all_installed_slots = False break this_choice.all_installed_slots = all_installed_slots if graph_db is None: if all_use_satisfied: if all_installed: if all_installed_slots: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if not all_use_unmasked: other.append(this_choice) elif all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) elif conflict_downgrade: other.append(this_choice) else: all_in_graph = True for atom in atoms: # New-style virtuals have zero cost to install. if atom.blocker or atom.cp.startswith("virtual/"): continue # We check if the matched package has actually been # added to the digraph, in order to distinguish between # those packages and installed packages that may need # to be uninstalled in order to resolve blockers. if not any(pkg in graph for pkg in graph_db.match_pkgs(atom)): all_in_graph = False break circular_atom = None if not (parent is None or priority is None) and \ (parent.onlydeps or (all_in_graph and priority.buildtime and not (priority.satisfied or priority.optional))): # Check if the atom would result in a direct circular # dependency and try to avoid that if it seems likely # to be unresolvable. This is only relevant for # buildtime deps that aren't already satisfied by an # installed package. cpv_slot_list = [parent] for atom in atoms: if atom.blocker: continue if vardb.match(atom): # If the atom is satisfied by an installed # version then it's not a circular dep. continue if atom.cp != parent.cp: continue if match_from_list(atom, cpv_slot_list): circular_atom = atom break if circular_atom is not None: other.append(this_choice) else: if all_use_satisfied: if all_in_graph: preferred_in_graph.append(this_choice) elif all_installed: if all_installed_slots: preferred_installed.append(this_choice) elif parent is None or want_update_pkg is None: preferred_any_slot.append(this_choice) else: # When appropriate, prefer a slot that is not # installed yet for bug #478188. want_update = True for slot_atom, avail_pkg in slot_map.items(): if avail_pkg in graph: continue # New-style virtuals have zero cost to install. if slot_atom.startswith("virtual/") or \ vardb.match(slot_atom): continue if not want_update_pkg(parent, avail_pkg): want_update = False break if want_update: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if not all_use_unmasked: other.append(this_choice) elif all_in_graph: unsat_use_in_graph.append(this_choice) elif all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) else: all_installed = True some_installed = False for atom in atoms: if not atom.blocker: if vardb.match(atom): some_installed = True else: all_installed = False if all_installed: this_choice.all_installed_slots = True other_installed.append(this_choice) elif some_installed: other_installed_some.append(this_choice) # Use Atom(atom.cp) for a somewhat "fuzzy" match, since # the whole atom may be too specific. For example, see # bug #522652, where using the whole atom leads to an # unsatisfiable choice. elif any( vardb.match(Atom(atom.cp)) for atom in atoms if not atom.blocker): other_installed_any_slot.append(this_choice) else: other.append(this_choice) # Prefer choices which contain upgrades to higher slots. This helps # for deps such as || ( foo:1 foo:2 ), where we want to prefer the # atom which matches the higher version rather than the atom furthest # to the left. Sorting is done separately for each of choice_bins, so # as not to interfere with the ordering of the bins. Because of the # bin separation, the main function of this code is to allow # --depclean to remove old slots (rather than to pull in new slots). for choices in choice_bins: if len(choices) < 2: continue # Prefer choices with all_installed_slots for bug #480736. choices.sort(key=operator.attrgetter('all_installed_slots'), reverse=True) for choice_1 in choices[1:]: cps = set(choice_1.cp_map) for choice_2 in choices: if choice_1 is choice_2: # choice_1 will not be promoted, so move on break intersecting_cps = cps.intersection(choice_2.cp_map) if not intersecting_cps: continue has_upgrade = False has_downgrade = False for cp in intersecting_cps: version_1 = choice_1.cp_map[cp] version_2 = choice_2.cp_map[cp] difference = vercmp(version_1.version, version_2.version) if difference != 0: if difference > 0: has_upgrade = True else: has_downgrade = True break if has_upgrade and not has_downgrade: # promote choice_1 in front of choice_2 choices.remove(choice_1) index_2 = choices.index(choice_2) choices.insert(index_2, choice_1) break for allow_masked in (False, True): for choices in choice_bins: for choice in choices: if choice.all_available or allow_masked: return choice.atoms assert (False) # This point should not be reachable
def up2date(self): if not self.error: return vercmp(self.gentoo_version, self.upstream_version) == 0
def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None): """ Takes an unreduced and reduced deplist and removes satisfied dependencies. Returned deplist contains steps that must be taken to satisfy dependencies. """ if trees is None: trees = portage.db writemsg("ZapDeps -- %s\n" % (use_binaries), 2) if not reduced or unreduced == ["||"] or dep_eval(reduced): return [] if unreduced[0] != "||": unresolved = [] for x, satisfied in zip(unreduced, reduced): if isinstance(x, list): unresolved += dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees) elif not satisfied: unresolved.append(x) return unresolved # We're at a ( || atom ... ) type level and need to make a choice deps = unreduced[1:] satisfieds = reduced[1:] # Our preference order is for an the first item that: # a) contains all unmasked packages with the same key as installed packages # b) contains all unmasked packages # c) contains masked installed packages # d) is the first item preferred_installed = [] preferred_in_graph = [] preferred_any_slot = [] preferred_non_installed = [] unsat_use_in_graph = [] unsat_use_installed = [] unsat_use_non_installed = [] other_installed = [] other_installed_some = [] other = [] # unsat_use_* must come after preferred_non_installed # for correct ordering in cases like || ( foo[a] foo[b] ). choice_bins = ( preferred_in_graph, preferred_installed, preferred_any_slot, preferred_non_installed, unsat_use_in_graph, unsat_use_installed, unsat_use_non_installed, other_installed, other_installed_some, other, ) # Alias the trees we'll be checking availability against parent = trees[myroot].get("parent") priority = trees[myroot].get("priority") graph_db = trees[myroot].get("graph_db") graph = trees[myroot].get("graph") vardb = None if "vartree" in trees[myroot]: vardb = trees[myroot]["vartree"].dbapi if use_binaries: mydbapi = trees[myroot]["bintree"].dbapi else: mydbapi = trees[myroot]["porttree"].dbapi # Sort the deps into installed, not installed but already # in the graph and other, not installed and not in the graph # and other, with values of [[required_atom], availablility] for x, satisfied in zip(deps, satisfieds): if isinstance(x, list): atoms = dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees) else: atoms = [x] if vardb is None: # When called by repoman, we can simply return the first choice # because dep_eval() handles preference selection. return atoms all_available = True all_use_satisfied = True slot_map = {} cp_map = {} for atom in atoms: if atom.blocker: continue # Ignore USE dependencies here since we don't want USE # settings to adversely affect || preference evaluation. avail_pkg = mydbapi.match(atom.without_use) if avail_pkg: avail_pkg = avail_pkg[-1] # highest (ascending order) avail_pkg = mydbapi._pkg_str(avail_pkg, atom.repo) avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) if not avail_pkg: all_available = False all_use_satisfied = False break if atom.use: avail_pkg_use = mydbapi.match(atom) if not avail_pkg_use: all_use_satisfied = False else: # highest (ascending order) avail_pkg_use = avail_pkg_use[-1] if avail_pkg_use != avail_pkg: avail_pkg = avail_pkg_use avail_pkg = mydbapi._pkg_str(avail_pkg, atom.repo) avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) slot_map[avail_slot] = avail_pkg highest_cpv = cp_map.get(avail_pkg.cp) if highest_cpv is None or \ vercmp(avail_pkg.version, highest_cpv.version) > 0: cp_map[avail_pkg.cp] = avail_pkg this_choice = (atoms, slot_map, cp_map, all_available) if all_available: # The "all installed" criterion is not version or slot specific. # If any version of a package is already in the graph then we # assume that it is preferred over other possible packages choices. all_installed = True for atom in set(Atom(atom.cp) for atom in atoms \ if not atom.blocker): # New-style virtuals have zero cost to install. if not vardb.match(atom) and not atom.startswith("virtual/"): all_installed = False break all_installed_slots = False if all_installed: all_installed_slots = True for slot_atom in slot_map: # New-style virtuals have zero cost to install. if not vardb.match(slot_atom) and \ not slot_atom.startswith("virtual/"): all_installed_slots = False break if graph_db is None: if all_use_satisfied: if all_installed: if all_installed_slots: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) else: all_in_graph = True for slot_atom in slot_map: # New-style virtuals have zero cost to install. if slot_atom.startswith("virtual/"): continue # We check if the matched package has actually been # added to the digraph, in order to distinguish between # those packages and installed packages that may need # to be uninstalled in order to resolve blockers. graph_matches = graph_db.match_pkgs(slot_atom) if not graph_matches or graph_matches[-1] not in graph: all_in_graph = False break circular_atom = None if all_in_graph: if parent is None or priority is None: pass elif priority.buildtime and \ not (priority.satisfied or priority.optional): # Check if the atom would result in a direct circular # dependency and try to avoid that if it seems likely # to be unresolvable. This is only relevant for # buildtime deps that aren't already satisfied by an # installed package. cpv_slot_list = [parent] for atom in atoms: if atom.blocker: continue if vardb.match(atom): # If the atom is satisfied by an installed # version then it's not a circular dep. continue if atom.cp != parent.cp: continue if match_from_list(atom, cpv_slot_list): circular_atom = atom break if circular_atom is not None: other.append(this_choice) else: if all_use_satisfied: if all_in_graph: preferred_in_graph.append(this_choice) elif all_installed: if all_installed_slots: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if all_in_graph: unsat_use_in_graph.append(this_choice) elif all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) else: all_installed = True some_installed = False for atom in atoms: if not atom.blocker: if vardb.match(atom): some_installed = True else: all_installed = False if all_installed: other_installed.append(this_choice) elif some_installed: other_installed_some.append(this_choice) else: other.append(this_choice) # Prefer choices which contain upgrades to higher slots. This helps # for deps such as || ( foo:1 foo:2 ), where we want to prefer the # atom which matches the higher version rather than the atom furthest # to the left. Sorting is done separately for each of choice_bins, so # as not to interfere with the ordering of the bins. Because of the # bin separation, the main function of this code is to allow # --depclean to remove old slots (rather than to pull in new slots). for choices in choice_bins: if len(choices) < 2: continue for choice_1 in choices[1:]: atoms_1, slot_map_1, cp_map_1, all_available_1 = choice_1 cps = set(cp_map_1) for choice_2 in choices: if choice_1 is choice_2: # choice_1 will not be promoted, so move on break atoms_2, slot_map_2, cp_map_2, all_available_2 = choice_2 intersecting_cps = cps.intersection(cp_map_2) if not intersecting_cps: continue has_upgrade = False has_downgrade = False for cp in intersecting_cps: version_1 = cp_map_1[cp] version_2 = cp_map_2[cp] difference = vercmp(version_1.version, version_2.version) if difference != 0: if difference > 0: has_upgrade = True else: has_downgrade = True break if has_upgrade and not has_downgrade: # promote choice_1 in front of choice_2 choices.remove(choice_1) index_2 = choices.index(choice_2) choices.insert(index_2, choice_1) break for allow_masked in (False, True): for choices in choice_bins: for atoms, slot_map, cp_map, all_available in choices: if all_available or allow_masked: return atoms assert (False) # This point should not be reachable
def pkgcmp_atom(pkgdir, pkg): ebuilds = [] for ent in listdir(pkgdir): if ent.endswith(".ebuild"): ebuilds.append(ent) ppkg = basename(strip_atoms(pkg)) revre = re.compile( ("^" + re.escape(ppkg) + "(-r\d+)?.ebuild$") ) # print "DBG: checking for %s" % pkg # print "DBG: Got %i ebuilds:" % len(ebuilds) # print ebuilds for ebuild in ebuilds: # workaround? for - prefix if pkg.startswith( "-" ): pkg = pkg[1:] if pkg.startswith( ("=", "~") ): if pkg.startswith("~"): if revre.match(ebuild): # print "DBG: revmatch '%s' '%s'" % (pkg, ebuild) return 1 else: # print "DBG: revmatch continue" continue if pkg.endswith("*"): if ebuild.startswith(ppkg): # print "DBG: startswith '%s' '%s'" % (pkg, ebuild) return 1 else: # print "DBG: startswith continue" continue else: if ebuild == (ppkg + ".ebuild"): # print "DBG: '%s' == '%s'" % (ppkg, ppkg) return 1 else: # print "DBG: == continue" continue if pkg.startswith( (">=", ">", "<=", "<") ): plain = strip_atoms(pkg) mypkg = pkgsplit(plain) ourpkg = pkgsplit(ebuild.rstrip(".ebuild")) mypkgv = mypkg[1] if mypkg[2] != "r0": mypkgv = mypkgv + "-" + mypkg[2] ourpkgv = ourpkg[1] if ourpkg[2] != "r0": ourpkgv = ourpkgv + "-" + ourpkg[2] # print "MYPKGV:", mypkgv, "OURPKGV:", ourpkgv, "RESULT 'vercmp('%s', '%s'): %i" % (mypkgv, ourpkgv, vercmp(mypkgv, ourpkgv)) if pkg.startswith(">="): if vercmp(mypkgv, ourpkgv) <= 0: # print "HIT: '%s' >= '%s'" % (ourpkg, mypkg) return 1 else: # print ">= continue" continue if pkg.startswith(">") and not pkg.startswith(">="): if vercmp(mypkgv, ourpkgv) < 0: # print "HIT: '%s' > '%s'" % (ourpkg, mypkg) return 1 else: # print "> continue" continue if pkg.startswith("<="): if vercmp(mypkgv, ourpkgv) >= 0: # print "HIT: '%s' <= '%s'" % (ourpkg, mypkg) return 1 else: # print "<= continue" continue if pkg.startswith("<") and not pkg.startswith("<="): if vercmp(mypkgv, ourpkgv) > 0: # print "HIT: '%s' < '%s'" % (ourpkg, mypkg) return 1 else: # print "< continue" continue # print "Nothing found... '%s' is invalid" % pkg return 0
def _prepare_conflict_msg_and_check_for_specificity(self): """ Print all slot conflicts in a human readable way. """ _pkg_use_enabled = self.depgraph._pkg_use_enabled msg = self.conflict_msg indent = " " msg.append("\n!!! Multiple package instances within a single " + \ "package slot have been pulled\n") msg.append("!!! into the dependency graph, resulting" + \ " in a slot conflict:\n\n") for (slot_atom, root), pkgs \ in self.slot_collision_info.items(): msg.append(str(slot_atom)) if root != '/': msg.append(" for %s" % (root,)) msg.append("\n\n") for pkg in pkgs: msg.append(indent) msg.append(str(pkg)) parent_atoms = self.all_parents.get(pkg) if parent_atoms: #Create a list of collision reasons and map them to sets #of atoms. #Possible reasons: # ("version", "ge") for operator >=, > # ("version", "eq") for operator =, ~ # ("version", "le") for operator <=, < # ("use", "<some use flag>") for unmet use conditionals collision_reasons = {} num_all_specific_atoms = 0 for ppkg, atom in parent_atoms: atom_set = InternalPackageSet(initial_atoms=(atom,)) atom_without_use_set = InternalPackageSet(initial_atoms=(atom.without_use,)) for other_pkg in pkgs: if other_pkg == pkg: continue if not atom_without_use_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): #The version range does not match. sub_type = None if atom.operator in (">=", ">"): sub_type = "ge" elif atom.operator in ("=", "~"): sub_type = "eq" elif atom.operator in ("<=", "<"): sub_type = "le" atoms = collision_reasons.get(("version", sub_type), set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[("version", sub_type)] = atoms elif not atom_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): #Use conditionals not met. violated_atom = atom.violated_conditionals(_pkg_use_enabled(other_pkg), \ other_pkg.iuse.is_valid_flag) for flag in violated_atom.use.enabled.union(violated_atom.use.disabled): atoms = collision_reasons.get(("use", flag), set()) atoms.add((ppkg, atom, other_pkg)) collision_reasons[("use", flag)] = atoms num_all_specific_atoms += 1 msg.append(" pulled in by\n") selected_for_display = set() for (type, sub_type), parents in collision_reasons.items(): #From each (type, sub_type) pair select at least one atom. #Try to select as few atoms as possible if type == "version": #Find the atom with version that is as far away as possible. best_matches = {} for ppkg, atom, other_pkg in parents: if atom.cp in best_matches: cmp = vercmp( \ cpv_getversion(atom.cpv), \ cpv_getversion(best_matches[atom.cp][1].cpv)) if (sub_type == "ge" and cmp > 0) \ or (sub_type == "le" and cmp < 0) \ or (sub_type == "eq" and cmp > 0): best_matches[atom.cp] = (ppkg, atom) else: best_matches[atom.cp] = (ppkg, atom) selected_for_display.update(best_matches.values()) elif type == "use": #Prefer atoms with unconditional use deps over, because it's #not possible to change them on the parent, which means there #are fewer possible solutions. use = sub_type hard_matches = set() conditional_matches = set() for ppkg, atom, other_pkg in parents: parent_use = None if isinstance(ppkg, Package): parent_use = _pkg_use_enabled(ppkg) violated_atom = atom.unevaluated_atom.violated_conditionals( \ _pkg_use_enabled(other_pkg), other_pkg.iuse.is_valid_flag, parent_use=parent_use) if use in violated_atom.use.enabled.union(violated_atom.use.disabled): hard_matches.add((ppkg, atom)) else: conditional_matches.add((ppkg, atom)) if hard_matches: matches = hard_matches else: matches = conditional_matches if not selected_for_display.intersection(matches): selected_for_display.add(matches.pop()) def highlight_violations(atom, version, use=[]): """Colorize parts of an atom""" atom_str = str(atom) if version: op = atom.operator ver = cpv_getversion(atom.cpv) slot = atom.slot atom_str = atom_str.replace(op, colorize("BAD", op), 1) start = atom_str.rfind(ver) end = start + len(ver) atom_str = atom_str[:start] + \ colorize("BAD", ver) + \ atom_str[end+1:] if slot: atom_str = atom_str.replace(":" + slot, colorize("BAD", ":" + slot)) if use and atom.use.tokens: use_part_start = atom_str.find("[") use_part_end = atom_str.find("]") new_tokens = [] for token in atom.use.tokens: if token.lstrip("-!").rstrip("=?") in use: new_tokens.append(colorize("BAD", token)) else: new_tokens.append(token) atom_str = atom_str[:use_part_start] \ + "[%s]" % (",".join(new_tokens),) + \ atom_str[use_part_end+1:] return atom_str for parent_atom in selected_for_display: parent, atom = parent_atom msg.append(2*indent) if isinstance(parent, (PackageArg, AtomArg)): # For PackageArg and AtomArg types, it's # redundant to display the atom attribute. msg.append(str(parent)) else: # Display the specific atom from SetArg or # Package types. version_violated = False use = [] for type, sub_type in collision_reasons: if type == "version": for x in collision_reasons[(type, sub_type)]: if ppkg == x[0] and atom == x[1]: version_violated = True elif type == "use": use.append(sub_type) atom_str = highlight_violations(atom.unevaluated_atom, version_violated, use) if version_violated: self.is_a_version_conflict = True msg.append("%s required by %s" % (atom_str, parent)) msg.append("\n") if not selected_for_display: msg.append(2*indent) msg.append("(no parents that aren't satisfied by other packages in this slot)\n") self.conflict_is_unspecific = True omitted_parents = num_all_specific_atoms - len(selected_for_display) if omitted_parents: msg.append(2*indent) if len(selected_for_display) > 1: msg.append("(and %d more with the same problems)\n" % omitted_parents) else: msg.append("(and %d more with the same problem)\n" % omitted_parents) else: msg.append(" (no parents)\n") msg.append("\n") msg.append("\n")
def _prepare_conflict_msg_and_check_for_specificity(self): """ Print all slot conflicts in a human readable way. """ _pkg_use_enabled = self.depgraph._pkg_use_enabled usepkgonly = "--usepkgonly" in self.myopts need_rebuild = {} verboseconflicts = "--verbose-conflicts" in self.myopts any_omitted_parents = False msg = self.conflict_msg indent = " " msg.append("\n!!! Multiple package instances within a single " + \ "package slot have been pulled\n") msg.append("!!! into the dependency graph, resulting" + \ " in a slot conflict:\n\n") for root, slot_atom, pkgs in self.all_conflicts: msg.append("%s" % (slot_atom,)) if root != self.depgraph._frozen_config._running_root.root: msg.append(" for %s" % (root,)) msg.append("\n\n") for pkg in pkgs: msg.append(indent) msg.append("%s" % (pkg,)) parent_atoms = self.all_parents.get(pkg) if parent_atoms: #Create a list of collision reasons and map them to sets #of atoms. #Possible reasons: # ("version", "ge") for operator >=, > # ("version", "eq") for operator =, ~ # ("version", "le") for operator <=, < # ("use", "<some use flag>") for unmet use conditionals collision_reasons = {} num_all_specific_atoms = 0 for ppkg, atom in parent_atoms: if not atom.soname: atom_set = InternalPackageSet( initial_atoms=(atom,)) atom_without_use_set = InternalPackageSet( initial_atoms=(atom.without_use,)) atom_without_use_and_slot_set = \ InternalPackageSet(initial_atoms=( atom.without_use.without_slot,)) for other_pkg in pkgs: if other_pkg == pkg: continue if atom.soname: # The soname does not match. key = ("soname", atom) atoms = collision_reasons.get(key, set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[key] = atoms elif not atom_without_use_and_slot_set.findAtomForPackage(other_pkg, modified_use=_pkg_use_enabled(other_pkg)): if atom.operator is not None: # The version range does not match. sub_type = None if atom.operator in (">=", ">"): sub_type = "ge" elif atom.operator in ("=", "~"): sub_type = "eq" elif atom.operator in ("<=", "<"): sub_type = "le" key = ("version", sub_type) atoms = collision_reasons.get(key, set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[key] = atoms elif not atom_without_use_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): # The slot and/or sub_slot does not match. key = ("slot", (atom.slot, atom.sub_slot, atom.slot_operator)) atoms = collision_reasons.get(key, set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[key] = atoms elif not atom_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): missing_iuse = other_pkg.iuse.get_missing_iuse( atom.unevaluated_atom.use.required) if missing_iuse: for flag in missing_iuse: atoms = collision_reasons.get(("use", flag), set()) atoms.add((ppkg, atom, other_pkg)) collision_reasons[("use", flag)] = atoms num_all_specific_atoms += 1 else: #Use conditionals not met. violated_atom = atom.violated_conditionals(_pkg_use_enabled(other_pkg), \ other_pkg.iuse.is_valid_flag) if violated_atom.use is None: # Something like bug #453400 caused the # above findAtomForPackage call to # return None unexpectedly. msg = ("\n\n!!! BUG: Detected " "USE dep match inconsistency:\n" "\tppkg: %s\n" "\tviolated_atom: %s\n" "\tatom: %s unevaluated: %s\n" "\tother_pkg: %s IUSE: %s USE: %s\n" % (ppkg, violated_atom, atom, atom.unevaluated_atom, other_pkg, sorted(other_pkg.iuse.all), sorted(_pkg_use_enabled(other_pkg)))) writemsg(msg, noiselevel=-2) raise AssertionError( 'BUG: USE dep match inconsistency') for flag in violated_atom.use.enabled.union(violated_atom.use.disabled): atoms = collision_reasons.get(("use", flag), set()) atoms.add((ppkg, atom, other_pkg)) collision_reasons[("use", flag)] = atoms num_all_specific_atoms += 1 elif isinstance(ppkg, AtomArg) and other_pkg.installed: parent_atoms = collision_reasons.get(("AtomArg", None), set()) parent_atoms.add((ppkg, atom)) collision_reasons[("AtomArg", None)] = parent_atoms num_all_specific_atoms += 1 msg.append(" pulled in by\n") selected_for_display = set() unconditional_use_deps = set() for (type, sub_type), parents in collision_reasons.items(): #From each (type, sub_type) pair select at least one atom. #Try to select as few atoms as possible if type == "version": #Find the atom with version that is as far away as possible. best_matches = {} for ppkg, atom, other_pkg in parents: if atom.cp in best_matches: cmp = vercmp( \ cpv_getversion(atom.cpv), \ cpv_getversion(best_matches[atom.cp][1].cpv)) if (sub_type == "ge" and cmp > 0) \ or (sub_type == "le" and cmp < 0) \ or (sub_type == "eq" and cmp > 0): best_matches[atom.cp] = (ppkg, atom) else: best_matches[atom.cp] = (ppkg, atom) if verboseconflicts: selected_for_display.add((ppkg, atom)) if not verboseconflicts: selected_for_display.update( best_matches.values()) elif type in ("soname", "slot"): # Check for packages that might need to # be rebuilt, but cannot be rebuilt for # some reason. for ppkg, atom, other_pkg in parents: if not (isinstance(ppkg, Package) and ppkg.installed): continue if not (atom.soname or atom.slot_operator_built): continue if self.depgraph._frozen_config.excluded_pkgs.findAtomForPackage(ppkg, modified_use=self.depgraph._pkg_use_enabled(ppkg)): selected_for_display.add((ppkg, atom)) need_rebuild[ppkg] = 'matched by --exclude argument' elif self.depgraph._frozen_config.useoldpkg_atoms.findAtomForPackage(ppkg, modified_use=self.depgraph._pkg_use_enabled(ppkg)): selected_for_display.add((ppkg, atom)) need_rebuild[ppkg] = 'matched by --useoldpkg-atoms argument' elif usepkgonly: # This case is tricky, so keep quiet in order to avoid false-positives. pass elif not self.depgraph._equiv_ebuild_visible(ppkg): selected_for_display.add((ppkg, atom)) need_rebuild[ppkg] = 'ebuild is masked or unavailable' for ppkg, atom, other_pkg in parents: selected_for_display.add((ppkg, atom)) if not verboseconflicts: break elif type == "use": #Prefer atoms with unconditional use deps over, because it's #not possible to change them on the parent, which means there #are fewer possible solutions. use = sub_type for ppkg, atom, other_pkg in parents: missing_iuse = other_pkg.iuse.get_missing_iuse( atom.unevaluated_atom.use.required) if missing_iuse: unconditional_use_deps.add((ppkg, atom)) else: parent_use = None if isinstance(ppkg, Package): parent_use = _pkg_use_enabled(ppkg) violated_atom = atom.unevaluated_atom.violated_conditionals( _pkg_use_enabled(other_pkg), other_pkg.iuse.is_valid_flag, parent_use=parent_use) # It's possible for autounmask to change # parent_use such that the unevaluated form # of the atom now matches, even though the # earlier evaluated form (from before # autounmask changed parent_use) does not. # In this case (see bug #374423), it's # expected that violated_atom.use is None. # Since the atom now matches, we don't want # to display it in the slot conflict # message, so we simply ignore it and rely # on the autounmask display to communicate # the necessary USE change to the user. if violated_atom.use is None: continue if use in violated_atom.use.enabled or \ use in violated_atom.use.disabled: unconditional_use_deps.add((ppkg, atom)) # When USE flags are removed, it can be # essential to see all broken reverse # dependencies here, so don't omit any. # If the list is long, people can simply # use a pager. selected_for_display.add((ppkg, atom)) elif type == "AtomArg": for ppkg, atom in parents: selected_for_display.add((ppkg, atom)) def highlight_violations(atom, version, use, slot_violated): """Colorize parts of an atom""" atom_str = "%s" % (atom,) colored_idx = set() if version: op = atom.operator ver = None if atom.cp != atom.cpv: ver = cpv_getversion(atom.cpv) slot = atom.slot sub_slot = atom.sub_slot slot_operator = atom.slot_operator if op == "=*": op = "=" ver += "*" slot_str = "" if slot: slot_str = ":" + slot if sub_slot: slot_str += "/" + sub_slot if slot_operator: slot_str += slot_operator # Compute color_idx before adding the color codes # as these change the indices of the letters. if op is not None: colored_idx.update(range(len(op))) if ver is not None: start = atom_str.rfind(ver) end = start + len(ver) colored_idx.update(range(start, end)) if slot_str: ii = atom_str.find(slot_str) colored_idx.update(range(ii, ii + len(slot_str))) if op is not None: atom_str = atom_str.replace(op, colorize("BAD", op), 1) if ver is not None: start = atom_str.rfind(ver) end = start + len(ver) atom_str = atom_str[:start] + \ colorize("BAD", ver) + \ atom_str[end:] if slot_str: atom_str = atom_str.replace(slot_str, colorize("BAD", slot_str), 1) elif slot_violated: slot = atom.slot sub_slot = atom.sub_slot slot_operator = atom.slot_operator slot_str = "" if slot: slot_str = ":" + slot if sub_slot: slot_str += "/" + sub_slot if slot_operator: slot_str += slot_operator if slot_str: ii = atom_str.find(slot_str) colored_idx.update(range(ii, ii + len(slot_str))) atom_str = atom_str.replace(slot_str, colorize("BAD", slot_str), 1) if use and atom.use.tokens: use_part_start = atom_str.find("[") use_part_end = atom_str.find("]") new_tokens = [] # Compute start index in non-colored atom. ii = str(atom).find("[") + 1 for token in atom.use.tokens: if token.lstrip("-!").rstrip("=?") in use: new_tokens.append(colorize("BAD", token)) colored_idx.update(range(ii, ii + len(token))) else: new_tokens.append(token) ii += 1 + len(token) atom_str = atom_str[:use_part_start] \ + "[%s]" % (",".join(new_tokens),) + \ atom_str[use_part_end+1:] return atom_str, colored_idx # Show unconditional use deps first, since those # are more problematic than the conditional kind. ordered_list = list(unconditional_use_deps) if len(selected_for_display) > len(unconditional_use_deps): for parent_atom in selected_for_display: if parent_atom not in unconditional_use_deps: ordered_list.append(parent_atom) for parent_atom in ordered_list: parent, atom = parent_atom if atom.soname: msg.append("%s required by %s\n" % (atom, parent)) elif isinstance(parent, PackageArg): # For PackageArg it's # redundant to display the atom attribute. msg.append("%s\n" % (parent,)) elif isinstance(parent, AtomArg): msg.append(2*indent) msg.append("%s (Argument)\n" % (atom,)) else: # Display the specific atom from SetArg or # Package types. version_violated = False slot_violated = False use = [] for (type, sub_type), parents in collision_reasons.items(): for x in parents: if parent == x[0] and atom == x[1]: if type == "version": version_violated = True elif type == "slot": slot_violated = True elif type == "use": use.append(sub_type) break atom_str, colored_idx = highlight_violations(atom.unevaluated_atom, version_violated, use, slot_violated) if version_violated or slot_violated: self.is_a_version_conflict = True cur_line = "%s required by %s\n" % (atom_str, parent) marker_line = "" for ii in range(len(cur_line)): if ii in colored_idx: marker_line += "^" else: marker_line += " " marker_line += "\n" msg.append(2*indent) msg.append(cur_line) msg.append(2*indent) msg.append(marker_line) if not selected_for_display: msg.append(2*indent) msg.append("(no parents that aren't satisfied by other packages in this slot)\n") self.conflict_is_unspecific = True omitted_parents = num_all_specific_atoms - len(selected_for_display) if omitted_parents: any_omitted_parents = True msg.append(2*indent) if len(selected_for_display) > 1: msg.append("(and %d more with the same problems)\n" % omitted_parents) else: msg.append("(and %d more with the same problem)\n" % omitted_parents) else: msg.append(" (no parents)\n") msg.append("\n") if any_omitted_parents: msg.append(colorize("INFORM", "NOTE: Use the '--verbose-conflicts'" " option to display parents omitted above")) msg.append("\n") if need_rebuild: msg.append("\n!!! The slot conflict(s) shown above involve package(s) which may need to\n") msg.append("!!! be rebuilt in order to solve the conflict(s). However, the following\n") msg.append("!!! package(s) cannot be rebuilt for the reason(s) shown:\n\n") for ppkg, reason in need_rebuild.items(): msg.append("%s%s: %s\n" % (indent, ppkg, reason)) msg.append("\n") msg.append("\n")
def __gt__(self, other): return vercmp(self, other) > 0
def getMinUpgrade(vulnerableList, unaffectedList, portdbapi, vardbapi, minimize=True): """ Checks if the systemstate is matching an atom in I{vulnerableList} and returns string describing the lowest version for the package that matches an atom in I{unaffectedList} and is greater than the currently installed version. It will return an empty list if the system is affected, and no upgrade is possible or None if the system is not affected. Both I{vulnerableList} and I{unaffectedList} should have the same base package. @type vulnerableList: List of Strings @param vulnerableList: atoms matching vulnerable package versions @type unaffectedList: List of Strings @param unaffectedList: atoms matching unaffected package versions @type portdbapi: portage.dbapi.porttree.portdbapi @param portdbapi: Ebuild repository @type vardbapi: portage.dbapi.vartree.vardbapi @param vardbapi: Installed package repository @type minimize: Boolean @param minimize: True for a least-change upgrade, False for emerge-like algorithm @rtype: String | None @return: the lowest unaffected version that is greater than the installed version. """ rValue = "" v_installed = reduce(operator.add, [match(v, vardbapi) for v in vulnerableList], []) u_installed = reduce(operator.add, [match(u, vardbapi) for u in unaffectedList], []) # remove all unaffected atoms from vulnerable list v_installed = list(set(v_installed).difference(set(u_installed))) if not v_installed: return None # this tuple holds all vulnerable atoms, and the related upgrade atom vuln_update = [] avail_updates = set() for u in unaffectedList: # TODO: This had match_type="match-all" before. I don't think it should # since we disregarded masked items later anyway (match(=rValue, "porttree")) avail_updates.update(match(u, portdbapi)) # if an atom is already installed, we should not consider it for upgrades avail_updates.difference_update(u_installed) for vuln in v_installed: update = "" for c in avail_updates: c_pv = portage.catpkgsplit(c) if vercmp(c.version, vuln.version) > 0 \ and (update == "" \ or (minimize ^ (vercmp(c.version, update.version) > 0))) \ and portdbapi._pkg_str(c, None).slot == vardbapi._pkg_str(vuln, None).slot: update = c_pv[0] + "/" + c_pv[1] + "-" + c_pv[2] if c_pv[3] != "r0": # we don't like -r0 for display update += "-" + c_pv[3] update = portdbapi._pkg_str(update, None) vuln_update.append([vuln, update]) return vuln_update
def __lt__(self, other): return vercmp(self, other) < 0
def available_portage_update(self, detected=False, init=False): """ Check if an update to portage is available. """ # TODO: be more verbose for debug ! name = 'available_portage_update' logger = logging.getLogger(f'{self.__logger_name}{name}::') logger.debug(f"Running with detected={detected}, init={init}") self.available = False self.latest = False self.current = False # First, any way get installed and latest current = vardbapi().match('portage')[0] latest = portdbapi().xmatch('bestmatch-visible', 'portage') # Then just compare # From site-packages/portage/versions.py # @param mypkg: either a pv or cpv # @return: # 1. None if input is invalid. # 2. (pn, ver, rev) if input is pv # 3. (cp, ver, rev) if input is a cpv result = pkgcmp(pkgsplit(latest), pkgsplit(current)) # From site-packages/portage/versions.py # Parameters: # pkg1 (list (example: ['test', '1.0', 'r1'])) - # package to compare with # pkg2 (list (example: ['test', '1.0', 'r1'])) - # package to compare againts # Returns: None or integer # None if package names are not the same # 1 if pkg1 is greater than pkg2 # -1 if pkg1 is less than pkg2 # 0 if pkg1 equals pkg2 if result == None or result == -1: msg = 'no result (package names are not the same ?!)' if result == -1: msg = ('the latest version available is lower than the' ' one installed...') logger.error("FAILED to compare versions when obtaining update " "informations for the portage package.") logger.error(f"Result is: {msg}") logger.error(f"Current portage version: {current}, latest:" f" {latest}.") # Return is ignored for the moment... # TODO ?? return # Split current version # as we don't know yet if latest > current split = pkgsplit(current) self.current = split[1] if not split[2] == 'r0': self.current = '-'.join(split[-2:]) # Check if an update to portage is available if result: # Now, split latest because > current split = pkgsplit(latest) self.latest = split[1] if not split[2] == 'r0': self.latest = '-'.join(split[-2:]) logger.debug(f"Found an update to portage (from {self.current}" f" to {self.latest}).") # Print only one time when program start if init: logger.info("Found an update to portage (from " f"{self.current} to {self.latest}).") self.available = True else: logger.debug("No update to portage package is available" f" (current version: {self.current})") self.available = False # For detected we have to compare current extracted # version and last current know version (so from # self.portage['current']. Both versions are already # split if detected: # From site-packages/portage/versions.py # Compare two versions # Example usage: # >>> from portage.versions import vercmp # >>> vercmp('1.0-r1','1.2-r3') # negative number # >>> vercmp('1.3','1.2-r3') # positive number # >>> vercmp('1.0_p3','1.0_p3') # 0 # @param pkg1: version to compare with # (see ver_regexp in portage.versions.py) # @type pkg1: string (example: "2.1.2-r3") # @param pkg2: version to compare againts # (see ver_regexp in portage.versions.py) # @type pkg2: string (example: "2.1.2_rc5") # @rtype: None or float # @return: # 1. positive if ver1 is greater than ver2 # 2. negative if ver1 is less than ver2 # 3. 0 if ver1 equals ver2 # 4. None if ver1 or ver2 are invalid # (see ver_regexp in portage.versions.py) compare = vercmp(self.portage['current'], self.current) msg = False add_msg = '' if compare < 0: # If not available and it have been updated # than it have been updated to latest one if not self.available: add_msg = 'latest ' msg = (f"The portage package has been updated (from " f"{self.portage['current']} to " f"{add_msg}{self.current}).") elif compare > 0: # Same here but reversed: if it was not # available (self.portage['available']) # and now it is (self.available) than # it have been downgraded from latest. if not self.portage['available'] and self.available: add_msg = 'latest ' msg = (f"The portage package has been downgraded (from " f"{add_msg}{self.portage['current']} to " f"{self.current}).") elif compare == 0: # This have been aborted msg = ("The portage package process has been aborted.") # Just skipp if msg = False # so that mean compare == None if msg: logger.info(msg) tosave = [] # Update only if change for key in 'current', 'latest', 'available': if not self.portage[key] == getattr(self, key): # This print if there a new version of portage available # even if there is already an older version available # TEST: if checking only for key latest than it could # be == to current so check also result. if key == 'latest' and result: logger.info("Found an update to portage (from " f"{self.current} to {self.latest}).") self.portage[key] = getattr(self, key) tosave.append([f'portage {key}', self.portage[key]]) if tosave: self.stateinfo.save(*tosave)
eoutput.ebegin("Finding broken GIR files") files_list = set() for dir in gir_dirs: # Walk the gir directories to find files for (path, dirs, files) in os.walk(osp.join(root, dir)): for f in files: if not f.endswith('.gir'): continue if force: files_list.add(osp.join(path, f)) continue spinner.update() # Get the .gir version version = get_version(osp.join(path, f)) # If not the same version as GIRepository.gir, rebuild it if vercmp(girversion, version) != 0: eoutput.ewarn("GIR file to be rebuilt: " + \ osp.join(path, f)) files_list.add(osp.join(path, f)) eoutput.eend(0) # FIXME: Doesn't warn if it was unable to assign a file to a package rebuild_list = set() if files_list: eoutput.ebegin("Assigning files to packages") files_assigned = set() for cpv in vardbapi.cpv_all(): spinner.update() # If some of the files of this package are in the gir file list files_owned = get_contents(cpv).intersection(files_list) if files_owned:
def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None, minimize_slots=False): """ Takes an unreduced and reduced deplist and removes satisfied dependencies. Returned deplist contains steps that must be taken to satisfy dependencies. """ if trees is None: trees = portage.db writemsg("ZapDeps -- %s\n" % (use_binaries), 2) if not reduced or unreduced == ["||"] or dep_eval(reduced): return [] if unreduced[0] != "||": unresolved = [] for x, satisfied in zip(unreduced, reduced): if isinstance(x, list): unresolved += dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees, minimize_slots=minimize_slots) elif not satisfied: unresolved.append(x) return unresolved # We're at a ( || atom ... ) type level and need to make a choice deps = unreduced[1:] satisfieds = reduced[1:] # Our preference order is for an the first item that: # a) contains all unmasked packages with the same key as installed packages # b) contains all unmasked packages # c) contains masked installed packages # d) is the first item preferred_in_graph = [] preferred_installed = preferred_in_graph preferred_any_slot = preferred_in_graph preferred_non_installed = [] unsat_use_in_graph = [] unsat_use_installed = [] unsat_use_non_installed = [] other_installed = [] other_installed_some = [] other_installed_any_slot = [] other = [] # unsat_use_* must come after preferred_non_installed # for correct ordering in cases like || ( foo[a] foo[b] ). choice_bins = ( preferred_in_graph, preferred_non_installed, unsat_use_in_graph, unsat_use_installed, unsat_use_non_installed, other_installed, other_installed_some, other_installed_any_slot, other, ) # Alias the trees we'll be checking availability against parent = trees[myroot].get("parent") virt_parent = trees[myroot].get("virt_parent") priority = trees[myroot].get("priority") graph_db = trees[myroot].get("graph_db") graph = trees[myroot].get("graph") pkg_use_enabled = trees[myroot].get("pkg_use_enabled") graph_interface = trees[myroot].get("graph_interface") downgrade_probe = trees[myroot].get("downgrade_probe") circular_dependency = trees[myroot].get("circular_dependency") vardb = None if "vartree" in trees[myroot]: vardb = trees[myroot]["vartree"].dbapi if use_binaries: mydbapi = trees[myroot]["bintree"].dbapi else: mydbapi = trees[myroot]["porttree"].dbapi try: mydbapi_match_pkgs = mydbapi.match_pkgs except AttributeError: def mydbapi_match_pkgs(atom): return [ mydbapi._pkg_str(cpv, atom.repo) for cpv in mydbapi.match(atom) ] # Sort the deps into installed, not installed but already # in the graph and other, not installed and not in the graph # and other, with values of [[required_atom], availablility] for x, satisfied in zip(deps, satisfieds): if isinstance(x, list): atoms = dep_zapdeps(x, satisfied, myroot, use_binaries=use_binaries, trees=trees, minimize_slots=minimize_slots) else: atoms = [x] if vardb is None: # When called by repoman, we can simply return the first choice # because dep_eval() handles preference selection. return atoms all_available = True all_use_satisfied = True all_use_unmasked = True conflict_downgrade = False installed_downgrade = False slot_atoms = collections.defaultdict(list) slot_map = {} cp_map = {} for atom in atoms: if atom.blocker: continue # It's not a downgrade if parent is replacing child. replacing = (parent and graph_interface and graph_interface.will_replace_child( parent, myroot, atom)) # Ignore USE dependencies here since we don't want USE # settings to adversely affect || preference evaluation. avail_pkg = mydbapi_match_pkgs(atom.without_use) if not avail_pkg and replacing: avail_pkg = [replacing] if avail_pkg: avail_pkg = avail_pkg[-1] # highest (ascending order) avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) if not avail_pkg: all_available = False all_use_satisfied = False break if not replacing and graph_db is not None and downgrade_probe is not None: slot_matches = graph_db.match_pkgs(avail_slot) if (len(slot_matches) > 1 and avail_pkg < slot_matches[-1] and not downgrade_probe(avail_pkg)): # If a downgrade is not desirable, then avoid a # choice that pulls in a lower version involved # in a slot conflict (bug #531656). conflict_downgrade = True if atom.use: avail_pkg_use = mydbapi_match_pkgs(atom) if not avail_pkg_use: all_use_satisfied = False if pkg_use_enabled is not None: # Check which USE flags cause the match to fail, # so we can prioritize choices that do not # require changes to use.mask or use.force # (see bug #515584). violated_atom = atom.violated_conditionals( pkg_use_enabled(avail_pkg), avail_pkg.iuse.is_valid_flag) # Note that violated_atom.use can be None here, # since evaluation can collapse conditional USE # deps that cause the match to fail due to # missing IUSE (match uses atom.unevaluated_atom # to detect such missing IUSE). if violated_atom.use is not None: for flag in violated_atom.use.enabled: if flag in avail_pkg.use.mask: all_use_unmasked = False break else: for flag in violated_atom.use.disabled: if flag in avail_pkg.use.force and \ flag not in avail_pkg.use.mask: all_use_unmasked = False break else: # highest (ascending order) avail_pkg_use = avail_pkg_use[-1] if avail_pkg_use != avail_pkg: avail_pkg = avail_pkg_use avail_slot = Atom("%s:%s" % (atom.cp, avail_pkg.slot)) if not replacing and downgrade_probe is not None and graph is not None: highest_in_slot = mydbapi_match_pkgs(avail_slot) highest_in_slot = (highest_in_slot[-1] if highest_in_slot else None) if (avail_pkg and highest_in_slot and avail_pkg < highest_in_slot and not downgrade_probe(avail_pkg) and (highest_in_slot.installed or highest_in_slot in graph)): installed_downgrade = True slot_map[avail_slot] = avail_pkg slot_atoms[avail_slot].append(atom) highest_cpv = cp_map.get(avail_pkg.cp) all_match_current = None all_match_previous = None if (highest_cpv is not None and highest_cpv.slot == avail_pkg.slot): # If possible, make the package selection internally # consistent by choosing a package that satisfies all # atoms which match a package in the same slot. Later on, # the package version chosen here is used in the # has_upgrade/has_downgrade logic to prefer choices with # upgrades, and a package choice that is not internally # consistent will lead the has_upgrade/has_downgrade logic # to produce invalid results (see bug 600346). all_match_current = all( a.match(avail_pkg) for a in slot_atoms[avail_slot]) all_match_previous = all( a.match(highest_cpv) for a in slot_atoms[avail_slot]) if all_match_previous and not all_match_current: continue current_higher = ( highest_cpv is None or vercmp(avail_pkg.version, highest_cpv.version) > 0) if current_higher or (all_match_current and not all_match_previous): cp_map[avail_pkg.cp] = avail_pkg want_update = False if graph_interface is None or graph_interface.removal_action: new_slot_count = len(slot_map) else: new_slot_count = 0 for slot_atom, avail_pkg in slot_map.items(): if parent is not None and graph_interface.want_update_pkg( parent, avail_pkg): want_update = True if (not slot_atom.cp.startswith("virtual/") and not graph_db.match_pkgs(slot_atom)): new_slot_count += 1 this_choice = _dep_choice(atoms=atoms, slot_map=slot_map, cp_map=cp_map, all_available=all_available, all_installed_slots=False, new_slot_count=new_slot_count, all_in_graph=False, want_update=want_update) if all_available: # The "all installed" criterion is not version or slot specific. # If any version of a package is already in the graph then we # assume that it is preferred over other possible packages choices. all_installed = True for atom in set(Atom(atom.cp) for atom in atoms \ if not atom.blocker): # New-style virtuals have zero cost to install. if not vardb.match(atom) and not atom.startswith("virtual/"): all_installed = False break all_installed_slots = False if all_installed: all_installed_slots = True for slot_atom in slot_map: # New-style virtuals have zero cost to install. if not vardb.match(slot_atom) and \ not slot_atom.startswith("virtual/"): all_installed_slots = False break this_choice.all_installed_slots = all_installed_slots if graph_db is None: if all_use_satisfied: if all_installed: if all_installed_slots: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if not all_use_unmasked: other.append(this_choice) elif all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) elif conflict_downgrade or installed_downgrade: other.append(this_choice) else: all_in_graph = True for atom in atoms: # New-style virtuals have zero cost to install. if atom.blocker or atom.cp.startswith("virtual/"): continue # We check if the matched package has actually been # added to the digraph, in order to distinguish between # those packages and installed packages that may need # to be uninstalled in order to resolve blockers. if not any(pkg in graph for pkg in graph_db.match_pkgs(atom)): all_in_graph = False break this_choice.all_in_graph = all_in_graph circular_atom = None if parent and parent.onlydeps: # Check if the atom would result in a direct circular # dependency and avoid that for --onlydeps arguments # since it can defeat the purpose of --onlydeps. # This check should only be used for --onlydeps # arguments, since it can interfere with circular # dependency backtracking choices, causing the test # case for bug 756961 to fail. cpv_slot_list = [parent] for atom in atoms: if atom.blocker: continue if vardb.match(atom): # If the atom is satisfied by an installed # version then it's not a circular dep. continue if atom.cp != parent.cp: continue if match_from_list(atom, cpv_slot_list): circular_atom = atom break if circular_atom is None and circular_dependency is not None: for circular_child in itertools.chain( circular_dependency.get(parent, []), circular_dependency.get(virt_parent, [])): for atom in atoms: if not atom.blocker and atom.match(circular_child): circular_atom = atom break if circular_atom is not None: break if circular_atom is not None: other.append(this_choice) else: if all_use_satisfied: if all_in_graph: preferred_in_graph.append(this_choice) elif all_installed: if all_installed_slots: preferred_installed.append(this_choice) else: preferred_any_slot.append(this_choice) else: preferred_non_installed.append(this_choice) else: if not all_use_unmasked: other.append(this_choice) elif all_in_graph: unsat_use_in_graph.append(this_choice) elif all_installed_slots: unsat_use_installed.append(this_choice) else: unsat_use_non_installed.append(this_choice) else: all_installed = True some_installed = False for atom in atoms: if not atom.blocker: if vardb.match(atom): some_installed = True else: all_installed = False if all_installed: this_choice.all_installed_slots = True other_installed.append(this_choice) elif some_installed: other_installed_some.append(this_choice) # Use Atom(atom.cp) for a somewhat "fuzzy" match, since # the whole atom may be too specific. For example, see # bug #522652, where using the whole atom leads to an # unsatisfiable choice. elif any( vardb.match(Atom(atom.cp)) for atom in atoms if not atom.blocker): other_installed_any_slot.append(this_choice) else: other.append(this_choice) # Prefer choices which contain upgrades to higher slots. This helps # for deps such as || ( foo:1 foo:2 ), where we want to prefer the # atom which matches the higher version rather than the atom furthest # to the left. Sorting is done separately for each of choice_bins, so # as not to interfere with the ordering of the bins. Because of the # bin separation, the main function of this code is to allow # --depclean to remove old slots (rather than to pull in new slots). for choices in choice_bins: if len(choices) < 2: continue if minimize_slots: # Prefer choices having fewer new slots. When used with DNF form, # this can eliminate unecessary packages that depclean would # ultimately eliminate (see bug 632026). Only use this behavior # when deemed necessary by the caller, since this will discard the # order specified in the ebuild, and the preferences specified # there can serve as a crucial sources of guidance (see bug 645002). # NOTE: Under some conditions, new_slot_count value may have some # variance from one calculation to the next because it depends on # the order that packages are added to the graph. This variance can # contribute to outcomes that appear to be random. Meanwhile, # the order specified in the ebuild is without variance, so it # does not have this problem. choices.sort(key=operator.attrgetter('new_slot_count')) for choice_1 in choices[1:]: cps = set(choice_1.cp_map) for choice_2 in choices: if choice_1 is choice_2: # choice_1 will not be promoted, so move on break if ( # Prefer choices where all_installed_slots is True, except # in cases where we want to upgrade to a new slot as in # bug 706278. Don't compare new_slot_count here since that # would aggressively override the preference order defined # in the ebuild, breaking the test case for bug 645002. (choice_1.all_installed_slots and not choice_2.all_installed_slots and not choice_2.want_update)): # promote choice_1 in front of choice_2 choices.remove(choice_1) index_2 = choices.index(choice_2) choices.insert(index_2, choice_1) break intersecting_cps = cps.intersection(choice_2.cp_map) has_upgrade = False has_downgrade = False for cp in intersecting_cps: version_1 = choice_1.cp_map[cp] version_2 = choice_2.cp_map[cp] difference = vercmp(version_1.version, version_2.version) if difference != 0: if difference > 0: has_upgrade = True else: has_downgrade = True if ( # Prefer upgrades. (has_upgrade and not has_downgrade) # Prefer choices where all packages have been pulled into # the graph, except for choices that eliminate upgrades. or (choice_1.all_in_graph and not choice_2.all_in_graph and not (has_downgrade and not has_upgrade))): # promote choice_1 in front of choice_2 choices.remove(choice_1) index_2 = choices.index(choice_2) choices.insert(index_2, choice_1) break for allow_masked in (False, True): for choices in choice_bins: for choice in choices: if choice.all_available or allow_masked: return choice.atoms assert False # This point should not be reachable
def _prepare_conflict_msg_and_check_for_specificity(self): """ Print all slot conflicts in a human readable way. """ _pkg_use_enabled = self.depgraph._pkg_use_enabled usepkgonly = "--usepkgonly" in self.myopts need_rebuild = {} verboseconflicts = "--verbose-conflicts" in self.myopts any_omitted_parents = False msg = self.conflict_msg indent = " " msg.append("\n!!! Multiple package instances within a single " + \ "package slot have been pulled\n") msg.append("!!! into the dependency graph, resulting" + \ " in a slot conflict:\n\n") for root, slot_atom, pkgs in self.all_conflicts: msg.append("%s" % (slot_atom,)) if root != self.depgraph._frozen_config._running_root.root: msg.append(" for %s" % (root,)) msg.append("\n\n") for pkg in pkgs: msg.append(indent) msg.append("%s %s" % (pkg, pkg_use_display(pkg, self.depgraph._frozen_config.myopts, modified_use=self.depgraph._pkg_use_enabled(pkg)))) parent_atoms = self.all_parents.get(pkg) if parent_atoms: #Create a list of collision reasons and map them to sets #of atoms. #Possible reasons: # ("version", "ge") for operator >=, > # ("version", "eq") for operator =, ~ # ("version", "le") for operator <=, < # ("use", "<some use flag>") for unmet use conditionals collision_reasons = {} num_all_specific_atoms = 0 for ppkg, atom in parent_atoms: if not atom.soname: atom_set = InternalPackageSet( initial_atoms=(atom,)) atom_without_use_set = InternalPackageSet( initial_atoms=(atom.without_use,)) atom_without_use_and_slot_set = \ InternalPackageSet(initial_atoms=( atom.without_use.without_slot,)) for other_pkg in pkgs: if other_pkg == pkg: continue if atom.soname: # The soname does not match. key = ("soname", atom) atoms = collision_reasons.get(key, set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[key] = atoms elif not atom_without_use_and_slot_set.findAtomForPackage(other_pkg, modified_use=_pkg_use_enabled(other_pkg)): if atom.operator is not None: # The version range does not match. sub_type = None if atom.operator in (">=", ">"): sub_type = "ge" elif atom.operator in ("=", "~"): sub_type = "eq" elif atom.operator in ("<=", "<"): sub_type = "le" key = ("version", sub_type) atoms = collision_reasons.get(key, set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[key] = atoms elif not atom_without_use_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): # The slot and/or sub_slot does not match. key = ("slot", (atom.slot, atom.sub_slot, atom.slot_operator)) atoms = collision_reasons.get(key, set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[key] = atoms elif not atom_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): missing_iuse = other_pkg.iuse.get_missing_iuse( atom.unevaluated_atom.use.required) if missing_iuse: for flag in missing_iuse: atoms = collision_reasons.get(("use", flag), set()) atoms.add((ppkg, atom, other_pkg)) collision_reasons[("use", flag)] = atoms num_all_specific_atoms += 1 else: #Use conditionals not met. violated_atom = atom.violated_conditionals(_pkg_use_enabled(other_pkg), \ other_pkg.iuse.is_valid_flag) if violated_atom.use is None: # Something like bug #453400 caused the # above findAtomForPackage call to # return None unexpectedly. msg = ("\n\n!!! BUG: Detected " "USE dep match inconsistency:\n" "\tppkg: %s\n" "\tviolated_atom: %s\n" "\tatom: %s unevaluated: %s\n" "\tother_pkg: %s IUSE: %s USE: %s\n" % (ppkg, violated_atom, atom, atom.unevaluated_atom, other_pkg, sorted(other_pkg.iuse.all), sorted(_pkg_use_enabled(other_pkg)))) writemsg(msg, noiselevel=-2) raise AssertionError( 'BUG: USE dep match inconsistency') for flag in violated_atom.use.enabled.union(violated_atom.use.disabled): atoms = collision_reasons.get(("use", flag), set()) atoms.add((ppkg, atom, other_pkg)) collision_reasons[("use", flag)] = atoms num_all_specific_atoms += 1 elif isinstance(ppkg, AtomArg) and other_pkg.installed: parent_atoms = collision_reasons.get(("AtomArg", None), set()) parent_atoms.add((ppkg, atom)) collision_reasons[("AtomArg", None)] = parent_atoms num_all_specific_atoms += 1 msg.append(" pulled in by\n") selected_for_display = set() unconditional_use_deps = set() for (type, sub_type), parents in collision_reasons.items(): #From each (type, sub_type) pair select at least one atom. #Try to select as few atoms as possible if type == "version": #Find the atom with version that is as far away as possible. best_matches = {} for ppkg, atom, other_pkg in parents: if atom.cp in best_matches: cmp = vercmp( \ cpv_getversion(atom.cpv), \ cpv_getversion(best_matches[atom.cp][1].cpv)) if (sub_type == "ge" and cmp > 0) \ or (sub_type == "le" and cmp < 0) \ or (sub_type == "eq" and cmp > 0): best_matches[atom.cp] = (ppkg, atom) else: best_matches[atom.cp] = (ppkg, atom) if verboseconflicts: selected_for_display.add((ppkg, atom)) if not verboseconflicts: selected_for_display.update( best_matches.values()) elif type in ("soname", "slot"): # Check for packages that might need to # be rebuilt, but cannot be rebuilt for # some reason. for ppkg, atom, other_pkg in parents: if not (isinstance(ppkg, Package) and ppkg.installed): continue if not (atom.soname or atom.slot_operator_built): continue if self.depgraph._frozen_config.excluded_pkgs.findAtomForPackage(ppkg, modified_use=self.depgraph._pkg_use_enabled(ppkg)): selected_for_display.add((ppkg, atom)) need_rebuild[ppkg] = 'matched by --exclude argument' elif self.depgraph._frozen_config.useoldpkg_atoms.findAtomForPackage(ppkg, modified_use=self.depgraph._pkg_use_enabled(ppkg)): selected_for_display.add((ppkg, atom)) need_rebuild[ppkg] = 'matched by --useoldpkg-atoms argument' elif usepkgonly: # This case is tricky, so keep quiet in order to avoid false-positives. pass elif not self.depgraph._equiv_ebuild_visible(ppkg): selected_for_display.add((ppkg, atom)) need_rebuild[ppkg] = 'ebuild is masked or unavailable' for ppkg, atom, other_pkg in parents: selected_for_display.add((ppkg, atom)) if not verboseconflicts: break elif type == "use": #Prefer atoms with unconditional use deps over, because it's #not possible to change them on the parent, which means there #are fewer possible solutions. use = sub_type for ppkg, atom, other_pkg in parents: missing_iuse = other_pkg.iuse.get_missing_iuse( atom.unevaluated_atom.use.required) if missing_iuse: unconditional_use_deps.add((ppkg, atom)) else: parent_use = None if isinstance(ppkg, Package): parent_use = _pkg_use_enabled(ppkg) violated_atom = atom.unevaluated_atom.violated_conditionals( _pkg_use_enabled(other_pkg), other_pkg.iuse.is_valid_flag, parent_use=parent_use) # It's possible for autounmask to change # parent_use such that the unevaluated form # of the atom now matches, even though the # earlier evaluated form (from before # autounmask changed parent_use) does not. # In this case (see bug #374423), it's # expected that violated_atom.use is None. # Since the atom now matches, we don't want # to display it in the slot conflict # message, so we simply ignore it and rely # on the autounmask display to communicate # the necessary USE change to the user. if violated_atom.use is None: continue if use in violated_atom.use.enabled or \ use in violated_atom.use.disabled: unconditional_use_deps.add((ppkg, atom)) # When USE flags are removed, it can be # essential to see all broken reverse # dependencies here, so don't omit any. # If the list is long, people can simply # use a pager. selected_for_display.add((ppkg, atom)) elif type == "AtomArg": for ppkg, atom in parents: selected_for_display.add((ppkg, atom)) def highlight_violations(atom, version, use, slot_violated): """Colorize parts of an atom""" atom_str = "%s" % (atom,) colored_idx = set() if version: op = atom.operator ver = None if atom.cp != atom.cpv: ver = cpv_getversion(atom.cpv) slot = atom.slot sub_slot = atom.sub_slot slot_operator = atom.slot_operator if op == "=*": op = "=" ver += "*" slot_str = "" if slot: slot_str = ":" + slot if sub_slot: slot_str += "/" + sub_slot if slot_operator: slot_str += slot_operator # Compute color_idx before adding the color codes # as these change the indices of the letters. if op is not None: colored_idx.update(range(len(op))) if ver is not None: start = atom_str.rfind(ver) end = start + len(ver) colored_idx.update(range(start, end)) if slot_str: ii = atom_str.find(slot_str) colored_idx.update(range(ii, ii + len(slot_str))) if op is not None: atom_str = atom_str.replace(op, colorize("BAD", op), 1) if ver is not None: start = atom_str.rfind(ver) end = start + len(ver) atom_str = atom_str[:start] + \ colorize("BAD", ver) + \ atom_str[end:] if slot_str: atom_str = atom_str.replace(slot_str, colorize("BAD", slot_str), 1) elif slot_violated: slot = atom.slot sub_slot = atom.sub_slot slot_operator = atom.slot_operator slot_str = "" if slot: slot_str = ":" + slot if sub_slot: slot_str += "/" + sub_slot if slot_operator: slot_str += slot_operator if slot_str: ii = atom_str.find(slot_str) colored_idx.update(range(ii, ii + len(slot_str))) atom_str = atom_str.replace(slot_str, colorize("BAD", slot_str), 1) if use and atom.use.tokens: use_part_start = atom_str.find("[") use_part_end = atom_str.find("]") new_tokens = [] # Compute start index in non-colored atom. ii = str(atom).find("[") + 1 for token in atom.use.tokens: if token.lstrip("-!").rstrip("=?") in use: new_tokens.append(colorize("BAD", token)) colored_idx.update(range(ii, ii + len(token))) else: new_tokens.append(token) ii += 1 + len(token) atom_str = atom_str[:use_part_start] \ + "[%s]" % (",".join(new_tokens),) + \ atom_str[use_part_end+1:] return atom_str, colored_idx # Show unconditional use deps first, since those # are more problematic than the conditional kind. ordered_list = list(unconditional_use_deps) if len(selected_for_display) > len(unconditional_use_deps): for parent_atom in selected_for_display: if parent_atom not in unconditional_use_deps: ordered_list.append(parent_atom) for parent_atom in ordered_list: parent, atom = parent_atom if isinstance(parent, Package): use_display = pkg_use_display(parent, self.depgraph._frozen_config.myopts, modified_use=self.depgraph._pkg_use_enabled(parent)) else: use_display = "" if atom.soname: msg.append("%s required by %s %s\n" % (atom, parent, use_display)) elif isinstance(parent, PackageArg): # For PackageArg it's # redundant to display the atom attribute. msg.append("%s\n" % (parent,)) elif isinstance(parent, AtomArg): msg.append(2*indent) msg.append("%s (Argument)\n" % (atom,)) else: # Display the specific atom from SetArg or # Package types. version_violated = False slot_violated = False use = [] for (type, sub_type), parents in collision_reasons.items(): for x in parents: if parent == x[0] and atom == x[1]: if type == "version": version_violated = True elif type == "slot": slot_violated = True elif type == "use": use.append(sub_type) break atom_str, colored_idx = highlight_violations(atom.unevaluated_atom, version_violated, use, slot_violated) if version_violated or slot_violated: self.is_a_version_conflict = True cur_line = "%s required by %s %s\n" % (atom_str, parent, use_display) marker_line = "" for ii in range(len(cur_line)): if ii in colored_idx: marker_line += "^" else: marker_line += " " marker_line += "\n" msg.append(2*indent) msg.append(cur_line) msg.append(2*indent) msg.append(marker_line) if not selected_for_display: msg.append(2*indent) msg.append("(no parents that aren't satisfied by other packages in this slot)\n") self.conflict_is_unspecific = True omitted_parents = num_all_specific_atoms - len(selected_for_display) if omitted_parents: any_omitted_parents = True msg.append(2*indent) if len(selected_for_display) > 1: msg.append("(and %d more with the same problems)\n" % omitted_parents) else: msg.append("(and %d more with the same problem)\n" % omitted_parents) else: msg.append(" (no parents)\n") msg.append("\n") if any_omitted_parents: msg.append(colorize("INFORM", "NOTE: Use the '--verbose-conflicts'" " option to display parents omitted above")) msg.append("\n") if need_rebuild: msg.append("\n!!! The slot conflict(s) shown above involve package(s) which may need to\n") msg.append("!!! be rebuilt in order to solve the conflict(s). However, the following\n") msg.append("!!! package(s) cannot be rebuilt for the reason(s) shown:\n\n") for ppkg, reason in need_rebuild.items(): msg.append("%s%s: %s\n" % (indent, ppkg, reason)) msg.append("\n") msg.append("\n")
def _prepare_conflict_msg_and_check_for_specificity(self): """ Print all slot conflicts in a human readable way. """ _pkg_use_enabled = self.depgraph._pkg_use_enabled verboseconflicts = "--verbose-conflicts" in self.myopts msg = self.conflict_msg indent = " " msg.append("\n!!! Multiple package instances within a single " + \ "package slot have been pulled\n") msg.append("!!! into the dependency graph, resulting" + \ " in a slot conflict:\n\n") for (slot_atom, root), pkgs \ in self.slot_collision_info.items(): msg.append("%s" % (slot_atom,)) if root != self.depgraph._frozen_config._running_root.root: msg.append(" for %s" % (root,)) msg.append("\n\n") for pkg in pkgs: msg.append(indent) msg.append("%s" % (pkg,)) parent_atoms = self.all_parents.get(pkg) if parent_atoms: #Create a list of collision reasons and map them to sets #of atoms. #Possible reasons: # ("version", "ge") for operator >=, > # ("version", "eq") for operator =, ~ # ("version", "le") for operator <=, < # ("use", "<some use flag>") for unmet use conditionals collision_reasons = {} num_all_specific_atoms = 0 for ppkg, atom in parent_atoms: atom_set = InternalPackageSet(initial_atoms=(atom,)) atom_without_use_set = InternalPackageSet(initial_atoms=(atom.without_use,)) for other_pkg in pkgs: if other_pkg == pkg: continue if not atom_without_use_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): if atom.operator is not None: # The version range does not match. sub_type = None if atom.operator in (">=", ">"): sub_type = "ge" elif atom.operator in ("=", "~"): sub_type = "eq" elif atom.operator in ("<=", "<"): sub_type = "le" key = ("version", sub_type) atoms = collision_reasons.get(key, set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[key] = atoms else: # The sub_slot does not match. key = ("sub-slot", atom.sub_slot) atoms = collision_reasons.get(key, set()) atoms.add((ppkg, atom, other_pkg)) num_all_specific_atoms += 1 collision_reasons[key] = atoms elif not atom_set.findAtomForPackage(other_pkg, \ modified_use=_pkg_use_enabled(other_pkg)): missing_iuse = other_pkg.iuse.get_missing_iuse( atom.unevaluated_atom.use.required) if missing_iuse: for flag in missing_iuse: atoms = collision_reasons.get(("use", flag), set()) atoms.add((ppkg, atom, other_pkg)) collision_reasons[("use", flag)] = atoms num_all_specific_atoms += 1 else: #Use conditionals not met. violated_atom = atom.violated_conditionals(_pkg_use_enabled(other_pkg), \ other_pkg.iuse.is_valid_flag) if violated_atom.use is None: # Something like bug #453400 caused the # above findAtomForPackage call to # return None unexpectedly. msg = ("\n\n!!! BUG: Detected " "USE dep match inconsistency:\n" "\tppkg: %s\n" "\tviolated_atom: %s\n" "\tatom: %s unevaluated: %s\n" "\tother_pkg: %s IUSE: %s USE: %s\n" % (ppkg, violated_atom, atom, atom.unevaluated_atom, other_pkg, sorted(other_pkg.iuse.all), sorted(_pkg_use_enabled(other_pkg)))) writemsg(msg, noiselevel=-2) raise AssertionError( 'BUG: USE dep match inconsistency') for flag in violated_atom.use.enabled.union(violated_atom.use.disabled): atoms = collision_reasons.get(("use", flag), set()) atoms.add((ppkg, atom, other_pkg)) collision_reasons[("use", flag)] = atoms num_all_specific_atoms += 1 msg.append(" pulled in by\n") selected_for_display = set() unconditional_use_deps = set() for (type, sub_type), parents in collision_reasons.items(): #From each (type, sub_type) pair select at least one atom. #Try to select as few atoms as possible if type == "version": #Find the atom with version that is as far away as possible. best_matches = {} for ppkg, atom, other_pkg in parents: if atom.cp in best_matches: cmp = vercmp( \ cpv_getversion(atom.cpv), \ cpv_getversion(best_matches[atom.cp][1].cpv)) if (sub_type == "ge" and cmp > 0) \ or (sub_type == "le" and cmp < 0) \ or (sub_type == "eq" and cmp > 0): best_matches[atom.cp] = (ppkg, atom) else: best_matches[atom.cp] = (ppkg, atom) if verboseconflicts: selected_for_display.add((ppkg, atom)) if not verboseconflicts: selected_for_display.update( best_matches.values()) elif type == "sub-slot": for ppkg, atom, other_pkg in parents: selected_for_display.add((ppkg, atom)) elif type == "use": #Prefer atoms with unconditional use deps over, because it's #not possible to change them on the parent, which means there #are fewer possible solutions. use = sub_type for ppkg, atom, other_pkg in parents: missing_iuse = other_pkg.iuse.get_missing_iuse( atom.unevaluated_atom.use.required) if missing_iuse: unconditional_use_deps.add((ppkg, atom)) else: parent_use = None if isinstance(ppkg, Package): parent_use = _pkg_use_enabled(ppkg) violated_atom = atom.unevaluated_atom.violated_conditionals( _pkg_use_enabled(other_pkg), other_pkg.iuse.is_valid_flag, parent_use=parent_use) # It's possible for autounmask to change # parent_use such that the unevaluated form # of the atom now matches, even though the # earlier evaluated form (from before # autounmask changed parent_use) does not. # In this case (see bug #374423), it's # expected that violated_atom.use is None. # Since the atom now matches, we don't want # to display it in the slot conflict # message, so we simply ignore it and rely # on the autounmask display to communicate # the necessary USE change to the user. if violated_atom.use is None: continue if use in violated_atom.use.enabled or \ use in violated_atom.use.disabled: unconditional_use_deps.add((ppkg, atom)) # When USE flags are removed, it can be # essential to see all broken reverse # dependencies here, so don't omit any. # If the list is long, people can simply # use a pager. selected_for_display.add((ppkg, atom)) def highlight_violations(atom, version, use=[]): """Colorize parts of an atom""" atom_str = "%s" % (atom,) if version: op = atom.operator ver = None if atom.cp != atom.cpv: ver = cpv_getversion(atom.cpv) slot = atom.slot if op == "=*": op = "=" ver += "*" if op is not None: atom_str = atom_str.replace(op, colorize("BAD", op), 1) if ver is not None: start = atom_str.rfind(ver) end = start + len(ver) atom_str = atom_str[:start] + \ colorize("BAD", ver) + \ atom_str[end:] if slot: atom_str = atom_str.replace(":" + slot, colorize("BAD", ":" + slot)) if use and atom.use.tokens: use_part_start = atom_str.find("[") use_part_end = atom_str.find("]") new_tokens = [] for token in atom.use.tokens: if token.lstrip("-!").rstrip("=?") in use: new_tokens.append(colorize("BAD", token)) else: new_tokens.append(token) atom_str = atom_str[:use_part_start] \ + "[%s]" % (",".join(new_tokens),) + \ atom_str[use_part_end+1:] return atom_str # Show unconditional use deps first, since those # are more problematic than the conditional kind. ordered_list = list(unconditional_use_deps) if len(selected_for_display) > len(unconditional_use_deps): for parent_atom in selected_for_display: if parent_atom not in unconditional_use_deps: ordered_list.append(parent_atom) for parent_atom in ordered_list: parent, atom = parent_atom msg.append(2*indent) if isinstance(parent, (PackageArg, AtomArg)): # For PackageArg and AtomArg types, it's # redundant to display the atom attribute. msg.append("%s" % (parent,)) else: # Display the specific atom from SetArg or # Package types. version_violated = False sub_slot_violated = False use = [] for (type, sub_type), parents in collision_reasons.items(): for x in parents: if parent == x[0] and atom == x[1]: if type == "version": version_violated = True elif type == "sub-slot": sub_slot_violated = True elif type == "use": use.append(sub_type) break atom_str = highlight_violations(atom.unevaluated_atom, version_violated, use) if version_violated or sub_slot_violated: self.is_a_version_conflict = True msg.append("%s required by %s" % (atom_str, parent)) msg.append("\n") if not selected_for_display: msg.append(2*indent) msg.append("(no parents that aren't satisfied by other packages in this slot)\n") self.conflict_is_unspecific = True omitted_parents = num_all_specific_atoms - len(selected_for_display) if omitted_parents: msg.append(2*indent) if len(selected_for_display) > 1: msg.append("(and %d more with the same problems)\n" % omitted_parents) else: msg.append("(and %d more with the same problem)\n" % omitted_parents) else: msg.append(" (no parents)\n") msg.append("\n") msg.append("\n")