[.NET/C#] アプリにgitのコミットIDを埋め込む

アプリのバージョン情報にgitのコミットIDが埋め込まれていることがよくあると思います。  

```bash:ffmpegの場合
> ffmpeg
ffmpeg version 2021-10-21-git-2aa343bb6f-full_build-www.gyan.dev Copyright (c) 2000-2021 the FFmpeg developers
```

これをC#で簡単に実現したいと思います。
今回はコミットIDとブランチ名を埋め込みます。

# VisualStudio利用時

説明文にすると長いですが、簡単です。

### 1.ビルド前イベントの登録
プロジェクトのプロパティ「ビルド前イベント」を利用してgitの情報をファイルに書き出します。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54535/3803e28d-bd03-8c16-7ae9-c61c4d93af78.png)

```bash
git rev-parse --short HEAD > $(ProjectDir)git_id.txt
git branch --show-current > $(ProjectDir)git_branch.txt
```
1行目は短いコミットID、2行目はブランチ名です。
ブランチ名が不要なら2行目は不要です。
ファイル名や出力ディレクトリは好きに変えてOKです。  

### 2.埋め込みリソース化

gitのコミットがある状態で一度ビルドすると2つのファイルができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54535/e086f975-3eff-01d9-3ec3-0bed0f046815.png)
(古いcsprojを使っている場合は「追加＞既存の項目」で2ファイルをプロジェクトに追加してください)

この2ファイルのプロパティで「ビルドアクション」を **「埋め込みリソース」** に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54535/b43e1316-af75-73b2-86e2-9a79799f0c3e.png)

### 3. .gitignoreに追記

この2ファイルをgitの管理対象外とするためにソリューションの「.gitignore」ファイルに1行追記します。

```txt:.gitignore
git_*.txt
```
VisualStudio上ではこのファイルにgitの管理対象外になったマークがつきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54535/b2e6c2d5-4b31-330b-d8ca-993cf171f5f3.png)
VisualStudio2022(17.4)以降なら「ソリューションエクスプローラー」や「git変更」画面でもこの作業は可能です。ファイルを右クリックして「このローカル項目を無視」として `.gitignore`に1ファイルずつ追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54535/643c4d80-8d8b-e772-3b86-0995200d4f17.png)



### 4.C#からコミットID情報を読み出し

C#側からは埋め込みリソースを読み出せばよいだけになります。
簡単なヘルパークラス`GitInfo`を作ってみました。

```csharp:GitInfo.cs
using System.IO;
using System.Linq;
using System.Reflection;

internal static class GitInfo
{
    public static string CommitId => GetResource("git_id.txt");
    public static string BranchName => GetResource("git_branch.txt");

    private static string GetResource(string name)
    {
        var asm = Assembly.GetExecutingAssembly();
        var resName = asm.GetManifestResourceNames().FirstOrDefault(a => a.EndsWith(name));
        if (resName == null)
            return string.Empty;
        using (var st = asm.GetManifestResourceStream(resName))
        {
            if (st == null) return string.Empty;
            var reader = new StreamReader(st);
            return reader.ReadToEnd().Trim('\r', '\n');
        }
    }
}
```
.NET Framework対応を無くせばもっとすっきり書けるのですが...
出力ファイル名を変えた方は適宜修正してください。

このクラスを使ってみます。
```csharp:Program.cs
private static void Main(string[] args)
{
    Console.WriteLine(GitInfo.CommitId);
    Console.WriteLine(GitInfo.BranchName);
}
```
以下のようにコミットIDとブランチ名が表示されました。
```
667a776
main
```

# VisualStudio以外の場合

VisualStudio編で行った上記１,２の作業を `csproj`ファイル編集で実現します。
通常は \<PropertyGroup\>しかないので以下にあるように\<Target\>以降を丸ごとコピペします。作業3,4はVisualStudio編と変わりません。

```xml:ConsoleApp1.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <!-- ここ以降を追記 -->

  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
    <Exec Command="git rev-parse --short HEAD &gt;$(ProjectDir)git_id.txt&#xD;&#xA;git branch --show-current &gt; $(ProjectDir)git_branch.txt" />
  </Target>

  <ItemGroup>
    <None Remove="git_branch.txt" />
    <None Remove="git_id.txt" />
  </ItemGroup>

  <ItemGroup>
    <EmbeddedResource Include="git_branch.txt" />
    <EmbeddedResource Include="git_id.txt" />
  </ItemGroup>

  <!-- ここまで -->
</Project>
```

# おわりに

以前は `AssemblyInfo.cs` の書き換えが主流でしたが、今回は別ファイル出力で対応しています。
これぐらいの機能はVisualStudio内の機能として実装してほしいですね・・。