vcprojファイルからcompile_commands.jsonを作成する
vcprojファイルからcompile_commands.jsonを作成するツールを作りました。
この記事は、作成中に解決していった際のメモです。
大きく2つの問題が有りました。
- vcprojファイルの解析
- ソースコードのエンコーディング
一応動くようになったと思いますが、まだ完成度は低いです。
モチベーション
なんの業かわかりませんが、2019年現在でVisual Studio 2008(VC++ 2008)で開発が必要になっていることがあります。
最近はclangを中心としたエコシステム(clang-tidyやclang-doc)が充実しその恩恵にあやかりたいところですが、clangおよび周辺ツールで使用するcompile_commands.jsonを使用する必要があります。
そのため、何らかの方法で、Visual C++ 2008のプロジェクトファイル(.vcproj)からcompile_commands.jsonを作成する必要があります。
Visual Studio 2008(VC++ 2008)からcmakeに移行し、CMakefile.txtからcompile_commands.jsonを作成すればいい話ではありますが、日々変わっていく既存の大量のプロジェクトを前にすると、とても移行できる気がしません。
いくつかのcompile_commands.jsonを作成するツールがありますが、なかなか容易にできるツールが無く、さらにWindows CE(!)のプロジェクトファイルをサポートしているものは絶望的だったので、ツールを作成しました。
vcprojファイルの解析
vcprojファイルはXMLファイルなので、一瞬変換できるか?と思ってしまいますが、一筋縄で行きません。
- vcprojのスキーマがわからない。
- システムインクルードディレクトリがわからない。
- マクロが展開できない。
という問題があります。
スキーマは一応あることはあるのですが、一番欲しいところのコンパイラの設定(<Tool Name="VCCLCompilerTool>"の部分)は含まれてません。
というわけで、自力での解析は諦めていりいろ調べているうちに、Visual Studio 2008 に同梱されているVCProjectEngine.dll(Windows CEだとVCProjectWCEPlatform.dll)というCOMライブラリの存在を知りました。
devenv.exeで参照できる情報は大方このライブラリのVCCLCompilerToolインターフェースで参照できるようなので、これを使うことに決めました。
COMライブラリなので、VC++でツールの実装はできるのですが、文字列処理が面倒そうなので、C#で実装しました。
あとはVCCLCompilerToolインターフェースをひたすらclangのコマンドラインオプションに変換するコードを書いていきます。
clangでコンパイルしたときの問題
ツールで作成したjsonですんなりclangが動作すればいいのですが、手元のコードやSDKは古い、かつポータビリティを考慮していないものが多いので問題が多発します。
_Complex
でエラーになる場合がある
SDKによっては、_cabs関数の宣言が、引数名がCのキーワード_Complex
になっており、エラーとなってしまうことが有りました。
double __cdecl _cabs(struct _complex _Complex);
ローカル環境のSDKのヘッダーを直すか、clangのコマンドラインで-D _Complex=_Complex_value
と置き換えることになります。
ツールのデフォルト設定で、clangのコマンドラインに-D _Complex=_Complex_value
を指定するようにしておきました。
ms-compatibility-versionで指定するバージョンは_MSC_VER
を指定しなければならない
当初、VARIANT
構造体の_VARIANT_BOOL bool
がエラーになることが有りました。
「あれ、MSVCって、boolを変数名にできるの?」と思いながら_VARIANT_BOOL
の定義を覗くと、下記のようになっています。
#if !__STDC__ && (_MSC_VER <= 1000)
/* For backward compatibility */
typedef VARIANT_BOOL _VARIANT_BOOL;
#else
/* ANSI C/C++ reserve bool as keyword */
#define _VARIANT_BOOL /##/
#endif
どうやら、_MSC_VER <= 1000
になっているようです。
-fms-compatibility-version=
に、VC++のバージョンを設定していましたが、ドキュメントを見ると_MSC_VER
の方にしないと行けないようでした。
それにしても、MSVCのバージョン体系はクソ。
_MoveFromCoprocessor/_MoveFromCoprocessor2でオーバーロードエラーになる
Windows CE(ARM)のintrinsicsヘッダーファイル armintr.h
を使用すると、エラーになる。
C:\Program Files (x86)\Windows Mobile 5.0 SDK R2\PocketPC\include\ARMV4I\armintr.h(59,5) : error: functions that differ only in their return type cannot be
overloaded
int _MoveFromCoprocessor(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int); //mrc
~~~ ^
C:\Program Files (x86)\Windows Mobile 5.0 SDK R2\PocketPC\include\ARMV4I\armintr.h(59,5) : note: previous implicit declaration is here
previous implicit declaration is hereに続いて何も出力されておらず、
clangのBuiltinを見ると、戻り値はunsigned intのようなので、おそらくこれが問題なのであろう。
https://github.com/llvm-mirror/clang/blob/master/include/clang/Basic/BuiltinsARM.def
LANGBUILTIN(_MoveFromCoprocessor, "UiIUiIUiIUiIUiIUi", "", ALL_MS_LANGUAGES)
LANGBUILTIN(_MoveFromCoprocessor2, "UiIUiIUiIUiIUiIUi", "", ALL_MS_LANGUAGES)
これはシステムのヘッダーファイルを修正しました。
但し、MSVCで警告が出るようになります。
armintr.h(59) : warning C4391: 'unsigned int _MoveFromCoprocessor(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int)' : 組み込み関数に対して戻り値の型が無効です。'int' であるべきです。
MSVC拡張
clangでは積極的にMSVC拡張に対応してくれているが、それでも対応していないものがある。
手元では、下記の2点が対応していなかった。
(clangが悪いわけではない)
class Foo abstact {};
-
virtual
修飾のない、void x() override {}
こんなコードを修正するべきです。
ソースコードのエンコーディング
clangは今の所、ソースコードのエンコーディングはUTF-8しか受け入れません。
(有志がShift-JIS対応を出されたりしてますが。)
こればっかりは仕方が無いので、ツールに簡易エンコーディング変換機能をつけました。
(手動でやっても良いのですが。)
compile_commands.jsonを作成しながらUTF-8に変換する --autoutf8
と、
プロジェクトに含まれるファイルをUTF-8に変換するだけ(jsonを作成しない)--utf8
です。
jsonを作成しながらUTF-8に変換するだけでも良い気がしそうですが、このツールではヘッダーファイルの依存関係がわからないので、予め全部変換してするのが通常の使い方になるかと思います。
心苦しいですが、UTF-8にはBOMを付けるので、一応はVisual Studioでは文字化けせずに開けます。
ツールのスコープ外の使い方
vcprojに沿ったcompile_commands.jsonに変換するようにしたつもりですが、clangおよび周辺ツールで使用することはあくまで「参考程度」と考えています。
クロスコンパイルのように、clangでオブジェクトが作れるレベルでの変換は目的にしてません。
参考にしたページ
Clang command line argument reference
Diagnostic flags in Clang
警告が出まくるので、システムヘッダーで出るような警告は無効にした。
GCC と Clang のオプション概要
Clang command line argument referenceだと説明が省かれていることがあるので、こちらで補いました。