Skip to content

Commit 34d97cf

Browse files
authored
Merge pull request #7677 from chrahunt/refactor/remove-req-set-cleanup
Globally manage and track some temp build directories
2 parents 500b0dd + e800cb1 commit 34d97cf

File tree

8 files changed

+83
-31
lines changed

8 files changed

+83
-31
lines changed

src/pip/_internal/build_env.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from pip import __file__ as pip_location
1919
from pip._internal.utils.subprocess import call_subprocess
20-
from pip._internal.utils.temp_dir import TempDirectory
20+
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
2121
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2222
from pip._internal.utils.ui import open_spinner
2323

@@ -54,10 +54,12 @@ class BuildEnvironment(object):
5454

5555
def __init__(self):
5656
# type: () -> None
57-
self._temp_dir = TempDirectory(kind="build-env")
57+
temp_dir = TempDirectory(
58+
kind=tempdir_kinds.BUILD_ENV, globally_managed=True
59+
)
5860

5961
self._prefixes = OrderedDict((
60-
(name, _Prefix(os.path.join(self._temp_dir.path, name)))
62+
(name, _Prefix(os.path.join(temp_dir.path, name)))
6163
for name in ('normal', 'overlay')
6264
))
6365

@@ -76,7 +78,7 @@ def __init__(self):
7678
get_python_lib(plat_specific=True),
7779
)
7880
}
79-
self._site_dir = os.path.join(self._temp_dir.path, 'site')
81+
self._site_dir = os.path.join(temp_dir.path, 'site')
8082
if not os.path.exists(self._site_dir):
8183
os.mkdir(self._site_dir)
8284
with open(os.path.join(self._site_dir, 'sitecustomize.py'), 'w') as fp:
@@ -133,10 +135,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
133135
else:
134136
os.environ[varname] = old_value
135137

