def test_evaluate_conditionals(self): test_cases = ( ("dev-libs/A[foo]", [], "dev-libs/A[foo]"), ("dev-libs/A[foo]", ["foo"], "dev-libs/A[foo]"), ("dev-libs/A:0[foo=]", ["foo"], "dev-libs/A:0[foo]"), ("dev-libs/A[foo,-bar]", [], "dev-libs/A[foo,-bar]"), ("dev-libs/A[-foo,bar]", [], "dev-libs/A[-foo,bar]"), ("dev-libs/A[a,b=,!c=,d?,!e?,-f]", [], "dev-libs/A[a,-b,c,-e,-f]"), ("dev-libs/A[a,b=,!c=,d?,!e?,-f]", ["a"], "dev-libs/A[a,-b,c,-e,-f]"), ("dev-libs/A[a,b=,!c=,d?,!e?,-f]", ["b"], "dev-libs/A[a,b,c,-e,-f]"), ("dev-libs/A[a,b=,!c=,d?,!e?,-f]", ["c"], "dev-libs/A[a,-b,-c,-e,-f]"), ("dev-libs/A[a,b=,!c=,d?,!e?,-f]", ["d"], "dev-libs/A[a,-b,c,d,-e,-f]"), ("dev-libs/A[a,b=,!c=,d?,!e?,-f]", ["e"], "dev-libs/A[a,-b,c,-f]"), ("dev-libs/A[a,b=,!c=,d?,!e?,-f]", ["f"], "dev-libs/A[a,-b,c,-e,-f]"), ("dev-libs/A[a(-),b(+)=,!c(-)=,d(+)?,!e(-)?,-f(+)]", ["d"], "dev-libs/A[a(-),-b(+),c(-),d(+),-e(-),-f(+)]"), ("dev-libs/A[a(+),b(-)=,!c(+)=,d(-)?,!e(+)?,-f(-)]", ["f"], "dev-libs/A[a(+),-b(-),c(+),-e(+),-f(-)]"), ) for atom, use, expected_atom in test_cases: a = Atom(atom) b = a.evaluate_conditionals(use) self.assertEqual(str(b), expected_atom) self.assertEqual(str(b.unevaluated_atom), atom)
def __call__(self, argv): """ @return: tuple of (stdout, stderr, returncode) """ # Python 3: # cmd, root, *args = argv cmd = argv[0] root = argv[1] args = argv[2:] warnings = [] warnings_str = '' db = self.get_db() eapi = self.settings.get('EAPI') root = normalize_path(root or os.sep).rstrip(os.sep) + os.sep if root not in db: return ('', '%s: Invalid ROOT: %s\n' % (cmd, root), 3) portdb = db[root]["porttree"].dbapi vardb = db[root]["vartree"].dbapi if cmd in ('best_version', 'has_version'): try: atom = Atom(args[0], allow_repo=False) except InvalidAtom: return ('', '%s: Invalid atom: %s\n' % (cmd, args[0]), 2) try: atom = Atom(args[0], allow_repo=False, eapi=eapi) except InvalidAtom as e: warnings.append("QA Notice: %s: %s" % (cmd, e)) use = self.settings.get('PORTAGE_BUILT_USE') if use is None: use = self.settings['PORTAGE_USE'] use = frozenset(use.split()) atom = atom.evaluate_conditionals(use) if warnings: warnings_str = self._elog('eqawarn', warnings) if cmd == 'has_version': if vardb.match(atom): returncode = 0 else: returncode = 1 return ('', warnings_str, returncode) elif cmd == 'best_version': m = best(vardb.match(atom)) return ('%s\n' % m, warnings_str, 0) else: return ('', 'Invalid command: %s\n' % cmd, 3)
def __call__(self, argv): """ @returns: tuple of (stdout, stderr, returncode) """ cmd, root, atom_str = argv eapi = self.settings.get('EAPI') allow_repo = eapi_has_repo_deps(eapi) try: atom = Atom(atom_str, allow_repo=allow_repo) except InvalidAtom: return ('', 'invalid atom: %s\n' % atom_str, 2) warnings = [] try: atom = Atom(atom_str, allow_repo=allow_repo, eapi=eapi) except InvalidAtom as e: warnings.append(_unicode_decode("QA Notice: %s: %s") % (cmd, e)) use = self.settings.get('PORTAGE_BUILT_USE') if use is None: use = self.settings['PORTAGE_USE'] use = frozenset(use.split()) atom = atom.evaluate_conditionals(use) db = self._db if db is None: db = portage.db warnings_str = '' if warnings: warnings_str = self._elog('eqawarn', warnings) root = normalize_path(root).rstrip(os.path.sep) + os.path.sep if root not in db: return ('', 'invalid ROOT: %s\n' % root, 2) vardb = db[root]["vartree"].dbapi if cmd == 'has_version': if vardb.match(atom): returncode = 0 else: returncode = 1 return ('', warnings_str, returncode) elif cmd == 'best_version': m = best(vardb.match(atom)) return ('%s\n' % m, warnings_str, 0) else: return ('', 'invalid command: %s\n' % cmd, 2)
def __call__(self, argv): """ @returns: tuple of (stdout, stderr, returncode) """ # Note that $USE is passed via IPC in order to ensure that # we have the correct value for built/installed packages, # since the config class doesn't currently provide a way # to access built/installed $USE that would work in all # possible scenarios. cmd, root, atom, use = argv try: atom = Atom(atom) except InvalidAtom: return ('', 'invalid atom: %s\n' % atom, 2) use = frozenset(use.split()) atom = atom.evaluate_conditionals(use) db = self._db if db is None: db = portage.db root = normalize_path(root).rstrip(os.path.sep) + os.path.sep if root not in db: return ('', 'invalid ROOT: %s\n' % root, 2) vardb = db[root]["vartree"].dbapi if cmd == 'has_version': if vardb.match(atom): returncode = 0 else: returncode = 1 return ('', '', returncode) elif cmd == 'best_version': m = best(vardb.match(atom)) return ('%s\n' % m, '', 0) else: return ('', 'invalid command: %s\n' % cmd, 2)
def clean_subslots(depatom, usel=None): if isinstance(depatom, list): # process the nested list. return [clean_subslots(x, usel) for x in depatom] try: # this can be either an atom or some special operator. # in the latter case, we get InvalidAtom and pass it as-is. a = Atom(depatom) except InvalidAtom: return depatom # if we're processing portdb, we need to evaluate USE flag # dependency conditionals to make them match vdb. this # requires passing the list of USE flags, so we reuse it # as conditional for the operation as well. if usel is not None: a = a.evaluate_conditionals(usel) # replace slot operator := dependencies with plain := # since we can't properly compare expanded slots # in vardb to abstract slots in portdb. return subslot_repl_re.sub(':=', a)
def clean_subslots(depatom, usel=None): if isinstance(depatom, list): # process the nested list. return [clean_subslots(x, usel) for x in depatom] else: try: # this can be either an atom or some special operator. # in the latter case, we get InvalidAtom and pass it as-is. a = Atom(depatom) except InvalidAtom: return depatom else: # if we're processing portdb, we need to evaluate USE flag # dependency conditionals to make them match vdb. this # requires passing the list of USE flags, so we reuse it # as conditional for the operation as well. if usel is not None: a = a.evaluate_conditionals(usel) # replace slot operator := dependencies with plain := # since we can't properly compare expanded slots # in vardb to abstract slots in portdb. return subslot_repl_re.sub(':=', a)
def __call__(self, argv): """ @return: tuple of (stdout, stderr, returncode) """ # Python 3: # cmd, root, *args = argv cmd = argv[0] root = argv[1] args = argv[2:] warnings = [] warnings_str = '' db = self.get_db() eapi = self.settings.get('EAPI') root = normalize_path(root or os.sep).rstrip(os.sep) + os.sep if root not in db: return ('', '%s: Invalid ROOT: %s\n' % (cmd, root), 3) portdb = db[root]["porttree"].dbapi vardb = db[root]["vartree"].dbapi if cmd in ('best_version', 'has_version'): allow_repo = eapi_has_repo_deps(eapi) try: atom = Atom(args[0], allow_repo=allow_repo) except InvalidAtom: return ('', '%s: Invalid atom: %s\n' % (cmd, args[0]), 2) try: atom = Atom(args[0], allow_repo=allow_repo, eapi=eapi) except InvalidAtom as e: warnings.append("QA Notice: %s: %s" % (cmd, e)) use = self.settings.get('PORTAGE_BUILT_USE') if use is None: use = self.settings['PORTAGE_USE'] use = frozenset(use.split()) atom = atom.evaluate_conditionals(use) if warnings: warnings_str = self._elog('eqawarn', warnings) if cmd == 'has_version': if vardb.match(atom): returncode = 0 else: returncode = 1 return ('', warnings_str, returncode) elif cmd == 'best_version': m = best(vardb.match(atom)) return ('%s\n' % m, warnings_str, 0) elif cmd in ('master_repositories', 'repository_path', 'available_eclasses', 'eclass_path', 'license_path'): repo = _repo_name_re.match(args[0]) if repo is None: return ('', '%s: Invalid repository: %s\n' % (cmd, args[0]), 2) try: repo = portdb.repositories[args[0]] except KeyError: return ('', warnings_str, 1) if cmd == 'master_repositories': return ('%s\n' % ' '.join(x.name for x in repo.masters), warnings_str, 0) elif cmd == 'repository_path': return ('%s\n' % repo.location, warnings_str, 0) elif cmd == 'available_eclasses': return ('%s\n' % ' '.join(sorted(repo.eclass_db.eclasses)), warnings_str, 0) elif cmd == 'eclass_path': try: eclass = repo.eclass_db.eclasses[args[1]] except KeyError: return ('', warnings_str, 1) return ('%s\n' % eclass.location, warnings_str, 0) elif cmd == 'license_path': paths = reversed([ os.path.join(x.location, 'licenses', args[1]) for x in list(repo.masters) + [repo] ]) for path in paths: if os.path.exists(path): return ('%s\n' % path, warnings_str, 0) return ('', warnings_str, 1) else: return ('', 'Invalid command: %s\n' % cmd, 3)
def _expand_new_virtuals( mysplit, edebug, mydbapi, mysettings, myroot="/", trees=None, use_mask=None, use_force=None, **kwargs ): """ In order to solve bug #141118, recursively expand new-style virtuals so as to collapse one or more levels of indirection, generating an expanded search space. In dep_zapdeps, new-style virtuals will be assigned zero cost regardless of whether or not they are currently installed. Virtual blockers are supported but only when the virtual expands to a single atom because it wouldn't necessarily make sense to block all the components of a compound virtual. When more than one new-style virtual is matched, the matches are sorted from highest to lowest versions and the atom is expanded to || ( highest match ... lowest match ).""" newsplit = [] mytrees = trees[myroot] portdb = mytrees["porttree"].dbapi pkg_use_enabled = mytrees.get("pkg_use_enabled") # Atoms are stored in the graph as (atom, id(atom)) tuples # since each atom is considered to be a unique entity. For # example, atoms that appear identical may behave differently # in USE matching, depending on their unevaluated form. Also, # specially generated virtual atoms may appear identical while # having different _orig_atom attributes. atom_graph = mytrees.get("atom_graph") parent = mytrees.get("parent") virt_parent = mytrees.get("virt_parent") graph_parent = None if parent is not None: if virt_parent is not None: graph_parent = virt_parent parent = virt_parent else: graph_parent = parent repoman = not mysettings.local_config if kwargs["use_binaries"]: portdb = trees[myroot]["bintree"].dbapi pprovideddict = mysettings.pprovideddict myuse = kwargs["myuse"] for x in mysplit: if x == "||": newsplit.append(x) continue elif isinstance(x, list): newsplit.append( _expand_new_virtuals( x, edebug, mydbapi, mysettings, myroot=myroot, trees=trees, use_mask=use_mask, use_force=use_force, **kwargs ) ) continue if not isinstance(x, Atom): raise ParseError(_("invalid token: '%s'") % x) if repoman: x = x._eval_qa_conditionals(use_mask, use_force) mykey = x.cp if not mykey.startswith("virtual/"): newsplit.append(x) if atom_graph is not None: atom_graph.add((x, id(x)), graph_parent) continue if x.blocker: # Virtual blockers are no longer expanded here since # the un-expanded virtual atom is more useful for # maintaining a cache of blocker atoms. newsplit.append(x) if atom_graph is not None: atom_graph.add((x, id(x)), graph_parent) continue if repoman or not hasattr(portdb, "match_pkgs") or pkg_use_enabled is None: if portdb.cp_list(x.cp): newsplit.append(x) else: a = [] myvartree = mytrees.get("vartree") if myvartree is not None: mysettings._populate_treeVirtuals_if_needed(myvartree) mychoices = mysettings.getvirtuals().get(mykey, []) for y in mychoices: a.append(Atom(x.replace(x.cp, y.cp, 1))) if not a: newsplit.append(x) elif len(a) == 1: newsplit.append(a[0]) else: newsplit.append(["||"] + a) continue pkgs = [] # Ignore USE deps here, since otherwise we might not # get any matches. Choices with correct USE settings # will be preferred in dep_zapdeps(). matches = portdb.match_pkgs(x.without_use) # Use descending order to prefer higher versions. matches.reverse() for pkg in matches: # only use new-style matches if pkg.cp.startswith("virtual/"): pkgs.append(pkg) mychoices = [] if not pkgs and not portdb.cp_list(x.cp): myvartree = mytrees.get("vartree") if myvartree is not None: mysettings._populate_treeVirtuals_if_needed(myvartree) mychoices = mysettings.getvirtuals().get(mykey, []) if not (pkgs or mychoices): # This one couldn't be expanded as a new-style virtual. Old-style # virtuals have already been expanded by dep_virtual, so this one # is unavailable and dep_zapdeps will identify it as such. The # atom is not eliminated here since it may still represent a # dependency that needs to be satisfied. newsplit.append(x) if atom_graph is not None: atom_graph.add((x, id(x)), graph_parent) continue a = [] for pkg in pkgs: virt_atom = "=" + pkg.cpv if x.unevaluated_atom.use: virt_atom += str(x.unevaluated_atom.use) virt_atom = Atom(virt_atom) if parent is None: if myuse is None: virt_atom = virt_atom.evaluate_conditionals(mysettings.get("PORTAGE_USE", "").split()) else: virt_atom = virt_atom.evaluate_conditionals(myuse) else: virt_atom = virt_atom.evaluate_conditionals(pkg_use_enabled(parent)) else: virt_atom = Atom(virt_atom) # Allow the depgraph to map this atom back to the # original, in order to avoid distortion in places # like display or conflict resolution code. virt_atom.__dict__["_orig_atom"] = x # According to GLEP 37, RDEPEND is the only dependency # type that is valid for new-style virtuals. Repoman # should enforce this. depstring = pkg._metadata["RDEPEND"] pkg_kwargs = kwargs.copy() pkg_kwargs["myuse"] = pkg_use_enabled(pkg) if edebug: writemsg_level(_("Virtual Parent: %s\n") % (pkg,), noiselevel=-1, level=logging.DEBUG) writemsg_level(_("Virtual Depstring: %s\n") % (depstring,), noiselevel=-1, level=logging.DEBUG) # Set EAPI used for validation in dep_check() recursion. mytrees["virt_parent"] = pkg try: mycheck = dep_check(depstring, mydbapi, mysettings, myroot=myroot, trees=trees, **pkg_kwargs) finally: # Restore previous EAPI after recursion. if virt_parent is not None: mytrees["virt_parent"] = virt_parent else: del mytrees["virt_parent"] if not mycheck[0]: raise ParseError("%s: %s '%s'" % (pkg, mycheck[1], depstring)) # pull in the new-style virtual mycheck[1].append(virt_atom) a.append(mycheck[1]) if atom_graph is not None: virt_atom_node = (virt_atom, id(virt_atom)) atom_graph.add(virt_atom_node, graph_parent) atom_graph.add(pkg, virt_atom_node) if not a and mychoices: # Check for a virtual package.provided match. for y in mychoices: new_atom = Atom(x.replace(x.cp, y.cp, 1)) if match_from_list(new_atom, pprovideddict.get(new_atom.cp, [])): a.append(new_atom) if atom_graph is not None: atom_graph.add((new_atom, id(new_atom)), graph_parent) if not a: newsplit.append(x) if atom_graph is not None: atom_graph.add((x, id(x)), graph_parent) elif len(a) == 1: newsplit.append(a[0]) else: newsplit.append(["||"] + a) return newsplit
def _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings, myroot="/", trees=None, use_mask=None, use_force=None, **kwargs): """ In order to solve bug #141118, recursively expand new-style virtuals so as to collapse one or more levels of indirection, generating an expanded search space. In dep_zapdeps, new-style virtuals will be assigned zero cost regardless of whether or not they are currently installed. Virtual blockers are supported but only when the virtual expands to a single atom because it wouldn't necessarily make sense to block all the components of a compound virtual. When more than one new-style virtual is matched, the matches are sorted from highest to lowest versions and the atom is expanded to || ( highest match ... lowest match ).""" newsplit = [] mytrees = trees[myroot] portdb = mytrees["porttree"].dbapi atom_graph = mytrees.get("atom_graph") parent = mytrees.get("parent") virt_parent = mytrees.get("virt_parent") graph_parent = None eapi = None if parent is not None: if virt_parent is not None: graph_parent = virt_parent eapi = virt_parent[0].metadata['EAPI'] else: graph_parent = parent eapi = parent.metadata["EAPI"] repoman = not mysettings.local_config if kwargs["use_binaries"]: portdb = trees[myroot]["bintree"].dbapi myvirtuals = mysettings.getvirtuals() pprovideddict = mysettings.pprovideddict myuse = kwargs["myuse"] for x in mysplit: if x == "||": newsplit.append(x) continue elif isinstance(x, list): newsplit.append(_expand_new_virtuals(x, edebug, mydbapi, mysettings, myroot=myroot, trees=trees, use_mask=use_mask, use_force=use_force, **kwargs)) continue if not isinstance(x, Atom): try: x = Atom(x) except InvalidAtom: if portage.dep._dep_check_strict: raise ParseError( _("invalid atom: '%s'") % x) else: # Only real Atom instances are allowed past this point. continue else: if x.blocker and x.blocker.overlap.forbid and \ eapi in ("0", "1") and portage.dep._dep_check_strict: raise ParseError( _("invalid atom: '%s'") % (x,)) if x.use and eapi in ("0", "1") and \ portage.dep._dep_check_strict: raise ParseError( _("invalid atom: '%s'") % (x,)) if repoman and x.use and x.use.conditional: evaluated_atom = remove_slot(x) if x.slot: evaluated_atom += ":%s" % x.slot evaluated_atom += str(x.use._eval_qa_conditionals( use_mask, use_force)) x = Atom(evaluated_atom) if not repoman : if 'lib32' not in x and portage.dep_getkey(x) not in mysettings.get("NO_AUTO_FLAG", None): if ']' in x: x = str(x).replace(']',',lib32?]') else: x = str(x) + '[lib32?]' try: x = portage.dep.Atom(x) except portage.exception.InvalidAtom: if portage.dep._dep_check_strict: raise portage.exception.ParseError( "invalid atom: '%s'" % x) if myuse is not None and isinstance(x, Atom) and x.use: if x.use.conditional: x = x.evaluate_conditionals(myuse) mykey = x.cp if not mykey.startswith("virtual/"): newsplit.append(x) if atom_graph is not None: atom_graph.add(x, graph_parent) continue mychoices = myvirtuals.get(mykey, []) if x.blocker: # Virtual blockers are no longer expanded here since # the un-expanded virtual atom is more useful for # maintaining a cache of blocker atoms. newsplit.append(x) if atom_graph is not None: atom_graph.add(x, graph_parent) continue if repoman or not hasattr(portdb, 'match_pkgs'): if portdb.cp_list(x.cp): newsplit.append(x) else: # TODO: Add PROVIDE check for repoman. a = [] for y in mychoices: a.append(Atom(x.replace(x.cp, y.cp, 1))) if not a: newsplit.append(x) elif len(a) == 1: newsplit.append(a[0]) else: newsplit.append(['||'] + a) continue pkgs = [] # Ignore USE deps here, since otherwise we might not # get any matches. Choices with correct USE settings # will be preferred in dep_zapdeps(). matches = portdb.match_pkgs(x.without_use) # Use descending order to prefer higher versions. matches.reverse() for pkg in matches: # only use new-style matches if pkg.cp.startswith("virtual/"): pkgs.append(pkg) if not (pkgs or mychoices): # This one couldn't be expanded as a new-style virtual. Old-style # virtuals have already been expanded by dep_virtual, so this one # is unavailable and dep_zapdeps will identify it as such. The # atom is not eliminated here since it may still represent a # dependency that needs to be satisfied. newsplit.append(x) if atom_graph is not None: atom_graph.add(x, graph_parent) continue a = [] for pkg in pkgs: virt_atom = '=' + pkg.cpv if x.use: virt_atom += str(x.use) virt_atom = Atom(virt_atom) # According to GLEP 37, RDEPEND is the only dependency # type that is valid for new-style virtuals. Repoman # should enforce this. depstring = pkg.metadata['RDEPEND'] pkg_kwargs = kwargs.copy() pkg_kwargs["myuse"] = pkg.use.enabled if edebug: writemsg_level(_("Virtual Parent: %s\n") \ % (pkg,), noiselevel=-1, level=logging.DEBUG) writemsg_level(_("Virtual Depstring: %s\n") \ % (depstring,), noiselevel=-1, level=logging.DEBUG) # Set EAPI used for validation in dep_check() recursion. mytrees["virt_parent"] = (pkg, virt_atom) try: mycheck = dep_check(depstring, mydbapi, mysettings, myroot=myroot, trees=trees, **pkg_kwargs) finally: # Restore previous EAPI after recursion. if virt_parent is not None: mytrees["virt_parent"] = virt_parent else: del mytrees["virt_parent"] if not mycheck[0]: raise ParseError( "%s: %s '%s'" % (y[0], mycheck[1], depstring)) # pull in the new-style virtual mycheck[1].append(virt_atom) a.append(mycheck[1]) if atom_graph is not None: atom_graph.add(virt_atom, graph_parent) # Plain old-style virtuals. New-style virtuals are preferred. if not pkgs: for y in mychoices: new_atom = Atom(x.replace(x.cp, y.cp, 1)) matches = portdb.match(new_atom) # portdb is an instance of depgraph._dep_check_composite_db, so # USE conditionals are already evaluated. if matches and mykey in \ portdb.aux_get(matches[-1], ['PROVIDE'])[0].split(): a.append(new_atom) if atom_graph is not None: atom_graph.add(new_atom, graph_parent) if not a and mychoices: # Check for a virtual package.provided match. for y in mychoices: new_atom = Atom(x.replace(x.cp, y.cp, 1)) if match_from_list(new_atom, pprovideddict.get(new_atom.cp, [])): a.append(new_atom) if atom_graph is not None: atom_graph.add(new_atom, graph_parent) if not a: newsplit.append(x) if atom_graph is not None: atom_graph.add(x, graph_parent) elif len(a) == 1: newsplit.append(a[0]) else: newsplit.append(['||'] + a) return newsplit
def _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings, myroot="/", trees=None, use_mask=None, use_force=None, **kwargs): """ In order to solve bug #141118, recursively expand new-style virtuals so as to collapse one or more levels of indirection, generating an expanded search space. In dep_zapdeps, new-style virtuals will be assigned zero cost regardless of whether or not they are currently installed. Virtual blockers are supported but only when the virtual expands to a single atom because it wouldn't necessarily make sense to block all the components of a compound virtual. When more than one new-style virtual is matched, the matches are sorted from highest to lowest versions and the atom is expanded to || ( highest match ... lowest match ). The result is normalized in the same way as use_reduce, having a top-level conjuction, and no redundant nested lists. """ newsplit = [] mytrees = trees[myroot] portdb = mytrees["porttree"].dbapi pkg_use_enabled = mytrees.get("pkg_use_enabled") # Atoms are stored in the graph as (atom, id(atom)) tuples # since each atom is considered to be a unique entity. For # example, atoms that appear identical may behave differently # in USE matching, depending on their unevaluated form. Also, # specially generated virtual atoms may appear identical while # having different _orig_atom attributes. atom_graph = mytrees.get("atom_graph") parent = mytrees.get("parent") virt_parent = mytrees.get("virt_parent") graph_parent = None if parent is not None: if virt_parent is not None: graph_parent = virt_parent parent = virt_parent else: graph_parent = parent repoman = not mysettings.local_config if kwargs["use_binaries"]: portdb = trees[myroot]["bintree"].dbapi pprovideddict = mysettings.pprovideddict myuse = kwargs["myuse"] is_disjunction = mysplit and mysplit[0] == '||' for x in mysplit: if x == "||": newsplit.append(x) continue elif isinstance(x, list): assert x, 'Normalization error, empty conjunction found in %s' % ( mysplit, ) if is_disjunction: assert x[0] != '||', \ 'Normalization error, nested disjunction found in %s' % (mysplit,) else: assert x[0] == '||', \ 'Normalization error, nested conjunction found in %s' % (mysplit,) x_exp = _expand_new_virtuals(x, edebug, mydbapi, mysettings, myroot=myroot, trees=trees, use_mask=use_mask, use_force=use_force, **kwargs) if is_disjunction: if len(x_exp) == 1: x = x_exp[0] if isinstance(x, list): # Due to normalization, a conjunction must not be # nested directly in another conjunction, so this # must be a disjunction. assert x and x[0] == '||', \ 'Normalization error, nested conjunction found in %s' % (x_exp,) newsplit.extend(x[1:]) else: newsplit.append(x) else: newsplit.append(x_exp) else: newsplit.extend(x_exp) continue if not isinstance(x, Atom): raise ParseError(_("invalid token: '%s'") % x) if repoman: x = x._eval_qa_conditionals(use_mask, use_force) mykey = x.cp if not mykey.startswith("virtual/"): newsplit.append(x) if atom_graph is not None: atom_graph.add((x, id(x)), graph_parent) continue if x.blocker: # Virtual blockers are no longer expanded here since # the un-expanded virtual atom is more useful for # maintaining a cache of blocker atoms. newsplit.append(x) if atom_graph is not None: atom_graph.add((x, id(x)), graph_parent) continue if repoman or not hasattr(portdb, 'match_pkgs') or \ pkg_use_enabled is None: if portdb.cp_list(x.cp): newsplit.append(x) else: a = [] myvartree = mytrees.get("vartree") if myvartree is not None: mysettings._populate_treeVirtuals_if_needed(myvartree) mychoices = mysettings.getvirtuals().get(mykey, []) for y in mychoices: a.append(Atom(x.replace(x.cp, y.cp, 1))) if not a: newsplit.append(x) elif is_disjunction: newsplit.extend(a) elif len(a) == 1: newsplit.append(a[0]) else: newsplit.append(['||'] + a) continue pkgs = [] # Ignore USE deps here, since otherwise we might not # get any matches. Choices with correct USE settings # will be preferred in dep_zapdeps(). matches = portdb.match_pkgs(x.without_use) # Use descending order to prefer higher versions. matches.reverse() for pkg in matches: # only use new-style matches if pkg.cp.startswith("virtual/"): pkgs.append(pkg) mychoices = [] if not pkgs and not portdb.cp_list(x.cp): myvartree = mytrees.get("vartree") if myvartree is not None: mysettings._populate_treeVirtuals_if_needed(myvartree) mychoices = mysettings.getvirtuals().get(mykey, []) if not (pkgs or mychoices): # This one couldn't be expanded as a new-style virtual. Old-style # virtuals have already been expanded by dep_virtual, so this one # is unavailable and dep_zapdeps will identify it as such. The # atom is not eliminated here since it may still represent a # dependency that needs to be satisfied. newsplit.append(x) if atom_graph is not None: atom_graph.add((x, id(x)), graph_parent) continue a = [] for pkg in pkgs: virt_atom = '=' + pkg.cpv if x.unevaluated_atom.use: virt_atom += str(x.unevaluated_atom.use) virt_atom = Atom(virt_atom) if parent is None: if myuse is None: virt_atom = virt_atom.evaluate_conditionals( mysettings.get("PORTAGE_USE", "").split()) else: virt_atom = virt_atom.evaluate_conditionals(myuse) else: virt_atom = virt_atom.evaluate_conditionals( pkg_use_enabled(parent)) else: virt_atom = Atom(virt_atom) # Allow the depgraph to map this atom back to the # original, in order to avoid distortion in places # like display or conflict resolution code. virt_atom.__dict__['_orig_atom'] = x # According to GLEP 37, RDEPEND is the only dependency # type that is valid for new-style virtuals. Repoman # should enforce this. depstring = pkg._metadata['RDEPEND'] pkg_kwargs = kwargs.copy() pkg_kwargs["myuse"] = pkg_use_enabled(pkg) if edebug: writemsg_level(_("Virtual Parent: %s\n") \ % (pkg,), noiselevel=-1, level=logging.DEBUG) writemsg_level(_("Virtual Depstring: %s\n") \ % (depstring,), noiselevel=-1, level=logging.DEBUG) # Set EAPI used for validation in dep_check() recursion. mytrees["virt_parent"] = pkg try: mycheck = dep_check(depstring, mydbapi, mysettings, myroot=myroot, trees=trees, **pkg_kwargs) finally: # Restore previous EAPI after recursion. if virt_parent is not None: mytrees["virt_parent"] = virt_parent else: del mytrees["virt_parent"] if not mycheck[0]: raise ParseError("%s: %s '%s'" % \ (pkg, mycheck[1], depstring)) # Replace the original atom "x" with "virt_atom" which refers # to the specific version of the virtual whose deps we're # expanding. The virt_atom._orig_atom attribute is used # by depgraph to map virt_atom back to the original atom. # We specifically exclude the original atom "x" from the # the expanded output here, since otherwise it could trigger # incorrect dep_zapdeps behavior (see bug #597752). mycheck[1].append(virt_atom) a.append(mycheck[1]) if atom_graph is not None: virt_atom_node = (virt_atom, id(virt_atom)) atom_graph.add(virt_atom_node, graph_parent) atom_graph.add(pkg, virt_atom_node) atom_graph.add((x, id(x)), graph_parent) if not a and mychoices: # Check for a virtual package.provided match. for y in mychoices: new_atom = Atom(x.replace(x.cp, y.cp, 1)) if match_from_list(new_atom, pprovideddict.get(new_atom.cp, [])): a.append(new_atom) if atom_graph is not None: atom_graph.add((new_atom, id(new_atom)), graph_parent) if not a: newsplit.append(x) if atom_graph is not None: atom_graph.add((x, id(x)), graph_parent) elif is_disjunction: newsplit.extend(a) elif len(a) == 1: newsplit.extend(a[0]) else: newsplit.append(['||'] + a) # For consistency with related functions like use_reduce, always # normalize the result to have a top-level conjunction. if is_disjunction: newsplit = [newsplit] return newsplit
def __call__(self, argv): """ @return: tuple of (stdout, stderr, returncode) """ # Python 3: # cmd, root, *args = argv cmd = argv[0] root = argv[1] args = argv[2:] warnings = [] warnings_str = '' db = self.get_db() eapi = self.settings.get('EAPI') root = normalize_path(root or os.sep).rstrip(os.sep) + os.sep if root not in db: return ('', '%s: Invalid ROOT: %s\n' % (cmd, root), 3) portdb = db[root]["porttree"].dbapi vardb = db[root]["vartree"].dbapi if cmd in ('best_version', 'has_version'): allow_repo = eapi_has_repo_deps(eapi) try: atom = Atom(args[0], allow_repo=allow_repo) except InvalidAtom: return ('', '%s: Invalid atom: %s\n' % (cmd, args[0]), 2) try: atom = Atom(args[0], allow_repo=allow_repo, eapi=eapi) except InvalidAtom as e: warnings.append("QA Notice: %s: %s" % (cmd, e)) use = self.settings.get('PORTAGE_BUILT_USE') if use is None: use = self.settings['PORTAGE_USE'] use = frozenset(use.split()) atom = atom.evaluate_conditionals(use) if warnings: warnings_str = self._elog('eqawarn', warnings) if cmd == 'has_version': if vardb.match(atom): returncode = 0 else: returncode = 1 return ('', warnings_str, returncode) elif cmd == 'best_version': m = best(vardb.match(atom)) return ('%s\n' % m, warnings_str, 0) elif cmd in ('master_repositories', 'repository_path', 'available_eclasses', 'eclass_path', 'license_path'): repo = _repo_name_re.match(args[0]) if repo is None: return ('', '%s: Invalid repository: %s\n' % (cmd, args[0]), 2) try: repo = portdb.repositories[args[0]] except KeyError: return ('', warnings_str, 1) if cmd == 'master_repositories': return ('%s\n' % ' '.join(x.name for x in repo.masters), warnings_str, 0) elif cmd == 'repository_path': return ('%s\n' % repo.location, warnings_str, 0) elif cmd == 'available_eclasses': return ('%s\n' % ' '.join(sorted(repo.eclass_db.eclasses)), warnings_str, 0) elif cmd == 'eclass_path': try: eclass = repo.eclass_db.eclasses[args[1]] except KeyError: return ('', warnings_str, 1) return ('%s\n' % eclass.location, warnings_str, 0) elif cmd == 'license_path': paths = reversed([os.path.join(x.location, 'licenses', args[1]) for x in list(repo.masters) + [repo]]) for path in paths: if os.path.exists(path): return ('%s\n' % path, warnings_str, 0) return ('', warnings_str, 1) else: return ('', 'Invalid command: %s\n' % cmd, 3)
def __call__(self, argv): """ @return: tuple of (stdout, stderr, returncode) """ # Python 3: # cmd, root, *args = argv cmd = argv[0] root = argv[1] args = argv[2:] warnings = [] warnings_str = "" db = self.get_db() eapi = self.settings.get("EAPI") root = normalize_path(root or os.sep).rstrip(os.sep) + os.sep if root not in db: return ("", "%s: Invalid ROOT: %s\n" % (cmd, root), 3) portdb = db[root]["porttree"].dbapi vardb = db[root]["vartree"].dbapi if cmd in ("best_version", "has_version"): allow_repo = eapi_has_repo_deps(eapi) try: atom = Atom(args[0], allow_repo=allow_repo) except InvalidAtom: return ("", "%s: Invalid atom: %s\n" % (cmd, args[0]), 2) try: atom = Atom(args[0], allow_repo=allow_repo, eapi=eapi) except InvalidAtom as e: warnings.append("QA Notice: %s: %s" % (cmd, e)) use = self.settings.get("PORTAGE_BUILT_USE") if use is None: use = self.settings["PORTAGE_USE"] use = frozenset(use.split()) atom = atom.evaluate_conditionals(use) if warnings: warnings_str = self._elog("eqawarn", warnings) if cmd == "has_version": if vardb.match(atom): returncode = 0 else: returncode = 1 return ("", warnings_str, returncode) if cmd == "best_version": m = best(vardb.match(atom)) return ("%s\n" % m, warnings_str, 0) if cmd in ( "master_repositories", "repository_path", "available_eclasses", "eclass_path", "license_path", ): repo = _repo_name_re.match(args[0]) if repo is None: return ("", "%s: Invalid repository: %s\n" % (cmd, args[0]), 2) try: repo = portdb.repositories[args[0]] except KeyError: return ("", warnings_str, 1) if cmd == "master_repositories": return ( "%s\n" % " ".join(x.name for x in repo.masters), warnings_str, 0, ) if cmd == "repository_path": return ("%s\n" % repo.location, warnings_str, 0) if cmd == "available_eclasses": return ( "%s\n" % " ".join(sorted(repo.eclass_db.eclasses)), warnings_str, 0, ) if cmd == "eclass_path": try: eclass = repo.eclass_db.eclasses[args[1]] except KeyError: return ("", warnings_str, 1) return ("%s\n" % eclass.location, warnings_str, 0) if cmd == "license_path": paths = reversed([ os.path.join(x.location, "licenses", args[1]) for x in list(repo.masters) + [repo] ]) for path in paths: if os.path.exists(path): return ("%s\n" % path, warnings_str, 0) return ("", warnings_str, 1) return ("", "Invalid command: %s\n" % cmd, 3)
def _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings, myroot="/", trees=None, use_mask=None, use_force=None, **kwargs): """ In order to solve bug #141118, recursively expand new-style virtuals so as to collapse one or more levels of indirection, generating an expanded search space. In dep_zapdeps, new-style virtuals will be assigned zero cost regardless of whether or not they are currently installed. Virtual blockers are supported but only when the virtual expands to a single atom because it wouldn't necessarily make sense to block all the components of a compound virtual. When more than one new-style virtual is matched, the matches are sorted from highest to lowest versions and the atom is expanded to || ( highest match ... lowest match ).""" newsplit = [] mytrees = trees[myroot] portdb = mytrees["porttree"].dbapi atom_graph = mytrees.get("atom_graph") parent = mytrees.get("parent") virt_parent = mytrees.get("virt_parent") graph_parent = None eapi = None if parent is not None: if virt_parent is not None: graph_parent = virt_parent eapi = virt_parent[0].metadata['EAPI'] else: graph_parent = parent eapi = parent.metadata["EAPI"] repoman = not mysettings.local_config if kwargs["use_binaries"]: portdb = trees[myroot]["bintree"].dbapi myvirtuals = mysettings.getvirtuals() pprovideddict = mysettings.pprovideddict myuse = kwargs["myuse"] for x in mysplit: if x == "||": newsplit.append(x) continue elif isinstance(x, list): newsplit.append( _expand_new_virtuals(x, edebug, mydbapi, mysettings, myroot=myroot, trees=trees, use_mask=use_mask, use_force=use_force, **kwargs)) continue if not isinstance(x, Atom): try: x = Atom(x) except InvalidAtom: if portage.dep._dep_check_strict: raise ParseError(_("invalid atom: '%s'") % x) else: # Only real Atom instances are allowed past this point. continue else: if x.blocker and x.blocker.overlap.forbid and \ eapi in ("0", "1") and portage.dep._dep_check_strict: raise ParseError(_("invalid atom: '%s'") % (x, )) if x.use and eapi in ("0", "1") and \ portage.dep._dep_check_strict: raise ParseError(_("invalid atom: '%s'") % (x, )) if repoman and x.use and x.use.conditional: evaluated_atom = remove_slot(x) if x.slot: evaluated_atom += ":%s" % x.slot evaluated_atom += str( x.use._eval_qa_conditionals(use_mask, use_force)) x = Atom(evaluated_atom) if not repoman and \ myuse is not None and isinstance(x, Atom) and x.use: if x.use.conditional: x = x.evaluate_conditionals(myuse) mykey = x.cp if not mykey.startswith("virtual/"): newsplit.append(x) if atom_graph is not None: atom_graph.add(x, graph_parent) continue mychoices = myvirtuals.get(mykey, []) if x.blocker: # Virtual blockers are no longer expanded here since # the un-expanded virtual atom is more useful for # maintaining a cache of blocker atoms. newsplit.append(x) if atom_graph is not None: atom_graph.add(x, graph_parent) continue if repoman or not hasattr(portdb, 'match_pkgs'): if portdb.cp_list(x.cp): newsplit.append(x) else: # TODO: Add PROVIDE check for repoman. a = [] for y in mychoices: a.append(Atom(x.replace(x.cp, y.cp, 1))) if not a: newsplit.append(x) elif len(a) == 1: newsplit.append(a[0]) else: newsplit.append(['||'] + a) continue pkgs = [] # Ignore USE deps here, since otherwise we might not # get any matches. Choices with correct USE settings # will be preferred in dep_zapdeps(). matches = portdb.match_pkgs(x.without_use) # Use descending order to prefer higher versions. matches.reverse() for pkg in matches: # only use new-style matches if pkg.cp.startswith("virtual/"): pkgs.append(pkg) if not (pkgs or mychoices): # This one couldn't be expanded as a new-style virtual. Old-style # virtuals have already been expanded by dep_virtual, so this one # is unavailable and dep_zapdeps will identify it as such. The # atom is not eliminated here since it may still represent a # dependency that needs to be satisfied. newsplit.append(x) if atom_graph is not None: atom_graph.add(x, graph_parent) continue a = [] for pkg in pkgs: virt_atom = '=' + pkg.cpv if x.use: virt_atom += str(x.use) virt_atom = Atom(virt_atom) # According to GLEP 37, RDEPEND is the only dependency # type that is valid for new-style virtuals. Repoman # should enforce this. depstring = pkg.metadata['RDEPEND'] pkg_kwargs = kwargs.copy() pkg_kwargs["myuse"] = pkg.use.enabled if edebug: writemsg_level(_("Virtual Parent: %s\n") \ % (pkg,), noiselevel=-1, level=logging.DEBUG) writemsg_level(_("Virtual Depstring: %s\n") \ % (depstring,), noiselevel=-1, level=logging.DEBUG) # Set EAPI used for validation in dep_check() recursion. mytrees["virt_parent"] = (pkg, virt_atom) try: mycheck = dep_check(depstring, mydbapi, mysettings, myroot=myroot, trees=trees, **pkg_kwargs) finally: # Restore previous EAPI after recursion. if virt_parent is not None: mytrees["virt_parent"] = virt_parent else: del mytrees["virt_parent"] if not mycheck[0]: raise ParseError("%s: %s '%s'" % (y[0], mycheck[1], depstring)) # pull in the new-style virtual mycheck[1].append(virt_atom) a.append(mycheck[1]) if atom_graph is not None: atom_graph.add(virt_atom, graph_parent) # Plain old-style virtuals. New-style virtuals are preferred. if not pkgs: for y in mychoices: new_atom = Atom(x.replace(x.cp, y.cp, 1)) matches = portdb.match(new_atom) # portdb is an instance of depgraph._dep_check_composite_db, so # USE conditionals are already evaluated. if matches and mykey in \ portdb.aux_get(matches[-1], ['PROVIDE'])[0].split(): a.append(new_atom) if atom_graph is not None: atom_graph.add(new_atom, graph_parent) if not a and mychoices: # Check for a virtual package.provided match. for y in mychoices: new_atom = Atom(x.replace(x.cp, y.cp, 1)) if match_from_list(new_atom, pprovideddict.get(new_atom.cp, [])): a.append(new_atom) if atom_graph is not None: atom_graph.add(new_atom, graph_parent) if not a: newsplit.append(x) if atom_graph is not None: atom_graph.add(x, graph_parent) elif len(a) == 1: newsplit.append(a[0]) else: newsplit.append(['||'] + a) return newsplit
def __call__(self, argv): """ @return: tuple of (stdout, stderr, returncode) """ # Python 3: # cmd, root, *args = argv cmd = argv[0] root = argv[1] args = argv[2:] warnings = [] warnings_str = "" db = self.get_db() eapi = self.settings.get("EAPI") root = normalize_path(root).rstrip(os.path.sep) + os.path.sep if root not in db: return ("", "%s: Invalid ROOT: %s\n" % (cmd, root), 3) portdb = db[root]["porttree"].dbapi vardb = db[root]["vartree"].dbapi if cmd in ("best_version", "has_version"): allow_repo = eapi_has_repo_deps(eapi) try: atom = Atom(args[0], allow_repo=allow_repo) except InvalidAtom: return ("", "%s: Invalid atom: %s\n" % (cmd, args[0]), 2) try: atom = Atom(args[0], allow_repo=allow_repo, eapi=eapi) except InvalidAtom as e: warnings.append(_unicode_decode("QA Notice: %s: %s") % (cmd, e)) use = self.settings.get("PORTAGE_BUILT_USE") if use is None: use = self.settings["PORTAGE_USE"] use = frozenset(use.split()) atom = atom.evaluate_conditionals(use) if warnings: warnings_str = self._elog("eqawarn", warnings) if cmd == "has_version": if vardb.match(atom): returncode = 0 else: returncode = 1 return ("", warnings_str, returncode) elif cmd == "best_version": m = best(vardb.match(atom)) return ("%s\n" % m, warnings_str, 0) elif cmd in ("master_repositories", "repository_path", "available_eclasses", "eclass_path", "license_path"): repo = _repo_name_re.match(args[0]) if repo is None: return ("", "%s: Invalid repository: %s\n" % (cmd, args[0]), 2) try: repo = portdb.repositories[args[0]] except KeyError: return ("", warnings_str, 1) if cmd == "master_repositories": return ("%s\n" % " ".join(x.name for x in repo.masters), warnings_str, 0) elif cmd == "repository_path": return ("%s\n" % repo.location, warnings_str, 0) elif cmd == "available_eclasses": return ("%s\n" % " ".join(sorted(repo.eclass_db.eclasses)), warnings_str, 0) elif cmd == "eclass_path": try: eclass = repo.eclass_db.eclasses[args[1]] except KeyError: return ("", warnings_str, 1) return ("%s\n" % eclass.location, warnings_str, 0) elif cmd == "license_path": paths = reversed([os.path.join(x.location, "licenses", args[1]) for x in list(repo.masters) + [repo]]) for path in paths: if os.path.exists(path): return ("%s\n" % path, warnings_str, 0) return ("", warnings_str, 1) else: return ("", "Invalid command: %s\n" % cmd, 3)