Gitのタグやハッシュの情報をアセンブリに埋め込む(.NET Framework)
.NET Frameworkのプロジェクトで、アセンブリにGitのタグやハッシュの情報を埋め込む方法を記載します。
動作確認時のバージョンは下記の通りです。
- Visual Studio 2022
- .NET Framework 4.7.2
言語はC#を利用して確認しています。
下記に動作確認に使ったプロジェクトを登録しています。
埋め込む情報
どのような状態で生成されたものかわかるように、下記のように設定することにします。
-
AssemblyVersion
: Gitのタグを元にファイルバージョンの形式にしたもの。- タグが
v1.1.2
ならば、1.1.2.0
となるように。
v1.0.0-beta1
となっていたら、-beta1
の部分も除去する。(フォーマットとして許容されないので)
- タグが
-
AssemblyFileVersion
:AssemblyVersion
と同じ。- タグが
v1.1.2
ならば、1.1.2
となるように。
AssemblyVersion
とは桁数が異なる。
- タグが
-
AssemblyInformationalVersion
: Gitのタグ+コミットハッシュ値も付与。- タグが
v1.1.2
ならば、ハッシュ(8桁)も付与して1.1.2.f15325d3
となるように。
v1.0.0-beta1
となっていたら、-beta1
の部分も付けたまま1.1.2-beta1.f15325d3
といった形で。(AssemblyInformationalVersion
はAssemblyVersion
のような制限がなく自由)
- タグが
MSBuild Community Tasks
.NET Frameworkのプロジェクトだと、どうやってAssemblyInfo.cs
に各種情報を動的に埋め込むのかっていうところがポイントになります。
今回はMSBuild Community Tasksというライブラリを利用します。
NuGetではMSBuildTasksという名前になっています。
このライブラリでは、様々な便利なタスクが提供されています。
その中の下記タスクを利用して実現します。
-
GitVersion
: Gitのコミットハッシュを取得するタスク -
GitDescribe
: 直近のタグの情報(git describe
で取得できる情報)を取得するタスク -
RegexReplace
: 正規表現による置換を行うタスク -
AssemblyInfo
:AssemblyInfo.cs
などを生成するタスク
MSBuild Community Tasks のインストール
ソリューションのNuGetパッケージの管理か、パッケージマネージャーコンソールでインストールします。
パッケージ名はMSBuildTasks
になります。
BeforeBuild ターゲットの追加
BeforeBuild
ターゲットでAssemblyInfo.cs
を生成し、そこでGitの情報を埋め込むようにします。
プロジェクトファイル(*.csproj)に追加します。
<Target Name="BeforeBuild">
<MakeDir Directories="Properties" />
<GitVersion ShortLength="8">
<Output TaskParameter="CommitHash" PropertyName="CommitHash" />
</GitVersion>
<GitDescribe Command="describe --first-parent" LightWeight="True" Match="v[0-9]*">
<Output TaskParameter="Tag" PropertyName="VersionTag" />
</GitDescribe>
<RegexReplace Input="$(VersionTag)" Expression="v([0-9\.]+).*" Replacement="$1">
<Output TaskParameter="Output" PropertyName="StrictVersion" />
</RegexReplace>
<AssemblyInfo CodeLanguage="CS"
OutputFile="Properties/AssemblyInfo.cs"
AssemblyTitle="Embed Version Sample"
AssemblyDescription="Embed Version Sample Description"
AssemblyConfiguration=""
AssemblyCompany=""
AssemblyProduct="Embed Version Sample"
AssemblyCopyright="Copyright c 2021"
AssemblyTrademark=""
AssemblyCulture=""
ComVisible="false"
Guid="ec86a331-8ca3-4979-a09b-e96995eac5b9"
AssemblyVersion="$(StrictVersion)"
AssemblyFileVersion="$(StrictVersion)"
AssemblyInformationalVersion="$(VersionTag.Substring(1)).$(CommitHash)" />
</Target>
以下は動作確認の際に設定した*.csprojの内容になります。
ターゲット内の各記述を詳しく説明します。
Propertiesディレクトリの作成
Properties/AssemblyInfo.cs
を生成する際に、事前にディレクトリが存在する必要があるため、MakeDir
タスクで生成します。
<MakeDir Directories="Properties" />
Gitのコミットハッシュ取得
GitVersion
タスクでGitのコミットハッシュを取得します。
ShortLength
でハッシュの桁数を指定しています。
Output
要素は、タスクの出力内容を指定したプロパティに出力するものです。下記だとタスクのCommitHash
という出力値を、CommitHash
というプロパティに出力しています。プロパティは後から$(CommitHash)
といった形で参照できます。
<GitVersion ShortLength="8">
<Output TaskParameter="CommitHash" PropertyName="CommitHash" />
</GitVersion>
GitVersion
タスクはドキュメントに例が用意されていないようなのですが、ソースコードを見ると取得できる情報とオプションの内容がわかります。
Gitの直近のタグ取得
GitDescribe
タスクでGitの直近のタグを取得します。Gitのdescribe
コマンドの内容が取得できます。
LightWeight
をTrue
とすると、注釈無しのタグも対象とします。git describe --tags
を指定したのと同じイメージです。
Match
で対象とするタグを指定できます。git describe --match
で指定したのと同じイメージです。ここではv
から始まるタグを対象にするようにしています。
git describe
では、マージ元も含めて直近のタグを参照します。マージ元の情報は欲しくないので、git describe --first-parent
の結果としたいのですが、--first-parent
を切り替えるようなオプションはありません。
Command
でgit
コマンドの引数を指定できます。デフォルトはdescribe
なのですが、そこにdescribe --first-parent
とすることで、--first-parent
を指定するようにしています。
最終的にgit describe --first-parent --tags --match "v[0-9]*"
として指定したことになります。
<GitDescribe Command="describe --first-parent" LightWeight="True" Match="v[0-9]*">
<Output TaskParameter="Tag" PropertyName="VersionTag" />
</GitDescribe>
GitDescribe
タスクもドキュメントに例が用意されていないようなのですが、ソースコードを見ると取得できる情報とオプションの内容がわかります。
アセンブリバージョンとして利用できる形式に整形
タグの情報のままだと、アセンブリバージョンとして使用できないので、RegexReplace
タスクでアセンブリバージョンとして利用できる部分を取り出します。
v1.0.0-beta
のようなタグだった場合、1.0.0
の部分のみにするようなイメージです。
<RegexReplace Input="$(VersionTag)" Expression="v([0-9\.]+).*" Replacement="$1">
<Output TaskParameter="Output" PropertyName="StrictVersion" />
</RegexReplace>
RegexReplace
タスクはドキュメントに例が記載されています。
ただ、ソースコードを見た方が理解が早いかもしれません。
AssemblyInfo.csの生成
AssemblyInfo
タスクを利用して、AssemblyInfo.cs
を生成します。
今まで取得した情報を利用することで、現在のGitの情報を元にしたAssemblyInfo.cs
を生成します。
<AssemblyInfo CodeLanguage="CS"
OutputFile="Properties/AssemblyInfo.cs"
AssemblyTitle="Embed Version Sample"
AssemblyDescription="Embed Version Sample Description"
AssemblyConfiguration=""
AssemblyCompany=""
AssemblyProduct="Embed Version Sample"
AssemblyCopyright="Copyright © 2021"
AssemblyTrademark=""
AssemblyCulture=""
ComVisible="false"
Guid="ec86a331-8ca3-4979-a09b-e96995eac5b9"
AssemblyVersion="$(StrictVersion)"
AssemblyFileVersion="$(StrictVersion)"
AssemblyInformationalVersion="$(VersionTag.Substring(1)).$(CommitHash)" />
AssemblyInfo
タスクはドキュメントに例が記載されています。
こちらもソースコードを見た方が何を指定可能なのかわかりやすかったです。
AssemblyInfo.cs
に必要な情報は全て設定できるようになっているように見えますが、もしも任意のコードを埋め込みたい場合には、AssemblyInfo
タスクの後に、FileUpdate
タスクを使ってさらに更新することでどうにかなりそうです。
AssemblyInfo.cs をバージョン管理から除外
AssemblyInfo.cs
はビルドのたびに生成されることになるため、Gitなどで管理してしまうと、毎回差分が発生することになりかねません。
AssemblyInfo.cs
を削除したうえで、バージョン管理から除外するようにします。
Gitだと.gitignore
に記載します。
(おまけ) アセンブリに埋め込んだ情報をプログラムから取得する
アセンブリに埋め込んだ情報ですが、プログラムから取得することができます。
var assembly = Assembly.GetExecutingAssembly();
Console.WriteLine($"AssemblyVersion: {assembly.GetName().Version}");
var versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
Console.WriteLine($"AssemblyFileVersion: {versionInfo.FileVersion}");
Console.WriteLine($"AssemblyInformationalVersion: {versionInfo.ProductVersion}");
AssemblyInformationalVersion
が一番多くの情報が含まれるので、AssemblyInformationalVersion
を起動時のログとして出しておくと、どのバージョンで実行された時のログなのかわかりやすくなると思います。
Discussion