C及びC++でIDEなしでそれなりに快適な開発環境


概要

現在僕が使っている開発環境を共有して、少しでもマシな開発環境にするアドバイスが欲しい。
ノウハウとしてはC++寄りだけれど、大抵の言語に横展開可能なテクニックだと思う。

想定されるご意見:IDE使え

うちのCLion2016.1.3(CL-145.1617)さん、プロジェクト開いただけでCPUを100%食いつぶしたまま帰ってこないんですが…。静的解析をオフにしてみたけどダメ。そもそも静的解析してくれないならなんのための統合開発環境かっていう話にもなるので、いつかマシになると信じてとりあえずEmacsで開発してるのが今。

環境セット

以下の組み合わせで開発している。

  • CMake(+Makefile)
  • gtest
  • Emacs
  • watchmedo
  • notify-send(MacならGrowlなりなんなり)

CMake

しばらくwafも使い込んでいたし、特に不便も感じていなかったけれど、CLionでそのまま使えなかったのと、CLionがデフォルトで用意してくれているのでそのまま使うことにしている。

手元のベースラインはこんな感じ。

CMakeList.txt
cmake_minimum_required(VERSION 3.3)
project(プロジェクト名)

enable_testing() # ctestを有効化するのに必要
add_subdirectory(src/third_party/gtest)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -std=c++11 -ggdb -Wall -Wextra -O3") # -fsanitize=address -fno-omit-frame-pointer") # 必要ならAddress Sanitizerも使える
set(SOURCE_FILES 使ってるソースファイル)

include_directories(src)
include_directories(src/third_party/gtest/googletest/include)
add_executable(実行ファイル ${SOURCE_FILES})

# 渡されたテスト名を実行用ファイルとしてctestの対象に加えていく関数
function (add_gtest_target test_name)
  add_executable(${test_name} src/${test_name}.cpp)
  target_link_libraries(${test_name} gtest_main)
  add_test(
    NAME ${test_name}
    COMMAND ./${test_name})
  link_directories(src/third_party/gtest/googlemock/gtest/)
endfunction()

set(gtest_targets # テストファイルはこの配列に追加していく
    hoge_test
    fuga_test)

# 上で定義されたテストファイル群をadd_gtest_target関数に渡すだけのループ
foreach(target ${gtest_targets})
  add_gtest_target(${target})
endforeach()

こういうのを用意して、src/third_party ディレクトリ以下で

$ git clone https://github.com/google/googletest

とでもしておく。

$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Relese ..

こうすれば build ディレクトリ以下に Makefileなどが一式用意されてビルドできるようになる。CMakeLists.txt と同じディレクトリで cmakeをすると後片付けが大変なので別ディレクトリを掘ったほうがいい。

watchmedo

毎回コードを更新するたびにターミナルに戻ってビルドコマンド走らせるのは辛いので監視させて継続的にビルドさせたい。
OMakeはそういう需要を満たす優れたソフトウェアだけれどCLionが対応してなさそうなので別の方法で解決した。
Pythonの watchdog というモジュールについてくる watchmedo というソフトが便利。

$ pip install watchdog

すると watchmedoコマンドが使えるようになるのでこんなふうに使う。

$ watchmedo shell-command --patterns="*pp" --recursive --wait --command="cd build && make -j8 > build.log 2>&1 && ctest >> build.log 2>&1 && notify-send BUILD-success || notify-send Failed."

これは「今居るディレクトリより下の、ファイル名がppで終わる全てのファイルを監視して、更新されたら --commandオプションで指定された make -j8 > build.log 2>&1 && ctest >> build.log 2>&1 && notify-send BUILD-success || notify-send Failed. を実行せよ。という意味になる。
もちろん開発を始めるたびにこんなクソ長いコマンド打ってられないので、プロジェクトのルートにシェルスクリプトとして置いている。特定のテストを重点的に行いたい場合はctestの部分を書き換えれば良いので楽。

notify-send

&& というのは論理演算と同様に「直前のコマンドが成功したらこの右辺を実行せよ」という意味で使えるモノで、この使い方なら「makeを実行して成功したらctestを実行して成功したら notify-send BUILD-successを実行せよ。」という意味になる。notify-sendというのはLinuxのgnome環境で使えるデスクトップ通知を飛ばすだけのソフトであり、Ubuntuなら

$ sudo apt-get install libnotify-bin

でインストールできる。
|| も論理演算と同様に「直前のコマンドが失敗したらこの右辺を実行せよ」という意味で使えるモノで、makeかctestのどちらかがコケたらFailedとデスクトップ通知で伝えてくれる。C++のビルドは時間がかかるので、エディタで保存したら通知が来るまでtwitterでも眺めていれば良い。

emacs

C++のコンパイルエラーはエラーになった先頭数行しか使わない事が多く、その割にはコンパイルエラーがターミナルを埋め尽くして辛いのでファイルに書き出すのが便利だ。grepもできるしエディタ上でログを検索・整形することだってできる。上のコマンドでも makeと ctestの結果は build.log ファイルに書き出している。build.logファイルがポンポン更新されるので

(global-auto-revert-mode 1)

を.emacs.el に加える事で自動的にリロードさせている。

現状の問題点

使っていて今のところ困っているのはこんな感じ。

  • watchmedoは何故か一度のファイル変更を複数回の変更とみなしてビルドが複数回トリガされてしまう。emacsの保存手続きが悪さしているかも知れない。
  • auto-revertの頻度が秒単位。もっとキビキビリロードして欲しい。