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 ) )