Apache ArrowとかMroongaとかいくつかC++ベースのビルドに時間がかかるプロジェクトの開発をしている須藤です。ccacheを使うことでこれらのプロジェクトのCI時間を短くする方法を紹介します。
ビルドキャッシュ
高速化のよくある方法がキャッシュです。一度計算した結果を保存しておき使いまわすことでそもそも処理をせず速くするというアプローチです。
C++のビルドでもキャッシュを使うことができます。これを実現するためのプロダクトはいくつかありますがccacheが代表的なプロダクトです。(元祖?ビルドキャッシュ関連のプロダクトの歴史には詳しくないのでだれか知っている人がいたら教えて。)
他にもMozillaが開発しているsccache(リモートストレージにキャシュを保存して複数マシンでキャッシュを共有できる)や、Microsoft Visual C++用に開発されたclcacheなどがあります。
Microsoft Visual C++で使うならclcacheしか選択肢がない時期もありましたが、今はccacheやsccacheなど他のプロダクトもMicrosoft Visual C++をサポートしていたり、clcacheはメンテナンスされていなかったりしてclcacheの出番はなくなっています。
さらっとccacheもMicrosoft Visual C++をサポートしていると書きましたが、実はサポートしたのはわりと最近です。2022-02-27にリリースされたccache 4.6からサポートしています。
Added support for caching calls to Microsoft Visual C++ (MSVC) and clang-cl (MSVC compatibility for Clang). [contributed by Cristian Adam, Luboš Luňák, Orgad Shaneh and Joel Rosdahl]
それでは、ccacheを使ってCMakeを使っているプロダクトのMicrosoft Visual C++でのビルド結果をキャッシュする方法を紹介します。
CMakeとccache
ccacheはccache cc ...
というように普通のコンパイラーのコマンドラインの前にccache
をつけて使います。(ccache
をcc
にシンボリックリンクして使う使い方もあります。)
CMakeはccacheのようにコンパイラーの前につけるタイプのツールをサポートするためにCMAKE_<LANG>_COMPILER_LAUNCHER
というCMake変数を用意しています。<LANG>
の部分にはC
やCXX
が入ります。C++のコンパイラーのコマンドラインの前にccache
をつける場合は-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
というように使います。
ということで、次のようにすればCMakeを使っているプロダクトのMicrosoft Visual C++でのビルド結果をccacheでキャッシュして高速化できそうですよね。(PowerShellのコマンドライン。)
cmake `
-G "Visual Studio 17 2022" `
-A x64 `
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache `
...
しかし!これは動きません。なぜならCMAKE_<LANG>_COMPILER_LAUNCHER
はVisual Studio系のジェネレーター(CMakeがどのビルドシステム用のファイルを生成するかを指定するやつ、みたいな感じ)では使えないからです。<LANG>_COMPILER_LAUNCHER
プロパティーの説明にあるとおり、MakefileかNinjaでしか使えません。
ということは、こう書くのか、と思いますよね。
cmake `
-G Ninja `
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache `
...
しかし!これは動きません。なぜならVisual C++のコンパイラー(cl.exe
)がどこにあるかわからないからです。
ではどうすればよいかというと、Visual C++のコンパイラーの場所を教えてあげればよいです。そのための便利バッチファイルをVisual Studioが提供しています。GitHub Actionsのwindows-2019
hosted runnerではC:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat
にあります。ということで、これを使ってこんな感じに書きます。バッチファイルを使うのでPowerShellではなくcmd.exe
を使わないといけないことに注意してください。
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
cmake ^
-G Ninja ^
-DCMAKE_BUILD_CXX_COMPILER_LAUNCHER=ccache ^
...
デバッグビルドとビルドキャッシュ
これでキャッシュが効くようになるのですが、この話はこれでは終わりません。実際は-DCMAKE_BUILD_TYPE
にRelease
(リリース用ビルド)やらDebug
(デバッグ用ビルド)やらRelWithDebInfo
(デバッグ情報付きリリース用ビルド)やらを指定してビルドします。そして、Debug
/RelWithDebInfo
を指定するとキャッシュが効きません。なんと。
CMakeは-DCMAKE_BUILD_TYPE
にDebug
/RelWithDebInfo
を指定したときはデフォルトで/Zi
オプション1を使います。/Zi
はデバッグ情報を含んだPDBファイルをビルド対象のオブジェクトファイルとは別に生成するためのオプションです。そして、/Zi
が指定されているとキャッシュが効きません。
詳細は以下の各プロダクトのissueやREADMEを読んでくださいなのですが、簡単にキャッシュできない理由を説明しておきます。/Zi
を使うと複数のファイルで共有のPDBファイルを使います。複数のファイルが同じファイルを生成・変更すると各ファイル単位でビルド結果が確定しないので各ファイル単位でのビルド結果をキャッシュできないのです。/Fd
オプションを使うことで各ファイルごとに別のPDBファイルを使うようにすることもでき、そうするとsccacheでは/Zi
付きでもキャッシュできるようになります。が、ccacheはまだそこまで頑張っていません。
- https://github.com/ccache/ccache/issues/1040
- https://github.com/mozilla/sccache#usage の"To egnerate PDB files ..."あたり
- https://github.com/frerich/clcache/issues/30
では、どうするかというと/Zi
の代わりに/Z7
を使います。/Z7
を使うと別途PDBファイルを作るのではなくオブジェクトファイルそのものにデバッグ情報を含めるようになります。そうすると、各ファイルのビルドでは他のファイルと成果物を共有しなくなるのでキャッシュできます。
CMakeLists.txt
を変更できるなら、sccacheのREADMEにもある通り、次のようにして/Zi
を/Z7
に書き換えることになります。
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
endif()
変更できないなら-DCMAKE_C_FLAGS_DEBUG=...
とか-DCMAKE_CXX_FLAGS_DEBUG=...
をいい感じに指定することになります。デフォルト値はCMakeのModules/Platform/Windows-MSVC.cmake
を見てください。
GitHub Actionsとビルドキャッシュ
ということでキャッシュできるようになったので、後はキャッシュ結果を各ビルド間で共有すればよいです。GitHub Actionsでキャッシュ結果を共有するにはactions/cacheを使います。
問題はなにをactions/cacheでキャッシュすればよいかというところです。キャッシュするのはccacheがキャッシュした内容です。それがどこにあるかはccache --show-config
やccache --show-stats --verbose
でわかります。たとえば、choco install ccache
でccacheをインストールした場合は~\AppData\Roaming\ccache\
以下にキャッシュした内容が置かれます。
ということで、次のような設定を書けばよいということになります。
- uses: actions/cache@v3
with:
path: |
~\AppData\Roaming\ccache
key: ccache-${{ hashFiles('**/*.cpp', '**/*.hpp') }}
restore-keys: ccache-
具体例はMroongaの設定でも見てみてください。
まとめ
GitHub Actionsでccacheを使ってCMake+Microsoft Visual C++のビルドを高速化する方法をまとめました。
新しめのccacheを使わないといけない、CMAKE_CXX_COMPILER_LAUNCHER
を使うならVisual Studio系のジェネレーターは使えない、-DCMAKE_BUILD_TYPE=Debug
/-DCMAKE_BUILD_TYPE=RelWithDebInfo
ではキャッシュが効かないとかいろいろハマりポイントがありますが、動くようになるとかなり高速になるのでCIが遅いと思っている人は使ってみてください。