C++ vim

vimからC++プロジェクトに対してCMakeでビルドツリー生成+コンパイル

投稿日:

筆者はテキストエディタとしてvimを日常的に使用しています。
今回は、C++のソースツリーに対して、vim越しにCMakeコマンドを使用してビルドツリーを作成し、そのビルドツリー上でコンパイルを行うvimscriptを作成しました。

背景

筆者は業務でC++プロジェクトを開発する際、svnを用いてプロジェクト管理をしています。
使い方として、branch内にtrunkのHEADから各プロジェクトのメインブランチを作成し、さらに個人がメインブランチを派生させて作業ブランチを作成しています。
リリース時には、個人の作業ブランチをメインブランチにマージして、メインブランチからタグを切る、という方式を取っています。

|--branches
|  |--dev_0.1  # trunkのHEADから作成したブランチ
|  |  |--include
|  |  |--lib
|  |  |--src
|  |--dev_0.1_NewModuleImpl  # dev_0.1から派生したブランチ
|  |  |--include
|  |  |--lib
|  |  |--src
|  |--dev_0.1_OldModuleBugFix  # dev_0.1から派生したブランチ
|  |  |--略
|  |--dev_0.2
|  |  |--略
|--tags
|  |--0.1
|  |  |--include
|  |  |--lib
|  |  |--src
|--trunk
|  |--include
|  |--lib
|  |--src

また、ビルドツリー生成システムとしてCMakeを使用しています。プロジェクトのルートにおいてcmakeコマンドを用いることで、ビルドツリーを生成し実行バイナリを作るようになっています。

サンプルプロジェクトの構成は以下になります。

|--CMakeLists.txt
|--include
|  |--hello.h
|--lib
|  |--CMakeLists.txt
|  |--hello.cpp
|--src
|  |--CMakeLists.txt
|  |--main.cpp

要件と仕様

要件は以下です。

  • サブディレクトリ(srcやlib,include)以下のファイルをvimで編集しているとき、vim上でCMakeコマンドを実行して、ビルドツリーを生成できるようにする
  • サブディレクトリ(srcやlib,include)以下のファイルをvimで編集しているとき、vim上でmakeコマンドを実行して、ビルドツリーからビルドできるようにする

仕様は以下になります。

  • vimscriptで、以下の動作を行うユーザ定義コマンドCMakeを作成する
    • 各サブディレクトリ上からプロジェクトのルートディレクトリを探索する
    • ルートディレクトリの下に、ビルドディレクトリ(./build/default)を生成する
    • (通常の)cmakeコマンドでビルドディレクトリ以下にビルドツリーを生成する
  • vimscriptで、以下の動作を行うユーザ定義コマンドMakeを作成する
    • 各サブディレクトリ上からプロジェクトのビルドディレクトリを探索する
    • ビルドディレクトリから(通常の)makeコマンドを実行しビルドを行う

設計・実装

以下の2種類のvimscriptファイルを作成しました

  • search_mark.vim:マーカファイルを探索する
  • make.vim:cmakeやmakeを行う

search_mark.vimの実装は以下になります。

if !exists("*SearchMark")
  function SearchMark(file_name) abort
    let l:current_path = getcwd()
    let l:maxdir = 5
    let l:i = 0
    let l:root_path = "\0"
    while l:i < l:maxdir
      if filereadable(a:file_name)
        let l:root_path = getcwd()
        break
      else
        cd `='../'`
      endif
      let i = i + 1
    endwhile
    cd `=fnameescape(l:current_path)`
    return l:root_path
  endfunction
endif
  • SearchMark関数を定義します。
    • カレントディレクトリから5階層まで遡り、引数で指定されたファイル名のマーカファイルの存在有無をサーチします
    • マーカファイルが存在するディレクトリを発見したら、そのディレクトリのパスを返します
    • マーカファイルが存在しなかった場合は、\0を返します

make.vimの実装は以下になります。

:source ~/.vim/cmd/search_mark.vim

command! Make call s:Make("", "")
" command! MakeX8664 call s:Make("x8664", "")  " for cross compile
command! Clean call s:Make("", "clean")
" command! CleanX8664 call s:Make("x8664", "clean")  " for cross compile
command! CMake call s:CMake()


function s:Make(arch, command) abort
  let l:current_path = getcwd()
  let g:project_marker_file_name = get(g:, "project_marker_file_name", ".emacs_ctrl.el")
  let l:src_root = SearchMark(g:project_marker_file_name)
  if l:src_root == "\0"
    echo "No mark file found. Please check path."
    return
  endif

  if a:arch == "x8664"
    " cd `=fnameescape(l:src_root)` | cd `='./build/x86_64'`
  else
    cd `=fnameescape(l:src_root)` | cd `='./build/default'`
  endif

  if a:command == ""
    make -C . -j 4
  elseif a:command == "clean"
    make clean -C . -j 4
  else
    echo "make error!"
  endif

  echo "make complete."
  cd `=fnameescape(l:current_path)`
