aboutsummaryrefslogtreecommitdiff
path: root/vim/bundle/YouCompleteMe/python/ycm/paths.py
blob: 17edafa3d8ceed9d289a0829b20ca30ff5efc4cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# Copyright (C) 2015 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 __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases()
from builtins import *  # noqa

import os
import sys
import vim
import functools
import re

# Can't import these from setup.py because it makes nosetests go crazy.
DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) )
DIR_OF_YCMD = os.path.join( DIR_OF_CURRENT_SCRIPT, '..', '..', 'third_party',
                            'ycmd' )
WIN_PYTHON_PATH = os.path.join( sys.exec_prefix, 'python.exe' )
PYTHON_BINARY_REGEX = re.compile(
  r'python((2(\.[67])?)|(3(\.[3-9])?))?(.exe)?$' )


def Memoize( obj ):
  cache = obj.cache = {}

  @functools.wraps( obj )
  def memoizer( *args, **kwargs ):
    key = str( args ) + str( kwargs )
    if key not in cache:
      cache[ key ] = obj( *args, **kwargs )
    return cache[ key ]
  return memoizer


@Memoize
def PathToPythonInterpreter():
  from ycmd import utils

  python_interpreter = vim.eval( 'g:ycm_server_python_interpreter' )

  if python_interpreter:
    if IsPythonVersionCorrect( python_interpreter ):
      return python_interpreter

    raise RuntimeError( "Path in 'g:ycm_server_python_interpreter' option "
                        "does not point to a valid Python 2.6+ or 3.3+." )

  python_interpreter = _PathToPythonUsedDuringBuild()
  if IsPythonVersionCorrect( python_interpreter ):
    return python_interpreter

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

  if IsPythonVersionCorrect( python_interpreter ):
    return python_interpreter

  # As a last resort, we search python in the PATH. We prefer Python 2 over 3
  # for the sake of backwards compatibility with ycm_extra_conf.py files out
  # there; few people wrote theirs to work on py3.
  # So we check 'python2' before 'python' because on some distributions (Arch
  # Linux for example), python refers to python3.
  python_interpreter = utils.PathToFirstExistingExecutable( [ 'python2',
                                                              'python',
                                                              'python3' ] )

  if IsPythonVersionCorrect( python_interpreter ):
    return python_interpreter

  raise RuntimeError( "Cannot find Python 2.6+ or 3.3+. You can set its path "
                      "using the 'g:ycm_server_python_interpreter' "
                      "option." )


def _PathToPythonUsedDuringBuild():
  from ycmd import utils

  try:
    filepath = os.path.join( DIR_OF_YCMD, 'PYTHON_USED_DURING_BUILDING' )
    return utils.ReadFile( filepath ).strip()
  # We need to check for IOError for Python2 and OSError for Python3
  except ( IOError, OSError ):
    return None


def EndsWithPython( path ):
  """Check if given path ends with a python 2.6+ or 3.3+ name."""
  return path and PYTHON_BINARY_REGEX.search( path ) is not None


def IsPythonVersionCorrect( path ):
  """Check if given path is the Python interpreter version 2.6+ or 3.3+."""
  from ycmd import utils

  if not EndsWithPython( path ):
    return False

  command = [ path,
              '-c',
              "import sys;"
              "major, minor = sys.version_info[ :2 ];"
              "good_python = ( major == 2 and minor >= 6 ) "
              "or ( major == 3 and minor >= 3 ) or major > 3;"
              # If this looks weird, remember that:
              #   int( True ) == 1
              #   int( False ) == 0
              "sys.exit( not good_python )" ]

  return utils.SafePopen( command ).wait() == 0


def PathToServerScript():
  return os.path.join( DIR_OF_YCMD, 'ycmd' )