From c7e32b3600020c65b39a0aba517a229609bcf23e Mon Sep 17 00:00:00 2001 From: micbou Date: Mon, 11 Jan 2016 13:33:43 +0100 Subject: [PATCH 1/5] Implement new strategy to find Python path --- python/ycm/paths.py | 50 +++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/python/ycm/paths.py b/python/ycm/paths.py index b5e1373d..fcd78572 100644 --- a/python/ycm/paths.py +++ b/python/ycm/paths.py @@ -18,14 +18,14 @@ # along with YouCompleteMe. If not, see . import os +import sys import vim import functools 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' ) def Memoize( obj ): @@ -42,29 +42,39 @@ 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 not CheckPythonVersion( python_interpreter ): + raise RuntimeError( "Path in 'g:ycm_path_to_python_interpreter' option " + "does not point to a valid Python 2.6 or 2.7." ) - # 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' ] + return python_interpreter - 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 not CheckPythonVersion( 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." ) - raise RuntimeError( 'Python 2.7/2.6 not installed!' ) + return python_interpreter + + +def CheckPythonVersion( python_interpreter ): + """Check if given path is the Python interpreter version 2.6 or 2.7.""" + command = [ python_interpreter, + '-c', + "import sys;" + "major, minor = sys.version_info[ :2 ];" + "sys.exit( major != 2 or minor < 6)" ] + + return utils.SafePopen( command ).wait() == 0 def PathToServerScript(): From a80846e35dd733f094f8ced9daeacba58eeb4316 Mon Sep 17 00:00:00 2001 From: micbou Date: Tue, 12 Jan 2016 21:24:23 +0100 Subject: [PATCH 2/5] Check Python interpreter pathname Rename CheckPythonVersion to IsPythonVersionCorrect. In embedders, sys.executable may contain a Vim path instead of a Python one. To avoid starting a Vim instance in this case, we check that given path ends with a Python 2.6 or 2.7 name using a regex. Add tests for this regex. --- python/ycm/paths.py | 18 +++++++++++--- python/ycm/tests/paths_test.py | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 python/ycm/tests/paths_test.py diff --git a/python/ycm/paths.py b/python/ycm/paths.py index fcd78572..15f82e81 100644 --- a/python/ycm/paths.py +++ b/python/ycm/paths.py @@ -21,11 +21,13 @@ 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_PYTHON_PATH = os.path.join( sys.exec_prefix, 'python.exe' ) +PYTHON_BINARY_REGEX = re.compile( r'python(2(\.[67])?)?(.exe)?$' ) def Memoize( obj ): @@ -45,7 +47,7 @@ def PathToPythonInterpreter(): python_interpreter = vim.eval( 'g:ycm_path_to_python_interpreter' ) if python_interpreter: - if not CheckPythonVersion( python_interpreter ): + if not IsPythonVersionCorrect( python_interpreter ): raise RuntimeError( "Path in 'g:ycm_path_to_python_interpreter' option " "does not point to a valid Python 2.6 or 2.7." ) @@ -58,7 +60,7 @@ def PathToPythonInterpreter(): python_interpreter = ( WIN_PYTHON_PATH if utils.OnWindows() else sys.executable ) - if not CheckPythonVersion( python_interpreter ): + if not IsPythonVersionCorrect( 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." ) @@ -66,9 +68,17 @@ def PathToPythonInterpreter(): return python_interpreter -def CheckPythonVersion( python_interpreter ): +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.""" - command = [ python_interpreter, + if not EndsWithPython( path ): + return False + + command = [ path, '-c', "import sys;" "major, minor = sys.version_info[ :2 ];" diff --git a/python/ycm/tests/paths_test.py b/python/ycm/tests/paths_test.py new file mode 100644 index 00000000..b7a43702 --- /dev/null +++ b/python/ycm/tests/paths_test.py @@ -0,0 +1,45 @@ +# +# 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_PythonPaths_test(): + python_paths = [ + 'python', + '/usr/bin/python2.6', + '/home/user/.pyenv/shims/python2.7', + r'C:\aPython26\python.exe' + ] + + for path in python_paths: + ok_( EndsWithPython( path ) ) + + +def EndsWithPython_NotPythonPaths_test(): + not_python_paths = [ + '/opt/local/bin/vim', + r'C:\Program Files\Vim\vim74\gvim.exe' + ] + + for path in not_python_paths: + ok_( not EndsWithPython( path ) ) From 1cdbe4a770c6b721b819b1ed6fde99068416a614 Mon Sep 17 00:00:00 2001 From: micbou Date: Tue, 12 Jan 2016 22:48:48 +0100 Subject: [PATCH 3/5] Add python3 failing tests --- python/ycm/tests/paths_test.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/ycm/tests/paths_test.py b/python/ycm/tests/paths_test.py index b7a43702..13c75dcc 100644 --- a/python/ycm/tests/paths_test.py +++ b/python/ycm/tests/paths_test.py @@ -23,7 +23,7 @@ from nose.tools import ok_ from ycm.paths import EndsWithPython -def EndsWithPython_PythonPaths_test(): +def EndsWithPython_Python2Paths_test(): python_paths = [ 'python', '/usr/bin/python2.6', @@ -35,10 +35,12 @@ def EndsWithPython_PythonPaths_test(): ok_( EndsWithPython( path ) ) -def EndsWithPython_NotPythonPaths_test(): +def EndsWithPython_NotPython2Paths_test(): not_python_paths = [ '/opt/local/bin/vim', - r'C:\Program Files\Vim\vim74\gvim.exe' + r'C:\Program Files\Vim\vim74\gvim.exe', + '/usr/bin/python3', + '/home/user/.pyenv/shims/python3', ] for path in not_python_paths: From 9a27c990db65650cc8505b40e485078e64747ec5 Mon Sep 17 00:00:00 2001 From: micbou Date: Tue, 12 Jan 2016 22:51:15 +0100 Subject: [PATCH 4/5] Fix typo in test --- python/ycm/tests/paths_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ycm/tests/paths_test.py b/python/ycm/tests/paths_test.py index 13c75dcc..d8e5ca7f 100644 --- a/python/ycm/tests/paths_test.py +++ b/python/ycm/tests/paths_test.py @@ -28,7 +28,7 @@ def EndsWithPython_Python2Paths_test(): 'python', '/usr/bin/python2.6', '/home/user/.pyenv/shims/python2.7', - r'C:\aPython26\python.exe' + r'C:\Python26\python.exe' ] for path in python_paths: From a0943d5d31a7b80b22588bd0d30bbca6cd0798fc Mon Sep 17 00:00:00 2001 From: micbou Date: Sat, 23 Jan 2016 16:58:24 +0100 Subject: [PATCH 5/5] Fall back to Python search in PATH --- python/ycm/paths.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/python/ycm/paths.py b/python/ycm/paths.py index 15f82e81..e103778c 100644 --- a/python/ycm/paths.py +++ b/python/ycm/paths.py @@ -47,11 +47,11 @@ def PathToPythonInterpreter(): python_interpreter = vim.eval( 'g:ycm_path_to_python_interpreter' ) if python_interpreter: - if not IsPythonVersionCorrect( python_interpreter ): - raise RuntimeError( "Path in 'g:ycm_path_to_python_interpreter' option " - "does not point to a valid Python 2.6 or 2.7." ) + if IsPythonVersionCorrect( python_interpreter ): + return python_interpreter - return python_interpreter + raise RuntimeError( "Path in 'g:ycm_path_to_python_interpreter' option " + "does not point to a valid Python 2.6 or 2.7." ) # On UNIX platforms, we use sys.executable as the Python interpreter path. # We cannot use sys.executable on Windows because for unknown reasons, it @@ -60,12 +60,21 @@ def PathToPythonInterpreter(): python_interpreter = ( WIN_PYTHON_PATH if utils.OnWindows() else sys.executable ) - if not IsPythonVersionCorrect( 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." ) + if IsPythonVersionCorrect( python_interpreter ): + return python_interpreter - return python_interpreter + # 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 ):