diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 374907c5341..5c49ca88cce 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -740,69 +740,7 @@ def fmt_warning(d: Dependency) -> str: f"Different requirements found for {warnings}." ) - # We need to check if one of the duplicate dependencies - # has no markers. If there is one, we need to change its - # environment markers to the inverse of the union of the - # other dependencies markers. - # For instance, if we have the following dependencies: - # - ipython - # - ipython (1.2.4) ; implementation_name == "pypy" - # - # the marker for `ipython` will become `implementation_name != "pypy"`. - # - # Further, we have to merge the constraints of the requirements - # without markers into the constraints of the requirements with markers. - # for instance, if we have the following dependencies: - # - foo (>= 1.2) - # - foo (!= 1.2.1) ; python == 3.10 - # - # the constraint for the second entry will become (!= 1.2.1, >= 1.2) - any_markers_dependencies = [d for d in deps if d.marker.is_any()] - other_markers_dependencies = [d for d in deps if not d.marker.is_any()] - - for dep_any in any_markers_dependencies: - for dep_other in other_markers_dependencies: - dep_other.constraint = dep_other.constraint.intersect( - dep_any.constraint - ) - - marker = other_markers_dependencies[0].marker - for other_dep in other_markers_dependencies[1:]: - marker = marker.union(other_dep.marker) - inverted_marker = marker.invert() - - if ( - not inverted_marker.is_empty() - and self._python_constraint.allows_any( - get_python_constraint_from_marker(inverted_marker) - ) - and (not self._env or inverted_marker.validate(self._env.marker_env)) - ): - if any_markers_dependencies: - for dep_any in any_markers_dependencies: - dep_any.marker = inverted_marker - else: - # if there is no any marker dependency - # and the inverted marker is not empty, - # a dependency with the inverted union of all markers is required - # in order to not miss other dependencies later, for instance: - # - foo (1.0) ; python == 3.7 - # - foo (2.0) ; python == 3.8 - # - bar (2.0) ; python == 3.8 - # - bar (3.0) ; python == 3.9 - # - # the last dependency would be missed without this, - # because the intersection with both foo dependencies is empty - inverted_marker_dep = deps[0].with_constraint(EmptyConstraint()) - inverted_marker_dep.marker = inverted_marker - deps.append(inverted_marker_dep) - else: - # Attention: Iterating over any_markers_dependencies and removing them - # from deps may remove wrong dependencies because markers are not - # considered in Dependency.__eq__ - for dep in deps[:]: - if dep.marker.is_any(): - deps.remove(dep) + self._handle_any_marker_dependencies(deps) overrides = [] overrides_marker_intersection: BaseMarker = AnyMarker() @@ -1035,3 +973,70 @@ def _merge_dependencies_by_marker( ) deps.append(_deps[0].with_constraint(new_constraint)) return deps + + def _handle_any_marker_dependencies(self, dependencies: list[Dependency]) -> None: + """ + We need to check if one of the duplicate dependencies + has no markers. If there is one, we need to change its + environment markers to the inverse of the union of the + other dependencies markers. + For instance, if we have the following dependencies: + - ipython + - ipython (1.2.4) ; implementation_name == "pypy" + + the marker for `ipython` will become `implementation_name != "pypy"`. + + Further, we have to merge the constraints of the requirements + without markers into the constraints of the requirements with markers. + for instance, if we have the following dependencies: + - foo (>= 1.2) + - foo (!= 1.2.1) ; python == 3.10 + + the constraint for the second entry will become (!= 1.2.1, >= 1.2). + """ + any_markers_dependencies = [d for d in dependencies if d.marker.is_any()] + other_markers_dependencies = [d for d in dependencies if not d.marker.is_any()] + + for dep_any in any_markers_dependencies: + for dep_other in other_markers_dependencies: + dep_other.constraint = dep_other.constraint.intersect( + dep_any.constraint + ) + + marker = other_markers_dependencies[0].marker + for other_dep in other_markers_dependencies[1:]: + marker = marker.union(other_dep.marker) + inverted_marker = marker.invert() + + if ( + not inverted_marker.is_empty() + and self._python_constraint.allows_any( + get_python_constraint_from_marker(inverted_marker) + ) + and (not self._env or inverted_marker.validate(self._env.marker_env)) + ): + if any_markers_dependencies: + for dep_any in any_markers_dependencies: + dep_any.marker = inverted_marker + else: + # if there is no any marker dependency + # and the inverted marker is not empty, + # a dependency with the inverted union of all markers is required + # in order to not miss other dependencies later, for instance: + # - foo (1.0) ; python == 3.7 + # - foo (2.0) ; python == 3.8 + # - bar (2.0) ; python == 3.8 + # - bar (3.0) ; python == 3.9 + # + # the last dependency would be missed without this, + # because the intersection with both foo dependencies is empty. + inverted_marker_dep = dependencies[0].with_constraint(EmptyConstraint()) + inverted_marker_dep.marker = inverted_marker + dependencies.append(inverted_marker_dep) + else: + # Attention: Iterating over any_markers_dependencies and removing them + # from dependencies may remove wrong dependencies because markers are not + # considered in Dependency.__eq__ + for dep in dependencies[:]: + if dep.marker.is_any(): + dependencies.remove(dep)