[原創] [YCM] YouCompleteMe安裝完全指南


  因為實在實在受不鳥ctags了: 代碼中有很多類具有相同名字的變量, 比如 "id". 當我想看下當前的這個 "id" 到底是哪個id的時候, 可怕的事情粗線了, , , 一口氣出來了10幾個備選. 而且, 不能跳轉到局部變量, 補全也不准確 , , , , , , 好吧, 我終於下定決心來折騰一下YouCompleteMe(YCM). 

  先簡要介紹下樓主的開發環境: 一台能連外網的windows pc, 一台內網服務器開發機(64位的redhat6, 這個服務器並不能連外網). 平時搬磚都是用pc ssh 到服務器上, 直接在服務器上用vim碼磚. 

  網上介紹YCM安裝的攻略也有不少, 但是他們並不適合我當前的處境. 於是決定啃一啃官網文檔. 畢竟這個總是最准確和詳細的. 官網參見: https://github.com/Valloric/YouCompleteMe#commands

  雖然官網文檔有一節說的是 "快速安裝" , 但是作者說 "並不一定適用於你的環境" . 我並不認為我的環境上可以執行成功, 干脆就按照Full Installation Guide操作了. 

 

  第一步, 升級vim.

  YCM要求vim版本為7.3以上, 而我的開發機的版本才是7.0. orz~~. 另外, 也不要指望0疼的內網yum源. 另外, 我也不想從源碼編譯vim(雖然YCM的作者說了一句"Don't worry, it's easy"~~). 於是開始在goooogle上找vim的rpm包. 那么問題來了, 一共需要幾個rpm包呢? 在開發機上用這個命令: rpm -qa | grep vim, 發現有四個包:  vim-common, vim-enhanced, vim-minimal, vim-filesystem. 

  幸好公司里面可以直接上goooogle, 直接google "vim  rpm" 找到了這么一個網站: https://www.rpmfind.net/linux/rpm2html/search.php. 然后在這里面挨個去搜這幾個包, 這個網站會把所有發行版的rpm包都列出來, 對於我的64位redhat6, 要選擇el6_x86_64后綴的包. 將他們下載到pc上, 再rz命令傳到開發機上, 安裝之. 安裝之前要先卸載掉之前的vim包. 然后就ok了, 的到了一個7.4版本的vim. (過程並不是輕描淡寫的兩句話就搞定了, 至少我把所謂的 "Unix哲學" fuck了一百遍, , , ,). 

  

  第二步, 下載YCM. 

  網上所有的攻略都說, 通過Vundle來下載YCM. 然而我這是內網機器啊啊啊啊, 並沒法使用Vundle. 我也嘗試通過在pc上搭了個http代理, 然后讓開發機通過pc的代理來訪問外網. 然而github上的鏈接都是https~~~~~Orz, 日了狗了. 仔細觀察了下YCM的目錄結構, 發現也就是和一個普通的vim插件差不多嘛~~於是在pc上git clone, 然后打個zip包, 再rz到開發機上. 等等, 文檔上有一句這樣的話: If you don't install YCM with Vundle, make sure you have run git submodule update --init --recursive after checking out the YCM repository (Vundle will do this for you) to fetch YCM's dependencies. 好吧, 先執行下git submodule update --init --recursive, 然后再打包傳過去. 發現這個命令執行下去后, 多出來好多東西. 這些東西都是后面用的上的. 

  將zip包解壓到~/.vim/bundle/YouCompleteMe目錄. 

  

  第三步, 安裝clang.

  YCM也是基於clang的補全. 安裝clang肯定是必要的. 用類似於安裝vim的方法, 在google上找clang的rpm包. 主要是clang-3.4.2-4.el6.x86_64.rpm, llvm-3.4.2-4.el6.x86_64.rpm, llvm-libs-3.4.2-4.el6.x86_64.rpm這三個包. 這里有一點很重要, 就是64位系統, 一定要裝x86_64的包. 開始的時候裝錯了, clang裝了i686的包(i686其實是32位的), 雖然clang也能正常用, 但是YCM編譯C++引擎時就會杯具啦. 

  

  第四步, 安裝CMake.

  這次內網的yum源終於有了點作用了. 直接yum install cmake就安裝成功了. 

  

  第五步, 編譯C++引擎(ycm_support_lib).

  如果不是用來補全C++, 那這一步就可以略過啦. 首先建立一個ycm_build目錄(隨便建在哪里都行. 這個只是一個臨時目錄而已). 然后cd ycm_build, 再執行

  cmake -G "Unix Makefiles" -DUSE_SYSTEM_LIBCLANG=ON  .   ~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp

  由於我使用的是系統的clang, 所以要用-DUSE_SYSTEM_LIBCLANG=ON. 

  這時候可以看到ycm_build目錄下多出來一堆東西. 

  然后再執行 cmake --build . --target ycm_support_libs. 

  這一步如果順利, 那再好不過了.

  第一次嘗試的時候沒有加-DUSE_SYSTEM_LIBCLANG=ON, 編譯編譯着就失敗了. 

  第二次, 加了-DUSE_SYSTEM_LIBCLANG=ON之后, 執行到91%, 還是報錯, 后來發現是clang安裝了32位的版本, , , , 

  終於編譯通過啦, 這時候, 可以看到~/.vim/bundle/YouCompleteMe/third_party/ycmd目錄下多了一個ycm_client_support.so和ycm_core.so, 這應該就是編譯出來的C++引擎了. 

  

  第六步, 使YCM生效

  YCM需要一個叫做.ycm_extra_conf.py 的文件作為YCM "入口" . 啟動vim的時候會去尋找這個文件, 然后加載它(從當前目錄逐層往上找). 這個文件主要的意義在於, 讓clang能把當前的源碼文件 "編譯 " 通過. 因為YCM是基於語義補全的, 會對.cpp進行語法分析和語義分析. 於是就得告訴clang一些具體的編譯參數(比較重要的是-I, 得讓clang知道去哪些目錄下找頭文件). 如果clang不能正確的編譯.cpp, 那么很多補全的功能就失效了. 通過 :YcmDiags 命令可以查看當前的文件有哪些編譯錯誤. 這個文件有兩個主要的部分: 一個是flags數組, 在這里面是填編譯選項的. 一個是compilation_database_folder. 這個compilation_database_folder是依賴於clang的一個特性. clang可以在編譯的時候導出一個數據庫, 然后用這個compilation_database_folder加載這個clang的數據庫就可以得到一個最精確最完整的補全和跳轉. 由於我們的代碼是gcc編譯的(相信用clang編譯的項目還是少數), 所以並沒有數據庫可以導出, 也只能用flags了. (這倆東西是二選一的. 如果有數據庫, 優先讀數據庫, 沒有的話就讀flags). 

  參照模板修改了一下flags(主要就是加上幾個 -I, 因為我們的代碼中引用了很多公司內部的庫的頭文件, 得讓clang找到這些頭文件的位置). 

  官網上的指南, 到了上一步就沒了. 然而這個時候YCM並沒有生效呢~~~. 嘗試把YouComplete目錄下的autoload/youcompleteme.vim和plugin目錄下的youcompleteme.vim均拷貝到~/.vim的autoload和plugin下, 這回終於有反應了, 但是報錯說找不到ycm_core.so. 打開youcompleteme.vim, 發現里面在嘗試從../third_party/ycmd和../python下找內容. 於是把~/.vim/bundle/YouCompleteMe/下的python和third_party目錄都拷貝到~/.vim目錄下. 

  這個時候, YCM終於可以用了. 寫了一個test.cpp, 簡單試了下, 不管是補全和跳轉, 都很給力, 完全符合自己的預期. 另外YCM還有語法檢查的功能, 但是我覺得錯誤標記太丑, 就給關了(let g:ycm_enable_diagnostic_signs = 0). 

  然后非常開心的用公司項目的源碼試了下, , , 頓時臉一黑, , , UnicodeDecodeError: 'utf8' codec can't decode byte 0xcd in position 0: invalid continuation byte. 反復的報這個錯誤, YCM的各種功能完全都木有了. T_T頓時感覺辛辛苦苦二十年, 一夜回到解放前. 仔細分析了下, 難道是源碼中的中文是gbk的格式導致的? 於是將一個cpp轉成utf8格式, 再試下, 果然YCM又能用了. NM, 這怎么辦, 難道要把公司的源碼都搞成utf8的么? 這不科學. 果斷先去goooogle一下, 發現已經有人遇到了這個問題: https://github.com/Valloric/YouCompleteMe/issues/1458 YCM的作者在另外一個帖子(https://github.com/Valloric/YouCompleteMe/issues/1378)中是這么回復的 "this might be related to whatever is set as your current file encoding in Vim". 看來猜測的不錯, 畢竟作者是歪果仁, 憑啥讓人家支持gbk嘞?

  不過畢竟是開源項目, 最大的好處是可以看到源碼, 並且可以去修改~~於是決定改改源碼讓YCM支持gbk. 首先利用這個命令 :YcmDebugInfo , 可以看到報錯的詳細信息:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/root/.vim/autoload/../python/ycm/youcompleteme.py", line 526, in DebugInfo
    'debug_info' )
  File "/root/.vim/autoload/../python/ycm/client/base_request.py", line 71, in PostDataToHandler
    timeout ) )
  File "/root/.vim/autoload/../python/ycm/client/base_request.py", line 166, in JsonFromFuture
    raise MakeServerException( response.json() )
