-
-
Notifications
You must be signed in to change notification settings - Fork 649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validate and maybe prune interpreter cache run over run #7225
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,18 +115,19 @@ def select_interpreter_for_targets(self, targets): | |
# Return the lowest compatible interpreter. | ||
return min(allowed_interpreters) | ||
|
||
def _interpreter_from_path(self, path, filters=()): | ||
try: | ||
executable = os.readlink(os.path.join(path, 'python')) | ||
if not os.path.exists(executable): | ||
if os.path.dirname(path) == self._cache_dir: | ||
def _interpreter_from_relpath(self, path, filters=()): | ||
path = os.path.join(self._cache_dir, path) | ||
if os.path.isdir(path): | ||
try: | ||
executable = os.readlink(os.path.join(path, 'python')) | ||
if not os.path.exists(executable): | ||
self._purge_interpreter(path) | ||
return None | ||
except OSError: | ||
return None | ||
except OSError: | ||
return None | ||
interpreter = PythonInterpreter.from_binary(executable, include_site_extras=False) | ||
if self._matches(interpreter, filters=filters): | ||
return self._resolve(interpreter) | ||
interpreter = PythonInterpreter.from_binary(executable, include_site_extras=False) | ||
if self._matches(interpreter, filters=filters): | ||
return self._resolve(interpreter) | ||
return None | ||
|
||
def _setup_interpreter(self, interpreter, cache_target_path): | ||
|
@@ -138,22 +139,20 @@ def _setup_interpreter(self, interpreter, cache_target_path): | |
def _setup_cached(self, filters=()): | ||
"""Find all currently-cached interpreters.""" | ||
for interpreter_dir in os.listdir(self._cache_dir): | ||
path = os.path.join(self._cache_dir, interpreter_dir) | ||
if os.path.isdir(path): | ||
pi = self._interpreter_from_path(path, filters=filters) | ||
if pi: | ||
logger.debug('Detected interpreter {}: {}'.format(pi.binary, str(pi.identity))) | ||
yield pi | ||
pi = self._interpreter_from_relpath(interpreter_dir, filters=filters) | ||
if pi: | ||
logger.debug('Detected interpreter {}: {}'.format(pi.binary, str(pi.identity))) | ||
yield pi | ||
|
||
def _setup_paths(self, paths, filters=()): | ||
"""Find interpreters under paths, and cache them.""" | ||
for interpreter in self._matching(PythonInterpreter.all(paths), filters=filters): | ||
identity_str = str(interpreter.identity) | ||
cache_path = os.path.join(self._cache_dir, identity_str) | ||
pi = self._interpreter_from_path(cache_path, filters=filters) | ||
pi = self._interpreter_from_relpath(identity_str, filters=filters) | ||
if pi is None: | ||
cache_path = os.path.join(self._cache_dir, identity_str) | ||
self._setup_interpreter(interpreter, cache_path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be a bit more symmetric on the whole and less error-prone here to make |
||
pi = self._interpreter_from_path(cache_path, filters=filters) | ||
pi = self._interpreter_from_relpath(identity_str, filters=filters) | ||
if pi: | ||
yield pi | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
from __future__ import absolute_import, division, print_function, unicode_literals | ||
|
||
import os | ||
import shutil | ||
from builtins import str | ||
from contextlib import contextmanager | ||
|
||
|
@@ -16,6 +17,7 @@ | |
from pants.backend.python.interpreter_cache import PythonInterpreter, PythonInterpreterCache | ||
from pants.subsystem.subsystem import Subsystem | ||
from pants.util.contextutil import temporary_dir | ||
from pants.util.dirutil import chmod_plus_x, safe_mkdir | ||
from pants_test.backend.python.interpreter_selection_utils import (PY_27, PY_36, | ||
python_interpreter_path, | ||
skip_unless_python27_and_python36) | ||
|
@@ -171,3 +173,32 @@ def test_setup_cached_warm(self): | |
def test_setup_cached_cold(self): | ||
with self._setup_cache() as (cache, _): | ||
self.assertEqual([], list(cache._setup_cached())) | ||
|
||
def test_interpreter_from_relpath_purges_stale_interpreter(self): | ||
""" | ||
Simulates a stale interpreter cache and tests that _interpreter_from_relpath | ||
properly detects it and removes the stale dist directory. | ||
|
||
See https://github.com/pantsbuild/pants/issues/3416 for more info. | ||
""" | ||
with temporary_dir() as temp_dir: | ||
# Setup a interpreter distribution that we can safely mutate. | ||
test_interpreter_binary = os.path.join(temp_dir, 'python2.7') | ||
src = '/usr/bin/python2.7' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It realy is more robust to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to use the output of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am experiencing a different issue with sys.exe:
(due to the venv I think) I think that /usr/bin/python2.7 gets me PATH coherence for free which is why I did that. I'm happy to play around with PYTHONPATH or temp PATH setting. |
||
shutil.copyfile(src, test_interpreter_binary) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
chmod_plus_x(test_interpreter_binary) | ||
|
||
with self._setup_cache(constraints=[]) as (cache, path): | ||
# Setup cache for test interpreter distribution. | ||
identity_str = str(PythonInterpreter.from_binary(test_interpreter_binary).identity) | ||
cached_interpreter_dir = os.path.join(cache._cache_dir, identity_str) | ||
safe_mkdir(cached_interpreter_dir) | ||
cached_symlink = os.path.join(cached_interpreter_dir, 'python') | ||
os.symlink(test_interpreter_binary, cached_symlink) | ||
|
||
# Remove the test interpreter binary from filesystem and assert that the cache is purged. | ||
os.remove(test_interpreter_binary) | ||
self.assertEqual(os.path.exists(test_interpreter_binary), False) | ||
self.assertEqual(os.path.exists(cached_interpreter_dir), True) | ||
cache._interpreter_from_relpath(identity_str) | ||
self.assertEqual(os.path.exists(cached_interpreter_dir), False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can ditch this check, the
os.readlink
below will already raise OSError if any part of the path DNE.