複数バージョンのVisual Studioで開けるC++のプロジェクトを作る

# 解決策1: CMakeをつかう

まあ一番確実だし、他のコンパイラでも使えるしコンパイラのバージョンなんでいうわかりにくい指標ではなく対応している言語機能で制約することもできる(らしい)が、まあそれは他のサイトに解説を譲る。

# 解決策2:あえてCMakeを使わないという選択肢

今回の本題。

## `Condition` attribute
C++プロジェクトの実体は`.vcxproj`という拡張子のxmlファイルである。xmlのattributeをハックして条件分岐できるようになっている、いわゆる`Condition` attributeだ、汚いです。

- [MSBuild の条件 | Microsoft Docs](https://docs.microsoft.com/ja-jp/visualstudio/msbuild/msbuild-conditions)
- [MSBuild Conditional Constructs | Microsoft Docs](https://docs.microsoft.com/ja-jp/visualstudio/msbuild/msbuild-conditional-constructs)

こいつを活用する。

## 複数バージョンで利用するときの障害となるのはなにか

そもそもなんで普通に作ったプロジェクトが他のVisual Studioのバージョンで開けないのかを確認する。

まあ理由は簡単で`Platform Toolset`が指定されているものと開いたVSの持っているVC++のバージョンが合わないと「アップグレードしますか？」というダイアログが出るわけだ。

![C++ Property Pages - List of all C++ Platform Toolsets](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/1709d295-5779-4965-8d20-377ed6d3549d.png)


`.vcxproj`の中で該当する部分を見てみよう。下のxmlは普通にC++のプロジェクトを作ったときのものである

```xml:.vcxproj
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- 中略 -->
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v141</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <!-- 中略 -->
</Project>
```

自動生成の設定ファイルでも`Condition` attributeが使われているあたりなんか怖いが、問題となっているのは`PlatformToolset` tagだ。

## `PlatformToolset`も`Condition`で分ければいいじゃない

```xml:vcxcompat.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Label="Configuration" Condition="'$(VisualStudioVersion)' == '12.0'">
    <PlatformToolset>v120</PlatformToolset>
  </PropertyGroup>
  <PropertyGroup Label="Configuration" Condition="'$(VisualStudioVersion)' == '14.0'">
    <PlatformToolset>v140</PlatformToolset>
  </PropertyGroup>
  <PropertyGroup Label="Configuration" Condition="'$(VisualStudioVersion)' == '15.0'">
    <PlatformToolset>v141</PlatformToolset>
  </PropertyGroup>
</Project>
```

つまりこういうファイルを作ってこれを読み込めばいい。なんで`VisualStudioVersion`が`15.0`のとき`PlatformToolset`が`v150`じゃないんや！という疑問については
[Visual C++ 2017のバージョン番号がややこしすぎる件](http://qiita.com/Chironian/items/1432eb8b59eb2eefcd2d)
を参照してほしい。主にはbinary capabilityの問題だ。
上記ではVS2013/VS2015/VS2017に対応させたが、もちろん必要に応じて適宜いじれば良い。

具体的には、これを`vcxcompat.props`という名前で(説明の都合上)`.vcxproj`がある場所に(説明の都合上、ただの相対パス指定)**UTF-8で**(BOMはどっちでも)保存し、`.vcxproj`は

```xml:.vcxproj
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- 中略 -->
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <Import Project="vcxcompat.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <!-- 中略 -->
</Project>
```

というふうに書き換えればよい。何が変わったかはまあdiffればいいと思うが、`Import` tagが増えて、`PlatformToolset`タグが消滅した。

## 実例

- [L-SMASH-Works/AviSynth at master · VFR-maniac/L-SMASH-Works](https://github.com/VFR-maniac/L-SMASH-Works/tree/master/AviSynth)
- [S-H-GAMELINKS/Novel.Game.Engine.LINKS](https://github.com/S-H-GAMELINKS/Novel.Game.Engine.LINKS)

# 追記

なんかあの有名なサクラエディタの[開発者ドキュメント](https://github.com/sakura-editor/sakura/blob/master/vcx-props/project-PlatformToolset.md)にこの記事のURLを見つけてめっちゃわろてます。 @m-tmatma さんが出した[PR](https://github.com/sakura-editor/sakura/pull/866)がきっかけのようで。わざわざリンクして頂いてありがとうございます。

https://github.com/sakura-editor/sakura/blob/master/vcx-props/vcxcompat.props

をみるとわかるように、Windows SDKの切り替えにも活用できます。

まあでも良い子のみんなは新しくプロジェクト始めるときは素直にCMake使おうな？

# License

[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.ja)

<a href="https://commons.wikimedia.org/wiki/File:CC_BY_icon.svg"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/46ac46ed-3b06-47bc-a4cb-1b8458c560f0.png" alt="CC-BY icon.svg" width="88px"></a>