Skip to content

Commit 83cb225

Browse files
authored
Merge pull request #4925 from pradyunsg/resolver/move-dependency-info-and-install-order
Move dependency info and install order into Resolver
2 parents 6694359 + 6caca65 commit 83cb225

File tree

4 files changed

+117
-96
lines changed

4 files changed

+117
-96
lines changed

src/pip/_internal/commands/install.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
)
1616
from pip._internal.locations import distutils_scheme, virtualenv_no_global
1717
from pip._internal.operations.prepare import RequirementPreparer
18-
from pip._internal.req import RequirementSet
18+
from pip._internal.req import RequirementSet, install_given_reqs
1919
from pip._internal.resolve import Resolver
2020
from pip._internal.status_codes import ERROR
2121
from pip._internal.utils.filesystem import check_path_owner
@@ -300,7 +300,11 @@ def run(self, options, args):
300300
session=session, autobuilding=True
301301
)
302302

303-
installed = requirement_set.install(
303+
to_install = resolver.get_installation_order(
304+
requirement_set
305+
)
306+
installed = install_given_reqs(
307+
to_install,
304308
install_options,
305309
global_options,
306310
root=options.root_path,

src/pip/_internal/req/__init__.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,69 @@
11
from __future__ import absolute_import
22

3+
import logging
4+
35
from .req_install import InstallRequirement
46
from .req_set import RequirementSet
57
from .req_file import parse_requirements
8+
from pip._internal.utils.logging import indent_log
9+
610

711
__all__ = [
812
"RequirementSet", "InstallRequirement",
9-
"parse_requirements",
13+
"parse_requirements", "install_given_reqs",
1014
]
15+
16+
logger = logging.getLogger(__name__)
17+
18+
19+
def install_given_reqs(to_install, install_options, global_options=(),
20+
*args, **kwargs):
21+
"""
22+
Install everything in the given list.
23+
24+
(to be called after having downloaded and unpacked the packages)
25+
"""
26+
27+
if to_install:
28+
logger.info(
29+
'Installing collected packages: %s',
30+
', '.join([req.name for req in to_install]),
31+
)
32+
33+
with indent_log():
34+
for requirement in to_install:
35+
if requirement.conflicts_with:
36+
logger.info(
37+
'Found existing installation: %s',
38+
requirement.conflicts_with,
39+
)
40+
with indent_log():
41+
uninstalled_pathset = requirement.uninstall(
42+
auto_confirm=True
43+
)
44+
try:
45+
requirement.install(
46+
install_options,
47+
global_options,
48+
*args,
49+
**kwargs
50+
)
51+
except:
52+
should_rollback = (
53+
requirement.conflicts_with and
54+
not requirement.install_succeeded
55+
)
56+
# if install did not succeed, rollback previous uninstall
57+
if should_rollback:
58+
uninstalled_pathset.rollback()
59+
raise
60+
else:
61+
should_commit = (
62+
requirement.conflicts_with and
63+
requirement.install_succeeded
64+
)
65+
if should_commit:
66+
uninstalled_pathset.commit()
67+
requirement.remove_temporary_source()
68+
69+
return to_install

src/pip/_internal/req/req_set.py

Lines changed: 7 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import absolute_import
22

33
import logging
4-
from collections import OrderedDict, defaultdict
4+
from collections import OrderedDict
55

66
from pip._internal.exceptions import InstallationError
77
from pip._internal.utils.logging import indent_log
@@ -32,8 +32,6 @@ def __init__(self,
3232
self.use_user_site = use_user_site
3333
self.target_dir = target_dir # set from --target option
3434
self.pycompile = pycompile
35-
# Maps from install_req -> dependencies_of_install_req
36-
self._dependencies = defaultdict(list)
3735

3836
def __str__(self):
3937
reqs = [req for req in self.requirements.values()
@@ -69,7 +67,7 @@ def add_requirement(self, install_req, parent_req_name=None,
6967
logger.info("Ignoring %s: markers '%s' don't match your "
7068
"environment", install_req.name,
7169
install_req.markers)
72-
return []
70+
return [], None
7371

7472
# This check has to come after we filter requirements with the
7573
# environment markers.
@@ -89,7 +87,7 @@ def add_requirement(self, install_req, parent_req_name=None,
8987
if not name:
9088
# url or path requirement w/o an egg fragment
9189
self.unnamed_requirements.append(install_req)
92-
return [install_req]
90+
return [install_req], None
9391
else:
9492
try:
9593
existing_req = self.get_requirement(name)
@@ -135,10 +133,10 @@ def add_requirement(self, install_req, parent_req_name=None,
135133
# Canonicalise to the already-added object for the backref
136134
# check below.
137135
install_req = existing_req
138-
if parent_req_name:
139-
parent_req = self.get_requirement(parent_req_name)
140-
self._dependencies[parent_req].append(install_req)
141-
return result
136+
137+
# We return install_req here to allow for the caller to add it to
138+
# the dependency information for the parent package.
139+
return result, install_req
142140

143141
def has_requirement(self, project_name):
144142
name = project_name.lower()
@@ -168,81 +166,3 @@ def cleanup_files(self):
168166
with indent_log():
169167
for req in self.reqs_to_cleanup:
170168
req.remove_temporary_source()
171-
172-
def _to_install(self):
173-
"""Create the installation order.
174-
175-
The installation order is topological - requirements are installed
176-
before the requiring thing. We break cycles at an arbitrary point,
177-
and make no other guarantees.
178-
"""
179-
# The current implementation, which we may change at any point
180-
# installs the user specified things in the order given, except when
181-
# dependencies must come earlier to achieve topological order.
182-
order = []
183-
ordered_reqs = set()
184-
185-
def schedule(req):
186-
if req.satisfied_by or req in ordered_reqs:
187-
return
188-
if req.constraint:
189-
return
190-
ordered_reqs.add(req)
191-
for dep in self._dependencies[req]:
192-
schedule(dep)
193-
order.append(req)
194-
195-
for install_req in self.requirements.values():
196-
schedule(install_req)
197-
return order
198-
199-
def install(self, install_options, global_options=(), *args, **kwargs):
200-
"""
201-
Install everything in this set (after having downloaded and unpacked
202-
the packages)
203-
"""
204-
to_install = self._to_install()
205-
206-
if to_install:
207-
logger.info(
208-
'Installing collected packages: %s',
209-
', '.join([req.name for req in to_install]),
210-
)
211-
212-
with indent_log():
213-
for requirement in to_install:
214-
if requirement.conflicts_with:
215-
logger.info(
216-
'Found existing installation: %s',
217-
requirement.conflicts_with,
218-
)
219-
with indent_log():
220-
uninstalled_pathset = requirement.uninstall(
221-
auto_confirm=True
222-
)
223-
try:
224-
requirement.install(
225-
install_options,
226-
global_options,
227-
*args,
228-
**kwargs
229-
)
230-
except:
231-
should_rollback = (
232-
requirement.conflicts_with and
233-
not requirement.install_succeeded
234-
)
235-
# if install did not succeed, rollback previous uninstall
236-
if should_rollback:
237-
uninstalled_pathset.rollback()
238-
raise
239-
else:
240-
should_commit = (
241-
requirement.conflicts_with and
242-
requirement.install_succeeded
243-
)
244-
if should_commit:
245-
uninstalled_pathset.commit()
246-
requirement.remove_temporary_source()
247-
248-
return to_install

src/pip/_internal/resolve.py

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
"""
1212

1313
import logging
14+
from collections import defaultdict
1415
from itertools import chain
1516

1617
from pip._internal.exceptions import (
1718
BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors,
1819
UnsupportedPythonVersion,
1920
)
21+
2022
from pip._internal.req.req_install import InstallRequirement
2123
from pip._internal.utils.logging import indent_log
2224
from pip._internal.utils.misc import dist_in_usersite, ensure_dir
@@ -56,6 +58,8 @@ def __init__(self, preparer, session, finder, wheel_cache, use_user_site,
5658
self.ignore_requires_python = ignore_requires_python
5759
self.use_user_site = use_user_site
5860

61+
self._discovered_dependencies = defaultdict(list)
62+
5963
def resolve(self, requirement_set):
6064
"""Resolve what operations need to be done
6165
@@ -273,19 +277,26 @@ def add_req(subreq, extras_requested):
273277
isolated=self.isolated,
274278
wheel_cache=self.wheel_cache,
275279
)
276-
more_reqs.extend(
277-
requirement_set.add_requirement(
278-
sub_install_req, req_to_install.name,
279-
extras_requested=extras_requested
280-
)
280+
parent_req_name = req_to_install.name
281+
to_scan_again, add_to_parent = requirement_set.add_requirement(
282+
sub_install_req,
283+
parent_req_name=parent_req_name,
284+
extras_requested=extras_requested,
281285
)
286+
if parent_req_name and add_to_parent:
287+
self._discovered_dependencies[parent_req_name].append(
288+
add_to_parent
289+
)
290+
more_reqs.extend(to_scan_again)
282291

283292
with indent_log():
284293
# We add req_to_install before its dependencies, so that we
285294
# can refer to it when adding dependencies.
286295
if not requirement_set.has_requirement(req_to_install.name):
287296
# 'unnamed' requirements will get added here
288-
requirement_set.add_requirement(req_to_install, None)
297+
requirement_set.add_requirement(
298+
req_to_install, parent_req_name=None,
299+
)
289300

290301
if not self.ignore_dependencies:
291302
if req_to_install.extras:
@@ -315,3 +326,30 @@ def add_req(subreq, extras_requested):
315326
requirement_set.successfully_downloaded.append(req_to_install)
316327

317328
return more_reqs
329+
330+
def get_installation_order(self, req_set):
331+
"""Create the installation order.
332+
333+
The installation order is topological - requirements are installed
334+
before the requiring thing. We break cycles at an arbitrary point,
335+
and make no other guarantees.
336+
"""
337+
# The current implementation, which we may change at any point
338+
# installs the user specified things in the order given, except when
339+
# dependencies must come earlier to achieve topological order.
340+
order = []
341+
ordered_reqs = set()
342+
343+
def schedule(req):
344+
if req.satisfied_by or req in ordered_reqs:
345+
return
346+
if req.constraint:
347+
return
348+
ordered_reqs.add(req)
349+
for dep in self._discovered_dependencies[req.name]:
350+
schedule(dep)
351+
order.append(req)
352+
353+
for install_req in req_set.requirements.values():
354+
schedule(install_req)
355+
return order

0 commit comments

Comments
 (0)