def testCheckRequiredUse(self): test_cases = ( ("a b c", ["a", "b", "c"]), ("|| ( a b c )", ["a", "b", "c"]), ("^^ ( a b c )", ["a", "b", "c"]), ("|| ( a b ^^ ( d e f ) )", ["a", "b", "d", "e", "f"]), ("^^ ( a b || ( d e f ) )", ["a", "b", "d", "e", "f"]), ("( ^^ ( a ( b ) ( || ( ( d e ) ( f ) ) ) ) )", ["a", "b", "d", "e", "f"]), ("a? ( ^^ ( b c ) )", ["a", "b", "c"]), ("a? ( ^^ ( !b !d? ( c ) ) )", ["a", "b", "c", "d"]), ) test_cases_xfail = ( ("^^ ( || ( a b ) ^^ ( b c )"), ("^^( || ( a b ) ^^ ( b c ) )"), ("^^ || ( a b ) ^^ ( b c )"), ("^^ ( ( || ) ( a b ) ^^ ( b c ) )"), ("^^ ( || ( a b ) ) ^^ ( b c ) )"), ) for required_use, expected in test_cases: result = get_required_use_flags(required_use) expected = set(expected) self.assertEqual(result, expected, \ "REQUIRED_USE: '%s', expected: '%s', got: '%s'" % (required_use, expected, result)) for required_use in test_cases_xfail: self.assertRaisesMsg("REQUIRED_USE: '%s'" % (required_use,), \ InvalidDependString, get_required_use_flags, required_use)
def testCheckRequiredUse(self): test_cases = ( ("a b c", ["a", "b", "c"]), ("|| ( a b c )", ["a", "b", "c"]), ("^^ ( a b c )", ["a", "b", "c"]), ("?? ( a b c )", ["a", "b", "c"]), ("?? ( )", []), ("|| ( a b ^^ ( d e f ) )", ["a", "b", "d", "e", "f"]), ("^^ ( a b || ( d e f ) )", ["a", "b", "d", "e", "f"]), ("( ^^ ( a ( b ) ( || ( ( d e ) ( f ) ) ) ) )", ["a", "b", "d", "e", "f"]), ("a? ( ^^ ( b c ) )", ["a", "b", "c"]), ("a? ( ^^ ( !b !d? ( c ) ) )", ["a", "b", "c", "d"]), ) test_cases_xfail = ( ("^^ ( || ( a b ) ^^ ( b c )"), ("^^( || ( a b ) ^^ ( b c ) )"), ("^^ || ( a b ) ^^ ( b c )"), ("^^ ( ( || ) ( a b ) ^^ ( b c ) )"), ("^^ ( || ( a b ) ) ^^ ( b c ) )"), ) for required_use, expected in test_cases: result = get_required_use_flags(required_use) expected = set(expected) self.assertEqual(result, expected, \ "REQUIRED_USE: '%s', expected: '%s', got: '%s'" % (required_use, expected, result)) for required_use in test_cases_xfail: self.assertRaisesMsg("REQUIRED_USE: '%s'" % (required_use,), \ InvalidDependString, get_required_use_flags, required_use)
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 _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