VisualStudio の T4 で AssemblyInfo.cs を一括置換する

# はじめに

ソリューションの中にたくさんのプロジェクトがあって、それを全部AssemblyVersion.csでバージョン指定してあると、バージョン番号の変更が大変ですよね。

ちょっと別ツールでそういう変更をやってくれるツールを作ったりもしていたのですが、Visual Studioの標準機能でもできるらしい。ということでメモを兼ねて投稿。

# T4 テンプレート

T4 というテンプレートが Visual Studio には組み込まれているので、それを使うとビルド前に自動的にファイルのテンプレート展開をやってくれる。

T4 テンプレートの適用は簡単で、プロジェクトに新しい項目の追加で「テキストテンプレート」を選べば良い。

デフォルトではこんなファイルが出来上がる。名前は AssemblyInfo.tt にしておく。この段階ではプロジェクトの直下に作る。

```text:T4
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".txt" #>
```

まずは output extension の項目を .cs に変更して、.cs ファイルを生成するようにしよう。ついでに hostspecific を true にして Visual Studio のファイルパスにアクセスできるようにしておく。

で、この後の内容にもともとの AssemblyInfo.cs をコピーしてくる。コピーしたら元の AssemblyInfo.cs はいらなくなるので削除する（そうしないと、Properties の下に移動できない）。
削除が終わったら、プロジェクトの直下にあった AssembyInfo.tt を Properties の下に移動する。

このままではまだ AssemblyInfo.tt を手書きで書き直さないといけないので、共通のファイルを読みにいくように変更しよう。

ソリューションの直下に以下のようなVersion.t4ファイルを作る。

```
<#
    int majorVersion = 1;
	int minorVersion = 2;
	int buildNumber = 1;
	int revision = 1;
#>
```

このファイルを読み込むように AssemblyInfo.tt にコードを書こう。
include ディレクティブを書けばよい。

```
<#@ include file="..\..\Version.t4" #>
```

このバージョン番号を使ってバージョン文字列を生成しよう。

```csharp
<#
	string verString = String.Format("{0}.{1}.{2}.{3}", majorVersion, minorVersion, buildNumber, revision);
#>
```

で、この値を使って AssemblyVerion, AssemblyFileVersion を書き換えよう。
```
[assembly: AssemblyVersion("<#= verString #>")]
[assembly: AssemblyFileVersion("<#= verString #>")]
```

# バージョンを変えたときに自動的にT4変換を実行させる

このままだと、.ttファイルを編集しないと Visual Studio は「変えなくて良いんだな～」と判断して、AssemblyInfo.cs を変更してくれない。そこで、拡張機能の「AutoT4」をインストールする。インストールすると設定不要で、いつでも.tt を変換してくれるようになる。



# Jenkins との連携
Jenkins （というか MSBuild）で自動的にテンプレート変換を実行するには骨が折れる様子。
http://mediaingenuity.github.io/2013/12/18/text-template-transformation-t4-integration-msbuild.html このページを参考にすればいけそうだが・・・？


以下を入れた上でちょっと色々いじるらしい。
Visual Studio 2013 SDK
https://www.microsoft.com/en-us/download/details.aspx?id=40758

Modeling SDK for Microsoft Visual Studio 2013
https://www.microsoft.com/en-us/download/details.aspx?id=40754


2010はこれ
Visual Studio 2010 SDK
https://www.microsoft.com/en-us/download/details.aspx?id=2680

2010SP1だとこれ。SP1当ててると上のは動かない。
http://www.microsoft.com/en-us/download/details.aspx?id=21835

Microsoft Visual Studio 2010 Visualization & Modeling SDK
https://www.microsoft.com/en-us/download/details.aspx?id=23025

2012も似たような名前なので適当に探して欲しい

.csprojに以下の変更を加える。Projectタグの直下でOKらしい。

```xml
<PropertyGroup>
  <TransformOnBuild>True</TransformOnBuild>
  <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets" />
```


・・・Visual Studio2013で作ってるんだけど、.Net Framework の4.5にしているせいか、＄（VisualStudioVersion)がv11.0になるせいでビルドできない・・・

# 手順＠VS2010

まずは適当なプロジェクトを作る。

![20150628-175411.png](https://qiita-image-store.s3.amazonaws.com/0/53147/eddb1e41-cf48-cfdf-b145-126f77601469.png)

新しい項目でAssemblyInfo.ttを作る。

![20150628-175725.png](https://qiita-image-store.s3.amazonaws.com/0/53147/34ea0704-ddea-2198-da5e-e42ec47cefc9.png)


できたらProperties以下に移動する。

ソリューション直下に各プロジェクトで共通で使うバージョン番号のテンプレートファイルを作る。
![20150628-180448.png](https://qiita-image-store.s3.amazonaws.com/0/53147/ff5aae21-36cf-fd13-558e-135c6c647d33.png)

AssemblyInfo.ttを変更する。
![20150628-181016.png](https://qiita-image-store.s3.amazonaws.com/0/53147/0d859578-4437-9b75-ff8b-497088ab5c43.png)

・・・と、ここまで書いておいて、Version.t4の変更を検知して.ttの再変換を行ってくれる方法が必要なことに気づいた。

http://www.matthiaseinig.de/2013/05/20/auto-generate-fileversion-for-all-projects-in-a-solution-with-t4/ では、AssemblyInfoを共通のファイルを使うやり方を紹介している。

MSBuildでT4変換できるようにすれば、ビルド前イベントでこうすれば良いようだ。

```
%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "$(ProjectPath)" /t:TransformAll
```

残る問題はチーム開発でこれ全部インストールする必要があることかな・・・
試してないけど、Importをこうやれば、VS SDK/Modeling SDKをインストールしていなくてもビルドエラーが出ないようになる・・・かも？

```xml
<Import Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v$(VisualStudioVersion)\Microsoft.TextTemplating.targets')" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v$(VisualStudioVersion)\Microsoft.TextTemplating.targets" />
```

## 追記

こんな感じの定義を.csprojの最後に入れればビルド前イベントでMSBuildを使わなくてもOKのようだ。一応、ファイルの有無チェックをしているので、VS SDK、VS Modeling SDKを入れてなくてもエラーは出ないはず・・・多分。

```xml:.csproj
  <PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
    <T4Path>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets</T4Path>
    <T4Path Condition="'$(VisualStudioVersion)' == '10.0'">$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TextTemplating\v$(VisualStudioVersion)\Microsoft.TextTemplating.targets</T4Path>
  </PropertyGroup>
  <Import Condition="Exists('$(T4Path)')" Project="$(T4Path)" />
  <PropertyGroup Condition="Exists('$(T4Path)')">
    <TranformOnBuild>true</TranformOnBuild>
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
    <BuildDependsOn>
      TransformAll;
      BeforeBuild;
      CoreBuild;
      AfterBuild;
    </BuildDependsOn>
  </PropertyGroup>
```