136-
def cleanup(self):
137-
# type: () -> None
138-
self._temp_dir.cleanup()
139-
140138
def check_requirements(self, reqs):
141139
# type: (Iterable[str]) -> Tuple[Set[Tuple[str, str]], Set[str]]
142140
"""Return 2 sets:

src/pip/_internal/cli/req_command.py

+47-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
import os
1010
from functools import partial
1111

12+
from pip._internal.cli import cmdoptions
1213
from pip._internal.cli.base_command import Command
1314
from pip._internal.cli.command_context import CommandContextMixIn
14-
from pip._internal.exceptions import CommandError
15+
from pip._internal.exceptions import CommandError, PreviousBuildDirError
1516
from pip._internal.index.package_finder import PackageFinder
1617
from pip._internal.legacy_resolve import Resolver
1718
from pip._internal.models.selection_prefs import SelectionPreferences
@@ -28,16 +29,22 @@
2829
make_link_collector,
2930
pip_self_version_check,
3031
)
32+
from pip._internal.utils.temp_dir import tempdir_kinds
3133
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
3234

3335
if MYPY_CHECK_RUNNING:
3436
from optparse import Values
35-
from typing import List, Optional, Tuple
37+
from typing import Any, List, Optional, Tuple
38+
3639
from pip._internal.cache import WheelCache
3740
from pip._internal.models.target_python import TargetPython
3841
from pip._internal.req.req_set import RequirementSet
3942
from pip._internal.req.req_tracker import RequirementTracker
40-
from pip._internal.utils.temp_dir import TempDirectory
43+
from pip._internal.utils.temp_dir import (
44+
TempDirectory,
45+
TempDirectoryTypeRegistry,
46+
)
47+
4148

4249
logger = logging.getLogger(__name__)
4350

@@ -149,8 +156,45 @@ def handle_pip_version_check(self, options):
149156
pip_self_version_check(session, options)
150157

151158

159+
KEEPABLE_TEMPDIR_TYPES = [tempdir_kinds.BUILD_ENV, tempdir_kinds.REQ_BUILD]
160+
161+
162+
def with_cleanup(func):
163+
# type: (Any) -> Any
164+
"""Decorator for common logic related to managing temporary
165+
directories.
166+
"""
167+
def configure_tempdir_registry(registry):
168+
# type: (TempDirectoryTypeRegistry) -> None
169+
for t in KEEPABLE_TEMPDIR_TYPES:
170+
registry.set_delete(t, False)
171+
172+
def wrapper(self, options, args):
173+
# type: (RequirementCommand, Values, List[Any]) -> Optional[int]
174+
assert self.tempdir_registry is not None
175+
if options.no_clean:
176+
configure_tempdir_registry(self.tempdir_registry)
177+
178+
try:
179+
return func(self, options, args)
180+
except PreviousBuildDirError:
181+
# This kind of conflict can occur when the user passes an explicit
182+
# build directory with a pre-existing folder. In that case we do
183+
# not want to accidentally remove it.
184+
configure_tempdir_registry(self.tempdir_registry)
185+
raise
186+
187+
return wrapper
188+
189+
152190
class RequirementCommand(IndexGroupCommand):
153191

192+
def __init__(self, *args, **kw):
193+
# type: (Any, Any) -> None
194+
super(RequirementCommand, self).__init__(*args, **kw)
195+
196+
self.cmd_opts.add_option(cmdoptions.no_clean())
197+
154198
@staticmethod
155199
def make_requirement_preparer(
156200
temp_build_dir, # type: TempDirectory

src/pip/_internal/commands/download.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from pip._internal.cli import cmdoptions
1010
from pip._internal.cli.cmdoptions import make_target_python
11-
from pip._internal.cli.req_command import RequirementCommand
11+
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
1212
from pip._internal.req import RequirementSet
1313
from pip._internal.req.req_tracker import get_requirement_tracker
1414
from pip._internal.utils.misc import ensure_dir, normalize_path, write_output
@@ -52,7 +52,6 @@ def __init__(self, *args, **kw):
5252
cmd_opts.add_option(cmdoptions.prefer_binary())
5353
cmd_opts.add_option(cmdoptions.src())
5454
cmd_opts.add_option(cmdoptions.pre())
55-
cmd_opts.add_option(cmdoptions.no_clean())
5655
cmd_opts.add_option(cmdoptions.require_hashes())
5756
cmd_opts.add_option(cmdoptions.progress_bar())
5857
cmd_opts.add_option(cmdoptions.no_build_isolation())
@@ -77,6 +76,7 @@ def __init__(self, *args, **kw):
7776
self.parser.insert_option_group(0, index_opts)
7877
self.parser.insert_option_group(0, cmd_opts)
7978

79+
@with_cleanup
8080
def run(self, options, args):
8181
options.ignore_installed = True
8282
# editable doesn't really make sense for `pip download`, but the bowels

src/pip/_internal/commands/install.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from pip._internal.cache import WheelCache
2222
from pip._internal.cli import cmdoptions
2323
from pip._internal.cli.cmdoptions import make_target_python
24-
from pip._internal.cli.req_command import RequirementCommand
24+
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
2525
from pip._internal.cli.status_codes import ERROR, SUCCESS
2626
from pip._internal.exceptions import (
2727
CommandError,
@@ -227,7 +227,6 @@ def __init__(self, *args, **kw):
227227
cmd_opts.add_option(cmdoptions.no_binary())
228228
cmd_opts.add_option(cmdoptions.only_binary())
229229
cmd_opts.add_option(cmdoptions.prefer_binary())
230-
cmd_opts.add_option(cmdoptions.no_clean())
231230
cmd_opts.add_option(cmdoptions.require_hashes())
232231
cmd_opts.add_option(cmdoptions.progress_bar())
233232

@@ -239,6 +238,7 @@ def __init__(self, *args, **kw):
239238
self.parser.insert_option_group(0, index_opts)
240239
self.parser.insert_option_group(0, cmd_opts)
241240

241+
@with_cleanup
242242
def run(self, options, args):
243243
# type: (Values, List[Any]) -> int
244244
cmdoptions.check_install_build_global(options)

src/pip/_internal/commands/wheel.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from pip._internal.cache import WheelCache
1313
from pip._internal.cli import cmdoptions
14-
from pip._internal.cli.req_command import RequirementCommand
14+
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
1515
from pip._internal.exceptions import CommandError, PreviousBuildDirError
1616
from pip._internal.req import RequirementSet
1717
from pip._internal.req.req_tracker import get_requirement_tracker
@@ -101,7 +101,6 @@ def __init__(self, *args, **kw):
101101
"pip only finds stable versions."),
102102
)
103103

104-
cmd_opts.add_option(cmdoptions.no_clean())
105104
cmd_opts.add_option(cmdoptions.require_hashes())
106105

107106
index_opts = cmdoptions.make_option_group(
@@ -112,6 +111,7 @@ def __init__(self, *args, **kw):
112111
self.parser.insert_option_group(0, index_opts)
113112
self.parser.insert_option_group(0, cmd_opts)
114113

114+
@with_cleanup
115115
def run(self, options, args):
116116
# type: (Values, List[Any]) -> None
117117
cmdoptions.check_install_build_global(options)

src/pip/_internal/req/req_install.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
rmtree,
5050
)
5151
from pip._internal.utils.packaging import get_metadata
52-
from pip._internal.utils.temp_dir import TempDirectory
52+
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
5353
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
5454
from pip._internal.utils.virtualenv import running_under_virtualenv
5555
from pip._internal.vcs import vcs
@@ -358,7 +358,9 @@ def ensure_build_location(self, build_dir):
358358
# Some systems have /tmp as a symlink which confuses custom
359359
# builds (such as numpy). Thus, we ensure that the real path
360360
# is returned.
361-
self._temp_build_dir = TempDirectory(kind="req-build")
361+
self._temp_build_dir = TempDirectory(
362+
kind=tempdir_kinds.REQ_BUILD, globally_managed=True
363+
)
362364

363365
return self._temp_build_dir.path
364366
if self.editable:
@@ -418,10 +420,7 @@ def remove_temporary_source(self):
418420
logger.debug('Removing source in %s', self.source_dir)
419421
rmtree(self.source_dir)
420422
self.source_dir = None
421-
if self._temp_build_dir:
422-
self._temp_build_dir.cleanup()
423-
self._temp_build_dir = None
424-
self.build_env.cleanup()
423+
self._temp_build_dir = None
425424

426425
def check_if_exists(self, use_user_site):
427426
# type: (bool) -> None

src/pip/_internal/utils/temp_dir.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from pip._vendor.contextlib2 import ExitStack
1111

12-
from pip._internal.utils.misc import rmtree
12+
from pip._internal.utils.misc import enum, rmtree
1313
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
1414

1515
if MYPY_CHECK_RUNNING:
@@ -21,6 +21,14 @@
2121
logger = logging.getLogger(__name__)
2222

2323

24+
# Kinds of temporary directories. Only needed for ones that are
25+
# globally-managed.
26+
tempdir_kinds = enum(
27+
BUILD_ENV="build-env",
28+
REQ_BUILD="req-build",
29+
)
30+
31+
2432
_tempdir_manager = None # type: Optional[ExitStack]
2533

2634

tests/unit/test_build_env.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def run_with_build_env(script, setup_script_contents,
2929
SelectionPreferences
3030
)
3131
from pip._internal.network.session import PipSession
32+
from pip._internal.utils.temp_dir import global_tempdir_manager
3233
3334
link_collector = LinkCollector(
3435
session=PipSession(),
@@ -41,19 +42,21 @@ def run_with_build_env(script, setup_script_contents,
4142
link_collector=link_collector,
4243
selection_prefs=selection_prefs,
4344
)
44-
build_env = BuildEnvironment()
4545
46-
try:
46+
with global_tempdir_manager():
47+
build_env = BuildEnvironment()
4748
''' % str(script.scratch_path)) +
4849
indent(dedent(setup_script_contents), ' ') +
49-
dedent(
50-
'''
50+
indent(
51+
dedent(
52+
'''
5153
if len(sys.argv) > 1:
5254
with build_env:
5355
subprocess.check_call((sys.executable, sys.argv[1]))
54-
finally:
55-
build_env.cleanup()
56-
''')
56+
'''
57+
),
58+
' '
59+
)
5760
)
5861
args = ['python', build_env_script]
5962
if test_script_contents is not None:

0 commit comments

Comments
 (0)