Skip to content

Commit b55212f

Browse files
radoeringneersighted
authored andcommitted
provider: do not merge dependencies from different sources
1 parent 2c610de commit b55212f

File tree

3 files changed

+85
-16
lines changed

3 files changed

+85
-16
lines changed

src/poetry/puzzle/provider.py

+50-12
Original file line numberDiff line numberDiff line change
@@ -670,19 +670,29 @@ def complete_package(
670670

671671
self.debug(f"<debug>Duplicate dependencies for {dep_name}</debug>")
672672

673-
non_direct_origin_deps: list[Dependency] = []
674-
direct_origin_deps: list[Dependency] = []
675-
for dep in deps:
676-
if dep.is_direct_origin():
677-
direct_origin_deps.append(dep)
678-
else:
679-
non_direct_origin_deps.append(dep)
680-
deps = (
681-
self._merge_dependencies_by_constraint(
682-
self._merge_dependencies_by_marker(non_direct_origin_deps)
673+
# Group dependencies for merging.
674+
# We must not merge dependencies from different sources!
675+
dep_groups = self._group_by_source(deps)
676+
deps = []
677+
for group in dep_groups:
678+
# In order to reduce the number of overrides we merge duplicate
679+
# dependencies by constraint. For instance, if we have:
680+
# - foo (>=2.0) ; python_version >= "3.6" and python_version < "3.7"
681+
# - foo (>=2.0) ; python_version >= "3.7"
682+
# we can avoid two overrides by merging them to:
683+
# - foo (>=2.0) ; python_version >= "3.6"
684+
# However, if we want to merge dependencies by constraint we have to
685+
# merge dependencies by markers first in order to avoid unnecessary
686+
# solver failures. For instance, if we have:
687+
# - foo (>=2.0) ; python_version >= "3.6" and python_version < "3.7"
688+
# - foo (>=2.0) ; python_version >= "3.7"
689+
# - foo (<2.1) ; python_version >= "3.7"
690+
# we must not merge the first two constraints but the last two:
691+
# - foo (>=2.0) ; python_version >= "3.6" and python_version < "3.7"
692+
# - foo (>=2.0,<2.1) ; python_version >= "3.7"
693+
deps += self._merge_dependencies_by_constraint(
694+
self._merge_dependencies_by_marker(group)
683695
)
684-
+ direct_origin_deps
685-
)
686696
if len(deps) == 1:
687697
self.debug(f"<debug>Merging requirements for {deps[0]!s}</debug>")
688698
dependencies.append(deps[0])
@@ -947,9 +957,33 @@ def debug(self, message: str, depth: int = 0) -> None:
947957

948958
self._io.write(debug_info)
949959

960+
def _group_by_source(
961+
self, dependencies: Iterable[Dependency]
962+
) -> list[list[Dependency]]:
963+
"""
964+
Takes a list of dependencies and returns a list of groups of dependencies,
965+
each group containing all dependencies from the same source.
966+
"""
967+
groups: list[list[Dependency]] = []
968+
for dep in dependencies:
969+
for group in groups:
970+
if (
971+
dep.is_same_source_as(group[0])
972+
and dep.source_name == group[0].source_name
973+
):
974+
group.append(dep)
975+
break
976+
else:
977+
groups.append([dep])
978+
return groups
979+
950980
def _merge_dependencies_by_constraint(
951981
self, dependencies: Iterable[Dependency]
952982
) -> list[Dependency]:
983+
"""
984+
Merge dependencies with the same constraint
985+
by building a union of their markers.
986+
"""
953987
by_constraint: dict[VersionConstraint, list[Dependency]] = defaultdict(list)
954988
for dep in dependencies:
955989
by_constraint[dep.constraint].append(dep)
@@ -975,6 +1009,10 @@ def _merge_dependencies_by_constraint(
9751009
def _merge_dependencies_by_marker(
9761010
self, dependencies: Iterable[Dependency]
9771011
) -> list[Dependency]:
1012+
"""
1013+
Merge dependencies with the same marker
1014+
by building the intersection of their constraints.
1015+
"""
9781016
by_marker: dict[BaseMarker, list[Dependency]] = defaultdict(list)
9791017
for dep in dependencies:
9801018
by_marker[dep.marker].append(dep)

tests/puzzle/test_provider.py

+23
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,29 @@ def test_search_for_file_wheel_with_extras(provider: Provider):
622622
}
623623

624624

625+
def test_complete_package_does_not_merge_different_source_names(
626+
provider: Provider, root: ProjectPackage
627+
) -> None:
628+
foo_source_1 = get_dependency("foo")
629+
foo_source_1.source_name = "source_1"
630+
foo_source_2 = get_dependency("foo")
631+
foo_source_2.source_name = "source_2"
632+
633+
root.add_dependency(foo_source_1)
634+
root.add_dependency(foo_source_2)
635+
636+
complete_package = provider.complete_package(
637+
DependencyPackage(root.to_dependency(), root)
638+
)
639+
640+
requires = complete_package.package.all_requires
641+
assert len(requires) == 2
642+
assert {requires[0].source_name, requires[1].source_name} == {
643+
"source_1",
644+
"source_2",
645+
}
646+
647+
625648
def test_complete_package_preserves_source_type(
626649
provider: Provider, root: ProjectPackage
627650
) -> None:

tests/puzzle/test_solver.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -1368,8 +1368,9 @@ def test_solver_duplicate_dependencies_different_constraints_merge_by_marker(
13681368
)
13691369

13701370

1371+
@pytest.mark.parametrize("git_first", [False, True])
13711372
def test_solver_duplicate_dependencies_different_sources_types_are_preserved(
1372-
solver: Solver, repo: Repository, package: ProjectPackage
1373+
solver: Solver, repo: Repository, package: ProjectPackage, git_first: bool
13731374
):
13741375
pendulum = get_package("pendulum", "2.0.3")
13751376
repo.add_package(pendulum)
@@ -1380,8 +1381,12 @@ def test_solver_duplicate_dependencies_different_sources_types_are_preserved(
13801381
dependency_git = Factory.create_dependency(
13811382
"demo", {"git": "https://github.com/demo/demo.git"}, groups=["dev"]
13821383
)
1383-
package.add_dependency(dependency_git)
1384-
package.add_dependency(dependency_pypi)
1384+
if git_first:
1385+
package.add_dependency(dependency_git)
1386+
package.add_dependency(dependency_pypi)
1387+
else:
1388+
package.add_dependency(dependency_pypi)
1389+
package.add_dependency(dependency_git)
13851390

13861391
demo = Package(
13871392
"demo",
@@ -1413,7 +1418,10 @@ def test_solver_duplicate_dependencies_different_sources_types_are_preserved(
14131418

14141419
assert len(complete_package.package.all_requires) == 2
14151420

1416-
pypi, git = complete_package.package.all_requires
1421+
if git_first:
1422+
git, pypi = complete_package.package.all_requires
1423+
else:
1424+
pypi, git = complete_package.package.all_requires
14171425

14181426
assert isinstance(pypi, Dependency)
14191427
assert pypi == dependency_pypi

0 commit comments

Comments
 (0)