Skip to content

Commit

Permalink
Auto merge of #467 - micbou:check-core, r=Valloric
Browse files Browse the repository at this point in the history
[READY] Add checks to ycm_core library when starting ycmd

Do the following checks on the ycm_core library when starting ycmd:
 - missing library;
 - compiled with Python 2 but imported with Python 3;
 - compiled with Python 3 but imported with Python 2;
 - outdated library.

When one of these errors is encountered, exit with a non-zero status code specific to the error. These status codes will be used in the YCM client to display an appropriate error message to the user. In addition, starting a separate process to check the core version will not be needed anymore. This should slighty improve the Vim startup time. See issue ycm-core/YouCompleteMe#2085.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/ycmd/467)
<!-- Reviewable:end -->
  • Loading branch information
homu committed May 2, 2016
2 parents f3dd8fd + 85d5f6b commit ff72c85
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 112 deletions.
11 changes: 11 additions & 0 deletions README.md
Expand Up @@ -153,6 +153,17 @@ keep-alive background thread that periodically pings ycmd (just call the
You can also turn this off by passing `--idle_suicide_seconds=0`, although that
isn't recommended.

### Exit codes

During startup, ycmd attempts to load the `ycm_core` library and exits with one
of the following return codes if unsuccessful:

- 3: unexpected error while loading the library;
- 4: the `ycm_core` library is missing;
- 5: the `ycm_core` library is compiled for Python 3 but loaded in Python 2;
- 6: the `ycm_core` library is compiled for Python 2 but loaded in Python 3;
- 7: the version of the `ycm_core` library is outdated.

User-level customization
-----------------------

Expand Down
44 changes: 0 additions & 44 deletions check_core_version.py

This file was deleted.

8 changes: 4 additions & 4 deletions ycmd/__main__.py
Expand Up @@ -25,7 +25,7 @@
import os

sys.path.insert( 0, os.path.dirname( os.path.abspath( __file__ ) ) )
from server_utils import SetUpPythonPath, CompatibleWithCurrentCoreVersion
from server_utils import SetUpPythonPath, CompatibleWithCurrentCore
SetUpPythonPath()

from future import standard_library
Expand Down Expand Up @@ -157,9 +157,9 @@ def Main():
YcmCoreSanityCheck()
extra_conf_store.CallGlobalExtraConfYcmCorePreloadIfExists()

if not CompatibleWithCurrentCoreVersion():
# ycm_core.[so|dll|dylib] is too old and needs to be recompiled.
sys.exit( 2 )
code = CompatibleWithCurrentCore()
if code:
sys.exit( code )

PossiblyDetachFromTerminal()

Expand Down
23 changes: 5 additions & 18 deletions ycmd/handlers.py
Expand Up @@ -23,30 +23,17 @@
standard_library.install_aliases()
from builtins import * # noqa

from os import path

try:
import ycm_core
except ImportError as e:
raise RuntimeError(
'Error importing ycm_core. Are you sure you have placed a '
'version 3.2+ libclang.[so|dll|dylib] in folder "{0}"? '
'See the Installation Guide in the docs. Full error: {1}'.format(
path.realpath( path.join( path.abspath( __file__ ), '..', '..' ) ),
str( e ) ) )

import atexit
import logging
import json
import bottle
import http.client
import json
import logging
import traceback
from bottle import request
from . import server_state
from ycmd import user_options_store

import ycm_core
from ycmd import extra_conf_store, hmac_plugin, server_state, user_options_store
from ycmd.responses import BuildExceptionResponse, BuildCompletionResponse
from ycmd import hmac_plugin
from ycmd import extra_conf_store
from ycmd.request_wrap import RequestWrap
from ycmd.bottle_utils import SetResponseHeader
from ycmd.completers.completer_utils import FilterAndSortCandidatesWrap
Expand Down
97 changes: 83 additions & 14 deletions ycmd/server_utils.py
Expand Up @@ -22,24 +22,57 @@
# No other imports from `future` because this module is loaded before we have
# put our submodules in sys.path

import sys
import os
import io
import logging
import os
import re
import sys

CORE_MISSING_ERROR_REGEX = re.compile( "No module named '?ycm_core'?" )
CORE_PYTHON2_ERROR_REGEX = re.compile(
'dynamic module does not define (?:init|module export) '
'function \(PyInit_ycm_core\)|'
'Module use of python2[0-9].dll conflicts with this version of Python\.$' )
CORE_PYTHON3_ERROR_REGEX = re.compile(
'dynamic module does not define init function \(initycm_core\)|'
'Module use of python3[0-9].dll conflicts with this version of Python\.$' )

CORE_MISSING_MESSAGE = (
'ycm_core library not detected; you need to compile it by running the '
'build.py script. See the documentation for more details.' )
CORE_PYTHON2_MESSAGE = (
'ycm_core library compiled for Python 2 but loaded in Python 3.' )
CORE_PYTHON3_MESSAGE = (
'ycm_core library compiled for Python 3 but loaded in Python 2.' )
CORE_OUTDATED_MESSAGE = (
'ycm_core library too old; PLEASE RECOMPILE by running the build.py script. '
'See the documentation for more details.' )

# Exit statuses returned by the CompatibleWithCurrentCore function:
# - CORE_COMPATIBLE_STATUS: ycm_core is compatible;
# - CORE_UNEXPECTED_STATUS: unexpected error while loading ycm_core;
# - CORE_MISSING_STATUS : ycm_core is missing;
# - CORE_PYTHON2_STATUS : ycm_core is compiled with Python 2 but loaded with
# Python 3;
# - CORE_PYTHON3_STATUS : ycm_core is compiled with Python 3 but loaded with
# Python 2;
# - CORE_OUTDATED_STATUS : ycm_core version is outdated.
# Values 1 and 2 are not used because 1 is for general errors and 2 has often a
# special meaning for Unix programs. See
# https://docs.python.org/2/library/sys.html#sys.exit
CORE_COMPATIBLE_STATUS = 0
CORE_UNEXPECTED_STATUS = 3
CORE_MISSING_STATUS = 4
CORE_PYTHON2_STATUS = 5
CORE_PYTHON3_STATUS = 6
CORE_OUTDATED_STATUS = 7

VERSION_FILENAME = 'CORE_VERSION'
CORE_NOT_COMPATIBLE_MESSAGE = (
'ycmd can\'t run: ycm_core lib too old, PLEASE RECOMPILE'
)

DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) )
DIR_PACKAGES_REGEX = re.compile( '(site|dist)-packages$' )


