def _find_suggestions(self): if not self.shortest_cycle: return None, None suggestions = [] final_solutions = {} for pos, pkg in enumerate(self.shortest_cycle): parent = self.shortest_cycle[pos-1] priorities = self.graph.nodes[parent][0][pkg] parent_atoms = self.all_parent_atoms.get(pkg) if priorities[-1].buildtime: dep = parent.metadata["DEPEND"] elif priorities[-1].runtime: dep = parent.metadata["RDEPEND"] for ppkg, atom in parent_atoms: if ppkg == parent: changed_parent = ppkg parent_atom = atom.unevaluated_atom break try: affecting_use = extract_affecting_use(dep, parent_atom, eapi=parent.metadata["EAPI"]) except InvalidDependString: if not parent.installed: raise affecting_use = set() # Make sure we don't want to change a flag that is # a) in use.mask or use.force # b) changed by autounmask usemask, useforce = self._get_use_mask_and_force(parent) autounmask_changes = self._get_autounmask_changes(parent) untouchable_flags = frozenset(chain(usemask, useforce, autounmask_changes)) affecting_use.difference_update(untouchable_flags) #If any of the flags we're going to touch is in REQUIRED_USE, add all #other flags in REQUIRED_USE to affecting_use, to not lose any solution. required_use_flags = get_required_use_flags( parent.metadata.get("REQUIRED_USE", "")) if affecting_use.intersection(required_use_flags): # TODO: Find out exactly which REQUIRED_USE flags are # entangled with affecting_use. We have to limit the # number of flags since the number of loops is # exponentially related (see bug #374397). total_flags = set() total_flags.update(affecting_use, required_use_flags) total_flags.difference_update(untouchable_flags) if len(total_flags) <= 10: affecting_use = total_flags affecting_use = tuple(affecting_use) if not affecting_use: continue #We iterate over all possible settings of these use flags and gather #a set of possible changes #TODO: Use the information encoded in REQUIRED_USE solutions = set() for use_state in product(("disabled", "enabled"), repeat=len(affecting_use)): current_use = set(self.depgraph._pkg_use_enabled(parent)) for flag, state in zip(affecting_use, use_state): if state == "enabled": current_use.add(flag) else: current_use.discard(flag) try: reduced_dep = use_reduce(dep, uselist=current_use, flat=True) except InvalidDependString: if not parent.installed: raise reduced_dep = None if reduced_dep is not None and \ parent_atom not in reduced_dep: #We found an assignment that removes the atom from 'dep'. #Make sure it doesn't conflict with REQUIRED_USE. required_use = parent.metadata.get("REQUIRED_USE", "") if check_required_use(required_use, current_use, parent.iuse.is_valid_flag): use = self.depgraph._pkg_use_enabled(parent) solution = set() for flag, state in zip(affecting_use, use_state): if state == "enabled" and \ flag not in use: solution.add((flag, True)) elif state == "disabled" and \ flag in use: solution.add((flag, False)) solutions.add(frozenset(solution)) for solution in solutions: ignore_solution = False for other_solution in solutions: if solution is other_solution: continue if solution.issuperset(other_solution): ignore_solution = True if ignore_solution: continue #Check if a USE change conflicts with use requirements of the parents. #If a requiremnet is hard, ignore the suggestion. #If the requirment is conditional, warn the user that other changes might be needed. followup_change = False parent_parent_atoms = self.depgraph._dynamic_config._parent_atoms.get(changed_parent) for ppkg, atom in parent_parent_atoms: atom = atom.unevaluated_atom if not atom.use: continue for flag, state in solution: if flag in atom.use.enabled or flag in atom.use.disabled: ignore_solution = True break elif atom.use.conditional: for flags in atom.use.conditional.values(): if flag in flags: followup_change = True break if ignore_solution: break if ignore_solution: continue changes = [] for flag, state in solution: if state: changes.append(colorize("red", "+"+flag)) else: changes.append(colorize("blue", "-"+flag)) msg = "- %s (Change USE: %s)\n" \ % (parent.cpv, " ".join(changes)) if followup_change: msg += " (This change might require USE changes on parent packages.)" suggestions.append(msg) final_solutions.setdefault(pkg, set()).add(solution) return final_solutions, suggestions
def _find_suggestions(self): if not self.shortest_cycle: return None, None suggestions = [] final_solutions = {} for pos, pkg in enumerate(self.shortest_cycle): parent = self.shortest_cycle[pos - 1] priorities = self.graph.nodes[parent][0][pkg] parent_atoms = self.all_parent_atoms.get(pkg) if priorities[-1].buildtime: dep = parent.metadata["DEPEND"] elif priorities[-1].runtime: dep = parent.metadata["RDEPEND"] for ppkg, atom in parent_atoms: if ppkg == parent: changed_parent = ppkg parent_atom = atom.unevaluated_atom break try: affecting_use = extract_affecting_use( dep, parent_atom, eapi=parent.metadata["EAPI"]) except InvalidDependString: if not parent.installed: raise affecting_use = set() # Make sure we don't want to change a flag that is # a) in use.mask or use.force # b) changed by autounmask usemask, useforce = self._get_use_mask_and_force(parent) autounmask_changes = self._get_autounmask_changes(parent) untouchable_flags = frozenset( chain(usemask, useforce, autounmask_changes)) affecting_use.difference_update(untouchable_flags) #If any of the flags we're going to touch is in REQUIRED_USE, add all #other flags in REQUIRED_USE to affecting_use, to not lose any solution. required_use_flags = get_required_use_flags( parent.metadata.get("REQUIRED_USE", "")) if affecting_use.intersection(required_use_flags): # TODO: Find out exactly which REQUIRED_USE flags are # entangled with affecting_use. We have to limit the # number of flags since the number of loops is # exponentially related (see bug #374397). total_flags = set() total_flags.update(affecting_use, required_use_flags) total_flags.difference_update(untouchable_flags) if len(total_flags) <= 10: affecting_use = total_flags affecting_use = tuple(affecting_use) if not affecting_use: continue #We iterate over all possible settings of these use flags and gather #a set of possible changes #TODO: Use the information encoded in REQUIRED_USE solutions = set() for use_state in product(("disabled", "enabled"), repeat=len(affecting_use)): current_use = set(self.depgraph._pkg_use_enabled(parent)) for flag, state in zip(affecting_use, use_state): if state == "enabled": current_use.add(flag) else: current_use.discard(flag) try: reduced_dep = use_reduce(dep, uselist=current_use, flat=True) except InvalidDependString: if not parent.installed: raise reduced_dep = None if reduced_dep is not None and \ parent_atom not in reduced_dep: #We found an assignment that removes the atom from 'dep'. #Make sure it doesn't conflict with REQUIRED_USE. required_use = parent.metadata.get("REQUIRED_USE", "") if check_required_use(required_use, current_use, parent.iuse.is_valid_flag): use = self.depgraph._pkg_use_enabled(parent) solution = set() for flag, state in zip(affecting_use, use_state): if state == "enabled" and \ flag not in use: solution.add((flag, True)) elif state == "disabled" and \ flag in use: solution.add((flag, False)) solutions.add(frozenset(solution)) for solution in solutions: ignore_solution = False for other_solution in solutions: if solution is other_solution: continue if solution.issuperset(other_solution): ignore_solution = True if ignore_solution: continue #Check if a USE change conflicts with use requirements of the parents. #If a requiremnet is hard, ignore the suggestion. #If the requirment is conditional, warn the user that other changes might be needed. followup_change = False parent_parent_atoms = self.depgraph._dynamic_config._parent_atoms.get( changed_parent) for ppkg, atom in parent_parent_atoms: atom = atom.unevaluated_atom if not atom.use: continue for flag, state in solution: if flag in atom.use.enabled or flag in atom.use.disabled: ignore_solution = True break elif atom.use.conditional: for flags in atom.use.conditional.values(): if flag in flags: followup_change = True break if ignore_solution: break if ignore_solution: continue changes = [] for flag, state in solution: if state: changes.append(colorize("red", "+" + flag)) else: changes.append(colorize("blue", "-" + flag)) msg = "- %s (Change USE: %s)\n" \ % (parent.cpv, " ".join(changes)) if followup_change: msg += " (This change might require USE changes on parent packages.)" suggestions.append(msg) final_solutions.setdefault(pkg, set()).add(solution) return final_solutions, suggestions
def testExtractAffectingUSE(self): test_cases = ( ("a? ( A ) !b? ( B ) !c? ( C ) d? ( D )", "A", ("a",)), ("a? ( A ) !b? ( B ) !c? ( C ) d? ( D )", "B", ("b",)), ("a? ( A ) !b? ( B ) !c? ( C ) d? ( D )", "C", ("c",)), ("a? ( A ) !b? ( B ) !c? ( C ) d? ( D )", "D", ("d",)), ("a? ( b? ( AB ) )", "AB", ("a", "b")), ("a? ( b? ( c? ( ABC ) ) )", "ABC", ("a", "b", "c")), ("a? ( A b? ( c? ( ABC ) AB ) )", "A", ("a",)), ("a? ( A b? ( c? ( ABC ) AB ) )", "AB", ("a", "b")), ("a? ( A b? ( c? ( ABC ) AB ) )", "ABC", ("a", "b", "c")), ("a? ( A b? ( c? ( ABC ) AB ) ) X", "X", []), ("X a? ( A b? ( c? ( ABC ) AB ) )", "X", []), ("ab? ( || ( A B ) )", "A", ("ab",)), ("!ab? ( || ( A B ) )", "B", ("ab",)), ("ab? ( || ( A || ( b? ( || ( B C ) ) ) ) )", "A", ("ab",)), ("ab? ( || ( A || ( b? ( || ( B C ) ) ) ) )", "B", ("ab", "b")), ("ab? ( || ( A || ( b? ( || ( B C ) ) ) ) )", "C", ("ab", "b")), ("( ab? ( || ( ( A ) || ( b? ( ( ( || ( B ( C ) ) ) ) ) ) ) ) )", "A", ("ab",)), ("( ab? ( || ( ( A ) || ( b? ( ( ( || ( B ( C ) ) ) ) ) ) ) ) )", "B", ("ab", "b")), ("( ab? ( || ( ( A ) || ( b? ( ( ( || ( B ( C ) ) ) ) ) ) ) ) )", "C", ("ab", "b")), ("a? ( A )", "B", []), ("a? ( || ( A B ) )", "B", ["a"]), # test USE dep defaults for bug #363073 ("a? ( >=dev-lang/php-5.2[pcre(+)] )", ">=dev-lang/php-5.2[pcre(+)]", ["a"]), ) test_cases_xfail = ( ("? ( A )", "A"), ("!? ( A )", "A"), ("( A", "A"), ("A )", "A"), ("||( A B )", "A"), ("|| (A B )", "A"), ("|| ( A B)", "A"), ("|| ( A B", "A"), ("|| A B )", "A"), ("|| A B", "A"), ("|| ( A B ) )", "A"), ("|| || B C", "A"), ("|| ( A B || )", "A"), ("a? A", "A"), ("( || ( || || ( A ) foo? ( B ) ) )", "A"), ("( || ( || bar? ( A ) foo? ( B ) ) )", "A"), ) for dep, atom, expected in test_cases: expected = set(expected) result = extract_affecting_use(dep, atom, eapi="0") fail_msg = "dep: " + dep + ", atom: " + atom + ", got: " + \ " ".join(sorted(result)) + ", expected: " + " ".join(sorted(expected)) self.assertEqual(result, expected, fail_msg) for dep, atom in test_cases_xfail: fail_msg = "dep: " + dep + ", atom: " + atom + ", got: " + \ " ".join(sorted(result)) + ", expected: " + " ".join(sorted(expected)) self.assertRaisesMsg(fail_msg, \ InvalidDependString, extract_affecting_use, dep, atom, eapi="0")
def testExtractAffectingUSE(self): test_cases = ( ("a? ( A ) !b? ( B ) !c? ( C ) d? ( D )", "A", ("a", )), ("a? ( A ) !b? ( B ) !c? ( C ) d? ( D )", "B", ("b", )), ("a? ( A ) !b? ( B ) !c? ( C ) d? ( D )", "C", ("c", )), ("a? ( A ) !b? ( B ) !c? ( C ) d? ( D )", "D", ("d", )), ("a? ( b? ( AB ) )", "AB", ("a", "b")), ("a? ( b? ( c? ( ABC ) ) )", "ABC", ("a", "b", "c")), ("a? ( A b? ( c? ( ABC ) AB ) )", "A", ("a", )), ("a? ( A b? ( c? ( ABC ) AB ) )", "AB", ("a", "b")), ("a? ( A b? ( c? ( ABC ) AB ) )", "ABC", ("a", "b", "c")), ("a? ( A b? ( c? ( ABC ) AB ) ) X", "X", []), ("X a? ( A b? ( c? ( ABC ) AB ) )", "X", []), ("ab? ( || ( A B ) )", "A", ("ab", )), ("!ab? ( || ( A B ) )", "B", ("ab", )), ("ab? ( || ( A || ( b? ( || ( B C ) ) ) ) )", "A", ("ab", )), ("ab? ( || ( A || ( b? ( || ( B C ) ) ) ) )", "B", ("ab", "b")), ("ab? ( || ( A || ( b? ( || ( B C ) ) ) ) )", "C", ("ab", "b")), ("( ab? ( || ( ( A ) || ( b? ( ( ( || ( B ( C ) ) ) ) ) ) ) ) )", "A", ("ab", )), ("( ab? ( || ( ( A ) || ( b? ( ( ( || ( B ( C ) ) ) ) ) ) ) ) )", "B", ("ab", "b")), ("( ab? ( || ( ( A ) || ( b? ( ( ( || ( B ( C ) ) ) ) ) ) ) ) )", "C", ("ab", "b")), ("a? ( A )", "B", []), ("a? ( || ( A B ) )", "B", ["a"]), # test USE dep defaults for bug #363073 ("a? ( >=dev-lang/php-5.2[pcre(+)] )", ">=dev-lang/php-5.2[pcre(+)]", ["a"]), ) test_cases_xfail = ( ("? ( A )", "A"), ("!? ( A )", "A"), ("( A", "A"), ("A )", "A"), ("||( A B )", "A"), ("|| (A B )", "A"), ("|| ( A B)", "A"), ("|| ( A B", "A"), ("|| A B )", "A"), ("|| A B", "A"), ("|| ( A B ) )", "A"), ("|| || B C", "A"), ("|| ( A B || )", "A"), ("a? A", "A"), ("( || ( || || ( A ) foo? ( B ) ) )", "A"), ("( || ( || bar? ( A ) foo? ( B ) ) )", "A"), ) for dep, atom, expected in test_cases: expected = set(expected) result = extract_affecting_use(dep, atom, eapi="0") fail_msg = "dep: " + dep + ", atom: " + atom + ", got: " + \ " ".join(sorted(result)) + ", expected: " + " ".join(sorted(expected)) self.assertEqual(result, expected, fail_msg) for dep, atom in test_cases_xfail: fail_msg = "dep: " + dep + ", atom: " + atom + ", got: " + \ " ".join(sorted(result)) + ", expected: " + " ".join(sorted(expected)) self.assertRaisesMsg(fail_msg, \ InvalidDependString, extract_affecting_use, dep, atom, eapi="0")
def _find_suggestions(self): if not self.shortest_cycle: return None, None suggestions = [] final_solutions = {} for pos, pkg in enumerate(self.shortest_cycle): parent = self.shortest_cycle[pos-1] priorities = self.graph.nodes[parent][0][pkg] parent_atoms = self.all_parent_atoms.get(pkg) if priorities[-1].buildtime: dep = parent.metadata["DEPEND"] elif priorities[-1].runtime: dep = parent.metadata["RDEPEND"] for ppkg, atom in parent_atoms: if ppkg == parent: changed_parent = ppkg parent_atom = atom.unevaluated_atom break affecting_use = extract_affecting_use(dep, parent_atom) # Make sure we don't want to change a flag that is # a) in use.mask or use.force # b) changed by autounmask usemask, useforce = self._get_use_mask_and_force(parent) autounmask_changes = self._get_autounmask_changes(parent) untouchable_flags = frozenset(chain(usemask, useforce, autounmask_changes)) affecting_use.difference_update(untouchable_flags) #If any of the flags we're going to touch is in REQUIRED_USE, add all #other flags in REQUIRED_USE to affecting_use, to not lose any solution. required_use_flags = get_required_use_flags(parent.metadata["REQUIRED_USE"]) if affecting_use.intersection(required_use_flags): affecting_use.update(required_use_flags) affecting_use.difference_update(untouchable_flags) affecting_use = tuple(affecting_use) if not affecting_use: continue #We iterate over all possible settings of these use flags and gather #a set of possible changes #TODO: Use the information encoded in REQUIRED_USE use_state = [] for flag in affecting_use: use_state.append("disabled") def _next_use_state(state, id=None): if id is None: id = len(state)-1 if id == 0 and state[0] == "enabled": return False if state[id] == "disabled": state[id] = "enabled" for i in range(id+1,len(state)): state[i] = "disabled" return True else: return _next_use_state(state, id-1) solutions = set() while(True): current_use = set(self.depgraph._pkg_use_enabled(parent)) for flag, state in zip(affecting_use, use_state): if state == "enabled": current_use.add(flag) else: current_use.discard(flag) reduced_dep = use_reduce(dep, uselist=current_use, flat=True) if parent_atom not in reduced_dep: #We found an assignment that removes the atom from 'dep'. #Make sure it doesn't conflict with REQUIRED_USE. required_use = parent.metadata["REQUIRED_USE"] if check_required_use(required_use, current_use, parent.iuse.is_valid_flag): use = self.depgraph._pkg_use_enabled(parent) solution = set() for flag, state in zip(affecting_use, use_state): if state == "enabled" and \ flag not in use: solution.add((flag, True)) elif state == "disabled" and \ flag in use: solution.add((flag, False)) solutions.add(frozenset(solution)) if not _next_use_state(use_state): break for solution in solutions: ignore_solution = False for other_solution in solutions: if solution is other_solution: continue if solution.issuperset(other_solution): ignore_solution = True if ignore_solution: continue #Check if a USE change conflicts with use requirements of the parents. #If a requiremnet is hard, ignore the suggestion. #If the requirment is conditional, warn the user that other changes might be needed. followup_change = False parent_parent_atoms = self.depgraph._dynamic_config._parent_atoms.get(changed_parent) for ppkg, atom in parent_parent_atoms: atom = atom.unevaluated_atom if not atom.use: continue for flag, state in solution: if flag in atom.use.enabled or flag in atom.use.disabled: ignore_solution = True break elif atom.use.conditional: for flags in atom.use.conditional.values(): if flag in flags: followup_change = True break if ignore_solution: break if ignore_solution: continue changes = [] for flag, state in solution: if state: changes.append(colorize("red", "+"+flag)) else: changes.append(colorize("blue", "-"+flag)) msg = "- %s (Change USE: %s)\n" \ % (parent.cpv, " ".join(changes)) if followup_change: msg += " (This change might require USE changes on parent packages.)" suggestions.append(msg) final_solutions.setdefault(pkg, set()).add(solution) return final_solutions, suggestions
def _find_suggestions(self): if not self.shortest_cycle: return None, None suggestions = [] final_solutions = {} for pos, pkg in enumerate(self.shortest_cycle): parent = self.shortest_cycle[pos - 1] priorities = self.graph.nodes[parent][0][pkg] parent_atoms = self.all_parent_atoms.get(pkg) if priorities[-1].buildtime: dep = parent.metadata["DEPEND"] elif priorities[-1].runtime: dep = parent.metadata["RDEPEND"] for ppkg, atom in parent_atoms: if ppkg == parent: changed_parent = ppkg parent_atom = atom.unevaluated_atom break affecting_use = extract_affecting_use(dep, parent_atom) # Make sure we don't want to change a flag that is # a) in use.mask or use.force # b) changed by autounmask usemask, useforce = self._get_use_mask_and_force(parent) autounmask_changes = self._get_autounmask_changes(parent) untouchable_flags = frozenset( chain(usemask, useforce, autounmask_changes)) affecting_use.difference_update(untouchable_flags) #If any of the flags we're going to touch is in REQUIRED_USE, add all #other flags in REQUIRED_USE to affecting_use, to not lose any solution. required_use_flags = get_required_use_flags( parent.metadata["REQUIRED_USE"]) if affecting_use.intersection(required_use_flags): affecting_use.update(required_use_flags) affecting_use.difference_update(untouchable_flags) affecting_use = tuple(affecting_use) if not affecting_use: continue #We iterate over all possible settings of these use flags and gather #a set of possible changes #TODO: Use the information encoded in REQUIRED_USE use_state = [] for flag in affecting_use: use_state.append("disabled") def _next_use_state(state, id=None): if id is None: id = len(state) - 1 if id == 0 and state[0] == "enabled": return False if state[id] == "disabled": state[id] = "enabled" for i in range(id + 1, len(state)): state[i] = "disabled" return True else: return _next_use_state(state, id - 1) solutions = set() while (True): current_use = set(self.depgraph._pkg_use_enabled(parent)) for flag, state in zip(affecting_use, use_state): if state == "enabled": current_use.add(flag) else: current_use.discard(flag) reduced_dep = use_reduce(dep, uselist=current_use, flat=True) if parent_atom not in reduced_dep: #We found an assignment that removes the atom from 'dep'. #Make sure it doesn't conflict with REQUIRED_USE. required_use = parent.metadata["REQUIRED_USE"] if check_required_use(required_use, current_use, parent.iuse.is_valid_flag): use = self.depgraph._pkg_use_enabled(parent) solution = set() for flag, state in zip(affecting_use, use_state): if state == "enabled" and \ flag not in use: solution.add((flag, True)) elif state == "disabled" and \ flag in use: solution.add((flag, False)) solutions.add(frozenset(solution)) if not _next_use_state(use_state): break for solution in solutions: ignore_solution = False for other_solution in solutions: if solution is other_solution: continue if solution.issuperset(other_solution): ignore_solution = True if ignore_solution: continue #Check if a USE change conflicts with use requirements of the parents. #If a requiremnet is hard, ignore the suggestion. #If the requirment is conditional, warn the user that other changes might be needed. followup_change = False parent_parent_atoms = self.depgraph._dynamic_config._parent_atoms.get( changed_parent) for ppkg, atom in parent_parent_atoms: atom = atom.unevaluated_atom if not atom.use: continue for flag, state in solution: if flag in atom.use.enabled or flag in atom.use.disabled: ignore_solution = True break elif atom.use.conditional: for flags in atom.use.conditional.values(): if flag in flags: followup_change = True break if ignore_solution: break if ignore_solution: continue changes = [] for flag, state in solution: if state: changes.append(colorize("red", "+" + flag)) else: changes.append(colorize("blue", "-" + flag)) msg = "- %s (Change USE: %s)\n" \ % (parent.cpv, " ".join(changes)) if followup_change: msg += " (This change might require USE changes on parent packages.)" suggestions.append(msg) final_solutions.setdefault(pkg, set()).add(solution) return final_solutions, suggestions