VisualStudioのプロジェクトファイルからMakefileを作る

# WindowsからLinuxへ

VisualStudioいいですね。これを超えるIDEはそうそうないと思います。ということで、Windows上で頑張ってプログラミングしていたところで、「それ、Linuxでも試したいよね」という上司からのお達し。そもそも、Windows依存ではないので、Linuxでも動くとは思いますが、ここで大きな問題が。

それは、**Makefile**が無い。

１人開発とはいえファイル数もそこそこなので、Makefileを書くのもかなり手間がかかりそうです。CMakeに移行するのも１つの方法としてはありますが、これもMakefileと同様にVisualStudioからの移行となるとだいぶ手間がかかりそうです。

また、何よりVisualStudioメインで開発しているため、VSプロジェクトに変更があるたびにMakefileを書き直すのも面倒くさいです。

# Makefile自動生成への道

私の要求としては、

* VisualStudioで開発し続けたい
* VSプロジェクトに変更があるたびにMakefileを書き直したくない
* CMakeには移行したくない

となります。

以上のことを考えると、VisualStudioのVSプロジェクトファイルからMakefileを自動生成できれば、全ての要求を満たすことができるのではと考えました。

# 探してみる

いろいろと探してみると、やはり同じことを考えている人はいて、いくつかオープンソースのプロジェクトを見つけましたが、最新のものでも５，６年前で開発が止まっていました。

# MakeItSo

その中でも筋がよさそうなものが、MakeItSoというものでした。
しかし、ReadMeに開発者がもう開発止めるからと書いてあるではないですか・・・。

はい、ここから宣伝です。ここまで自作自演です。
ということで、MakeItSoをGoogleCodeからgithubにexportして、開発を続けました。
数年前に趣味のプログラミングで何度か使って、そのまま放置していたのですが、最初にも書いたように仕事で必要になったので、メンテナンス＆改造するようになりました。

