diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py
index ccc2394f754..7c87f7401a1 100644
--- a/src/poetry/puzzle/provider.py
+++ b/src/poetry/puzzle/provider.py
@@ -740,7 +740,7 @@ def fmt_warning(d: Dependency) -> str:
f"Different requirements found for {warnings}."
)
- self._handle_any_marker_dependencies(deps)
+ deps = self._handle_any_marker_dependencies(deps)
overrides = []
overrides_marker_intersection: BaseMarker = AnyMarker()
@@ -974,7 +974,9 @@ 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:
+ def _handle_any_marker_dependencies(
+ self, dependencies: list[Dependency]
+ ) -> list[Dependency]:
"""
We need to check if one of the duplicate dependencies
has no markers. If there is one, we need to change its
@@ -997,32 +999,42 @@ def _handle_any_marker_dependencies(self, dependencies: list[Dependency]) -> Non
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 any_markers_dependencies:
- for dep_any in any_markers_dependencies:
- dep_any.marker = inverted_marker
- for dep_other in other_markers_dependencies:
- dep_other.constraint = dep_other.constraint.intersect(
- dep_any.constraint
- )
- elif not inverted_marker.is_empty() and self._python_constraint.allows_any(
+ 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 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)
+ 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:
+ dependencies = other_markers_dependencies
+ return dependencies
diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py
index d55ea5b2915..7d05ffae884 100644
--- a/tests/puzzle/test_solver.py
+++ b/tests/puzzle/test_solver.py
@@ -1480,6 +1480,143 @@ def test_solver_duplicate_dependencies_different_constraints_merge_no_markers(
)
+def test_solver_duplicate_dependencies_different_constraints_discard_no_markers1(
+ solver: Solver, repo: Repository, package: ProjectPackage
+) -> None:
+ """
+ Initial dependencies:
+ A (>=1.0)
+ A (<1.2) ; python >= 3.10
+ A (<1.1) ; python < 3.10
+
+ Merged dependencies:
+ A (>=1.0) ;
+ A (>=1.0,<1.2) ; python >= 3.10
+ A (>=1.0,<1.1) ; python < 3.10
+
+ The dependency with an empty marker has to be ignored.
+ """
+ package.add_dependency(Factory.create_dependency("A", ">=1.0"))
+ package.add_dependency(
+ Factory.create_dependency("A", {"version": "<1.2", "python": ">=3.10"})
+ )
+ package.add_dependency(
+ Factory.create_dependency("A", {"version": "<1.1", "python": "<3.10"})
+ )
+ package.add_dependency(Factory.create_dependency("B", "*"))
+
+ package_a10 = get_package("A", "1.0")
+ package_a11 = get_package("A", "1.1")
+ package_a12 = get_package("A", "1.2")
+ package_b = get_package("B", "1.0")
+ package_b.add_dependency(Factory.create_dependency("A", "*"))
+
+ repo.add_package(package_a10)
+ repo.add_package(package_a11)
+ repo.add_package(package_a12)
+ repo.add_package(package_b)
+
+ transaction = solver.solve()
+
+ check_solver_result(
+ transaction,
+ [
+ # only a10 and a11, not a12
+ {"job": "install", "package": package_a10},
+ {"job": "install", "package": package_a11},
+ {"job": "install", "package": package_b},
+ ],
+ )
+
+
+def test_solver_duplicate_dependencies_different_constraints_discard_no_markers2(
+ solver: Solver, repo: Repository, package: ProjectPackage
+) -> None:
+ """
+ Initial dependencies:
+ A (>=1.0)
+ A (<1.2) ; python == 3.10
+
+ Merged dependencies:
+ A (>=1.0) ; python != 3.10
+ A (>=1.0,<1.2) ; python == 3.10
+
+ The first dependency has to be ignored
+ because it is not compatible with the project's python constraint.
+ """
+ set_package_python_versions(solver.provider, "~3.10")
+ package.add_dependency(Factory.create_dependency("A", ">=1.0"))
+ package.add_dependency(
+ Factory.create_dependency("A", {"version": "<1.2", "python": "3.10"})
+ )
+ package.add_dependency(Factory.create_dependency("B", "*"))
+
+ package_a10 = get_package("A", "1.0")
+ package_a11 = get_package("A", "1.1")
+ package_a12 = get_package("A", "1.2")
+ package_b = get_package("B", "1.0")
+ package_b.add_dependency(Factory.create_dependency("A", "*"))
+
+ repo.add_package(package_a10)
+ repo.add_package(package_a11)
+ repo.add_package(package_a12)
+ repo.add_package(package_b)
+
+ transaction = solver.solve()
+
+ check_solver_result(
+ transaction,
+ [
+ {"job": "install", "package": package_a11}, # only a11, not a12
+ {"job": "install", "package": package_b},
+ ],
+ )
+
+
+def test_solver_duplicate_dependencies_different_constraints_discard_no_markers3(
+ solver: Solver, repo: Repository, package: ProjectPackage
+) -> None:
+ """
+ Initial dependencies:
+ A (>=1.0)
+ A (<1.2) ; python == 3.10
+
+ Merged dependencies:
+ A (>=1.0) ; python != 3.10
+ A (>=1.0,<1.2) ; python == 3.10
+
+ The first dependency has to be ignored
+ because it is not compatible with the current environment.
+ """
+ package.add_dependency(Factory.create_dependency("A", ">=1.0"))
+ package.add_dependency(
+ Factory.create_dependency("A", {"version": "<1.2", "python": "3.10"})
+ )
+ package.add_dependency(Factory.create_dependency("B", "*"))
+
+ package_a10 = get_package("A", "1.0")
+ package_a11 = get_package("A", "1.1")
+ package_a12 = get_package("A", "1.2")
+ package_b = get_package("B", "1.0")
+ package_b.add_dependency(Factory.create_dependency("A", "*"))
+
+ repo.add_package(package_a10)
+ repo.add_package(package_a11)
+ repo.add_package(package_a12)
+ repo.add_package(package_b)
+
+ with solver.use_environment(MockEnv((3, 10, 0))):
+ transaction = solver.solve()
+
+ check_solver_result(
+ transaction,
+ [
+ {"job": "install", "package": package_a11}, # only a11, not a12
+ {"job": "install", "package": package_b},
+ ],
+ )
+
+
def test_solver_duplicate_dependencies_ignore_overrides_with_empty_marker_intersection(
solver: Solver, repo: Repository, package: ProjectPackage
):