.NET Core のNugetパッケージと中身のDLLのバージョンにGitのコミットIDを含める

だいぶ前に、.NET Framework でAssemblyInfoをいじってファイルバージョンを自動採番するというのをやったのですが、

gozuk16.hatenablog.com

最近は、.NET Coreのプロジェクトが始まってやり方が色々違うし、どうやらJavaでやってたようにGitのコミットIDを入れられそうだというので試してみました。 あと、最近はNexusリポジトリにNugetパッケージをアップしているのですが、開発中にファイル名が同じだとキャッシュが更新されないことがあったのでビルドのたびにファイル名を変えたいというのがあります。 合わせて中身のDllの製品バージョンも一緒にできるといいなーと。

tech.guitarrapc.com

ここのサイトでは、GitVersioningとGitInfoが紹介されていたのでまずはシンプルそうなGitInfoを試してみました。

プロジェクトを新規作成

>dotnet new console -o assemblyInfoTest

cd assemblyInfoTest

>dotnet run
Hello World!

プロジェクトにGitInfoを追加

www.nuget.org

>dotnet add package GitInfo --version 2.0.20

早速Gitの情報を取得してみます。

using System;
using System.Diagnostics;
using System.Reflection;

namespace assemblyInfoTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine($"GitBranch: {ThisAssembly.Git.Branch}");
            Console.WriteLine($"GitCommit: {ThisAssembly.Git.Commit}");
            Console.WriteLine($"GitSha: {ThisAssembly.Git.Sha}");
            Console.WriteLine($"GitBaseVersionMajor: {ThisAssembly.Git.BaseVersion.Major}");
            Console.WriteLine($"GitBaseVersionMinor: {ThisAssembly.Git.BaseVersion.Minor}");
            Console.WriteLine($"GitBaseVersionPatch: {ThisAssembly.Git.BaseVersion.Patch}");
            Console.WriteLine($"GitCommits: {ThisAssembly.Git.Commits}");
            Console.WriteLine($"GitTag: {ThisAssembly.Git.Tag}");
            Console.WriteLine($"GitBaseTag: {ThisAssembly.Git.BaseTag}");
            Console.WriteLine($"GitSemVerMajor: {ThisAssembly.Git.SemVer.Major}");
            Console.WriteLine($"GitSemVerMinor: {ThisAssembly.Git.SemVer.Minor}");
            Console.WriteLine($"GitSemVerPatch: {ThisAssembly.Git.SemVer.Patch}");
            Console.WriteLine($"GitSemVerLabel: {ThisAssembly.Git.SemVer.Label}");
            Console.WriteLine($"GitSemVerDashLabel: {ThisAssembly.Git.SemVer.DashLabel}");
            Console.WriteLine($"GitSemVerSource: {ThisAssembly.Git.SemVer.Source}");
            Console.WriteLine($"GitIsDirty: {ThisAssembly.Git.IsDirty}");

            var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
            var fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
            var productVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
            Console.WriteLine($"assemblyVersion: {assemblyVersion}");
            Console.WriteLine($"fileVersion: {fileVersion}");
            Console.WriteLine($"productVersion: {productVersion}");
        }
    }
}
C:\home\gozu\projects\assemblyInfoTest>dotnet run
Hello World!
GitBranch: master
GitCommit: 5ac94b0
GitSha: 5ac94b013eef929d3b8a932247966bd5f9a87952
GitBaseVersionMajor: 0
GitBaseVersionMinor: 0
GitBaseVersionPatch: 0
GitCommits: 2
GitTag:
GitBaseTag:
GitSemVerMajor: 0
GitSemVerMinor: 0
GitSemVerPatch: 2
GitSemVerLabel:
GitSemVerDashLabel:
GitSemVerSource: Default
GitIsDirty: True
assemblyVersion: 1.0.0.0
fileVersion: 1.0.0.0
productVersion: 1.0.0

簡単に取れました。

Git情報を埋め込んでみる

以下を追加しました。

[assembly: AssemblyVersion(ThisAssembly.Git.BaseVersion.Major + "." + ThisAssembly.Git.BaseVersion.Minor + ThisAssembly.Git.BaseVersion.Patch)]
[assembly: AssemblyFileVersion(ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch)]
[assembly: AssemblyInformationalVersion(ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch + "-" +
    ThisAssembly.Git.Branch + "+" + ThisAssembly.Git.Commit)]
>dotnet run
obj\Debug\netcoreapp2.2\assemblyInfoTest.AssemblyInfo.cs(12,12): error CS0579: System.Reflection.AssemblyFileVersionAttribute' 属性が重複しています。 [C:\home\gozu\projects\assemblyInfoTest\assemblyInfoTest.csproj]
obj\Debug\netcoreapp2.2\assemblyInfoTest.AssemblyInfo.cs(13,12): error CS0579: System.Reflection.AssemblyInformationalVersionAttribute' 属性が重複しています。 [C:\home\gozu\projects\assemblyInfoTest\assemblyInfoTest.csproj]
obj\Debug\netcoreapp2.2\assemblyInfoTest.AssemblyInfo.cs(16,12): error CS0579: System.Reflection.AssemblyVersionAttribute' 属性が重複しています。 [C:\home\gozu\projects\assemblyInfoTest\assemblyInfoTest.csproj]