[makeitso](https://github.com/nackdai/makeitso)

## オリジナル

VisualStudio2008、2010のC++、C#プロジェクトからMakefileを出力できます。C#プロジェクトは試したことがありませんが、C++プロジェクトからはLinuxでちゃんとMakeが通るものを出力できていました。また、ReadMeには記載されていません（Wikiの方には説明があります）が、コンフィグファイルで細かい設定ができます。例えば、VisualStudio上では必要ないけど、Linux上でのビルド時にリンクする必要があるライブラリを指定するなどです。

## 何を変えた

VS2008からの変換でできることと、VS2010からの変換の場合で挙動が異なっていたのをできるだけ同じになるようにしています。さらに、今の時代にVS2008、2010で開発することもないと思いますし、私も開発に使っているのはVS2015なので、VS2015対応しました。
あとは、現在CUDAの開発をしているので、CUDAプロジェクトに部分的ではありますが対応しました。

# 使い方

## .slnファイルからMakefileを生成

以下のコマンドで利用します。

`MakeItSo.exe -file=foo.sln -config=blah.config`

`file`オプションにはMakefileを作成する`sln`ファイルを指定します。`sln`ファイルにぶら下がっている`vcxproj`ファイルごとのMakefileと全体のMakefileを出力します。

`config`オプションにはMakefile生成時の設定を記載したコンフィグファイルを指定します。どのような設定ができるのかは後述します。

例えば、以下のような`sln`ファイルがあったとすると、

![make_sln_sample.png](https://qiita-image-store.s3.amazonaws.com/0/128377/9a477bae-2b63-bc4e-6bf1-038ef79c5ac1.png)


以下のようなMakefileが出力されます。

````Makefile:Makefile
# Builds all the projects in the solution...
.PHONY: all_projects
all_projects: Debug Release 

.PHONY: Debug
Debug: example_Debug 

.PHONY: Release
Release: example_Release 

# Builds project 'example'...
.PHONY: example_Debug
example_Debug: 
	make --directory="." --file=example.makefile Debug

.PHONY: example_Release
example_Release: 
	make --directory="." --file=example.makefile Release

# Cleans all projects...
.PHONY: clean
clean:
	make --directory="." --file=example.makefile clean
````

````Makefile:example.makefile
# Compiler flags...
CPP_COMPILER = g++
C_COMPILER = gcc

# Include paths...
Debug_Include_Path=
Release_Include_Path=

# Library paths...
Debug_Library_Path=
Release_Library_Path=

# Additional libraries...
Debug_Libraries=
Release_Libraries=

# Preprocessor definitions...
Debug_Preprocessor_Definitions=-D GCC_BUILD 
Release_Preprocessor_Definitions=-D GCC_BUILD 

# Implictly linked object files...
Debug_Implicitly_Linked_Objects=
Release_Implicitly_Linked_Objects=

# Compiler flags...
Debug_Compiler_Flags=-O0 -g 
Release_Compiler_Flags=-O2 -g 

# Builds all configurations for this project...
.PHONY: build_all_configurations
build_all_configurations: Debug Release 

# Builds the Debug configuration...
.PHONY: Debug
Debug: create_folders gccDebug/main.o 
	g++ gccDebug/main.o  $(Debug_Library_Path) $(Debug_Libraries) -Wl,-rpath,./ -o gccDebug/example.exe

# Compiles file main.cpp for the Debug configuration...
-include gccDebug/main.d
gccDebug/main.o: main.cpp
	$(CPP_COMPILER) $(Debug_Preprocessor_Definitions) $(Debug_Compiler_Flags) -c main.cpp $(Debug_Include_Path) -o gccDebug/main.o
	$(CPP_COMPILER) $(Debug_Preprocessor_Definitions) $(Debug_Compiler_Flags) -MM main.cpp $(Debug_Include_Path) > gccDebug/main.d

# Builds the Release configuration...
.PHONY: Release
Release: create_folders gccRelease/main.o 
	g++ gccRelease/main.o  $(Release_Library_Path) $(Release_Libraries) -Wl,-rpath,./ -o gccRelease/example.exe

# Compiles file main.cpp for the Release configuration...
-include gccRelease/main.d
gccRelease/main.o: main.cpp
	$(CPP_COMPILER) $(Release_Preprocessor_Definitions) $(Release_Compiler_Flags) -c main.cpp $(Release_Include_Path) -o gccRelease/main.o
	$(CPP_COMPILER) $(Release_Preprocessor_Definitions) $(Release_Compiler_Flags) -MM main.cpp $(Release_Include_Path) > gccRelease/main.d

# Creates the intermediate and output folders for each configuration...
.PHONY: create_folders
create_folders:
	mkdir -p gccDebug
	mkdir -p gccRelease

# Cleans intermediate and output files (objects, libraries, executables)...
.PHONY: clean
clean:
	rm -f gccDebug/*.o
	rm -f gccDebug/*.d
	rm -f gccDebug/example.exe
	rm -f gccRelease/*.o
	rm -f gccRelease/*.d
	rm -f gccRelease/example.exe
````

`Makefile`が`sln`ファイルに相当し、`[プロジェクト名].makefile`というMakefileがプロジェクトごとに生成されます。Makefileを見ていただくとわかりますが、`Debug`、`Release`とコンフィギュレーションごとにビルドターゲットが出力されています。
リンクするライブラリ、参照するライブラリパス、インクルードパス、プリプロセッサ定義などはプロジェクトファイルの設定から自動で出力されます。コンパイルフラグは、`Debug`では`-O0 -g`が、`Release`では`-O2 -g`が自動で出力されます。

## コンフィグファイルによる詳細設定

それでは、コンフィグファイルでどのような設定ができるのか説明します。
基本的には、GoogleCodeからサルベージしてきたwikiの日本語訳となります。

### 基本構造
コンフィグファイルのフォーマットはXMLで基本構造は以下のようになります。

````XML:MakeItSo.config
<MakeItSo>
  
  <!-- Settings that apply to all projects in the solution -->
  <AllProjects>

  </AllProjects>

  <!-- Settings for specific named projects -->
  <Project name="App">

  </Project>
  
</MakeItSo>
````

`<AllProjects>`セクションの設定はすべてのプロジェクトに反映されます。`<Project name="[name]">`セクションは個別プロジェクトへの設定になります。`<AllProjects>`セクションの設定は個別プロジェクトに反映されます。ただし、`<Project name="[name]">`セクションに同じ設定がある場合は、そちらが優先されます。
（オリジナルでは`<AllProjects>`セクションの設定は個別プロジェクトに反映されなかったのですが、いろいろと実用してみると面倒だったので変更しています。）

### ライブラリの追加と削除

もし、プロジェクトがWindows依存の外部ライブラリをリンクしている場合などは、この設定でLinux環境のものに置き換えるなどできます。ライブラリ名のプレフィックスに`lib`がついている場合は、`lib`を削除し、`-lxxxx`の形に整形して出力します。

````XML
<RemoveLibrary library="TextLibrary.lib"/>
<AddLibrary configuration="Debug" library="libTextLibD.a"/>
<AddLibrary configuration="Release" library="libTextLibR.a"/>
````

### ライブラリパスの追加と削除

ライブラリの追加と削除と併せて、必要に応じてライブラリパスも同様に追加、削除することができます。`-L`を付け、ダブルクォーテーションで囲んで出力します。

````XML
<RemoveLibraryPath path="Externals/TextLibrary/Libs/Windows x86/Debug"/>
<RemoveLibraryPath path="Externals/TextLibrary/Libs/Windows x86/Release"/>
<AddLibraryPath configuration="Debug" path="Externals/TextLibrary/libs/gcc/Debug"/>
<AddLibraryPath configuration="Release" path="Externals/TextLibrary/libs/gcc/Release"/>
````

### インクルードパスの追加と削除

ライブラリパスと同様にインクルードパスも追加、削除することができます。`-I`を付け、ダブルクォーテーションで囲んで出力します。

````XML
<RemoveIncludePath path="Include/WindowsTemplates"/>
<AddIncludePath configuration="Debug" path="Include/gccTemplates"/>
<AddIncludePath configuration="Release" path="Include/gccTemplates"/>
````

### プリプロセッサ定義の追加と削除

プリプロセッサ定義についても追加、削除することができます。`-D`を付けて出力します。

````XML
<RemovePreprocessorDefinition definition="TO_REPLACE" />
<AddPreprocessorDefinition configuration="Debug" definition="REPLACEMENT_DEBUG" />
<AddPreprocessorDefinition configuration="Release" definition="REPLACEMENT_RELEASE" />
````
### コンパイルフラグの追加と削除

コンパイルフラグについても追加、削除することができます。上述したように、`Debug`では`-O0 -g`が、`Release`では`-O2 -g`が自動で出力されますが、この設定で追加、削除することができます。例えば、C++11でコンパイルする場合ですが、残念ながら自動で判断できないので、`-std=c++11`を追加する必要があります。また、OpenMPの使用についても同様に自動で判断できないので、`-fopenmp`を追加する必要があります。

````XML
<RemoveCompilerFlag flag="-O2"/>
<AddCompilerFlag configuration="Release" flag="-O3"/>
````

### コンパイラの指定

MakeItSoではデフォルトで以下のようにコンパイラを出力します。これをコンフィグファイルで変更することもできます。

* Cファイルの場合、`gcc`
* C++ファイルの場合、`g++`
* C#ファイルの場合、`gmcs`

````XML
<CSharpCompiler compiler="dmcs"/>
<CCompiler compiler="gcc2.1"/>
<CPPCompiler compiler="g++4.3"/>
````

### 特定ファイル、特定ディレクトリ以下のファイルの無視

特定ファイル、特定ディレクトリ以下のファイルを出力しないようにすることができます。例えば、マルチプラットフォームで開発しているときに、Windows依存の実装ファイルを無視することができます。`regex`でファイル、ディレクトリに含まれている文字列を指定します。これにより、指定された文字列を含むファイルおよびディレクトリ以下のファイルを無視するようになります。

````XML
<RemoveFilesAndDirectories regex="windows"/>
````

この設定は元々は無かったのですが、私自身が必要になったため追加したものになります。

### 特定のコンフィギュレーションの削除

MakeItSoは基本的にはすべてのコンフィギュレーションについてMakefileに出力しようとしますが、場合にはよっては不要なものもある可能性があります。そうしたときには、この設定により特定のコンフィギュレーションを無視することができます。また、MakeItSoではコンフィギュレーションの区別をプラットフォームも含めて判断します。つまり、`Debug|Win32`と`Debug|x64`は別として扱います。そのため、この設定は特定のプラットフォームの削除にも使えます。

````XML
<RemoveConfiguration config="Debug|Win32"/>
<RemoveConfiguration config="Release|Win32"/>
````

この設定は元々は無かったのですが、私自身が必要になったため追加したものになります。

### 出力ディレクトリのプレフィックス指定

`example.makefile`を見ていただくと、出力ディレクトリが、`gccDebug`、`gccRelease`というようにプレフィックスに`gcc`と付いているのがわかると思います。これはMakeItSoがデフォルトで付加しています。このプレフィックスを指定できます。

````XML
<CPPFolderPrefix prefix="hoge"/>
````

プレフィックスが不要の場合は、`<CPPFolderPrefix prefix=""/>`として下さい。

### プロジェクトの無視

`sln`ファイルには多数のプロジェクトが含まれる場合があると思います。しかし、そのすべてについてMakefileを生成する必要があるとは限りません。そうした場合に、特定のプロジェクトについてMakefileを生成しないように指定できます。

````XML
<IgnoreProject project="App1"/>
````

## CUDAプロジェクト

従来のMakeItSoはC++、C#プロジェクトのみでCUDAプロジェクトは対象になっていません。CUDAプロジェクトからMakefileを生成した場合、`cpp`ファイルのみが対象となり、`cu`ファイルなどCUDAソースコードは無視されてしまいます。

そこで、MakeItSoに手を入れて、CUDAプロジェクトも扱えるようにしました。ただし、`nvcc`に渡すオプションを全てカバーしているわけではなく、自分で使うのに必要最低限のみしか対応していません。

## 制限事項

制限事項というか一部はバグぽくもありますが、以下のような制限事項があります。

* `x86`、`x64`といった複数プラットフォームを同時に正しく出力することはできません。どちらかのみを出力するようにコンフィグファイルで設定する必要があります。
* コンフィグファイルの解説でも記述しましたが、Windowsのみのライブラリであっても関係なくリンクするように出力します。リンカへの設定に含まれないようにコンフィグファイルで設定する必要があります。
* コンフィグファイルの解説でも記述しましたが、C++11でコンパイルする必要がある場合は、自動で判断できないので、コンフィグファイルの設定でコンパイルフラグに`-std=c++11`を追加する必要があります。
* コンフィグファイルの解説でも記述しましたが、OpenMPの使用する場合は、自動で判断できないので、コンフィグファイルの設定でコンパイルフラグに`-fopenmp`を追加する必要があります。
* MakeItSoはプロジェクトファイルに登録されているファイルしか対象にしません。そのため、マルチプラットフォーム開発しているときにLinux依存の実装ファイルをプロジェクトファイルに登録していないと、Makefileに出力されません。そのような場合は、プロジェクトファイルに追加してビルド対象から外すようにして下さい。
* VS2015以外は未サポートです

他にもあるかもしれませんが、私が実際に利用して把握できているものは以上です。

# 最後に

~~個人的には当面の目的は果たしたため、頻繁な更新はしないと思いますが、何かリクエストがあればgithubにでもアップしていただければと思います。~~

~~[makeitso](https://github.com/nackdai/makeitso)~~

[2023.05.02追記]
相当の期間メンテナンスしていないので、（多分ないと思いますが）リクエストがあっても対応はしないので、あしからず