TL;dr
- 現在使えるWindows環境のClangはどれも一長一短
- バイナリは最新のものを、MSBuildツールセットの設定はClang/C2を使いたい
- そうだ、ツールセットを自作しよう
Windows環境でつかえるC++コンパイラ
不自由なWindows環境に囚われし呪われたみなさん、こんにちは1。
私としては、Linuxが覇権を握ってWindowsを滅ぼしてくれても一向に構わないのですが、デスクトップPCでWindowsのシェアがトップである現状、ソフトをより多くのユーザーに使ってもらうためにはWindowsを考慮しないわけにはいきません。
Windowsで動くソフトを作るにはWindowsを入れるしかない、Windowsを入れるならどうせライセンス料がかかる、というわけで私は、実機にはWindowsをインストールして、Linuxを使いたい時はゲストにする人生を送って来ました。
さて、Windowsで使えるC++と言うと、まず真っ先にMSVCが立ちふさがるわけですが、MSVCのC++17対応はまだお粗末と言わざるを得ません。
どのくらいお粗末かと言うと、このコードが内部エラーでコンパイルできないくらいにはお粗末です。
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を使ってビルドできます。
公式でバイナリを配布していますし、(さすがにNightlyとはいかないようですが)新しめのスナップショットを手に入れることもできますので、手軽に試すことができます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。
さて、ところで、MSVCからClangを使う方法としてはもう一つ、Clang/C2というものがあります。Clang with Microsoft Codegenとも呼ばれているものです。
LLVMがClangにcl互換レイヤーをかぶせたのとは逆のアプローチで、MicrosoftはMSBuildにClangModeというビルドモードをぶちこんで、Clang本来のコンパイルオプションを直接渡せるようにしています。Clang/C2自体はほぼClangですが、Microsoftがいくつか変更を加えているものと思われます。
で、このClang/C2なのですが、ぶっちゃけ古くて使い物になりません。
#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。
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ターゲット」「v14110」ツールセットのパスは
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かどうか判定するからです(参照)。今時、CMakeで使えないツールセットなんて価値がありませんね。
まあ、元からVisual Studioインストールディレクトリの下を弄るので自己責任でやってください案件ではあるのですが、名前も被せていかないといけない汚いハックです。我慢してv999_clang_hogehoge
とかにしましょう。v999でいいかどうかは分かりませんが。
Toolset.propsとToolset.targetsはMSBuildプロジェクトファイルスキーマで書かれています。基本的に、条件判定しながらプロパティを設定していく形式の、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ファイルと一緒に渡すことができません。
これを何とかするために、以下の回避策が考えられます。
- clangのソースコードに手を入れる
- clangにコマンドライン引数を転送するプロキシアプリを作る
1を実行するには、ビルド済みバイナリと一緒に配布することになります。まあそれはそれで便利かもしれませんが、LLVMの公式バイナリを使いたいみたいな要望もあるでしょうし、ソース管理が面倒なのでなるべく手は入れたくないです。
という訳で、2の方法を選びました。これは普通の(つまり面倒くさい)C++プログラミングなので割愛します。
成果物
という訳で、完成品がこちらです。
インストールスクリプトを起動して、LLVMのパス(デフォルトはレジストリを見て探したインストール済みのパス)やツールセット名を指定すると、Visual Studio 2017のツールセットディレクトリの下にインストールしてくれます。
なおアンインストールの仕組みは作ってないのでお別れする時はゴミ箱にポイしなければいけません。
突貫工事で作ったので、うまく動かない環境があったりするかもしれませんが、人柱になってやるぜという方はぜひ使ってみてください。
既知の問題
- 32bitターゲットにちゃんと対応していない
- 32bitホストにも対応していない
- 既に存在するツールセット名を指定した場合問答無用で上書きされてしまう
- アンインストールができない
-
かくいう私もWindowsユーザーでね……。 ↩
-
MinGWでlibc++をビルドできれば、libc++を静的ライブラリとしてリンクすることで依存をなくせるかもしれませんが、そこまでできるかどうかは分かりません。 ↩
-
私はそれでも最新が使いたいので自前でビルドします。 ↩
-
解釈してくれても機能実装状況はお察しなのですが。 ↩
-
ツールセットの設定項目になくても、追加のオプションを加えることはできるじゃないか、と思うかもしれません。
もちろんそうなのですが、CMakeに.vcxprojファイルを自動生成させる時、直接CMAKE_CXX_FLAGSに指定してもなぜか追加してくれないのです。つらい。(追記:どうやら何か勘違いをしていたらしいです。CMAKE_CXX_FLAGSを指定すればc++17にできました。このツールの有用性が下がった……) ↩ -
そのうちLLVMがやってくれるかもしれませんが……。 ↩
-
MSVCのC++ヘッダは
<iostream>
が<type_traits>
に依存しているので<iostream>
が使えません。std::printf
を使っているのはそういう訳です。 ↩ -
あるいはそれより新しい自前ビルド ↩
-
本当は、MicrosoftがClang/C2のバージョンを上げるなりLLVMがClangModeの設定ファイルを作るなりしてくれるのが一番いい気がしますが……。 ↩
-
v141はVisual Studio 2017のデフォルトのツールセット ↩