見切り発車

とりあえずかきとめたい

Visual Studio のClangCL ツールセットのコンパイラを差し替える

動機

そもそもの動機は、libclang でソースコードを解析するときに渡すオプションの類をVisual Studio のvcxproj から引っ張り出せないか、というものです。

vcxproj はxml 形式のテキストファイルで、中を覗くとコンパイルオプションなどが含まれているのがわかりますが、Import でどこからともなく別ファイルを取り込んできたり$VCInstallDir のようなどこで定義されているのか分からないプロパティ(環境変数?)があったり、Condition 属性であまり馴染みのない高度な機能を持っているっぽい条件式を評価する必要があったりと、解析するのはなかなか大変です。

MSBuild でうまいことやる方法とか無いものか、と考えつつ試行錯誤していく中で、ふと「Visual Studio がclang に渡しているであろう引数を、clang のふりをして受け取ってしまえばよいのでは?」と思いつきました。

そこで「visual studio clang 自作」でググってみたところ、Visual Studioでも最新のClangが使いたい! という記事が見つかりました。

内容はMS のclang に渡されるオプションを公式のclang に渡すためにツールセットを自作する、ということのようです。やろうとしていることにとても近いので大いに参考にさせていただくことにしました。

しかし、対象となっているVisual Studio のバージョンが2017 なので現在利用できる2022 ではそのまま使えないところもあるため独自の調査も必要そうです。

ひとまず呼び出されるコンパイラを差し替えてみよう、というのが今回試した内容です。

とっかかり

ツールセットのフォルダに含まれるToolset.props, Toolset.targets を独自に編集することでどうにかできるということはわかりましたが何をどうすればいいのかがはっきりしません。

そこで、vcxproj の中身を展開してみます。ツールセットにClangCL(LLVM clang-cl) が指定されたvcxproj をMSBuildプリプロセスしてやるとImport 要素をすべて取り込むことができます。

> MSBuild /pp:test.pp.vcxproj test.vcxproj

/pp オプションに出力ファイル名を指定しますがこれは任意でよさそうです。またMSBuild のパスが通っていない場合はVisual Studio の開発者用コンソールを使用するとよいです。

出力されたファイル内を探すと、clang-cl.exe の名前が見つかります。

  <PropertyGroup>
    <TargetPlatformIdentifier>Clang.Windows</TargetPlatformIdentifier>
    <ToolsetISenseIdentifier>$(TargetPlatformIdentifier)</ToolsetISenseIdentifier>
    <CLToolExe>clang-cl.exe</CLToolExe>
    <LinkToolExe>lld-link.exe</LinkToolExe>
    <LibToolExe>llvm-lib.exe</LibToolExe>
    <!-- 略 -->
    <ExecutablePath>$(LLVMInstallDir)\bin;$(ExecutablePath)</ExecutablePath>
    <!-- 略 -->
  </PropertyGroup>

今のところ詳細は不明ですが、CLToolExe という要素でコンパイラを指定しているようです。また、CLToolExe にはexe ファイル名だけが指定されていてパスはExecutablePath 要素で指定しているようです。このふたつを変えてどうなるのか見てみます。

やってみる

今回試してみたものをgithub に上げました。

github.com

まず、clang-cl.exe と置き換えるものとしてclang-hook.exe というプログラムを用意しています。これは受け取った起動引数を標準出力に表示してから終了コード1 で終了します。コンパイラを置き換えているのにオブジェクトファイルを出力していないのでそこでエラーとしてリンクに進まないようにするためです。今回はコンパイラを差し替える方法の調査が目的なのでこの先の処理のことは後で考えます。

次にclamg-hook.exe を適用する対象としてtest というプロジェクトを用意しています。中身は何でもよいです。

CLToolExe, ExecutablePath の変更は参考先の記事ではツールセットを自作して実現していますが、今回はCMake で生成したvcxproj を後行程で加工するようなPowerShellスクリプトを書いています。実行してみるとtest.vcxproj の末尾にPropertyGroup を追加します。

  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <ImportGroup Label="ExtensionTargets">
  </ImportGroup>
  <PropertyGroup>
    <CLToolExe>clang-hook.exe</CLToolExe>
    <ExecutablePath>$(SolutionDir)hook\$(Configuration);$(ExecutablePath)</ExecutablePath>
  </PropertyGroup>
</Project>

CLToolExe, ExecutablePath は直接vcxproj に含まれてはいませんが最後に上書きしてやろうというねらいです。

結果

スクリプトを実行し生成されたclang-hook.sln をVisual Studio で開いてビルドしてみると、出力にclang-hook.exe がエラーを返した旨が表示されます。また、clang-hook.exe には引数としてclang-cl に渡すためのオプションをまとめた.rsp ファイルのパスが渡されているのもわかります。

このようにコンパイラの差し替えはわりと単純に実現できそうなことがわかりました。素敵な仕組みのようです。