ycmd.responses.ServerError: UnicodeDecodeError: 'utf8' codec can't decode byte 0xc1 in position 0: invalid start byte
E858: Eval did not return a valid python object

  挨個文件打開看了看, 最后定位到問題可能出現在這里: 

  /root/.vim/third_party/ycmd/ycmd/utils.py, 中有一個RecursiveEncodeUnicodeToUtf8函數. 這個函數大概的意思是把源代碼文件變成utf8格式, 然后后面再按utf8格式decode. 如果這一步有問題的話, 那么decode肯定會出錯. 於是修改了這個函數成如下的樣子: 

def RecursiveEncodeUnicodeToUtf8( value ):
  if isinstance( value, unicode ):
    return value.encode( 'utf8' )
  if isinstance( value, str ):
    try:
        value = value.decode('GBK').encode('utf8')
    except UnicodeDecodeError,e:
        pass
    return value
  elif isinstance( value, collections.Mapping ):
    return dict( map( RecursiveEncodeUnicodeToUtf8, value.iteritems() ) ) 
  elif isinstance( value, collections.Iterable ):
    return type( value )( map( RecursiveEncodeUnicodeToUtf8, value ) ) 
  else:
    return value

  主要是針對if isinstance( value, str ):分支的修改. 之前是直接return value了. 這就意味着返回了一個gbk格式的字符串, 放到后面按utf8解析肯定會錯. 於是將這個值先按gbk decode, 再encode成utf8. 然后, 問題就解決啦!!

