Visual Studioでも最新のClangが使いたい！

# TL;dr

* 現在使えるWindows環境のClangはどれも一長一短
* バイナリは最新のものを、MSBuildツールセットの設定はClang/C2を使いたい
* そうだ、[ツールセットを自作しよう](https://github.com/kazatsuyu/fafnir/)

# Windows環境でつかえるC++コンパイラ

不自由なWindows環境に囚われし呪われたみなさん、こんにちは[^1]。

[^1]: かくいう私もWindowsユーザーでね……。

私としては、Linuxが覇権を握ってWindowsを滅ぼしてくれても一向に構わないのですが、デスクトップPCでWindowsのシェアがトップである現状、ソフトをより多くのユーザーに使ってもらうためにはWindowsを考慮しないわけにはいきません。
Windowsで動くソフトを作るにはWindowsを入れるしかない、Windowsを入れるならどうせライセンス料がかかる、というわけで私は、実機にはWindowsをインストールして、Linuxを使いたい時はゲストにする人生を送って来ました。
さて、Windowsで使えるC++と言うと、まず真っ先にMSVCが立ちふさがるわけですが、MSVCのC++17対応はまだお粗末と言わざるを得ません。
どのくらいお粗末かと言うと、このコードが内部エラーでコンパイルできないくらいにはお粗末です。

```cpp
class X {
    static inline auto x = 0;
};
```

なので、C++17を使いたいマンとしては早々にMSVCに見切りを付けて、別のコンパイラを使うことにします。
Windows環境で使えるコンパイラ、まずGCCは

* GCC on Cygwin
* GCC on MinGW
* GCC on (Ubuntu | openSUSE Leap | SUSE Linux Enterprise Server) on WSL

等があるわけですが、どれも基本はLinuxアプリケーションをWindowsで実行するための枠組みです。
CygwinはLinuxPOSIXラッパーのcygwin.1.dllに依存しますし、WSLはWindowsがLinuxカーネルの機能を提供しているため、WSL上で走るバイナリはLinuxのものと変わりありません。
強いて言えば、MinGWでCプログラムをビルドするとWindowsのライブラリのみに依存するバイナリが作れるのですが、C++はlibstdc++に依存してしまいます[^2]。
まあ、CygwinやMinGWに依存してもいいのであれば、依存ライブラリを含めて配布するのも手ではあると思いますが、ここでは扱いません。

さて、Clangはどうかというと、Cygwin、MinGW、WSLそれぞれで利用する方法はありますが、LLVMは公式でMSVCのcl.exe互換の実行ファイル（clang-cl.exe）をビルドできるようになっています。
LLVM自体、CygwinやMinGWも必要なく、CMakeでMSVCのソリューションファイルを生成してMSBuildを使ってビルドできます。
[公式でバイナリを配布しています](http://releases.llvm.org/download.html)し、（さすがにNightlyとはいかないようですが）[新しめのスナップショット](http://llvm.org/builds/)を手に入れることもできますので、手軽に試すことができます[^3]。

[^2]: MinGWでlibc++をビルドできれば、libc++を静的ライブラリとしてリンクすることで依存をなくせるかもしれませんが、そこまでできるかどうかは分かりません。
[^3]: 私はそれでも最新が使いたいので自前でビルドします。

# MSBuildでClangを使う方法

実はLLVMの公式インストーラーは、MSBuildのツールセットを追加してくれます。
なので、Visual Studioのプロジェクトのプラットフォーム ツールセットを"LLVM-vs20xx"みたいな名前の奴にすればclang-clを使ってビルドすることができるようになるわけなのですが、このツールセット、対応範囲がVisual Studio 2015までなのです。もちろんVisual Studio 2017のMSBuildはこれをコンパイルすることができますが、問題は、最近のcl.exeが解釈できる/stdコンパイラオプションを指定できないのです。
cl.exeでC++17の機能を使いたい場合、（最新のものであれば）/std:c++17オプションを解釈してくれます。それより新しい言語機能を使いたければ、/std:c++latestという手もあります[^4]。
clang-clも、/stdオプションを与えてコンパイルすることができます。ツールセットの設定項目さえちゃんと存在すれば、ちゃんとC++17でビルドできるはずなのです[^5]。
つまり、現状可能な解決策その1としては、LLVMツールセットの2017版を作るというものになります[^6]。

[^4]: 解釈してくれても機能実装状況はお察しなのですが。
[^5]: ツールセットの設定項目になくても、追加のオプションを加えることはできるじゃないか、と思うかもしれません。~~もちろんそうなのですが、CMakeに.vcxprojファイルを自動生成させる時、直接CMAKE_CXX_FLAGSに指定してもなぜか追加してくれないのです。つらい。~~（追記：どうやら何か勘違いをしていたらしいです。CMAKE_CXX_FLAGSを指定すればc++17にできました。このツールの有用性が下がった……）
[^6]: そのうちLLVMがやってくれるかもしれませんが……。

さて、ところで、MSVCからClangを使う方法としてはもう一つ、Clang/C2というものがあります。Clang with Microsoft Codegenとも呼ばれているものです。
LLVMがClangにcl互換レイヤーをかぶせたのとは逆のアプローチで、MicrosoftはMSBuildにClangModeというビルドモードをぶちこんで、Clang本来のコンパイルオプションを直接渡せるようにしています。Clang/C2自体はほぼClangですが、Microsoftがいくつか変更を加えているものと思われます。
で、このClang/C2なのですが、ぶっちゃけ古くて使い物になりません。

```cpp
#include <cstdio>

int main() {
    std::printf("%s", __clang_version__);
}
```

こいつを実行すると3.8.0とか表示されます。いつの時代のClangだ、HEADは7.0.0だぞ！
MSVC特有のコンパイラマジックもちゃんと読み取れないので、`<type_traits>`とかincludeすると余裕で死にます[^7]。
やはりLLVM公式のバイナリを使ったほうがマシだ……となるのですが、Clang/C2がイケてないのはバージョンが古いことで、Clang/C2のために追加されたMSBuildのClangModeは良いものだと思います。cl互換のオプションを無理矢理渡すより、Clang本来のオプションを渡したほうが良さそうですね。
となると、バイナリはLLVM公式のもの[^8]、ツールセットはClangModeで動作する設定ファイルを作るのが最適解のような気がします[^9]。

[^7]: MSVCのC++ヘッダは`<iostream>`が`<type_traits>`に依存しているので`<iostream>`が使えません。`std::printf`を使っているのはそういう訳です。
[^8]: あるいはそれより新しい自前ビルド
[^9]: 本当は、MicrosoftがClang/C2のバージョンを上げるなりLLVMがClangModeの設定ファイルを作るなりしてくれるのが一番いい気がしますが……。

# MSBuildツールセットの作り方

Visual Studio 2017のツールセット設定ファイルは、`$(VSInstallDir)Common7\IDE\VC\VCTargets`の下あたりに入っています。
`$(VSInstallDir)`がどこかと言うと、レジストリの`HKLM\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7`キーの下の`15.0`の値がそれになっているはずです。64bit Windowsの場合、Professionalなら`C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\`、Communityなら`C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\`を見れば見つかるのではないでしょうか。
デスクトップアプリは`VCTargets`ディレクトリ直下の`Platforms`の下に、ストアアプリは`Application Type\Windows Store\10.0\Platforms`の下に、ターゲットアーキテクチャごとのディレクトリがあります。更にその下の`PlatformToolsets`ディレクトリの中に、ツールセットの名のディレクトリがあり、その中に設定ファイル`Toolset.targets`と`Toolset.props`というファイルが入っています。
一例ですが、「64bit Windows」「Visual Studio 2017 Professional」「デスクトップアプリ用」「64bitターゲット」「v141[^10]」ツールセットのパスは

[^10]: v141はVisual Studio 2017のデフォルトのツールセット

```
C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets\Platforms\x64\PlatformToolsets\v141
```

にあります。
ツールセットを追加するにはここに新しいフォルダを追加する必要があるのですが、このとき、新しいツールセットの名前は、`/v[0-9]+_clang_.*/`という正規表現にマッチしないといけません。
何故かと言うと、Clang/C2のツールセット名が`v141_clang_c2`になっていて、CMakeがこの名前を頼りにツールセットがClangModeかどうか判定するからです([参照](https://github.com/Kitware/CMake/blob/c1930caffe827b937d05dca9ffb82ba0b0b20eb2/Modules/Platform/Windows-MSVC.cmake#L298))。今時、CMakeで使えないツールセットなんて価値がありませんね。
まあ、元からVisual Studioインストールディレクトリの下を弄るので自己責任でやってください案件ではあるのですが、名前も被せていかないといけない汚いハックです。我慢して`v999_clang_hogehoge`とかにしましょう。v999でいいかどうかは分かりませんが。

Toolset.propsとToolset.targetsは[MSBuildプロジェクトファイルスキーマ](https://docs.microsoft.com/ja-jp/visualstudio/msbuild/msbuild-project-file-schema-reference)で書かれています。基本的に、条件判定しながらプロパティを設定していく形式の、XMLの皮を被ったスクリプトファイルです。これにツールセットのバイナリのパスや、ビルド時のコマンドライン引数などを設定すればツールセットが出来上がります。

Visual Studio 2017がインストールする設定ファイルに`Microsoft.Cpp.Clang.props`や`Microsoft.Cpp.Clang.targets`といったファイルがあります。このファイルにClangModeの基本的な設定が入っているので、後は必要な部分を上書きしていきます。
Clang/C2をインストールするとさらに複数の設定ファイルが追加されるので、それらを参考にすれば良いです。

# RSPファイルのパース問題

MSBuildは、ビルド時にコマンドライン引数をRSPファイルという一時ファイルに出力し、その一時ファイルのパスを@に続けてコンパイラに渡すという挙動をします。
例えば、

```
clang.exe -c -std=c++17 -o hoge.obj hoge.cpp
```

というコマンドを渡す場合でも、実際には

```
clang.exe @path_to_tmp\hogehoge.rsp
```

というコマンドが呼ばれ、`hogehoge.rsp`の中身が`-c -std=c++17 -o hoge.obj hoge.cpp`になっています。
ClangにはこのRSPファイルの解釈方法が2通りあり、引数がPOSIXシェルの方式であると理解する場合と、Windowsシェルの方式であると理解する場合があります。
ClangのデフォルトはPOSIXなので、Windowsモードで解釈してもらわないといけません（なお、Clang/C2はMSが手を入れてWindowsモードがデフォルトになるようにしてあるようです）
Windowsモードで解釈してもらうには、RSPファイルと一緒に`--rsp-quoting=windows`というオプションを渡す必要があるのですが、単にオプションを指定してもRSPファイルの中に入ってしまうので、RSPファイルと一緒に渡すことができません。
これを何とかするために、以下の回避策が考えられます。

1. clangのソースコードに手を入れる
2. clangにコマンドライン引数を転送するプロキシアプリを作る

1を実行するには、ビルド済みバイナリと一緒に配布することになります。まあそれはそれで便利かもしれませんが、LLVMの公式バイナリを使いたいみたいな要望もあるでしょうし、ソース管理が面倒なのでなるべく手は入れたくないです。
という訳で、2の方法を選びました。これは普通の（つまり面倒くさい）C++プログラミングなので割愛します。

# 成果物

という訳で、[完成品がこちらです](https://github.com/kazatsuyu/fafnir/releases/tag/v1.0.0.1)。
インストールスクリプトを起動して、LLVMのパス（デフォルトはレジストリを見て探したインストール済みのパス）やツールセット名を指定すると、Visual Studio 2017のツールセットディレクトリの下にインストールしてくれます。
なおアンインストールの仕組みは作ってないのでお別れする時はゴミ箱にポイしなければいけません。
突貫工事で作ったので、うまく動かない環境があったりするかもしれませんが、人柱になってやるぜという方はぜひ使ってみてください。

# 既知の問題

* 32bitターゲットにちゃんと対応していない
* 32bitホストにも対応していない
* 既に存在するツールセット名を指定した場合問答無用で上書きされてしまう
* アンインストールができない