endfunction


function s:CMake() abort
  let l:current_path = getcwd()
 let g:project_marker_file_name = get(g:, "project_marker_file_name", ".emacs_ctrl.el") 
 let l:src_root = SearchMark(g:project_marker_file_name)
  if l:src_root == "\0"
    echo "No mark file found. Please check path."
    return
  else
    cd `=fnameescape(l:src_root)` | call s:MakeBuildTree()
    echo "cmake..."
    " cd `=fnameescape(l:src_root)` | cd `='./build/x86_64'`
    " call system("cmake -D COMPILE_TARGET=I386 ../../")
    cd `=fnameescape(l:src_root)` | cd `='./build/default'`
    call system("cmake ../../")
    echo "cmake Comnplete."
  endif
  cd `=fnameescape(l:current_path)`
endfunction


function s:MakeBuildTree() abort
  if isdirectory("build")
    echo "Build directory exists. Delete."
    silent !rm -rf build
  endif
  echo "Make Build directory."
  silent !mkdir build
  " silent !mkdir build/x86_64
  silent !mkdir build/default
endfunction
  • 以下のコマンドを定義します
    • Makeコマンド:ビルドディレクトリに移動してmakeを行う
    • Cleanコマンド:(おまけ)ビルドディレクトリに移動してmake cleanを行う
    • CMakeコマンド:プロジェクトのルートディレクトリに移動して、cmakeコマンドでビルドディレクトリを作成する
  • MakeコマンドはMake関数に処理を移譲します
    • SearchMark関数を使用して、マーカファイルを検索します。デフォルトで検索するファイルは「.emacs_ctrl.el」にしてあります(プロジェクト管理者がemacsユーザなので。。。)
    • SearchMark関数の返り値のディレクトリまで移動した後、事前にCMakeコマンドで作成しておいたビルドディレクトリ(project_root/build/default)に移動します
    • ビルドディレクトリからmakeコマンドを実行します
  • CleanコマンドはMake関数に、引数Cleanを与えて処理を移譲します
    • 引数Cleanを受けたMake関数は、make時にcleanオプションを付加します。
  • CMakeコマンドはCMake関数に処理を移譲します
    • SearchMark関数を使用して、マーカファイルを検索します
    • SearchMark関数の返り値のディレクトリまで移動した後、ビルドツリー用のディレクトリ(project_root/build/default)をMakeBuildTree関数で作成します
    • 作成したビルドツリー用ディレクトリに移動してcmakeを実行します

上記2ファイルは、~/.vim/cmd以下に配置します

/home/user/.vim
|--cmd
|  |--make.vim
|  |--search_mark.vim

最後に、上記ファイルを~/使用するために~/.vimrcを編集します

:source ~/.vim/cmd/make.vim

" Set Project Marker File Name. Default:.emacs_ctrl.el
" let g:project_marker_file_name = '.vim_ctrl.vim'
  • グローバル変数g:project_marker_file_nameを定義することで、マーカファイルの名前を変更できます

動作方法

まず、サンプルプロジェクトのルートディレクトリに、マーカファイルを配置します

|--.emacs_ctrl.el  # マーカファイル
|--CMakeLists.txt
|--include
|  |--hello.h
|--lib
|  |--CMakeLists.txt
|  |--hello.cpp
|--src
|  |--CMakeLists.txt
|  |--main.cpp

各サブディレクトリに移動して、ファイルをvimで編集後、:CMakeを実行すると、buildディレクトリが追加されます。