======================2016.03.15補充======================

  發現有些頭文件中, 使用命名空間 :: 不能補全. 使用YcmToggleLogs命令觀察到日志中有一個錯誤: (僅截取部分出錯的調用棧)

  File "~/.vim/third_party/ycmd/third_party/bottle/bottle.py", line 861, in _handle
    return route.call(**args)
  File "~/.vim/third_party/ycmd/third_party/bottle/bottle.py", line 1734, in wrapper
    rv = callback(*a, **ka)
  File "~/.vim/third_party/ycmd/ycmd/../ycmd/watchdog_plugin.py", line 100, in wrapper
    return callback( *args, **kwargs )
  File "~/.vim/third_party/ycmd/ycmd/../ycmd/hmac_plugin.py", line 62, in wrapper
    body = callback( *args, **kwargs )
  File "~/.vim/third_party/ycmd/ycmd/../ycmd/handlers.py", line 126, in GetCompletions
    errors = errors ) ) 
  File "~/.vim/third_party/ycmd/ycmd/../ycmd/handlers.py", line 226, in _JsonResponse
    return json.dumps( data, default = _UniversalSerialize )
  File "/usr/lib64/python2.6/json/__init__.py", line 237, in dumps
    **kw).encode(obj)
  File "/usr/lib64/python2.6/json/encoder.py", line 367, in encode
    chunks = list(self.iterencode(o))

  問題仍然出在處理gbk編碼上. 使用如下方法修改:  ~/.vim/third_party/ycmd/ycmd/../ycmd/handlers.py文件, _JsonResponse函數中:

