Open4

yunibuild: ビルドトレースの組込み

okuokuokuoku

yuniframeやWasmLinuxでは基本的にFOSSは先頭を追いたいと考えている。というわけで、ビルドトレースをビルド環境に組込み、差分や警告の発生をトラッキングすることを考える。

今回はyuniframeで使用しているSDLを使って、各コンパイル環境(Visual Studio + MSBuild、Visual Studio + CMake/Ninja 、 MinGW + CMake/Ninja)でのトレース取得を目指す。SDLはこれらの環境で機能的に等価なexecutableが得られることを期待しているので、何かズレがあれば指摘したいところ。

okuokuokuoku

普通にビルドする

... そもそも普通にビルドできないとお話しにならないよな。。

ソースコードをvolumeにチェックアウトする

ユーザーのパーミッションとかが面倒になるので、Bind mountではなくDocker volumeを使うことにする。

volumeは docker volume コマンドで作成できる。

$ docker volume create sdl-source
sdl-source

これを適当なコンテナイメージで操作することでソースコードを投入する。

docker run --rm -it -v sdl-source:c:/source:rw yunibuild-msys2

今回はMSYS2のイメージをGit入りで作っているのでそれを使うことにした。

Visual Studio + MSBuild

$ docker run --rm -it -v sdl-source:c:/source:rw yunibuild-msvc17

Visual Studio付属コマンドを使うためには、 vcvers64.bat を実行して環境変数類を設定してやる必要がある。

"c:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat"

今回のDocker imageにはVisual Studio標準のCMakeやNinjaも入れてある。

C:\>where ninja
c:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe

C:\>where cmake
c:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe

ビルドは普通。

msbuild /m /bl:log.binlog /p:Platform=x64 /p:Configuration=Release SDL.sln

Visual Studio + CMake/Ninja

SDLが提供するVisual Studioソリューション(SDL.sln)ではテストも同時にビルドされるが、CMakeではデフォルトではビルドされないため、 -DSDL_TESTS=ON を渡している。

cmake -G Ninja -DSDL_TESTS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo c:/source/SDL
okuokuokuoku

ビルドトレースの取得

yunibuildにおけるビルドトレースには3種類ある。

  1. 生成時トレース は、CMakeのNinjaジェネレーターで生成された build.ninja から生成されるトレースとなる。CMakeにも compile_commands.json を生成する機能があるがこれは実際のトレースに必要な機能を提供していないため、専用品が必要となる。これは meson のようなNinjaを採用した他のビルドシステムでも同様に利用できると期待される。Makeプロジェクトは kati https://github.com/google/kati を通してサポートできるかもしれない。
  2. 侵入トレース は、ツールチェインのexecutableをトレーサーに置き換え、トレーサーがコマンドラインを記録する。同様のものにbear https://github.com/rizsotto/Bear がある(ただしBearはexecutable自体をhookすることでトレーサーを不要にしている)。
  3. ビルドログトレース は、実際のビルドログをトレースとして利用する。これはmsbuildが生成時トレースを原理上サポートできないことに対する代替となる。 https://zenn.dev/okuoku/scraps/5cb30908986936

こんなにいっぱい要らないだろという感じがするが、実際にはそれぞれにメリット・デメリットがある。

pros / cons

生成時トレースはプロジェクトをビルドする必要が無く、さらに侵入も不要なためもっとも手軽なトレース手法になり得る(例えばIDE統合に適している)。が、CMakeにせよmesonにせよconfigure自体にそれなりの演算コストが掛かるため実際の運用(ABIの監視)ではこの点はあまりメリットにならない。

侵入トレースは最も強力で汎用的なトレース手法で、(Windows上では)割とよくアンチウイルス類にやられてしまう事を除けばどのようなプロジェクトでもトレースを取得できるという点で大きなアドバンテージを持っている。ただし、 事前に ツールチェーンをhookする必要がある。

ビルドログトレースはMSBuildでしか使わない。Ninjaは全てを事前計算するため、生成時トレース = ビルドログトレースとなることが充分に期待される。MSBuildは /preprocess オプションでvcxprojを展開することができるものの、この段階では各タスクへの入力がわかるのみで実際のコマンドラインに落とすことができない。

侵入トレースとビルドログトレースは、プロジェクトを 完全にクリーンビルド する必要がある。プロジェクトをインクリメンタルビルドすると本来実行されるべきコマンドが省略され、トレースが不完全になってしまう。

生成時トレースはビルド不要なため開発者の手元に組込むのに適しているが、コマンドラインに対してヒューリスティックを適用しないと "生の" コマンドラインを得られないケースが多い。

okuokuokuoku

ビルド環境

ビルドトレースの配備は複雑な作業になるため、スクリプトでwrapする。ビルド環境は統一されたテンプレートに従う。(将来的にyunibuildはDocker以外にqemuのようなVMもサポートする予定で、そこでも同様の構成を利用する。)

セッション

1つのビルドは単一のセッション、複数のビルドパス(context)で構成される。GitHub Actionsのような通常のCIシステムと異なり OSを跨いだ単一のビルドセッション が存在する可能性がある。これは特にクロス開発で必要となる。

(ただし、GitHub Actions等では明示的なアーティファクトを用いてビルド間の連携を行うことができる)

コンテナ内ディレクトリ構成

  • /rspriv -- 制御スクリプト等を格納するディレクトリ。bind mountを期待する。
  • /source -- リードオンリーのソースコード。bind mountを期待する。
  • /build -- ビルドのテンポラリ。名前付きvolume。ビルドが複数パスである場合も保持される。
  • /output -- 出力ディレクトリ。bind mountを期待する。

テンポラリや出力ディレクトリは、ホスト側では後述するセッションIDを後置される。

Windowsでは常に c:/ がプレフィックスされる。

環境変数

パラメタは環境変数を通して渡される。 (RSは旧プロジェクト名の Reposoup の略)

コンテナ内では以下の環境変数が管理される:

  • RS_EXECLOG_LOGDIR -- 侵入トレースの記録先 (/output/rstrace/<build-step-id>)
  • RS_EXECLOG_CONTEXT -- 侵入トレースのコンテキストID (<build-step-id>)
  • RS_EXECLOG_ENABLE -- 侵入トレースが有効な場合は 1
  • RS_NODE_RSPRIV_DIR -- CMake形式パス (/rspriv or c:/rspriv)
  • RS_NODE_SOURCE_DIR
  • RS_NODE_BUILD_DIR
  • RS_NODE_OUTPUT_DIR

CMake変数

  • RSHOST_SESSION_ID -- ビルドパイプラインのID (256bit hex)
  • RSHOST_BUILD_STEP_ID -- ビルドステップのID (256bit hex)。Configure 、 Build 、 Install でそれぞれ別のIDを振ることになる。

"レシピファイル" はCMakeスクリプトであり、以下のデータを設定することを期待される:

  • RSRECIPE_STEPS -- ビルドステップの順序付きリスト。
  • cmd_<STEP> / cmd_<STEP>_<num> -- ビルドステップとして実行するコマンドのリスト。 コマンドが複数の場合は序数 _1 をサフィックスする必要がある。