Commit a8f6b5b5 by Randy Döring

provider/solver: support for duplicate direct origin dependencies with same version

parent 8cb3aab3
...@@ -612,39 +612,31 @@ class Provider: ...@@ -612,39 +612,31 @@ class Provider:
# An example of this is: # An example of this is:
# - pypiwin32 (220); sys_platform == "win32" and python_version >= "3.6" # - pypiwin32 (220); sys_platform == "win32" and python_version >= "3.6"
# - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6" # - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6"
# duplicates: dict[str, list[Dependency]] = defaultdict(list)
# Additional care has to be taken to ensure that hidden constraints like that
# of source type, reference etc. are taking into consideration when duplicates
# are identified.
duplicates: dict[
tuple[str, str | None, str | None, str | None, str | None], list[Dependency]
] = {}
for dep in dependencies: for dep in dependencies:
key = ( duplicates[dep.complete_name].append(dep)
dep.complete_name,
dep.source_type,
dep.source_url,
dep.source_reference,
dep.source_subdirectory,
)
if key not in duplicates:
duplicates[key] = []
duplicates[key].append(dep)
dependencies = [] dependencies = []
for key, deps in duplicates.items(): for dep_name, deps in duplicates.items():
if len(deps) == 1: if len(deps) == 1:
dependencies.append(deps[0]) dependencies.append(deps[0])
continue continue
extra_keys = ", ".join(k for k in key[1:] if k is not None)
dep_name = f"{key[0]} ({extra_keys})" if extra_keys else key[0]
self.debug(f"<debug>Duplicate dependencies for {dep_name}</debug>") self.debug(f"<debug>Duplicate dependencies for {dep_name}</debug>")
deps = self._merge_dependencies_by_marker(deps) non_direct_origin_deps: list[Dependency] = []
deps = self._merge_dependencies_by_constraint(deps) direct_origin_deps: list[Dependency] = []
for dep in deps:
if dep.is_direct_origin():
direct_origin_deps.append(dep)
else:
non_direct_origin_deps.append(dep)
deps = (
self._merge_dependencies_by_constraint(
self._merge_dependencies_by_marker(non_direct_origin_deps)
)
+ direct_origin_deps
)
if len(deps) == 1: if len(deps) == 1:
self.debug(f"<debug>Merging requirements for {deps[0]!s}</debug>") self.debug(f"<debug>Merging requirements for {deps[0]!s}</debug>")
dependencies.append(deps[0]) dependencies.append(deps[0])
......
...@@ -514,27 +514,25 @@ def test_search_for_file_wheel_with_extras(provider: Provider): ...@@ -514,27 +514,25 @@ def test_search_for_file_wheel_with_extras(provider: Provider):
} }
def test_complete_package_preserves_source_type(provider: Provider) -> None: def test_complete_package_preserves_source_type(
provider: Provider, root: ProjectPackage
) -> None:
fixtures = Path(__file__).parent.parent / "fixtures" fixtures = Path(__file__).parent.parent / "fixtures"
project_dir = fixtures.joinpath("with_conditional_path_deps") project_dir = fixtures.joinpath("with_conditional_path_deps")
poetry = Factory().create_poetry(cwd=project_dir) for folder in ["demo_one", "demo_two"]:
path = (project_dir / folder).as_posix()
root.add_dependency(Factory.create_dependency("demo", {"path": path}))
complete_package = provider.complete_package( complete_package = provider.complete_package(
DependencyPackage(poetry.package.to_dependency(), poetry.package) DependencyPackage(root.to_dependency(), root)
) )
requires = complete_package.package.all_requires requires = complete_package.package.all_requires
assert len(requires) == 2 assert len(requires) == 2
assert {requires[0].source_url, requires[1].source_url} == { assert {requires[0].source_url, requires[1].source_url} == {
project_dir.joinpath("demo_one").as_posix(), project_dir.joinpath("demo_one").as_posix(),
project_dir.joinpath("demo_two").as_posix(), project_dir.joinpath("demo_two").as_posix(),
} }
assert {str(requires[0].marker), str(requires[1].marker)} == {
'sys_platform == "linux"',
'sys_platform == "win32"',
}
def test_complete_package_preserves_source_type_with_subdirectories( def test_complete_package_preserves_source_type_with_subdirectories(
...@@ -545,7 +543,6 @@ def test_complete_package_preserves_source_type_with_subdirectories( ...@@ -545,7 +543,6 @@ def test_complete_package_preserves_source_type_with_subdirectories(
{ {
"git": "https://github.com/demo/subdirectories.git", "git": "https://github.com/demo/subdirectories.git",
"subdirectory": "one", "subdirectory": "one",
"platform": "linux",
}, },
) )
dependency_one_copy = Factory.create_dependency( dependency_one_copy = Factory.create_dependency(
...@@ -553,7 +550,6 @@ def test_complete_package_preserves_source_type_with_subdirectories( ...@@ -553,7 +550,6 @@ def test_complete_package_preserves_source_type_with_subdirectories(
{ {
"git": "https://github.com/demo/subdirectories.git", "git": "https://github.com/demo/subdirectories.git",
"subdirectory": "one-copy", "subdirectory": "one-copy",
"platform": "win32",
}, },
) )
dependency_two = Factory.create_dependency( dependency_two = Factory.create_dependency(
...@@ -567,7 +563,6 @@ def test_complete_package_preserves_source_type_with_subdirectories( ...@@ -567,7 +563,6 @@ def test_complete_package_preserves_source_type_with_subdirectories(
{ {
"git": "https://github.com/demo/subdirectories.git", "git": "https://github.com/demo/subdirectories.git",
"subdirectory": "one", "subdirectory": "one",
"platform": "linux",
}, },
) )
) )
......
...@@ -1418,7 +1418,7 @@ def test_solver_duplicate_dependencies_different_sources_types_are_preserved( ...@@ -1418,7 +1418,7 @@ def test_solver_duplicate_dependencies_different_sources_types_are_preserved(
assert len(complete_package.all_requires) == 2 assert len(complete_package.all_requires) == 2
git, pypi = complete_package.all_requires pypi, git = complete_package.all_requires
assert isinstance(pypi, Dependency) assert isinstance(pypi, Dependency)
assert pypi == dependency_pypi assert pypi == dependency_pypi
...@@ -1632,6 +1632,63 @@ def test_solver_duplicate_dependencies_sub_dependencies( ...@@ -1632,6 +1632,63 @@ def test_solver_duplicate_dependencies_sub_dependencies(
) )
def test_duplicate_path_dependencies(solver: Solver, package: ProjectPackage) -> None:
solver.provider.set_package_python_versions("^3.7")
fixtures = Path(__file__).parent.parent / "fixtures"
project_dir = fixtures / "with_conditional_path_deps"
path1 = (project_dir / "demo_one").as_posix()
demo1 = Package("demo", "1.2.3", source_type="directory", source_url=path1)
package.add_dependency(
Factory.create_dependency(
"demo", {"path": path1, "markers": "sys_platform == 'linux'"}
)
)
path2 = (project_dir / "demo_two").as_posix()
demo2 = Package("demo", "1.2.3", source_type="directory", source_url=path2)
package.add_dependency(
Factory.create_dependency(
"demo", {"path": path2, "markers": "sys_platform == 'win32'"}
)
)
transaction = solver.solve()
check_solver_result(
transaction,
[
{"job": "install", "package": demo1},
{"job": "install", "package": demo2},
],
)
def test_duplicate_path_dependencies_same_path(
solver: Solver, package: ProjectPackage
) -> None:
solver.provider.set_package_python_versions("^3.7")
fixtures = Path(__file__).parent.parent / "fixtures"
project_dir = fixtures / "with_conditional_path_deps"
path1 = (project_dir / "demo_one").as_posix()
demo1 = Package("demo", "1.2.3", source_type="directory", source_url=path1)
package.add_dependency(
Factory.create_dependency(
"demo", {"path": path1, "markers": "sys_platform == 'linux'"}
)
)
package.add_dependency(
Factory.create_dependency(
"demo", {"path": path1, "markers": "sys_platform == 'win32'"}
)
)
transaction = solver.solve()
check_solver_result(transaction, [{"job": "install", "package": demo1}])
def test_solver_fails_if_dependency_name_does_not_match_package( def test_solver_fails_if_dependency_name_does_not_match_package(
solver: Solver, repo: Repository, package: ProjectPackage solver: Solver, repo: Repository, package: ProjectPackage
): ):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment