Skip to content
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

Honor sys.executable unless macOS Framework. #1065

Merged
merged 1 commit into from
Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions pex/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,17 @@ class UnknownRequirement(Error):

@classmethod
def get(cls, binary=None):
# type: (Optional[str]) -> PythonIdentity

# N.B.: We should not need to look past `sys.executable` to learn the current interpreter's
# executable path, but on OSX there has been a bug where the `sys.executable` reported is
# _not_ the path of the current interpreter executable:
# https://bugs.python.org/issue22490#msg283859
if binary and binary != sys.executable:
TRACER.log(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These logs were never viewable since they were emitted durring a subprocess that captures both stdout and stderr as part of the execute_parallel Job / Retain framework. As such, just killed.

"Identifying interpreter found at {} which reports an incorrect sys.executable of "
"{}.".format(binary, sys.executable),
V=9,
)
# https://bugs.python.org/issue22490#msg283859
# That case is distinguished by the presence of a `__PYVENV_LAUNCHER__` environment
# variable as detailed in the Python bug linked above.
if binary and binary != sys.executable and "__PYVENV_LAUNCHER__" not in os.environ:
# Here we assume sys.executable is accurate and binary is something like a pyenv shim.
binary = sys.executable

supported_tags = tuple(tags.sys_tags())
preferred_tag = supported_tags[0]
Expand Down
20 changes: 15 additions & 5 deletions pex/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from textwrap import dedent

from pex.common import open_zip, safe_mkdir, safe_mkdtemp, safe_rmtree, temporary_dir, touch
from pex.compatibility import to_unicode
from pex.distribution_target import DistributionTarget
from pex.executor import Executor
from pex.interpreter import PythonInterpreter
Expand All @@ -27,6 +28,7 @@
if TYPE_CHECKING:
from typing import (
Any,
Callable,
Dict,
Iterable,
Iterator,
Expand Down Expand Up @@ -416,33 +418,41 @@ def bootstrap_python_installer(dest):


def ensure_python_distribution(version):
# type: (str) -> Tuple[str, str]
# type: (str) -> Tuple[str, str, Callable[[Iterable[str]], Text]]
if version not in _VERSIONS:
raise ValueError("Please constrain version to one of {}".format(_VERSIONS))

pyenv_root = os.path.join(os.getcwd(), ".pyenv_test")
interpreter_location = os.path.join(pyenv_root, "versions", version)

pyenv = os.path.join(pyenv_root, "bin", "pyenv")
pyenv_env = os.environ.copy()
pyenv_env["PYENV_ROOT"] = pyenv_root

pip = os.path.join(interpreter_location, "bin", "pip")

if not os.path.exists(os.path.join(pyenv_root, _INTERPRETER_SET_FINGERPRINT)):
bootstrap_python_installer(pyenv_root)

if not os.path.exists(interpreter_location):
env = os.environ.copy()
env["PYENV_ROOT"] = pyenv_root
env = pyenv_env.copy()
if sys.platform.lower() == "linux":
env["CONFIGURE_OPTS"] = "--enable-shared"
subprocess.check_call([pyenv, "install", "--keep", version], env=env)
subprocess.check_call([pip, "install", "-U", "pip"])

python = os.path.join(interpreter_location, "bin", "python" + version[0:3])
return python, pip

def run_pyenv(args):
# type: (Iterable[str]) -> Text
return to_unicode(subprocess.check_output([pyenv] + list(args), env=pyenv_env))

return python, pip, run_pyenv


def ensure_python_interpreter(version):
# type: (str) -> str
python, _ = ensure_python_distribution(version)
python, _, _ = ensure_python_distribution(version)
return python


Expand Down
4 changes: 2 additions & 2 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1970,7 +1970,7 @@ def test_issues_745_extras_isolation():
# type: () -> None
# Here we ensure one of our extras, `subprocess32`, is properly isolated in the transition from
# pex bootstrapping where it is imported by `pex.executor` to execution of user code.
python, pip = ensure_python_distribution(PY27)
python, pip, _ = ensure_python_distribution(PY27)
subprocess.check_call([pip, "install", "subprocess32"])
with temporary_dir() as td:
src_dir = os.path.join(td, "src")
Expand Down Expand Up @@ -2046,7 +2046,7 @@ def write_pth(pth_path, sitedir):


def test_issues_1025_extras_isolation(issues_1025_pth):
python, pip = ensure_python_distribution(PY36)
python, pip, _ = ensure_python_distribution(PY36)
interpreter = PythonInterpreter.from_binary(python)
_, stdout, _ = interpreter.execute(args=["-c", "import site; print(site.getsitepackages()[0])"])
with temporary_dir() as tmpdir:
Expand Down
45 changes: 44 additions & 1 deletion tests/test_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@
from pex.common import temporary_dir, touch
from pex.compatibility import PY3
from pex.interpreter import PythonInterpreter
from pex.testing import PY27, PY35, ensure_python_interpreter
from pex.testing import (
PY27,
PY35,
PY36,
ensure_python_distribution,
ensure_python_interpreter,
environment_as,
)
from pex.typing import TYPE_CHECKING
from pex.variables import ENV

try:
from mock import patch
Expand Down Expand Up @@ -176,3 +184,38 @@ def test_iter_interpreter_path_filter_symlink(self, test_interpreter1, test_inte
)
)
assert os.path.basename(expected_interpreter.binary) != "jake"

def test_pyenv_shims(self):
# type: () -> None
py35, _, run_pyenv = ensure_python_distribution(PY35)
py36, _, _ = ensure_python_distribution(PY36)

pyenv_root = str(run_pyenv(["root"]).strip())
pyenv_shims = os.path.join(pyenv_root, "shims")

def pyenv_global(*versions):
run_pyenv(["global"] + list(versions))

def assert_shim(shim_name, expected_binary_path):
python = PythonInterpreter.from_binary(os.path.join(pyenv_shims, shim_name))
assert expected_binary_path == python.binary

with temporary_dir() as pex_root:
with ENV.patch(PEX_ROOT=pex_root) as pex_env:
with environment_as(PYENV_ROOT=pyenv_root, **pex_env):
pyenv_global(PY35, PY36)
assert_shim("python3", py35)

pyenv_global(PY36, PY35)
# The python3 shim is now pointing at python3.6 but the Pex cache has a valid
# entry for the old python3.5 association (the interpreter still exists.)
assert_shim("python3", py35)

# The shim pointer is now invalid since python3.5 was uninstalled and so should
# be re-read.
py35_deleted = "{}.uninstalled".format(py35)
os.rename(py35, py35_deleted)
try:
assert_shim("python3", py36)
finally:
os.rename(py35_deleted, py35)