Commit b7da0867 by Sébastien Eustace

Fix dependency resolution for children of conditional dependencies

parent c2516250
...@@ -14,7 +14,7 @@ class Term(object): ...@@ -14,7 +14,7 @@ class Term(object):
See https://github.com/dart-lang/pub/tree/master/doc/solver.md#term. See https://github.com/dart-lang/pub/tree/master/doc/solver.md#term.
""" """
def __init__(self, dependency, is_positive): # type: Dependency # type: bool def __init__(self, dependency, is_positive): # type: (Dependency, bool) -> None
self._dependency = dependency self._dependency = dependency
self._positive = is_positive self._positive = is_positive
...@@ -158,7 +158,10 @@ class Term(object): ...@@ -158,7 +158,10 @@ class Term(object):
if constraint.is_empty(): if constraint.is_empty():
return return
return Term(Dependency(self.dependency.name, constraint), is_positive) dep = Dependency(self.dependency.name, constraint)
dep.python_versions = str(self.dependency.python_versions)
return Term(dep, is_positive)
def __str__(self): def __str__(self):
return "{}{}".format("not " if not self.is_positive() else "", self._dependency) return "{}{}".format("not " if not self.is_positive() else "", self._dependency)
......
...@@ -10,7 +10,6 @@ from poetry.version.markers import AnyMarker ...@@ -10,7 +10,6 @@ from poetry.version.markers import AnyMarker
from poetry.version.markers import parse_marker from poetry.version.markers import parse_marker
from .constraints import parse_constraint as parse_generic_constraint from .constraints import parse_constraint as parse_generic_constraint
from .constraints.any_constraint import AnyConstraint
from .constraints.constraint import Constraint from .constraints.constraint import Constraint
from .constraints.multi_constraint import MultiConstraint from .constraints.multi_constraint import MultiConstraint
from .constraints.union_constraint import UnionConstraint from .constraints.union_constraint import UnionConstraint
...@@ -49,6 +48,8 @@ class Dependency(object): ...@@ -49,6 +48,8 @@ class Dependency(object):
self._python_versions = "*" self._python_versions = "*"
self._python_constraint = parse_constraint("*") self._python_constraint = parse_constraint("*")
self._transitive_python_versions = None
self._transitive_python_constraint = None
self._extras = [] self._extras = []
self._in_extras = [] self._in_extras = []
...@@ -96,10 +97,29 @@ class Dependency(object): ...@@ -96,10 +97,29 @@ class Dependency(object):
) )
@property @property
def transitive_python_versions(self):
if self._transitive_python_versions is None:
return self._python_versions
return self._transitive_python_versions
@transitive_python_versions.setter
def transitive_python_versions(self, value):
self._transitive_python_versions = value
self._transitive_python_constraint = parse_constraint(value)
@property
def python_constraint(self): def python_constraint(self):
return self._python_constraint return self._python_constraint
@property @property
def transitive_python_constraint(self):
if self._transitive_python_constraint is None:
return self._python_constraint
return self._transitive_python_constraint
@property
def extras(self): # type: () -> list def extras(self): # type: () -> list
return self._extras return self._extras
......
...@@ -317,7 +317,7 @@ class Provider: ...@@ -317,7 +317,7 @@ class Provider:
def incompatibilities_for( def incompatibilities_for(
self, package self, package
): # type: (Package) -> List[Incompatibility] ): # type: (DependencyPackage) -> List[Incompatibility]
""" """
Returns incompatibilities that encapsulate a given package's dependencies, Returns incompatibilities that encapsulate a given package's dependencies,
or that it can't be safely selected. or that it can't be safely selected.
...@@ -335,14 +335,19 @@ class Provider: ...@@ -335,14 +335,19 @@ class Provider:
if not package.python_constraint.allows_all( if not package.python_constraint.allows_all(
self._package.python_constraint self._package.python_constraint
): ):
if ( intersection = package.python_constraint.intersect(
package.dependency.python_constraint.is_any() package.dependency.transitive_python_constraint
or not self._package.python_constraint.allows_all(
package.dependency.python_constraint
) )
or not package.python_constraint.allows_all( difference = package.dependency.transitive_python_constraint.difference(
package.dependency.python_constraint intersection
) )
if (
package.dependency.transitive_python_constraint.is_any()
or self._package.python_constraint.intersect(
package.dependency.python_constraint
).is_empty()
or intersection.is_empty()
or not difference.is_empty()
): ):
return [ return [
Incompatibility( Incompatibility(
...@@ -368,7 +373,9 @@ class Provider: ...@@ -368,7 +373,9 @@ class Provider:
for dep in dependencies for dep in dependencies
] ]
def complete_package(self, package): # type: (str, Version) -> Package def complete_package(
self, package
): # type: (DependencyPackage) -> DependencyPackage
if package.is_root(): if package.is_root():
return package return package
...@@ -518,6 +525,14 @@ class Provider: ...@@ -518,6 +525,14 @@ class Provider:
) )
raise CompatibilityError(*python_constraints) raise CompatibilityError(*python_constraints)
if not package.dependency.python_constraint.is_any():
for dep in dependencies:
dep.transitive_python_versions = str(
dep.python_constraint.intersect(
package.dependency.python_constraint
)
)
package.requires = dependencies package.requires = dependencies
return package return package
......
...@@ -432,9 +432,6 @@ class MultiMarker(BaseMarker): ...@@ -432,9 +432,6 @@ class MultiMarker(BaseMarker):
if other.is_empty(): if other.is_empty():
return other return other
if other in self._markers:
return other
new_markers = self._markers + [other] new_markers = self._markers + [other]
return MultiMarker.of(*new_markers) return MultiMarker.of(*new_markers)
......
...@@ -962,6 +962,39 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir ...@@ -962,6 +962,39 @@ def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requir
) )
def test_solver_does_not_trigger_conflict_for_python_constraint_if_python_requirement_is_compatible_multiple(
solver, repo, package
):
package.python_versions = "~2.7 || ^3.4"
package.add_dependency("A", {"version": "^1.0", "python": "^3.6"})
package.add_dependency("B", {"version": "^1.0", "python": "^3.5.3"})
package_a = get_package("A", "1.0.0")
package_a.python_versions = ">=3.6"
package_a.add_dependency("B", "^1.0")
package_b = get_package("B", "1.0.0")
package_b.python_versions = ">=3.5.3"
repo.add_package(package_a)
repo.add_package(package_b)
ops = solver.solve()
check_solver_result(
ops,
[
{"job": "install", "package": package_b},
{"job": "install", "package": package_a},
],
)
assert str(ops[0].package.marker) == (
'python_version >= "3.6" and python_version < "4.0" '
'or python_version >= "3.5.3" and python_version < "4.0.0"'
)
def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_with_package_python( def test_solver_triggers_conflict_for_dependency_python_not_fully_compatible_with_package_python(
solver, repo, package solver, repo, package
): ):
......
...@@ -70,6 +70,15 @@ def test_single_marker_intersect_with_multi(): ...@@ -70,6 +70,15 @@ def test_single_marker_intersect_with_multi():
) )
def test_single_marker_intersect_with_multi_with_duplicate():
m = parse_marker('python_version < "4.0"')
intersection = m.intersect(
parse_marker('sys_platform == "darwin" and python_version < "4.0"')
)
assert str(intersection) == 'sys_platform == "darwin" and python_version < "4.0"'
def test_single_marker_intersect_with_multi_compacts_constraint(): def test_single_marker_intersect_with_multi_compacts_constraint():
m = parse_marker('python_version < "3.6"') m = parse_marker('python_version < "3.6"')
......
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