From 436017bd4dd2d86d3a20e777d02290c406031750 Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Tue, 15 Oct 2013 11:19:56 -0700 Subject: [PATCH] Now using new ycm_client_support shared lib This means we can now load just ycm_client_support (which is a much smaller library) into Vim and ycm_core into ycmd. Since ycm_client_support never depends on libclang.so, we never have to load that into Vim which makes things much, much easier. --- autoload/youcompleteme.vim | 2 +- cpp/BoostParts/CMakeLists.txt | 4 +- cpp/ycm/CMakeLists.txt | 89 +++++++++++++++++++++--------- cpp/ycm/tests/CMakeLists.txt | 5 +- cpp/ycm/versioning.cpp | 26 +++++++++ cpp/ycm/versioning.h | 22 ++++++++ cpp/ycm/ycm_client_support.cpp | 44 +++++++++++++++ cpp/ycm/ycm_core.cpp | 11 +--- plugin/youcompleteme.vim | 12 ++-- python/ycm/base.py | 16 +----- python/ycm/completers/completer.py | 4 +- python/ycm/server/handlers.py | 15 ++++- python/ycm/server/ycmd.py | 9 +-- python/ycm/vimsupport.py | 9 ++- python/ycm/youcompleteme.py | 18 ++++++ style_format.sh | 1 + 16 files changed, 221 insertions(+), 66 deletions(-) create mode 100644 cpp/ycm/versioning.cpp create mode 100644 cpp/ycm/versioning.h create mode 100644 cpp/ycm/ycm_client_support.cpp diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index fd6f77e7..11152f71 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -58,7 +58,7 @@ function! youcompleteme#Enable() if !pyeval( 'base.CompatibleWithYcmCore()') echohl WarningMsg | - \ echomsg "YouCompleteMe unavailable: ycm_core too old, PLEASE RECOMPILE ycm_core" | + \ echomsg "YouCompleteMe unavailable: YCM support libs too old, PLEASE RECOMPILE" | \ echohl None return endif diff --git a/cpp/BoostParts/CMakeLists.txt b/cpp/BoostParts/CMakeLists.txt index 0b1da46e..b2a648df 100644 --- a/cpp/BoostParts/CMakeLists.txt +++ b/cpp/BoostParts/CMakeLists.txt @@ -26,8 +26,8 @@ cmake_minimum_required( VERSION 2.8 ) project( BoostParts ) -set( Python_ADDITIONAL_VERSIONS 2.7 2.6 2.5 ) -find_package( PythonLibs 2.5 REQUIRED ) +set( Python_ADDITIONAL_VERSIONS 2.7 2.6 ) +find_package( PythonLibs 2.6 REQUIRED ) if ( NOT PYTHONLIBS_VERSION_STRING VERSION_LESS "3.0.0" ) message( FATAL_ERROR diff --git a/cpp/ycm/CMakeLists.txt b/cpp/ycm/CMakeLists.txt index 3c0654d8..fa7d30b1 100644 --- a/cpp/ycm/CMakeLists.txt +++ b/cpp/ycm/CMakeLists.txt @@ -17,10 +17,12 @@ cmake_minimum_required( VERSION 2.8 ) -project( ycm_core ) +project( ycm_support_libs ) +set( CLIENT_LIB "ycm_client_support" ) +set( SERVER_LIB "ycm_core" ) -set( Python_ADDITIONAL_VERSIONS 2.7 2.6 2.5 ) -find_package( PythonLibs 2.5 REQUIRED ) +set( Python_ADDITIONAL_VERSIONS 2.7 2.6 ) +find_package( PythonLibs 2.6 REQUIRED ) if ( NOT PYTHONLIBS_VERSION_STRING VERSION_LESS "3.0.0" ) message( FATAL_ERROR @@ -138,15 +140,15 @@ include_directories( ${CLANG_INCLUDES_DIR} ) -file( GLOB_RECURSE SOURCES *.h *.cpp ) +file( GLOB_RECURSE SERVER_SOURCES *.h *.cpp ) # The test sources are a part of a different target, so we remove them # The CMakeFiles cpp file is picked up when the user creates an in-source build, -# and we don't want that. -file( GLOB_RECURSE to_remove tests/*.h tests/*.cpp CMakeFiles/*.cpp ) +# and we don't want that. We also remove client-specific code +file( GLOB_RECURSE to_remove tests/*.h tests/*.cpp CMakeFiles/*.cpp *client* ) if( to_remove ) - list( REMOVE_ITEM SOURCES ${to_remove} ) + list( REMOVE_ITEM SERVER_SOURCES ${to_remove} ) endif() if ( USE_CLANG_COMPLETER ) @@ -158,7 +160,7 @@ else() file( GLOB_RECURSE to_remove_clang ClangCompleter/*.h ClangCompleter/*.cpp ) if( to_remove_clang ) - list( REMOVE_ITEM SOURCES ${to_remove_clang} ) + list( REMOVE_ITEM SERVER_SOURCES ${to_remove_clang} ) endif() endif() @@ -217,11 +219,33 @@ endif() ############################################################################# -add_library( ${PROJECT_NAME} SHARED - ${SOURCES} +# We don't actually need all of the files this picks up, just the ones needed by +# PythonSupport.cpp. But this is easier to maintain and dead code elemination +# will remove unused code. +file( GLOB CLIENT_SOURCES *.h *.cpp ) +file( GLOB SERVER_SPECIFIC *ycm_core* ) + +if( SERVER_SPECIFIC ) + list( REMOVE_ITEM CLIENT_SOURCES ${SERVER_SPECIFIC} ) +endif() + +add_library( ${CLIENT_LIB} SHARED + ${CLIENT_SOURCES} ) -target_link_libraries( ${PROJECT_NAME} +target_link_libraries( ${CLIENT_LIB} + BoostParts + ${PYTHON_LIBRARIES} + ${EXTRA_LIBS} + ) + +############################################################################# + +add_library( ${SERVER_LIB} SHARED + ${SERVER_SOURCES} + ) + +target_link_libraries( ${SERVER_LIB} BoostParts ${PYTHON_LIBRARIES} ${LIBCLANG_TARGET} @@ -231,35 +255,43 @@ target_link_libraries( ${PROJECT_NAME} if( LIBCLANG_TARGET ) if( NOT WIN32 ) add_custom_command( - TARGET ${PROJECT_NAME} + TARGET ${SERVER_LIB} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy "${LIBCLANG_TARGET}" "$" + COMMAND ${CMAKE_COMMAND} -E copy "${LIBCLANG_TARGET}" "$" ) else() add_custom_command( - TARGET ${PROJECT_NAME} + TARGET ${SERVER_LIB} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy "${PATH_TO_LLVM_ROOT}/bin/libclang.dll" "$") + COMMAND ${CMAKE_COMMAND} -E copy "${PATH_TO_LLVM_ROOT}/bin/libclang.dll" "$") endif() endif() +############################################################################# + +# Convenience target that builds both support libs. +add_custom_target( ${PROJECT_NAME} + DEPENDS ${CLIENT_LIB} ${SERVER_LIB} ) + + ############################################################################# # Things are a bit different on Macs when using an external libclang.dylib; here # we want to make sure we use @loader_path/libclang.dylib instead of -# @rpath/libclang.dylib in the final ycm_core.so. If we use the @rpath version, -# then it may load the system libclang which the user explicitely does not want -# (otherwise the user would specify USE_SYSTEM_LIBCLANG). With @loader_path, we -# make sure that only the libclang.dylib present in the same directory as our -# ycm_core.so is used. +# @rpath/libclang.dylib in the final ycm_core.so. If we use the +# @rpath version, then it may load the system libclang which the user +# explicitely does not want (otherwise the user would specify +# USE_SYSTEM_LIBCLANG). With @loader_path, we make sure that only the +# libclang.dylib present in the same directory as our ycm_core.so +# is used. if ( EXTERNAL_LIBCLANG_PATH AND APPLE ) - add_custom_command( TARGET ${PROJECT_NAME} + add_custom_command( TARGET ${SERVER_LIB} POST_BUILD COMMAND install_name_tool "-change" "@rpath/libclang.dylib" "@loader_path/libclang.dylib" - "$" + "$" ) endif() @@ -268,19 +300,24 @@ endif() # We don't want the "lib" prefix, it can screw up python when it tries to search # for our module -set_target_properties( ${PROJECT_NAME} PROPERTIES PREFIX "") +set_target_properties( ${CLIENT_LIB} PROPERTIES PREFIX "") +set_target_properties( ${SERVER_LIB} PROPERTIES PREFIX "") if ( WIN32 OR CYGWIN ) # This is the extension for compiled Python modules on Windows - set_target_properties( ${PROJECT_NAME} PROPERTIES SUFFIX ".pyd") + set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".pyd") + set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".pyd") else() # Even on macs, we want a .so extension instead of a .dylib which is what # cmake would give us by default. Python won't recognize a .dylib as a module, # but it will recognize a .so - set_target_properties( ${PROJECT_NAME} PROPERTIES SUFFIX ".so") + set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".so") + set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".so") endif() -set_target_properties( ${PROJECT_NAME} PROPERTIES +set_target_properties( ${CLIENT_LIB} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../../python ) +set_target_properties( ${SERVER_LIB} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../../python ) ############################################################################# diff --git a/cpp/ycm/tests/CMakeLists.txt b/cpp/ycm/tests/CMakeLists.txt index 951c0aba..1383cc0f 100644 --- a/cpp/ycm/tests/CMakeLists.txt +++ b/cpp/ycm/tests/CMakeLists.txt @@ -28,7 +28,7 @@ endif() add_subdirectory( gmock ) include_directories( - ${ycm_core_SOURCE_DIR} + ${ycm_support_libs_SOURCE_DIR} ) include_directories( @@ -67,7 +67,8 @@ add_executable( ${PROJECT_NAME} ) target_link_libraries( ${PROJECT_NAME} - ycm_core + ${SERVER_LIB} + ${CLIENT_LIB} gmock ) diff --git a/cpp/ycm/versioning.cpp b/cpp/ycm/versioning.cpp new file mode 100644 index 00000000..0007ff2e --- /dev/null +++ b/cpp/ycm/versioning.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2013 Strahinja Val Markovic +// +// 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 . + +namespace YouCompleteMe { + +int YcmCoreVersion() { + // We increment this every time when we want to force users to recompile + // ycm_core. + return 7; +} + +} // namespace YouCompleteMe diff --git a/cpp/ycm/versioning.h b/cpp/ycm/versioning.h new file mode 100644 index 00000000..2b2fc081 --- /dev/null +++ b/cpp/ycm/versioning.h @@ -0,0 +1,22 @@ +// Copyright (C) 2013 Strahinja Val Markovic +// +// 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 . + +namespace YouCompleteMe { + +int YcmCoreVersion(); + +} // namespace YouCompleteMe diff --git a/cpp/ycm/ycm_client_support.cpp b/cpp/ycm/ycm_client_support.cpp new file mode 100644 index 00000000..25b77194 --- /dev/null +++ b/cpp/ycm/ycm_client_support.cpp @@ -0,0 +1,44 @@ +// Copyright (C) 2013 Strahinja Val Markovic +// +// 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 . + +#include "IdentifierCompleter.h" +#include "PythonSupport.h" +#include "versioning.h" + +#include +#include + + +BOOST_PYTHON_MODULE(ycm_client_support) +{ + using namespace boost::python; + using namespace YouCompleteMe; + + // Necessary because of usage of the ReleaseGil class + PyEval_InitThreads(); + + def( "FilterAndSortCandidates", FilterAndSortCandidates ); + def( "YcmCoreVersion", YcmCoreVersion ); +} + +// Boost.Thread forces us to implement this. +// We don't use any thread-specific (local) storage so it's fine to implement +// this as an empty function. +namespace boost { +void tss_cleanup_implemented() {} +}; + diff --git a/cpp/ycm/ycm_core.cpp b/cpp/ycm/ycm_core.cpp index 4aa3003d..be347652 100644 --- a/cpp/ycm/ycm_core.cpp +++ b/cpp/ycm/ycm_core.cpp @@ -17,6 +17,7 @@ #include "IdentifierCompleter.h" #include "PythonSupport.h" +#include "versioning.h" #ifdef USE_CLANG_COMPLETER # include "ClangCompleter.h" @@ -32,8 +33,7 @@ #include #include -bool HasClangSupport() -{ +bool HasClangSupport() { #ifdef USE_CLANG_COMPLETER return true; #else @@ -41,13 +41,6 @@ bool HasClangSupport() #endif // USE_CLANG_COMPLETER } -int YcmCoreVersion() -{ - // We increment this every time when we want to force users to recompile - // ycm_core. - return 6; -} - BOOST_PYTHON_MODULE(ycm_core) { diff --git a/plugin/youcompleteme.vim b/plugin/youcompleteme.vim index d6879287..384a098a 100644 --- a/plugin/youcompleteme.vim +++ b/plugin/youcompleteme.vim @@ -37,11 +37,14 @@ let s:script_folder_path = escape( expand( ':p:h' ), '\' ) function! s:HasYcmCore() let path_prefix = s:script_folder_path . '/../python/' - if filereadable(path_prefix . 'ycm_core.so') + if filereadable(path_prefix . 'ycm_client_support.so') && + \ filereadable(path_prefix . 'ycm_core.so') return 1 - elseif filereadable(path_prefix . 'ycm_core.pyd') + elseif filereadable(path_prefix . 'ycm_client_support.pyd') && + \ filereadable(path_prefix . 'ycm_core.pyd') return 1 - elseif filereadable(path_prefix . 'ycm_core.dll') + elseif filereadable(path_prefix . 'ycm_client_support.dll') + \ filereadable(path_prefix . 'ycm_core.dll') return 1 endif return 0 @@ -52,7 +55,8 @@ let g:ycm_check_if_ycm_core_present = if g:ycm_check_if_ycm_core_present && !s:HasYcmCore() echohl WarningMsg | - \ echomsg "ycm_core.[so|pyd|dll] not detected; you need to compile " . + \ echomsg "ycm_client_support.[so|pyd|dll] and " . + \ "ycm_core.[so|pyd|dll] not detected; you need to compile " . \ "YCM before using it. Read the docs!" | \ echohl None finish diff --git a/python/ycm/base.py b/python/ycm/base.py index 8bf94784..e578fa3b 100644 --- a/python/ycm/base.py +++ b/python/ycm/base.py @@ -17,25 +17,15 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -import os import re import vim from ycm import vimsupport from ycm import utils from ycm import user_options_store +import ycm_client_support YCM_VAR_PREFIX = 'ycm_' -try: - import ycm_core -except ImportError as e: - vimsupport.PostVimMessage( - '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( - os.path.dirname( os.path.dirname( os.path.abspath( __file__ ) ) ), - str( e ) ) ) - def BuildServerConf(): """Builds a dictionary mapping YCM Vim user options to values. Option names @@ -152,11 +142,11 @@ def AdjustCandidateInsertionText( candidates ): return new_candidates -COMPATIBLE_WITH_CORE_VERSION = 6 +COMPATIBLE_WITH_CORE_VERSION = 7 def CompatibleWithYcmCore(): try: - current_core_version = ycm_core.YcmCoreVersion() + current_core_version = ycm_client_support.YcmCoreVersion() except AttributeError: return False diff --git a/python/ycm/completers/completer.py b/python/ycm/completers/completer.py index 4668076d..3e248531 100644 --- a/python/ycm/completers/completer.py +++ b/python/ycm/completers/completer.py @@ -18,7 +18,7 @@ # along with YouCompleteMe. If not, see . import abc -import ycm_core +import ycm_client_support from ycm.utils import ToUtf8IfNeeded, ForceSemanticCompletion from ycm.completers.completer_utils import TriggersForFiletype @@ -207,7 +207,7 @@ class Completer( object ): elif 'insertion_text' in candidates[ 0 ]: sort_property = 'insertion_text' - matches = ycm_core.FilterAndSortCandidates( + matches = ycm_client_support.FilterAndSortCandidates( candidates, sort_property, ToUtf8IfNeeded( query ) ) diff --git a/python/ycm/server/handlers.py b/python/ycm/server/handlers.py index 2ebf1c68..a20134f7 100644 --- a/python/ycm/server/handlers.py +++ b/python/ycm/server/handlers.py @@ -17,6 +17,18 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . +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 @@ -28,6 +40,7 @@ from ycm import user_options_store from ycm.server.responses import BuildExceptionResponse from ycm import extra_conf_store + # num bytes for the request body buffer; request.json only works if the request # size is less than this bottle.Request.MEMFILE_MAX = 300 * 1024 @@ -135,8 +148,6 @@ def LoadExtraConfFile(): @app.post( '/debug_info' ) def DebugInfo(): - # This can't be at the top level because of possible extra conf preload - import ycm_core LOGGER.info( 'Received debug info request' ) output = [] diff --git a/python/ycm/server/ycmd.py b/python/ycm/server/ycmd.py index a0c78247..1173e792 100755 --- a/python/ycm/server/ycmd.py +++ b/python/ycm/server/ycmd.py @@ -59,13 +59,14 @@ def Main(): if args.options_file: options = json.load( open( args.options_file, 'r' ) ) user_options_store.SetAll( options ) - # This ensures that ycm_core is not loaded before extra conf preload - # was run. + # This ensures that ycm_core is not loaded before extra conf + # preload was run. YcmCoreSanityCheck() extra_conf_store.CallGlobalExtraConfYcmCorePreloadIfExists() - # This can't be a top-level import because it transitively imports ycm_core - # which we want to be imported ONLY after extra conf preload has executed. + # This can't be a top-level import because it transitively imports + # ycm_core which we want to be imported ONLY after extra conf + # preload has executed. import handlers handlers.UpdateUserOptions( options ) waitress.serve( handlers.app, diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 21d1daf2..7138cf49 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -146,7 +146,14 @@ def NumLinesInBuffer( buffer_object ): # time of writing, YCM only uses the GUI thread inside Vim (this used to not be # the case). def PostVimMessage( message ): - vim.command( "echohl WarningMsg | echomsg '{0}' | echohl None" + vim.command( "echohl WarningMsg | echom '{0}' | echohl None" + .format( EscapeForVim( str( message ) ) ) ) + +# Unlike PostVimMesasge, this supports messages with newlines in them because it +# uses 'echo' instead of 'echomsg'. This also means that the message will NOT +# appear in Vim's message log. +def PostMultiLineNotice( message ): + vim.command( "echohl WarningMsg | echo '{0}' | echohl None" .format( EscapeForVim( str( message ) ) ) ) diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 56811635..be59c4c6 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -40,6 +40,11 @@ try: except ImportError: USE_ULTISNIPS_DATA = False +SERVER_CRASH_MESSAGE_STDERR_FILE = 'The ycmd server crashed with output:\n' +SERVER_CRASH_MESSAGE_SAME_STDERR = ( + 'The ycmd server crashed, check console output for logs!' ) + + class YouCompleteMe( object ): def __init__( self, user_options ): self._user_options = user_options @@ -83,6 +88,18 @@ class YouCompleteMe( object ): self._server_popen = subprocess.Popen( args, stdout = fstdout, stderr = fstderr ) + self._CheckIfServerCrashed() + + + def _CheckIfServerCrashed( self ): + server_crashed = self._server_popen.poll() + if server_crashed: + if self._server_stderr: + with open( self._server_stderr, 'r' ) as server_stderr_file: + vimsupport.PostMultiLineNotice( SERVER_CRASH_MESSAGE_STDERR_FILE + + server_stderr_file.read() ) + else: + vimsupport.PostVimMessage( SERVER_CRASH_MESSAGE_SAME_STDERR ) def CreateCompletionRequest( self, force_semantic = False ): @@ -126,6 +143,7 @@ class YouCompleteMe( object ): def OnFileReadyToParse( self ): + self._CheckIfServerCrashed() self._omnicomp.OnFileReadyToParse( None ) extra_data = {} diff --git a/style_format.sh b/style_format.sh index 2988c68b..d0983cf5 100755 --- a/style_format.sh +++ b/style_format.sh @@ -19,6 +19,7 @@ astyle \ --recursive \ --exclude=gmock \ --exclude=testdata \ +--exclude=ycm_client_support.cpp \ --exclude=ycm_core.cpp \ --exclude=CustomAssert.h \ --exclude=CustomAssert.cpp \