Commit 58e3cc23 by Sébastien Eustace

Fix dependency overriding in dev-dependencies

parent 47a083ca
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
### Fixed ### Fixed
- Fixed the dependency resolver selecting incompatible packages. - Fixed the dependency resolver selecting incompatible packages.
- Fixed override of dependency with dependency with extras in `dev-dependencies`.
## [0.9.1] - 2018-05-18 ## [0.9.1] - 2018-05-18
......
...@@ -330,6 +330,7 @@ class Provider: ...@@ -330,6 +330,7 @@ class Provider:
package.python_versions = complete_package.python_versions package.python_versions = complete_package.python_versions
package.platform = complete_package.platform package.platform = complete_package.platform
package.hashes = complete_package.hashes package.hashes = complete_package.hashes
package.extras = complete_package.extras
return [ return [
r for r in package.requires r for r in package.requires
......
...@@ -107,14 +107,22 @@ class Solver: ...@@ -107,14 +107,22 @@ class Solver:
) )
) )
def _get_tags_for_package(self, package, packages, requested, original=None): def _get_graph_for_package(self, package, packages, requested, original=None):
category = 'dev' graph = {
optional = True package.name: {
'category': 'dev',
root = None 'optional': True,
'python_version': None,
'platform': None,
'dependencies': {},
'parents': {},
},
}
roots = []
for dep in requested: for dep in requested:
if dep.name == package.name: if dep.name == package.name:
root = dep roots.append(dep)
origins = [] origins = []
for pkg in packages: for pkg in packages:
...@@ -126,63 +134,129 @@ class Solver: ...@@ -126,63 +134,129 @@ class Solver:
if dep.name == package.name: if dep.name == package.name:
origins.append((pkg, dep)) origins.append((pkg, dep))
if root is not None and not origins: if roots and (not origins or len(roots) > 1):
# Original dependency # Root dependency
if len(roots) == 1:
root = roots[0]
else:
root1 = [r for r in roots if r.category == 'main'][0]
root2 = [r for r in roots if r.category == 'dev'][0]
if root1.extras == root2.extras or original is None:
root = root1
else:
root1_extra_dependencies = []
for extra in root1.extras:
if extra in package.extras:
for dep in package.extras[extra]:
root1_extra_dependencies.append(dep.name)
root2_extra_dependencies = []
for extra in root2.extras:
if extra in package.extras:
for dep in package.extras[extra]:
root2_extra_dependencies.append(dep.name)
if (
original.name in root1_extra_dependencies
and original.name in root2_extra_dependencies
):
root = root1
elif original.name in root2_extra_dependencies:
root = root2
else:
root = root1
category = root.category category = root.category
optional = root.is_optional() optional = root.is_optional()
python_version = str(root.python_constraint) python_version = str(root.python_constraint)
platform = str(root.platform_constraint) platform = str(root.platform_constraint)
return category, optional, python_version, platform graph[package.name]['category'] = category
graph[package.name]['optional'] = optional
graph[package.name]['python_version'] = python_version
graph[package.name]['platform'] = platform
python_versions = [] return graph
platforms = []
for pkg, dep in origins: for pkg, dep in origins:
python_version = dep.python_versions graph[package.name]['dependencies'][pkg.name] = {
platform = dep.platform 'constraint': dep.pretty_constraint,
'python_version': dep.python_versions,
(top_category, 'platform': dep.platform,
top_optional, }
top_python_version, graph[package.name]['parents'].update(
top_platform) = self._get_tags_for_package( self._get_graph_for_package(
pkg, packages, requested, original=package pkg, packages, requested, original=package
)
) )
if top_category == 'main': return graph
category = top_category
optional = optional and top_optional def _get_tags_for_package(self, package, packages, requested):
graph = self._get_graph_for_package(package, packages, requested)[package.name]
# Take the most restrictive constraints return self._get_tags_from_graph(graph, packages)
if top_python_version is not None:
if python_version is not None:
previous = parse_constraint(python_version)
current = parse_constraint(top_python_version)
if previous.allows_all(current): def _get_tags_from_graph(self, graph, packages):
python_versions.append(top_python_version) category = graph['category']
else: optional = graph['optional']
python_versions.append(python_version) python_version = graph['python_version']
else: platform = graph['platform']
python_versions.append(top_python_version)
elif python_version is not None:
python_versions.append(python_version)
if top_platform is not None: if not graph['parents']:
if platform is not None: # Root dependency
previous = GenericConstraint.parse(platform) return category, optional, python_version, platform
current = GenericConstraint.parse(top_platform)
if top_platform != '*' and previous.matches(current): python_versions = []
platforms.append(top_platform) platforms = []
else:
platforms.append(platform) for parent_name, parent_graph in graph['parents'].items():
else: dep_python_version = graph['dependencies'][parent_name]['python_version']
platforms.append(top_platform) dep_platform = graph['dependencies'][parent_name]['platform']
elif platform is not None:
platforms.append(platform) for pkg in packages:
if pkg.name == parent_name:
(top_category,
top_optional,
top_python_version,
top_platform) = self._get_tags_from_graph(parent_graph, packages)
if category is None or category != 'main':
category = top_category
optional = optional and top_optional
# Take the most restrictive constraints
if top_python_version is not None:
if dep_python_version is not None:
previous = parse_constraint(dep_python_version)
current = parse_constraint(top_python_version)
if previous.allows_all(current):
python_versions.append(top_python_version)
else:
python_versions.append(dep_python_version)
else:
python_versions.append(top_python_version)
elif dep_python_version is not None:
python_versions.append(dep_python_version)
if top_platform is not None:
if dep_platform is not None:
previous = GenericConstraint.parse(dep_platform)
current = GenericConstraint.parse(top_platform)
if top_platform != '*' and previous.matches(current):
platforms.append(top_platform)
else:
platforms.append(dep_platform)
else:
platforms.append(top_platform)
elif dep_platform is not None:
platforms.append(dep_platform)
break
if not python_versions: if not python_versions:
python_version = None python_version = None
......
...@@ -574,3 +574,94 @@ def test_solver_sub_dependencies_with_not_supported_python_version(solver, repo, ...@@ -574,3 +574,94 @@ def test_solver_sub_dependencies_with_not_supported_python_version(solver, repo,
check_solver_result(ops, [ check_solver_result(ops, [
{'job': 'install', 'package': package_a}, {'job': 'install', 'package': package_a},
]) ])
def test_solver_with_dependency_in_both_main_and_dev_dependencies(solver, repo, package):
package.python_versions = '^3.5'
package.add_dependency('A')
package.add_dependency('A', {'version': '*', 'extras': ['foo']}, category='dev')
package_a = get_package('A', '1.0')
package_a.extras['foo'] = [get_dependency('C')]
package_a.add_dependency('C', {'version': '^1.0', 'optional': True})
package_a.add_dependency('B', {'version': '^1.0'})
package_b = get_package('B', '1.0')
package_c = get_package('C', '1.0')
package_c.add_dependency('D', '^1.0')
package_d = get_package('D', '1.0')
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
ops = solver.solve()
check_solver_result(ops, [
{'job': 'install', 'package': package_b},
{'job': 'install', 'package': package_c},
{'job': 'install', 'package': package_d},
{'job': 'install', 'package': package_a},
])
b = ops[0].package
c = ops[1].package
d = ops[2].package
a = ops[3].package
assert d.category == 'dev'
assert c.category == 'dev'
assert b.category == 'main'
assert a.category == 'main'
def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_dependent(solver, repo, package):
package.add_dependency('A')
package.add_dependency('E')
package.add_dependency('A', {'version': '*', 'extras': ['foo']}, category='dev')
package_a = get_package('A', '1.0')
package_a.extras['foo'] = [get_dependency('C')]
package_a.add_dependency('C', {'version': '^1.0', 'optional': True})
package_a.add_dependency('B', {'version': '^1.0'})
package_b = get_package('B', '1.0')
package_c = get_package('C', '1.0')
package_c.add_dependency('D', '^1.0')
package_d = get_package('D', '1.0')
package_e = get_package('E', '1.0')
package_e.add_dependency('A', '^1.0')
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
repo.add_package(package_e)
ops = solver.solve()
check_solver_result(ops, [
{'job': 'install', 'package': package_b},
{'job': 'install', 'package': package_c},
{'job': 'install', 'package': package_d},
{'job': 'install', 'package': package_a},
{'job': 'install', 'package': package_e},
])
b = ops[0].package
c = ops[1].package
d = ops[2].package
a = ops[3].package
e = ops[4].package
assert d.category == 'dev'
assert c.category == 'dev'
assert b.category == 'main'
assert a.category == 'main'
assert e.category == 'main'
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