def SetUpPythonPath():
sys.path.insert( 0, os.path.join( DIR_OF_CURRENT_SCRIPT, '..' ) )

AddNearestThirdPartyFoldersToSysPath( __file__ )
_logger = logging.getLogger( __name__ )


def ExpectedCoreVersion():
Expand All @@ -48,13 +81,49 @@ def ExpectedCoreVersion():
return int( f.read() )


def CompatibleWithCurrentCoreVersion():
import ycm_core
def ImportCore():
"""Imports and returns the ycm_core module. This function exists for easily
mocking this import in tests."""
import ycm_core as ycm_core
return ycm_core


def CompatibleWithCurrentCore():
"""Checks if ycm_core library is compatible and returns with an exit
status."""
try:
ycm_core = ImportCore()
except ImportError as error:
message = str( error )
if CORE_MISSING_ERROR_REGEX.match( message ):
_logger.exception( CORE_MISSING_MESSAGE )
return CORE_MISSING_STATUS
if CORE_PYTHON2_ERROR_REGEX.match( message ):
_logger.exception( CORE_PYTHON2_MESSAGE )
return CORE_PYTHON2_STATUS
if CORE_PYTHON3_ERROR_REGEX.match( message ):
_logger.exception( CORE_PYTHON3_MESSAGE )
return CORE_PYTHON3_STATUS
_logger.exception( message )
return CORE_UNEXPECTED_STATUS

try:
current_core_version = ycm_core.YcmCoreVersion()
except AttributeError:
return False
return ExpectedCoreVersion() == current_core_version
_logger.exception( CORE_OUTDATED_MESSAGE )
return CORE_OUTDATED_STATUS

if ExpectedCoreVersion() != current_core_version:
_logger.error( CORE_OUTDATED_MESSAGE )
return CORE_OUTDATED_STATUS

return CORE_COMPATIBLE_STATUS


def SetUpPythonPath():
sys.path.insert( 0, os.path.join( DIR_OF_CURRENT_SCRIPT, '..' ) )

AddNearestThirdPartyFoldersToSysPath( __file__ )


def AncestorFolders( path ):
Expand Down
31 changes: 0 additions & 31 deletions ycmd/tests/check_core_version_test.py

This file was deleted.

0 comments on commit ff72c85

Please sign in to comment.