Commit 789fcc16 by Sébastien Eustace

Change the dependency installation order

parent 04d5bdd9
...@@ -6,6 +6,10 @@ ...@@ -6,6 +6,10 @@
- Added support for `packages`, `include` and `exclude` properties. - Added support for `packages`, `include` and `exclude` properties.
### Changed
- Changed the dependency installation order, deepest dependencies are now installed first.
### Fixed ### Fixed
- Fixed handling of duplicate dependencies with different constraints. - Fixed handling of duplicate dependencies with different constraints.
......
...@@ -28,7 +28,7 @@ class Solver: ...@@ -28,7 +28,7 @@ class Solver:
def solve(self, use_latest=None): # type: (...) -> List[Operation] def solve(self, use_latest=None): # type: (...) -> List[Operation]
with self._provider.progress(): with self._provider.progress():
packages = self._solve(use_latest=use_latest) packages, depths = self._solve(use_latest=use_latest)
requested = self._package.all_requires requested = self._package.all_requires
...@@ -70,14 +70,14 @@ class Solver: ...@@ -70,14 +70,14 @@ class Solver:
operations.append(op) operations.append(op)
requested_names = [r.name for r in self._package.all_requires]
return sorted( return sorted(
operations, operations,
key=lambda o: ( key=lambda o: (
1 if o.package.name in requested_names else 0, o.job_type == "uninstall",
# Packages to be uninstalled have no depth so we default to 0
# since it actually doesn't matter since removals are always on top.
-depths[packages.index(o.package)] if o.job_type != "uninstall" else 0,
o.package.name, o.package.name,
o.package.version,
), ),
) )
...@@ -87,6 +87,7 @@ class Solver: ...@@ -87,6 +87,7 @@ class Solver:
locked[package.name] = package locked[package.name] = package
packages = [] packages = []
depths = []
for constraint in constraints: for constraint in constraints:
constraint = parse_constraint(constraint) constraint = parse_constraint(constraint)
intersection = constraint.intersect(self._package.python_constraint) intersection = constraint.intersect(self._package.python_constraint)
...@@ -96,9 +97,11 @@ class Solver: ...@@ -96,9 +97,11 @@ class Solver:
"for Python ({}).</comment>".format(intersection) "for Python ({}).</comment>".format(intersection)
) )
with self._package.with_python_versions(str(intersection)): with self._package.with_python_versions(str(intersection)):
for package in self._solve(use_latest=use_latest): _packages, _depths = self._solve(use_latest=use_latest)
for index, package in enumerate(_packages):
if package not in packages: if package not in packages:
packages.append(package) packages.append(package)
depths.append(_depths[index])
continue continue
current_package = packages[packages.index(package)] current_package = packages[packages.index(package)]
...@@ -106,7 +109,7 @@ class Solver: ...@@ -106,7 +109,7 @@ class Solver:
if dep not in current_package.requires: if dep not in current_package.requires:
current_package.requires.append(dep) current_package.requires.append(dep)
return list(set(packages)) return packages, depths
def _solve(self, use_latest=None): def _solve(self, use_latest=None):
locked = {} locked = {}
...@@ -126,13 +129,14 @@ class Solver: ...@@ -126,13 +129,14 @@ class Solver:
except SolveFailure as e: except SolveFailure as e:
raise SolverProblemError(e) raise SolverProblemError(e)
requested = self._package.all_requires
graph = self._build_graph(self._package, packages) graph = self._build_graph(self._package, packages)
depths = []
for package in packages: for package in packages:
category, optional, python, platform = self._get_tags_for_package( category, optional, python, platform, depth = self._get_tags_for_package(
package, graph package, graph
) )
depths.append(depth)
package.category = category package.category = category
package.optional = optional package.optional = optional
...@@ -147,7 +151,7 @@ class Solver: ...@@ -147,7 +151,7 @@ class Solver:
package.requirements = requirements package.requirements = requirements
return packages return packages, depths
def _build_graph( def _build_graph(
self, package, packages, previous=None, previous_dep=None, dep=None self, package, packages, previous=None, previous_dep=None, dep=None
...@@ -188,13 +192,15 @@ class Solver: ...@@ -188,13 +192,15 @@ class Solver:
for dependency in package.all_requires: for dependency in package.all_requires:
if dependency.is_optional(): if dependency.is_optional():
if not package.is_root() and (not dep or not dep.extras): if not package.is_root() and (
not previous_dep or not previous_dep.extras
):
continue continue
is_activated = False is_activated = False
for group, extras in package.extras.items(): for group, extras in package.extras.items():
if dep: if dep:
extras = dep.extras extras = previous_dep.extras
elif package.is_root(): elif package.is_root():
extras = package.extras extras = package.extras
else: else:
...@@ -213,7 +219,10 @@ class Solver: ...@@ -213,7 +219,10 @@ class Solver:
# we merge the requirements # we merge the requirements
existing = None existing = None
for child in graph["children"]: for child in graph["children"]:
if child["name"] == pkg.name: if (
child["name"] == pkg.name
and child["category"] == dependency.category
):
existing = child existing = child
continue continue
...@@ -233,26 +242,32 @@ class Solver: ...@@ -233,26 +242,32 @@ class Solver:
return graph return graph
def _get_tags_for_package(self, package, graph): def _get_tags_for_package(self, package, graph, depth=0):
categories = ["dev"] categories = ["dev"]
optionals = [True] optionals = [True]
python_versions = [] python_versions = []
platforms = [] platforms = []
_depths = [0]
children = graph["children"] children = graph["children"]
found = False
for child in children: for child in children:
if child["name"] == package.name: if child["name"] == package.name:
category = child["category"] category = child["category"]
optional = child["optional"] optional = child["optional"]
python_version = child["python_version"] python_version = child["python_version"]
platform = child["platform"] platform = child["platform"]
_depths.append(depth)
else: else:
( (
category, category,
optional, optional,
python_version, python_version,
platform, platform,
) = self._get_tags_for_package(package, child) _depth,
) = self._get_tags_for_package(package, child, depth=depth + 1)
_depths.append(_depth)
categories.append(category) categories.append(category)
optionals.append(optional) optionals.append(optional)
...@@ -300,4 +315,6 @@ class Solver: ...@@ -300,4 +315,6 @@ class Solver:
elif current.matches(previous): elif current.matches(previous):
platform = constraint platform = constraint
return category, optional, python_version, platform depth = max(*(_depths + [0]))
return category, optional, python_version, platform, depth
...@@ -846,8 +846,8 @@ def test_run_install_duplicate_dependencies_different_constraints( ...@@ -846,8 +846,8 @@ def test_run_install_duplicate_dependencies_different_constraints(
installs = installer.installer.installs installs = installer.installer.installs
assert len(installs) == 3 assert len(installs) == 3
assert installs[0] == package_b10 assert installs[0] == package_c12
assert installs[1] == package_c12 assert installs[1] == package_b10
assert installs[2] == package_a assert installs[2] == package_a
updates = installer.installer.updates updates = installer.installer.updates
...@@ -1013,8 +1013,6 @@ def test_run_update_uninstalls_after_removal_transient_dependency( ...@@ -1013,8 +1013,6 @@ def test_run_update_uninstalls_after_removal_transient_dependency(
installer.update(True) installer.update(True)
installer.run() installer.run()
print(locker.written_data)
installs = installer.installer.installs installs = installer.installer.installs
assert len(installs) == 0 assert len(installs) == 0
updates = installer.installer.updates updates = installer.installer.updates
......
...@@ -194,8 +194,8 @@ def test_install_with_deps_in_order(solver, repo, package): ...@@ -194,8 +194,8 @@ def test_install_with_deps_in_order(solver, repo, package):
ops, ops,
[ [
{"job": "install", "package": package_a}, {"job": "install", "package": package_a},
{"job": "install", "package": package_b},
{"job": "install", "package": package_c}, {"job": "install", "package": package_c},
{"job": "install", "package": package_b},
], ],
) )
...@@ -342,6 +342,7 @@ def test_solver_fails_if_mismatch_root_python_versions(solver, repo, package): ...@@ -342,6 +342,7 @@ def test_solver_fails_if_mismatch_root_python_versions(solver, repo, package):
def test_solver_solves_optional_and_compatible_packages(solver, repo, package): def test_solver_solves_optional_and_compatible_packages(solver, repo, package):
package.python_versions = "^3.4" package.python_versions = "^3.4"
package.extras["foo"] = [get_dependency("B")]
package.add_dependency("A", {"version": "*", "python": "~3.5"}) package.add_dependency("A", {"version": "*", "python": "~3.5"})
package.add_dependency("B", {"version": "*", "optional": True}) package.add_dependency("B", {"version": "*", "optional": True})
...@@ -528,8 +529,8 @@ def test_solver_sub_dependencies_with_requirements(solver, repo, package): ...@@ -528,8 +529,8 @@ def test_solver_sub_dependencies_with_requirements(solver, repo, package):
check_solver_result( check_solver_result(
ops, ops,
[ [
{"job": "install", "package": package_c},
{"job": "install", "package": package_d}, {"job": "install", "package": package_d},
{"job": "install", "package": package_c},
{"job": "install", "package": package_a}, {"job": "install", "package": package_a},
{"job": "install", "package": package_b}, {"job": "install", "package": package_b},
], ],
...@@ -570,25 +571,25 @@ def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package ...@@ -570,25 +571,25 @@ def test_solver_sub_dependencies_with_requirements_complex(solver, repo, package
check_solver_result( check_solver_result(
ops, ops,
[ [
{"job": "install", "package": package_d},
{"job": "install", "package": package_e}, {"job": "install", "package": package_e},
{"job": "install", "package": package_f}, {"job": "install", "package": package_f},
{"job": "install", "package": package_a},
{"job": "install", "package": package_b}, {"job": "install", "package": package_b},
{"job": "install", "package": package_d},
{"job": "install", "package": package_a},
{"job": "install", "package": package_c}, {"job": "install", "package": package_c},
], ],
) )
op = ops[0] op = ops[3] # d
assert op.package.requirements == {"python": "<4.0"} assert op.package.requirements == {"python": "<4.0"}
op = ops[1] op = ops[0] # e
assert op.package.requirements == {"platform": "win32", "python": "<5.0"} assert op.package.requirements == {"platform": "win32", "python": "<5.0"}
op = ops[2] op = ops[1] # f
assert op.package.requirements == {"python": "<5.0"} assert op.package.requirements == {"python": "<5.0"}
op = ops[4] op = ops[4] # a
assert op.package.requirements == {"python": "<5.0"} assert op.package.requirements == {"python": "<5.0"}
...@@ -641,16 +642,16 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies( ...@@ -641,16 +642,16 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies(
check_solver_result( check_solver_result(
ops, ops,
[ [
{"job": "install", "package": package_d},
{"job": "install", "package": package_b}, {"job": "install", "package": package_b},
{"job": "install", "package": package_c}, {"job": "install", "package": package_c},
{"job": "install", "package": package_d},
{"job": "install", "package": package_a}, {"job": "install", "package": package_a},
], ],
) )
b = ops[0].package b = ops[1].package
c = ops[1].package c = ops[2].package
d = ops[2].package d = ops[0].package
a = ops[3].package a = ops[3].package
assert d.category == "dev" assert d.category == "dev"
...@@ -693,17 +694,17 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_ ...@@ -693,17 +694,17 @@ def test_solver_with_dependency_in_both_main_and_dev_dependencies_with_one_more_
ops, ops,
[ [
{"job": "install", "package": package_b}, {"job": "install", "package": package_b},
{"job": "install", "package": package_c},
{"job": "install", "package": package_d}, {"job": "install", "package": package_d},
{"job": "install", "package": package_a}, {"job": "install", "package": package_a},
{"job": "install", "package": package_c},
{"job": "install", "package": package_e}, {"job": "install", "package": package_e},
], ],
) )
b = ops[0].package b = ops[0].package
c = ops[1].package c = ops[3].package
d = ops[2].package d = ops[1].package
a = ops[3].package a = ops[2].package
e = ops[4].package e = ops[4].package
assert d.category == "dev" assert d.category == "dev"
...@@ -872,16 +873,16 @@ def test_solver_duplicate_dependencies_sub_dependencies(solver, repo, package): ...@@ -872,16 +873,16 @@ def test_solver_duplicate_dependencies_sub_dependencies(solver, repo, package):
check_solver_result( check_solver_result(
ops, ops,
[ [
{"job": "install", "package": package_b10},
{"job": "install", "package": package_b20},
{"job": "install", "package": package_c12}, {"job": "install", "package": package_c12},
{"job": "install", "package": package_c15}, {"job": "install", "package": package_c15},
{"job": "install", "package": package_b10},
{"job": "install", "package": package_b20},
{"job": "install", "package": package_a}, {"job": "install", "package": package_a},
], ],
) )
op = ops[0] op = ops[2]
assert op.package.requirements == {"python": "<3.4"} assert op.package.requirements == {"python": "<3.4"}
op = ops[1] op = ops[3]
assert op.package.requirements == {"python": ">=3.4"} assert op.package.requirements == {"python": ">=3.4"}
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