Jelajahi Sumber

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.
Strahinja Val Markovic 11 tahun lalu
induk
melakukan
436017bd4d

+ 1 - 1
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

+ 2 - 2
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

+ 63 - 26
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}" "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
+      COMMAND ${CMAKE_COMMAND} -E copy "${LIBCLANG_TARGET}" "$<TARGET_FILE_DIR:${SERVER_LIB}>"
     )
   else()
     add_custom_command(
-      TARGET ${PROJECT_NAME}
+      TARGET ${SERVER_LIB}
       POST_BUILD
-      COMMAND ${CMAKE_COMMAND} -E copy "${PATH_TO_LLVM_ROOT}/bin/libclang.dll" "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
+      COMMAND ${CMAKE_COMMAND} -E copy "${PATH_TO_LLVM_ROOT}/bin/libclang.dll" "$<TARGET_FILE_DIR:${SERVER_LIB}>")
   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"
-                      "$<TARGET_FILE:${PROJECT_NAME}>"
+                      "$<TARGET_FILE:${SERVER_LIB}>"
                     )
 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 )
 
 #############################################################################

+ 3 - 2
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 )
 
 

+ 26 - 0
cpp/ycm/versioning.cpp

@@ -0,0 +1,26 @@
+// Copyright (C) 2013  Strahinja Val Markovic  <val@markovic.io>
+//
+// 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/>.
+
+namespace YouCompleteMe {
+
+int YcmCoreVersion() {
+  // We increment this every time when we want to force users to recompile
+  // ycm_core.
+  return 7;
+}
+
+}  // namespace YouCompleteMe

+ 22 - 0
cpp/ycm/versioning.h

@@ -0,0 +1,22 @@
+// Copyright (C) 2013  Strahinja Val Markovic  <val@markovic.io>
+//
+// 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/>.
+
+namespace YouCompleteMe {
+
+int YcmCoreVersion();
+
+}  // namespace YouCompleteMe

+ 44 - 0
cpp/ycm/ycm_client_support.cpp

@@ -0,0 +1,44 @@
+// Copyright (C) 2013  Strahinja Val Markovic  <val@markovic.io>
+//
+// 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/>.
+
+#include "IdentifierCompleter.h"
+#include "PythonSupport.h"
+#include "versioning.h"
+
+#include <boost/python.hpp>
+#include <boost/utility.hpp>
+
+
+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() {}
+};
+

+ 2 - 9
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 <boost/utility.hpp>
 #include <boost/python/suite/indexing/vector_indexing_suite.hpp>
 
-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)
 {

+ 8 - 4
plugin/youcompleteme.vim

@@ -37,11 +37,14 @@ let s:script_folder_path = escape( expand( '<sfile>: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

+ 3 - 13
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 <http://www.gnu.org/licenses/>.
 
-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
 

+ 2 - 2
python/ycm/completers/completer.py

@@ -18,7 +18,7 @@
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
 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 ) )

+ 13 - 2
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 <http://www.gnu.org/licenses/>.
 
+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 = []

+ 5 - 4
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,

+ 8 - 1
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 ) ) ) )
 
 

+ 18 - 0
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 = {}

+ 1 - 0
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 \