forked from VimPlug/jedi
Improve Environment
It only takes `executable` and gets all the information from the subprocess directly. Fixes https://github.com/davidhalter/jedi/issues/1107.
This commit is contained in:
committed by
Dave Halter
parent
f6bc166ea7
commit
2fc91ceb64
@@ -3,14 +3,12 @@ Environments are a way to activate different Python versions or Virtualenvs for
|
|||||||
static analysis. The Python binary in that environment is going to be executed.
|
static analysis. The Python binary in that environment is going to be executed.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import hashlib
|
import hashlib
|
||||||
import filecmp
|
import filecmp
|
||||||
from subprocess import PIPE
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from jedi._compatibility import GeneralizedPopen, which
|
from jedi._compatibility import highest_pickle_protocol, which
|
||||||
from jedi.cache import memoize_method, time_cache
|
from jedi.cache import memoize_method, time_cache
|
||||||
from jedi.evaluate.compiled.subprocess import get_subprocess, \
|
from jedi.evaluate.compiled.subprocess import get_subprocess, \
|
||||||
EvaluatorSameProcess, EvaluatorSubprocess
|
EvaluatorSameProcess, EvaluatorSubprocess
|
||||||
@@ -46,49 +44,52 @@ class _BaseEnvironment(object):
|
|||||||
return self._hash
|
return self._hash
|
||||||
|
|
||||||
|
|
||||||
|
def _get_info():
|
||||||
|
return (
|
||||||
|
sys.executable,
|
||||||
|
sys.prefix,
|
||||||
|
sys.version_info[:3],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Environment(_BaseEnvironment):
|
class Environment(_BaseEnvironment):
|
||||||
"""
|
"""
|
||||||
This class is supposed to be created by internal Jedi architecture. You
|
This class is supposed to be created by internal Jedi architecture. You
|
||||||
should not create it directly. Please use create_environment or the other
|
should not create it directly. Please use create_environment or the other
|
||||||
functions instead. It is then returned by that function.
|
functions instead. It is then returned by that function.
|
||||||
"""
|
"""
|
||||||
def __init__(self, path, executable):
|
def __init__(self, executable):
|
||||||
self.path = os.path.abspath(path)
|
try:
|
||||||
"""
|
self._subprocess = get_subprocess(executable)
|
||||||
The path to an environment, matches ``sys.prefix``.
|
info = self._subprocess._send(None, _get_info)
|
||||||
"""
|
except Exception as exc:
|
||||||
self.executable = os.path.abspath(executable)
|
raise InvalidPythonEnvironment(
|
||||||
|
"Could not get version information for %r: %r" % (
|
||||||
|
executable,
|
||||||
|
exc))
|
||||||
|
|
||||||
|
self.executable = info[0]
|
||||||
"""
|
"""
|
||||||
The Python executable, matches ``sys.executable``.
|
The Python executable, matches ``sys.executable``.
|
||||||
"""
|
"""
|
||||||
self.version_info = self._get_version()
|
self.path = info[1]
|
||||||
|
"""
|
||||||
|
The path to an environment, matches ``sys.prefix``.
|
||||||
|
"""
|
||||||
|
self.version_info = _VersionInfo(*info[2])
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Like ``sys.version_info``. A tuple to show the current Environment's
|
Like ``sys.version_info``. A tuple to show the current Environment's
|
||||||
Python version.
|
Python version.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _get_version(self):
|
# Adjust pickle protocol according to host and client version.
|
||||||
try:
|
self._subprocess._pickle_protocol = highest_pickle_protocol([
|
||||||
process = GeneralizedPopen([self.executable, '--version'], stdout=PIPE, stderr=PIPE)
|
sys.version_info, self.version_info])
|
||||||
stdout, stderr = process.communicate()
|
|
||||||
retcode = process.poll()
|
|
||||||
if retcode:
|
|
||||||
raise InvalidPythonEnvironment(
|
|
||||||
"Exited with %d (stdout=%r, stderr=%r)" % (
|
|
||||||
retcode, stdout, stderr))
|
|
||||||
except OSError as exc:
|
|
||||||
raise InvalidPythonEnvironment(
|
|
||||||
"Could not get version information: %r" % exc)
|
|
||||||
|
|
||||||
# Until Python 3.4 wthe version string is part of stderr, after that
|
# py2 sends bytes via pickle apparently?!
|
||||||
# stdout.
|
if self.version_info.major == 2:
|
||||||
output = stdout + stderr
|
self.executable = self.executable.decode()
|
||||||
match = re.match(br'Python (\d+)\.(\d+)\.(\d+)', output)
|
self.path = self.path.decode()
|
||||||
if match is None:
|
|
||||||
raise InvalidPythonEnvironment("--version not working")
|
|
||||||
|
|
||||||
return _VersionInfo(*[int(m) for m in match.groups()])
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
version = '.'.join(str(i) for i in self.version_info)
|
version = '.'.join(str(i) for i in self.version_info)
|
||||||
@@ -98,7 +99,7 @@ class Environment(_BaseEnvironment):
|
|||||||
return EvaluatorSubprocess(evaluator, self._get_subprocess())
|
return EvaluatorSubprocess(evaluator, self._get_subprocess())
|
||||||
|
|
||||||
def _get_subprocess(self):
|
def _get_subprocess(self):
|
||||||
return get_subprocess(self.executable, self.version_info)
|
return get_subprocess(self.executable)
|
||||||
|
|
||||||
@memoize_method
|
@memoize_method
|
||||||
def get_sys_path(self):
|
def get_sys_path(self):
|
||||||
@@ -118,10 +119,9 @@ class Environment(_BaseEnvironment):
|
|||||||
|
|
||||||
class SameEnvironment(Environment):
|
class SameEnvironment(Environment):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(SameEnvironment, self).__init__(sys.prefix, sys.executable)
|
self.executable = sys.executable
|
||||||
|
self.path = sys.prefix
|
||||||
def _get_version(self):
|
self.version_info = _VersionInfo(*sys.version_info[:3])
|
||||||
return _VersionInfo(*sys.version_info[:3])
|
|
||||||
|
|
||||||
|
|
||||||
class InterpreterEnvironment(_BaseEnvironment):
|
class InterpreterEnvironment(_BaseEnvironment):
|
||||||
@@ -222,7 +222,7 @@ def find_virtualenvs(paths=None, **kwargs):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
executable = _get_executable_path(path, safe=safe)
|
executable = _get_executable_path(path, safe=safe)
|
||||||
yield Environment(path, executable)
|
yield Environment(executable)
|
||||||
except InvalidPythonEnvironment:
|
except InvalidPythonEnvironment:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -246,23 +246,6 @@ def find_system_environments():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# TODO: the logic to find the Python prefix is much more complicated than that.
|
|
||||||
# See Modules/getpath.c for UNIX and PC/getpathp.c for Windows in CPython's
|
|
||||||
# source code. A solution would be to deduce it by running the Python
|
|
||||||
# interpreter and printing the value of sys.prefix.
|
|
||||||
def _get_python_prefix(executable):
|
|
||||||
if os.name != 'nt':
|
|
||||||
return os.path.dirname(os.path.dirname(executable))
|
|
||||||
landmark = os.path.join('Lib', 'os.py')
|
|
||||||
prefix = os.path.dirname(executable)
|
|
||||||
while prefix:
|
|
||||||
if os.path.join(prefix, landmark):
|
|
||||||
return prefix
|
|
||||||
prefix = os.path.dirname(prefix)
|
|
||||||
raise InvalidPythonEnvironment(
|
|
||||||
"Cannot find prefix of executable %s." % executable)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: this function should probably return a list of environments since
|
# TODO: this function should probably return a list of environments since
|
||||||
# multiple Python installations can be found on a system for the same version.
|
# multiple Python installations can be found on a system for the same version.
|
||||||
def get_system_environment(version):
|
def get_system_environment(version):
|
||||||
@@ -277,17 +260,17 @@ def get_system_environment(version):
|
|||||||
if exe:
|
if exe:
|
||||||
if exe == sys.executable:
|
if exe == sys.executable:
|
||||||
return SameEnvironment()
|
return SameEnvironment()
|
||||||
return Environment(_get_python_prefix(exe), exe)
|
return Environment(exe)
|
||||||
|
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
for prefix, exe in _get_executables_from_windows_registry(version):
|
for exe in _get_executables_from_windows_registry(version):
|
||||||
return Environment(prefix, exe)
|
return Environment(exe)
|
||||||
raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
|
raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
|
||||||
|
|
||||||
|
|
||||||
def create_environment(path, safe=True):
|
def create_environment(path, safe=True):
|
||||||
"""
|
"""
|
||||||
Make it possible to manually create an environment by specifying a
|
Make it possible to manually create an Environment object by specifying a
|
||||||
Virtualenv path or an executable path.
|
Virtualenv path or an executable path.
|
||||||
|
|
||||||
:raises: :exc:`.InvalidPythonEnvironment`
|
:raises: :exc:`.InvalidPythonEnvironment`
|
||||||
@@ -295,8 +278,8 @@ def create_environment(path, safe=True):
|
|||||||
"""
|
"""
|
||||||
if os.path.isfile(path):
|
if os.path.isfile(path):
|
||||||
_assert_safe(path, safe)
|
_assert_safe(path, safe)
|
||||||
return Environment(_get_python_prefix(path), path)
|
return Environment(path)
|
||||||
return Environment(path, _get_executable_path(path, safe=safe))
|
return Environment(_get_executable_path(path, safe=safe))
|
||||||
|
|
||||||
|
|
||||||
def _get_executable_path(path, safe=True):
|
def _get_executable_path(path, safe=True):
|
||||||
@@ -318,9 +301,9 @@ def _get_executable_path(path, safe=True):
|
|||||||
def _get_executables_from_windows_registry(version):
|
def _get_executables_from_windows_registry(version):
|
||||||
# The winreg module is named _winreg on Python 2.
|
# The winreg module is named _winreg on Python 2.
|
||||||
try:
|
try:
|
||||||
import winreg
|
import winreg
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import _winreg as winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
# TODO: support Python Anaconda.
|
# TODO: support Python Anaconda.
|
||||||
sub_keys = [
|
sub_keys = [
|
||||||
@@ -337,7 +320,7 @@ def _get_executables_from_windows_registry(version):
|
|||||||
prefix = winreg.QueryValueEx(key, '')[0]
|
prefix = winreg.QueryValueEx(key, '')[0]
|
||||||
exe = os.path.join(prefix, 'python.exe')
|
exe = os.path.join(prefix, 'python.exe')
|
||||||
if os.path.isfile(exe):
|
if os.path.isfile(exe):
|
||||||
yield prefix, exe
|
yield exe
|
||||||
except WindowsError:
|
except WindowsError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import traceback
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from jedi._compatibility import queue, is_py3, force_unicode, \
|
from jedi._compatibility import queue, is_py3, force_unicode, \
|
||||||
pickle_dump, pickle_load, highest_pickle_protocol, GeneralizedPopen
|
pickle_dump, pickle_load, GeneralizedPopen
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.evaluate.compiled.subprocess import functions
|
from jedi.evaluate.compiled.subprocess import functions
|
||||||
from jedi.evaluate.compiled.access import DirectObjectAccess, AccessPath, \
|
from jedi.evaluate.compiled.access import DirectObjectAccess, AccessPath, \
|
||||||
@@ -29,12 +29,11 @@ _subprocesses = {}
|
|||||||
_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
|
_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
|
||||||
|
|
||||||
|
|
||||||
def get_subprocess(executable, version):
|
def get_subprocess(executable):
|
||||||
try:
|
try:
|
||||||
return _subprocesses[executable]
|
return _subprocesses[executable]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
sub = _subprocesses[executable] = _CompiledSubprocess(executable,
|
sub = _subprocesses[executable] = _CompiledSubprocess(executable)
|
||||||
version)
|
|
||||||
return sub
|
return sub
|
||||||
|
|
||||||
|
|
||||||
@@ -125,12 +124,21 @@ class EvaluatorSubprocess(_EvaluatorProcess):
|
|||||||
|
|
||||||
class _CompiledSubprocess(object):
|
class _CompiledSubprocess(object):
|
||||||
_crashed = False
|
_crashed = False
|
||||||
|
# Start with 2, gets set after _get_info.
|
||||||
|
_pickle_protocol = 2
|
||||||
|
|
||||||
def __init__(self, executable, version):
|
def __init__(self, executable):
|
||||||
self._executable = executable
|
self._executable = executable
|
||||||
self._evaluator_deletion_queue = queue.deque()
|
self._evaluator_deletion_queue = queue.deque()
|
||||||
self._pickle_protocol = highest_pickle_protocol([sys.version_info,
|
|
||||||
version])
|
def __repr__(self):
|
||||||
|
pid = os.getpid()
|
||||||
|
return '<_CompiledSubprocess _executable=%r, _pickle_protocol=%r, _crashed=%r, pid=%r>' % (
|
||||||
|
self._executable,
|
||||||
|
self._pickle_protocol,
|
||||||
|
self._crashed,
|
||||||
|
pid,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@memoize_method
|
@memoize_method
|
||||||
@@ -140,7 +148,7 @@ class _CompiledSubprocess(object):
|
|||||||
self._executable,
|
self._executable,
|
||||||
_MAIN_PATH,
|
_MAIN_PATH,
|
||||||
os.path.dirname(os.path.dirname(parso_path)),
|
os.path.dirname(os.path.dirname(parso_path)),
|
||||||
str(self._pickle_protocol)
|
'.'.join(str(x) for x in sys.version_info[:3]),
|
||||||
)
|
)
|
||||||
return GeneralizedPopen(
|
return GeneralizedPopen(
|
||||||
args,
|
args,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def _get_paths():
|
def _get_paths():
|
||||||
@@ -45,7 +45,11 @@ else:
|
|||||||
load('jedi')
|
load('jedi')
|
||||||
from jedi.evaluate.compiled import subprocess # NOQA
|
from jedi.evaluate.compiled import subprocess # NOQA
|
||||||
|
|
||||||
|
from jedi._compatibility import highest_pickle_protocol # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
# Retrieve the pickle protocol.
|
# Retrieve the pickle protocol.
|
||||||
pickle_protocol = int(sys.argv[2])
|
host_sys_version = [int(x) for x in sys.argv[2].split('.')]
|
||||||
|
pickle_protocol = highest_pickle_protocol([sys.version_info, host_sys_version])
|
||||||
# And finally start the client.
|
# And finally start the client.
|
||||||
subprocess.Listener(pickle_protocol).listen()
|
subprocess.Listener(pickle_protocol=pickle_protocol).listen()
|
||||||
|
|||||||
Reference in New Issue
Block a user