Commit 10c49e3f by Sébastien Eustace

Fix dependency resolution for Python-restricted dependencies

parent 10c3d0d5
...@@ -4,10 +4,12 @@ import re ...@@ -4,10 +4,12 @@ import re
from poetry.version.requirements import Requirement from poetry.version.requirements import Requirement
from .dependency import Dependency from .dependency import Dependency
from .dependency_package import DependencyPackage
from .directory_dependency import DirectoryDependency from .directory_dependency import DirectoryDependency
from .file_dependency import FileDependency from .file_dependency import FileDependency
from .locker import Locker from .locker import Locker
from .package import Package from .package import Package
from .package_collection import PackageCollection
from .project_package import ProjectPackage from .project_package import ProjectPackage
from .utils.link import Link from .utils.link import Link
from .utils.utils import convert_markers from .utils.utils import convert_markers
......
from .package import Package
class DependencyPackage:
def __init__(self, dependency, package):
self._dependency = dependency
self._package = package
@property
def dependency(self):
return self._dependency
@property
def package(self):
return self._package
def __getattr__(self, name):
return getattr(self._package, name)
def __str__(self):
return str(self._package)
def __repr__(self):
return repr(self._package)
def __hash__(self):
return hash(self._package)
def __eq__(self, other):
if isinstance(other, DependencyPackage):
other = other.package
return self._package == other
from .dependency_package import DependencyPackage
class PackageCollection(list):
def __init__(self, dependency, packages=None):
self._dependency = dependency
if packages is None:
packages = []
super(PackageCollection, self).__init__()
for package in packages:
self.append(package)
def append(self, package):
if not isinstance(package, DependencyPackage):
package = DependencyPackage(self._dependency, package)
return super(PackageCollection, self).append(package)
...@@ -12,9 +12,11 @@ from tempfile import mkdtemp ...@@ -12,9 +12,11 @@ from tempfile import mkdtemp
from typing import List from typing import List
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.packages import DependencyPackage
from poetry.packages import DirectoryDependency from poetry.packages import DirectoryDependency
from poetry.packages import FileDependency from poetry.packages import FileDependency
from poetry.packages import Package from poetry.packages import Package
from poetry.packages import PackageCollection
from poetry.packages import VCSDependency from poetry.packages import VCSDependency
from poetry.packages import dependency_from_pep_508 from poetry.packages import dependency_from_pep_508
...@@ -101,10 +103,10 @@ class Provider: ...@@ -101,10 +103,10 @@ class Provider:
order, so the latest version ought to be last. order, so the latest version ought to be last.
""" """
if dependency.is_root: if dependency.is_root:
return [self._package] return PackageCollection(dependency, [self._package])
if dependency in self._search_for: if dependency in self._search_for:
return self._search_for[dependency] return PackageCollection(dependency, self._search_for[dependency])
if dependency.is_vcs(): if dependency.is_vcs():
packages = self.search_for_vcs(dependency) packages = self.search_for_vcs(dependency)
...@@ -132,7 +134,7 @@ class Provider: ...@@ -132,7 +134,7 @@ class Provider:
self._search_for[dependency] = packages self._search_for[dependency] = packages
return self._search_for[dependency] return PackageCollection(dependency, self._search_for[dependency])
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package]
""" """
...@@ -314,6 +316,41 @@ class Provider: ...@@ -314,6 +316,41 @@ class Provider:
if not package.python_constraint.allows_all( if not package.python_constraint.allows_all(
self._package.python_constraint self._package.python_constraint
): ):
# The package Python requirement is not compatible
# with the root package python requirement.
#
# However, it should be accepted if it comes from
# a dependency with a compatible Python constraint.
#
# An example of this is:
# - The root package is compatible with Python ~2.7 || ^3.6
# - The root package depends on black for Python ^3.6
# - black is only compatible with Python >=3.6
# - black should be authorized.
#
# In this particular case, we notify the resolver that it needs
# to branch the dependency tree. What this means is if we have
# root Python ~2.7 || ^3.6 and dependency Python >=3.6
# we have to resolve for ^3.6 (>=3.6, <4.0)
if (
not package.dependency.python_constraint.is_any()
and not package.python_constraint.intersect(
package.dependency.python_constraint
).is_empty()
):
self.debug(
"<warning>Found conditional dependency for {} (Python {}).</warning>".format(
package, package.dependency.python_constraint
)
)
raise CompatibilityError(
str(
package.python_constraint.intersect(
package.dependency.python_constraint
)
)
)
return [ return [
Incompatibility( Incompatibility(
[Term(package.to_dependency(), True)], [Term(package.to_dependency(), True)],
...@@ -354,8 +391,11 @@ class Provider: ...@@ -354,8 +391,11 @@ class Provider:
return package return package
if package.source_type not in {"directory", "file", "git"}: if package.source_type not in {"directory", "file", "git"}:
package = self._pool.package( package = DependencyPackage(
package.name, package.version.text, extras=package.requires_extras package.dependency,
self._pool.package(
package.name, package.version.text, extras=package.requires_extras
),
) )
dependencies = [ dependencies = [
......
...@@ -2,6 +2,7 @@ from typing import List ...@@ -2,6 +2,7 @@ from typing import List
from poetry.mixology import resolve_version from poetry.mixology import resolve_version
from poetry.mixology.failure import SolveFailure from poetry.mixology.failure import SolveFailure
from poetry.packages import DependencyPackage
from poetry.packages.constraints.generic_constraint import GenericConstraint from poetry.packages.constraints.generic_constraint import GenericConstraint
from poetry.semver import parse_constraint from poetry.semver import parse_constraint
...@@ -84,7 +85,7 @@ class Solver: ...@@ -84,7 +85,7 @@ class Solver:
def solve_in_compatibility_mode(self, constraints, use_latest=None): def solve_in_compatibility_mode(self, constraints, use_latest=None):
locked = {} locked = {}
for package in self._locked.packages: for package in self._locked.packages:
locked[package.name] = package locked[package.name] = DependencyPackage(package.to_dependency(), package)
packages = [] packages = []
depths = [] depths = []
...@@ -114,7 +115,7 @@ class Solver: ...@@ -114,7 +115,7 @@ class Solver:
def _solve(self, use_latest=None): def _solve(self, use_latest=None):
locked = {} locked = {}
for package in self._locked.packages: for package in self._locked.packages:
locked[package.name] = package locked[package.name] = DependencyPackage(package.to_dependency(), package)
try: try:
result = resolve_version( result = resolve_version(
...@@ -217,7 +218,9 @@ class Solver: ...@@ -217,7 +218,9 @@ class Solver:
break break
for pkg in packages: for pkg in packages:
if pkg.name == dependency.name: if pkg.name == dependency.name and dependency.constraint.allows(
pkg.version
):
# If there is already a child with this name # If there is already a child with this name
# we merge the requirements # we merge the requirements
existing = None existing = None
......
...@@ -22,7 +22,7 @@ classifiers = [ ...@@ -22,7 +22,7 @@ classifiers = [
# Requirements # Requirements
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "~2.7 || ^3.4" python = "~2.7.9 || ^3.4"
cleo = "^0.6.7" cleo = "^0.6.7"
requests = "^2.18" requests = "^2.18"
cachy = "^0.2" cachy = "^0.2"
......
from poetry.packages import DependencyPackage
from poetry.packages import Package from poetry.packages import Package
from poetry.mixology.failure import SolveFailure from poetry.mixology.failure import SolveFailure
from poetry.mixology.version_solver import VersionSolver from poetry.mixology.version_solver import VersionSolver
...@@ -18,6 +19,9 @@ def add_to_repo(repository, name, version, deps=None, python=None): ...@@ -18,6 +19,9 @@ def add_to_repo(repository, name, version, deps=None, python=None):
def check_solver_result( def check_solver_result(
root, provider, result=None, error=None, tries=None, locked=None, use_latest=None root, provider, result=None, error=None, tries=None, locked=None, use_latest=None
): ):
if locked is not None:
locked = {k: DependencyPackage(l.to_dependency(), l) for k, l in locked.items()}
solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest) solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest)
try: try:
......
...@@ -785,7 +785,7 @@ def test_solver_duplicate_dependencies_same_constraint(solver, repo, package): ...@@ -785,7 +785,7 @@ def test_solver_duplicate_dependencies_same_constraint(solver, repo, package):
) )
op = ops[0] op = ops[0]
assert op.package.requirements == {"python": ">=2.7,<2.8 || >=3.4"} assert op.package.requirements == {"python": "2.7 || >=3.4"}
def test_solver_duplicate_dependencies_different_constraints(solver, repo, package): def test_solver_duplicate_dependencies_different_constraints(solver, repo, package):
...@@ -962,3 +962,19 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package): ...@@ -962,3 +962,19 @@ def test_solver_can_resolve_git_dependencies_with_extras(solver, repo, package):
{"job": "install", "package": get_package("demo", "0.1.2")}, {"job": "install", "package": get_package("demo", "0.1.2")},
], ],
) )
def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible(
solver, repo, package
):
package.python_versions = "~2.7 || ^3.6"
package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
package_a = get_package("A", "1.0.0")
package_a.python_versions = ">=3.6"
repo.add_package(package_a)
ops = solver.solve()
check_solver_result(ops, [{"job": "install", "package": package_a}])
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