vcprojファイルからcompile_commands.jsonを作成する 

# vcprojファイルからcompile_commands.jsonを作成する 

[vcprojファイルからcompile_commands.jsonを作成するツール](https://github.com/udaken/VCProj2CompilationDatabase)を作りました。
この記事は、作成中に解決していった際のメモです。

大きく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を作成するツール](https://sarcasm.github.io/notes/dev/compilation-database.html#how-to-generate-a-json-compilation-database)がありますが、なかなか容易にできるツールが無く、さらにWindows CE(!)のプロジェクトファイルをサポートしているものは絶望的だったので、ツールを作成しました。

## vcprojファイルの解析

vcprojファイルはXMLファイルなので、一瞬変換できるか？と思ってしまいますが、一筋縄で行きません。

1. vcprojのスキーマがわからない。
2. システムインクルードディレクトリがわからない。
3. マクロが展開できない。

という問題があります。
[スキーマは一応ある](https://gist.github.com/udaken/644b0c0e9d94a4c39bf312d1a1174bf2)ことはあるのですが、一番欲しいところのコンパイラの設定(\<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`になっており、エラーとなってしまうことが有りました。

```c
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`の定義を覗くと、下記のようになっています。

```c
#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

```c
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が悪いわけではない)

1. `class Foo abstact {};`
2. `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 Compiler User’s Manual](https://clang.llvm.org/docs/UsersManual.html#windows)

[Clang command line argument reference](https://clang.llvm.org/docs/ClangCommandLineReference.html)

[Diagnostic flags in Clang](https://clang.llvm.org/docs/DiagnosticsReference.html)
警告が出まくるので、システムヘッダーで出るような警告は無効にした。

[GCC と Clang のオプション概要](http://solid.kmckk.com/doc/skit/current/solid_toolchain/overview.html)
Clang command line argument referenceだと説明が省かれていることがあるので、こちらで補いました。

[Compilation database](https://sarcasm.github.io/notes/dev/compilation-database.html)

[JSON Compilation Database Format Specification](https://clang.llvm.org/docs/JSONCompilationDatabase.html)