Auto merge of #1896 - micbou:path-to-python-interpreter, r=Valloric

[READY] Implement new strategy to find the Python interpreter path

See discussion in issue #1891 for details.

Implement a new strategy to find the Python interpreter path:
  - if specified, use `g:ycm_path_to_python_interpreter` option.
  - on UNIX platforms, use `sys.executable` as the path to Python interpreter;
  - on Windows, deduce it from `os.__file__` path (it should always be in the parent folder).

In all cases, check the version (2.6 or 2.7) of the Python interpreter path by running it.

This PR may break things. It needs thorough testing.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/1896)
<!-- Reviewable:end -->
This commit is contained in:
Homu 2016-01-25 08:24:34 +09:00
commit 8d5f164feb
2 changed files with 96 additions and 20 deletions

View File

@ -18,14 +18,16 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import os import os
import sys
import vim import vim
import functools import functools
import re
from ycmd import utils from ycmd import utils
DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) ) DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) )
WIN_PYTHON27_PATH = 'C:\python27\python.exe' WIN_PYTHON_PATH = os.path.join( sys.exec_prefix, 'python.exe' )
WIN_PYTHON26_PATH = 'C:\python26\python.exe' PYTHON_BINARY_REGEX = re.compile( r'python(2(\.[67])?)?(.exe)?$' )
def Memoize( obj ): def Memoize( obj ):
@ -42,29 +44,56 @@ def Memoize( obj ):
@Memoize @Memoize
def PathToPythonInterpreter(): def PathToPythonInterpreter():
user_path_to_python = vim.eval( 'g:ycm_path_to_python_interpreter' ) python_interpreter = vim.eval( 'g:ycm_path_to_python_interpreter' )
if user_path_to_python: if python_interpreter:
return user_path_to_python if IsPythonVersionCorrect( python_interpreter ):
return python_interpreter
# We check for 'python2' before 'python' because some OS's (I'm looking at raise RuntimeError( "Path in 'g:ycm_path_to_python_interpreter' option "
# you Arch Linux) have made the... interesting decision to point "does not point to a valid Python 2.6 or 2.7." )
# /usr/bin/python to python3.
python_names = [ 'python2', 'python' ]
path_to_python = utils.PathToFirstExistingExecutable( python_names ) # On UNIX platforms, we use sys.executable as the Python interpreter path.
if path_to_python: # We cannot use sys.executable on Windows because for unknown reasons, it
return path_to_python # returns the Vim executable. Instead, we use sys.exec_prefix to deduce the
# interpreter path.
python_interpreter = ( WIN_PYTHON_PATH if utils.OnWindows() else
sys.executable )
# On Windows, Python may not be on the PATH at all, so we check some common if IsPythonVersionCorrect( python_interpreter ):
# install locations. return python_interpreter
if utils.OnWindows():
if os.path.exists( WIN_PYTHON27_PATH ):
return WIN_PYTHON27_PATH
elif os.path.exists( WIN_PYTHON26_PATH ):
return WIN_PYTHON26_PATH
raise RuntimeError( 'Python 2.7/2.6 not installed!' ) # As a last resort, we search python in the PATH. We check 'python2' before
# 'python' because on some distributions (Arch Linux for example), python
# refers to python3.
python_interpreter = utils.PathToFirstExistingExecutable( [ 'python2',
'python' ] )
if IsPythonVersionCorrect( python_interpreter ):
return python_interpreter
raise RuntimeError( "Cannot find Python 2.6 or 2.7. You can set its path "
"using the 'g:ycm_path_to_python_interpreter' "
"option." )
def EndsWithPython( path ):
"""Check if given path ends with a python 2.6 or 2.7 name."""
return PYTHON_BINARY_REGEX.search( path ) is not None
def IsPythonVersionCorrect( path ):
"""Check if given path is the Python interpreter version 2.6 or 2.7."""
if not EndsWithPython( path ):
return False
command = [ path,
'-c',
"import sys;"
"major, minor = sys.version_info[ :2 ];"
"sys.exit( major != 2 or minor < 6)" ]
return utils.SafePopen( command ).wait() == 0
def PathToServerScript(): def PathToServerScript():

View File

@ -0,0 +1,47 @@
#
# Copyright (C) 2016 YouCompleteMe contributors
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from ycm.test_utils import MockVimModule
MockVimModule()
from nose.tools import ok_
from ycm.paths import EndsWithPython
def EndsWithPython_Python2Paths_test():
python_paths = [
'python',
'/usr/bin/python2.6',
'/home/user/.pyenv/shims/python2.7',
r'C:\Python26\python.exe'
]
for path in python_paths:
ok_( EndsWithPython( path ) )
def EndsWithPython_NotPython2Paths_test():
not_python_paths = [
'/opt/local/bin/vim',
r'C:\Program Files\Vim\vim74\gvim.exe',
'/usr/bin/python3',
'/home/user/.pyenv/shims/python3',
]
for path in not_python_paths:
ok_( not EndsWithPython( path ) )