ビルドに失敗しました。ビルド エラーを修正して、もう一度実行してください。

.NET CoreだとAssemblyInfo.csが生成されるので書き換えたいプロパティの生成を抑制する必要があるらしい。

csprojに以下を追加しました。

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
    <GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
    <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
  </PropertyGroup>
>dotnet run
Hello World!
GitBranch: master
GitCommit: 37fdbb4
GitSha: 37fdbb48f4c47b29089f6dd0ce36178c21500861
GitBaseVersionMajor: 0
GitBaseVersionMinor: 0
GitBaseVersionPatch: 0
GitCommits: 3
GitTag:
GitBaseTag:
GitSemVerMajor: 0
GitSemVerMinor: 0
GitSemVerPatch: 3
GitSemVerLabel:
GitSemVerDashLabel:
GitSemVerSource: Default
GitIsDirty: False
assemblyVersion: 0.0.0.0
fileVersion: 0.0.3
productVersion: 0.0.3-master+37fdbb4

おー、出来た!

CIで外部から指定できるように

想定ではJenkinsでnupkgのファイル名とDllの製品バージョンをビルドごとに一意なバージョンにするなので、GitのコミットIDを付加する方向で行きます。

外部から指定できるようにcsprojに Version を定義しておきます。 1.0.0としてますが書き換わるので気にしない。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
    <!--GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute-->
    <!--GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute-->
    <Version>1.0.0</Version>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="GitInfo" Version="2.0.20">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

</Project>

コードの方も調整します。 FileVersionだけは入れておこうかという感じです。

using System;
using System.Diagnostics;
using System.Reflection;

//[assembly: AssemblyVersion(ThisAssembly.Git.BaseVersion.Major + "." + ThisAssembly.Git.BaseVersion.Minor + ThisAssembly.Git.BaseVersion.Patch)]
[assembly: AssemblyFileVersion(ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch)]
//[assembly: AssemblyInformationalVersion("1.0.0" + "-alpha." + ThisAssembly.Git.Commit)]

namespace assemblyInfoTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine($"GitBranch: {ThisAssembly.Git.Branch}");
            Console.WriteLine($"GitCommit: {ThisAssembly.Git.Commit}");
            Console.WriteLine($"GitSha: {ThisAssembly.Git.Sha}");
            Console.WriteLine($"GitBaseVersionMajor: {ThisAssembly.Git.BaseVersion.Major}");
            Console.WriteLine($"GitBaseVersionMinor: {ThisAssembly.Git.BaseVersion.Minor}");
            Console.WriteLine($"GitBaseVersionPatch: {ThisAssembly.Git.BaseVersion.Patch}");
            Console.WriteLine($"GitCommits: {ThisAssembly.Git.Commits}");
            Console.WriteLine($"GitTag: {ThisAssembly.Git.Tag}");
            Console.WriteLine($"GitBaseTag: {ThisAssembly.Git.BaseTag}");
            Console.WriteLine($"GitSemVerMajor: {ThisAssembly.Git.SemVer.Major}");
            Console.WriteLine($"GitSemVerMinor: {ThisAssembly.Git.SemVer.Minor}");
            Console.WriteLine($"GitSemVerPatch: {ThisAssembly.Git.SemVer.Patch}");
            Console.WriteLine($"GitSemVerLabel: {ThisAssembly.Git.SemVer.Label}");
            Console.WriteLine($"GitSemVerDashLabel: {ThisAssembly.Git.SemVer.DashLabel}");
            Console.WriteLine($"GitSemVerSource: {ThisAssembly.Git.SemVer.Source}");
            Console.WriteLine($"GitIsDirty: {ThisAssembly.Git.IsDirty}");

            var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
            var fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
            var productVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
            Console.WriteLine($"assemblyVersion: {assemblyVersion}");
            Console.WriteLine($"fileVersion: {fileVersion}");
            Console.WriteLine($"productVersion: {productVersion}");

        }
    }
}

Jenkinsfileで以下のようにdotnetコマンドを実行してビルドとパッケージングをします。

bat """chcp 65001

for /f "usebackq tokens=*" %%a in (`git rev-parse --short HEAD`) do @set GIT_COMMIT=%%a
for /f "usebackq tokens=*" %%a in (`git rev-parse --abbrev-ref HEAD`) do @set GIT_BRANCH=%%a

dotnet build -p:Version="1.0.0-alpha.%GIT_COMMIT%"
dotnet pack --no-build -p:PackageVersion="1.0.0-alpha.%GIT_COMMIT%"
"""

これで、製品バージョンに 1.0.0-alpha.xxxxxxx と埋め込まれたDllを含めた、 assemblyInfoTest.1.0.0-alpha.xxxxxxx.nupkg が出来ました。

1.0.0-aplpha のところはまあどこかで一元的に定義しないといけないですけどね。