|--build
|  |--default
|  |  |--CMakeCache.txt
|  |  |--CMakeFiles
|  |  |  |--2.8.12.2
|  |  |  |  |--CMakeCCompiler.cmake
|  |  |  |  |--CMakeCXXCompiler.cmake
|  |  |  |  |--CMakeDetermineCompilerABI_C.bin
|  |  |  |  |--CMakeDetermineCompilerABI_CXX.bin
|  |  |  |  |--CMakeSystem.cmake
|  |  |  |  |--CompilerIdC
|  |  |  |  |  |--a.out
|  |  |  |  |  |--CMakeCCompilerId.c
|  |  |  |  |--CompilerIdCXX
|  |  |  |  |  |--a.out
|  |  |  |  |  |--CMakeCXXCompilerId.cpp
|  |  |  |--cmake.check_cache
|  |  |  |--CMakeDirectoryInformation.cmake
|  |  |  |--CMakeOutput.log
|  |  |  |--CMakeTmp
|  |  |  |--Makefile2
|  |  |  |--Makefile.cmake
|  |  |  |--progress.marks
|  |  |  |--TargetDirectories.txt
|  |  |--cmake_install.cmake
|  |  |--lib
|  |  |  |--CMakeFiles
|  |  |  |  |--CMakeDirectoryInformation.cmake
|  |  |  |  |--libexample.dir
|  |  |  |  |  |--build.make
|  |  |  |  |  |--cmake_clean.cmake
|  |  |  |  |  |--cmake_clean_target.cmake
|  |  |  |  |  |--DependInfo.cmake
|  |  |  |  |  |--depend.make
|  |  |  |  |  |--flags.make
|  |  |  |  |  |--link.txt
|  |  |  |  |  |--progress.make
|  |  |  |  |--progress.marks
|  |  |  |--cmake_install.cmake
|  |  |  |--Makefile
|  |  |--Makefile
|  |  |--src
|  |  |  |--CMakeFiles
|  |  |  |  |--CMakeDirectoryInformation.cmake
|  |  |  |  |--example.dir
|  |  |  |  |  |--build.make
|  |  |  |  |  |--cmake_clean.cmake
|  |  |  |  |  |--DependInfo.cmake
|  |  |  |  |  |--depend.make
|  |  |  |  |  |--flags.make
|  |  |  |  |  |--link.txt
|  |  |  |  |  |--progress.make
|  |  |  |  |--progress.marks
|  |  |  |--cmake_install.cmake
|  |  |  |--Makefile
|--CMakeLists.txt
|--.emacs_ctrl.el
|--include
|  |--hello.h
|--lib
|  |--CMakeLists.txt
|  |--hello.cpp
|--src
|  |--CMakeLists.txt
|  |--main.cpp

その後、vimから:Makeコマンドを実行することで、ビルドツリーでmakeを走らせることができます。

user@hostname:/tmp/project-root/src# vim main.cpp 
make: Entering directory `/tmp/project-root/build/default'
make[1]: Entering directory `/tmp/project-root/build/default'
make[2]: Entering directory `/tmp/project-root/build/default'
Scanning dependencies of target libexample
make[2]: Leaving directory `/tmp/project-root/build/default'
make[2]: Entering directory `/tmp/project-root/build/default'
[ 50%] Building CXX object lib/CMakeFiles/libexample.dir/hello.o
Linking CXX static library libexample.a
make[2]: Leaving directory `/tmp/project-root/build/default'
[ 50%] Built target libexample
make[2]: Entering directory `/tmp/project-root/build/default'
Scanning dependencies of target example
make[2]: Leaving directory `/tmp/project-root/build/default'
make[2]: Entering directory `/tmp/project-root/build/default'
[100%] Building CXX object src/CMakeFiles/example.dir/main.o
Linking CXX executable example
make[2]: Leaving directory `/tmp/project-root/build/default'
[100%] Built target example
make[1]: Leaving directory `/tmp/project-root/build/default'
make: Leaving directory `/tmp/project-root/build/default'

Press ENTER or type command to continue

 

-C++, vim
-,

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

関連記事

ltraceを用いてstd::vector::push_backの動作を見える化する

仕事で使用したltraceの使い方を、メモがてら残しておきます。 目次1 概要1.1 環境2 設計・実装3 ltraceを用いたdynamic libraryのトレース4 終わりに5 メモ5.0.1 …

no image

cmakeでのDEBUG/RELEASEモード別・サブディレクトリ別のコンパイルオプションの分け方

仕事で使うので備忘録として。 対象となるソースコードツリーは、拙作のcppのプロジェクトのテンプレートで行う shinjikirino/cpp_project_templatehttps://gith …

pthread_cancelすると何が行われるのか

仕事でのメモ。 目次1 pthread_cancel2 pthread_cancelで何が行われるのか2.1 シグナル送出2.2 注意2.3 ソースコードを読む2.4 pthread_cancel時の …

Dockerを使用した簡単なC++実行環境の構築

今回は、C++の機能を調査するための簡単なテスト環境を、Dockerを用いて構築する手順を解説します。 目次1 要件2 仕様3 実装3.1 元になるイメージ3.2 Dockerfileの作成3.3 実 …

C++11で作るTypeList(型情報コンテナ)とfor_each

目次1 経緯2 要件と仕様3 設計と実装3.1 LokiのTypeList3.2 C++11時代のTypeList3.3 TypeListへのアクセッサ(リストのサイズ・添字アクセス・型からの添字抽出 …