CygwinでYouCompleteMeを無理矢理つかう
YouCompleteMeはvim用の補完プラグインで、基本的にCygwinはサポートしていない。まぁでも手でビルドすれば(動く動かないという意味で言えば)動くので使っている。
ycm_core.dllのビルド
YouCompleteMeは、
と、ycmdと呼ばれる
を内包したHTTPサーバで構成されていて、C/C++で書かれたネイティブ部分はインストール時にビルドする必要がある。Cygwin上でlibclangを使用したプログラムをリンクするには微妙なコツが有り、
EXTRA_CMAKE_ARGS="-DCMAKE_CXX_FLAGS=-Wa,-mbig-obj" ./build.py --system-libclang --clang-completer --enable-debug
のようにしてリポジトリ( https://github.com/Valloric/ycmd )上のbuild.pyを起動する。このとき、Cygwin側にはlibclang等が予め導入されている必要がある(最近のCygwinではディストリビューションに含まれるため、setup.exeから普通にインストールできる)。
mbig-objはCOFFの制約を回避するのにどうしても必要になる。このオプションが無いと、
/home/oku/repos2/ycmd/cpp/ycm/ycm_core.cpp
/usr/lib/gcc/x86_64-pc-cygwin/5.4.0/../../../../x86_64-pc-cygwin/bin/as:
ycm/CMakeFiles/ycm_core.dir/ycm_core.cpp.o: too many sections (36370)
のようなエラーが出てリンクに失敗する。
標準のbuild.pyはPythonのライブラリをちゃんと検出しないため、
のようにパッチする必要がある。
ビルドするとリポジトリのルートに ycm_core.dll が作成されるので、これをプラグイン本体( https://github.com/Valloric/YouCompleteMe )のチェックアウトの third_party/ycmd/ycm_core.dll にsymlinkする。
手順を纏めると、
- cygwinにlibclang-develやpython-devel等必要そうなライブラリを適当に導入する
- リポジトリ https://github.com/Valloric/YouCompleteMe と https://github.com/Valloric/ycmd をチェックアウトし、submoduleを git submodule update --init --recursive で初期化する
- ycmdをビルドする
- https://github.com/okuoku/ycmd/commit/81be9b8e349564195d9ce98d82eb6cdf3127d52c のようにパッチ
- EXTRA_CMAKE_ARGS を与えてbuild.pyを起動
- YouCompleteMe/ycmd/ycm_core.dll のsymlinkを作る
- VimのプラグインとしてYouCompleteMeのチェックアウトを追加する
個人的にはvimのプラグインはvim-plug( https://github.com/junegunn/vim-plug )で入れているので、
call plug#begin('~/.vim/plugged/') Plug '~/repos/YouCompleteMe', {'on':[]} call plug#end()
のようにして指定している。後ろの'on'指定はデフォルトでは初期化しない指定(後述)。
特定のディレクトリを編集するときにだけYouCompleteMeを起動する
↑のように、vim-plugで導入したプラグインは有効化条件を空にすることで.vimrcなりなんなりで有効化条件を自由に設定できる。
別にどういう方法を使っても良い気がするけど、個人的にはvim-rooter( https://github.com/airblade/vim-rooter )を使って
- .vimrc
function! UseYcm() call plug#load('YouCompleteMe') endfunction "" Project definitions if filereadable(expand('~/.projects.vim')) source ~/.projects.vim endif " Project dispatch function! MyProjectInit() if !exists("g:myProjects") return endif let s:myProjectPaths = keys(g:myProjects) let s:myRootDir = FindRootDirectory() for e in s:myProjectPaths let pth = expand(e) if pth !=# s:myRootDir continue endif call UseYcm() endfor endfunction au VimEnter \ *.c,*.cc,*.cxx,*.cpp \ call MyProjectInit()
- .projects.vim (手書き)
let g:myProjects = { \ '~/repos/yuni' : 'native' }
FindRootDirectory()はvim-rooterが提供する関数で、編集するファイルが事前に定義したプロジェクトに含まれるかどうかを判定するのに使っている。vim-plugはload手続きを使うことで任意のプラグインを後からロードさせることができる。
CMakeビルドディレクトリのcompile_commands.jsonへの対応
nmoshにせよteslawireにせよ、個人的なプロジェクトは全てCMakeでビルド環境を作っていて、ソースコードとビルドディレクトリは分割している。今のところYouCompleteMeはこのような分割を良くサポートしていないので、手でスクリプトを書くことでどうにかする必要がある。
YouCompleteMeでは、FindFlagsForFile手続きをディレクトリ毎に提供することでこれを行わせるのが通常の方法となるが、流石に面倒なのでグローバルなスクリプトで対応することを考える。
- .vim/my_ycm_conf.py
import os import ycm_core class cmakeProject: def __init__(self, projdir, builddir): self.absProjDir = os.path.abspath(os.path.expanduser(projdir)) self.absBuildDir = os.path.abspath(os.path.expanduser(builddir)) self.db = False def doGetFlags(self, fn): if not self.db: self.db = ycm_core.CompilationDatabase(self.absBuildDir) if self.db: ifo = self.db.GetCompilationInfoForFile(fn) if not ifo: return False return ifo.compiler_flags_ else: return False def getFlags(self, fn): apath = os.path.abspath(fn) cmn0 = os.path.commonprefix([apath, self.absProjDir]) cmn = os.path.commonprefix([cmn0, self.absProjDir]) if cmn == self.absProjDir: return self.doGetFlags(fn) else: return False projects = [ cmakeProject("~/repos/yuni", "~/yuni") ] defaultflags = [ '-Wall', '-isystem', '/usr/include', '-isystem', '/usr/local/include', '-isystem', '/usr/include/w32api' ] def FlagsForFile(fn, **args): flgs = defaultflags # FIXME: Implement header file handling... for prj in projects: f = prj.getFlags(fn) if f: flgs = f break return { 'flags' : flgs }
このような内容の .vim/my_ycm_conf.py を作り、.vimrc側で
let g:ycm_confirm_extra_conf = 0 let g:ycm_global_ycm_extra_conf = '~/.vim/my_ycm_conf.py'
のように設定する。これで、事前にcmakeProjectとして設定したディレクトリのファイルを編集する際は、ビルドディレクトリ側のcompile_commands.jsonを読ませることができる。