diff --git a/python/ycm/paths.py b/python/ycm/paths.py index b5e1373d..e103778c 100644 --- a/python/ycm/paths.py +++ b/python/ycm/paths.py @@ -18,14 +18,16 @@ # along with YouCompleteMe. If not, see . import os +import sys import vim import functools +import re from ycmd import utils DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) ) -WIN_PYTHON27_PATH = 'C:\python27\python.exe' -WIN_PYTHON26_PATH = 'C:\python26\python.exe' +WIN_PYTHON_PATH = os.path.join( sys.exec_prefix, 'python.exe' ) +PYTHON_BINARY_REGEX = re.compile( r'python(2(\.[67])?)?(.exe)?$' ) def Memoize( obj ): @@ -42,29 +44,56 @@ def Memoize( obj ): @Memoize 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: - return user_path_to_python + if python_interpreter: + if IsPythonVersionCorrect( python_interpreter ): + return python_interpreter - # We check for 'python2' before 'python' because some OS's (I'm looking at - # you Arch Linux) have made the... interesting decision to point - # /usr/bin/python to python3. - python_names = [ 'python2', 'python' ] + raise RuntimeError( "Path in 'g:ycm_path_to_python_interpreter' option " + "does not point to a valid Python 2.6 or 2.7." ) - path_to_python = utils.PathToFirstExistingExecutable( python_names ) - if path_to_python: - return path_to_python + # On UNIX platforms, we use sys.executable as the Python interpreter path. + # We cannot use sys.executable on Windows because for unknown reasons, it + # 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 - # install locations. - 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 + if IsPythonVersionCorrect( python_interpreter ): + return python_interpreter - 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(): diff --git a/python/ycm/tests/paths_test.py b/python/ycm/tests/paths_test.py new file mode 100644 index 00000000..d8e5ca7f --- /dev/null +++ b/python/ycm/tests/paths_test.py @@ -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 . + +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 ) )