from utils import RecursiveEncodeUnicodeToUtf8

def _JsonResponse( data ):
  response.set_header( 'Content-Type', 'application/json' )
  #return json.dumps( data, default = _UniversalSerialize )
  return json.dumps( RecursiveEncodeUnicodeToUtf8(data), default = _UniversalSerialize )

  用處理編碼的函數處理一下傳入的data參數, 問題解決. 

   

  至此, YCM真的可以用啦!

  效果確實完爆ctags幾條街, 不枉我這么費力的折騰. 

  最后放上我的YCM配置: 

let g:ycm_global_ycm_extra_conf = '/search/odin/code/.ycm_extra_conf.py'
let g:ycm_confirm_extra_conf = 0 
let g:ycm_key_invoke_completion='<C-i>'
set completeopt=longest,menu
autocmd InsertLeave * if pumvisible() == 0|pclose|endif
inoremap <expr> <CR> pumvisible() ? "\<C-y>" : "\<CR>"
let g:ycm_enable_diagnostic_signs = 0 
let g:ycm_enable_diagnostic_highlighting = 1 
let g:ycm_collect_identifiers_from_comments_and_strings = 0 
let g:ycm_complete_in_comments = 0 
let g:ycm_complete_in_strings = 0 
let g:ycm_min_num_of_chars_for_completion = 2 

  更多的選項可以參見官網上的 option 部分. 

 

  總結

  google是在比baidu牛逼太多了. 沒有google絕對玩不轉. 

  仔細啃文檔, 遇到的很多問題, 文檔中都有提及. 

 

====================2016.06.22更新=======================

  到了新公司, 要在新的開發機上裝ycm. 新公司的開發機雖然可以連外網(也就是可以用vundle來裝ycm了), 但是沒有root權限, 於是並沒有辦法通過yum或者rpm裝clang. 而且也不想手工從源碼編譯clang. 

  不過幸好新公司的開發機也是redhat6, 將原來的clang相關的.so拷貝過來, 然后在.bashrc中增加一行export LD_LIBRARY_PATH="LD_LIBRARY_PATH:~/.vim/third_party/ycmd/ycmd/"

  這個時候加載ycm_core.so時就可以正確的找到libclang.so, 從而可以正確的調用clang了. 

====================2016.09.14更新=======================

  公司換開發機了. 新開發機的vim是7.2版本的, 而且沒有sudo權限沒法更版本, , , YCM又用不了了. 考慮從源碼編譯安裝vim, 但是依賴的編譯工具也需要安裝, 還是繞不開sudo權限. 后來腦子靈光一閃, 直接把舊開發機上的vim的可執行文件拷過來就可以嘞. 先 whereis vim, 找到vim安裝目錄, 在/usr/bin/vim /usr/share/vim中, 然后把對應目錄里的內容拷貝到自己的home目錄下, 在 bashrc 里面把 vim alias 成home目錄下的新版vim. 這時候報錯, vim會嘗試在/usr/share/vim/vim74中嘗試尋找系統配置, 這個路徑是編譯的時候就確定了的, 沒法運行時去改. 只好去找管理員同學, 讓幫忙建了一個軟連接指向自己home目錄的內容. 最終搞定了. 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM