🍗

Nerdbank.GitVersioningでNuGetパッケージのバージョン管理をしよう

2021/03/12に公開

NuGetパッケージをリリースする際、パッケージのバージョン番号を管理するのは煩雑で、本質的ではない作業です。そんなバージョン番号管理を助けてくれるツールとして、Nerdbank.GitVersioningを紹介いたします。

Nerdbank.GitVersioningのGitHubページはこちら。

Nerdbank.GitVersioningを学びたいシナリオについても少し説明しますが、とにかく早くNerdbank.GitVersioningの使い方を知りたい方は絵文字🚀のある場所を読んでみて、前提などが良く分からない時に他の部分を読んでみるとよいかもしれません。

セマンティックバージョニング

NuGetパッケージのバージョン番号を決めるとき、Microsoftによって推奨されている番号の付け方としてセマンティックバージョニング 2.0.0を使うのが安定した選択となるでしょう。

セマンティックバージョニングの考え方では、バージョンは「メジャーバージョン」「マイナーバージョン」「パッチバージョン」「プレリリースバージョン」から成ります。

プレリリース版を頻繁に出したい!

ここから先の節を通じて、NuGetパッケージのバージョンを管理するためにNerdbank.GitVersioningを利用する方法を解説します。今回は使い方の一例として、ぼくが実際に直面したシナリオを紹介しようと思います。

くわしい背景

ここ最近、ぼくは"Imfact"という静的なDIコンテナを開発していたのですが、開発が進んでくるとNuGet上にプレリリース版のパッケージを頻繁にアップロードしたいと思うようになりました。

しかしNuGetパッケージを作成してアップロードするという手順は、何十回も繰り返すには煩雑すぎますので、PowerShellなどのスクリプトを利用して自動化を進めていました。そんな中、自動化することが特に面倒なタスクはバージョン番号を管理することでした。

バージョン番号を自動的に進めるには、最新のバージョン番号が書かれたファイルを用意し、それを読み取り、バージョンを上げて、作業の終わった後はファイルに書き込む……といったIOのからむ複雑なスクリプトを書く必要があります。

ぼく自身でこういった作業を自動化するアプリを作ることもできますが、すでにあるものを利用できないか?と思い、調べているうちにNerdbank.GitVersioningというツールを見つけました。

nbgvのつかいかた🚀

ここから先は、Nerdbank.GitVersioningのことをnbgvと呼ぶことにします。

それから、以下のことを前提とします。

  • dotnetコマンドをインストールし、すでに使えるようになっていること
  • 管理したいプロジェクトを含むgitのリポジトリがすでにあること
  • mainという名前のブランチ上で作業していること
    • ブランチ名がmasterの方向けにはこの後補足があります。
    • ついでに、mainブランチという呼び方に馴染みのない方はこちらの記事が参考になるかもしれません。

nbgvをインストールする

まずは、nbgvをPCにインストールします。以下のコマンドを実行しましょう。

dotnet tool install -g nbgv

version.jsonを生成する

nbgvのインストールが済んだら、version.jsonというファイルのひな形を生成してみます。このファイルは、nbgvが現在のバージョンを認識するための情報などが書かれた設定ファイルです。

以下のコマンドを、対象のgitリポジトリのルートで実行してください。

nbgv install

以下のようなファイルが生成されます。

version.json
{
  "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
  "version": "1.0-beta",
  "publicReleaseRefSpec": [
    "^refs/heads/master$",
    "^refs/heads/v\\d+(?:\\.\\d+)?$"
  ],
  "cloudBuild": {
    "buildNumber": {
      "enabled": true
    }
  }
}

この時点で、このリポジトリ内のプロジェクトのバージョンは1.0-betaであることになります。このversion.jsonファイルをgitにコミットしたら、次のステップに進みます。

get-versionサブコマンドを試してみよう

version.jsonがあるフォルダで、以下のコマンドを実行してみてください。

nbgv get-version
実行結果
Version:                      1.0.1.3707
AssemblyVersion:              1.0.0.0
AssemblyInformationalVersion: 1.0.1-beta+7b0efb11e9
NuGetPackageVersion:          1.0.1-beta-g7b0efb11e9
NpmPackageVersion:            1.0.1-beta.g7b0efb11e9

get-versionサブコマンドを使うことで、現在のバージョンを取得することができます。また、このコマンドにはバージョンの書式を指定するオプションを渡すことができます。

nbgv get-version -v NuGetPackageVersion
実行結果
1.0.1-beta-g7b0efb11e9

-vオプションと、その値としてNuGetPackageVersionを指定することで、オプション無しで実行したバージョンのリストの中からひとつを選び出して表示することができました。こうして出力できたバージョン番号は、シェルスクリプトなどでNuGetのパッケージバージョンとして使用できることでしょう。

とはいえ、バージョンの末尾にg7b0efb11e9などという謎のハッシュがついていて、このままパッケージのバージョン番号として使いたい感じではないですね。このハッシュの取り除き方についてはこの後説明します。

謎のハッシュを取り除く

get-versionしたときについてくる謎のハッシュ文字列は、get-versionサブコマンドを実行した時点でのgitのコミットIDと対応しています。ただし、先頭にgが追加されています。

この状態は、パッケージのバージョン番号をコミットIDと対応させたい場面では役に立ちそうですが、ユーザーに提供するプレリリース版としては適切ではないかもしれません。

このハッシュ文字列を取り除くためには、version.jsonを少し書き換えます。publicReleaseRefSpecという配列内にある、masterという文字列をmainに書き換えましょう。

version.json
{
  "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
  "version": "1.0-beta",
  "publicReleaseRefSpec": [
-    "^refs/heads/master$",
+    "^refs/heads/main$",
    "^refs/heads/v\\d+(?:\\.\\d+)?$"
  ],
  "cloudBuild": {
    "buildNumber": {
      "enabled": true
    }
  }
}

この状態で、nbgv get-version コマンドを実行してみてください。

実行結果
Version:                      1.0.2.30851
AssemblyVersion:              1.0.0.0
AssemblyInformationalVersion: 1.0.2-beta+8378bd9a46
NuGetPackageVersion:          1.0.2-beta
NpmPackageVersion:            1.0.2-beta

これで、ハッシュ文字列が追記されないようになりました。

このハッシュ文字列は、publicReleaseRefSpec配列の中に正規表現で書かれたブランチ名が、現在作業中のブランチ名とマッチしていない場合に末尾に追記されるものです。バージョン番号にハッシュがついて困っているときは、publicReleaseRefSpecを確認してみてください。

バージョンを上げるには

バージョンを上げる2つの方法

おさらいですが、セマンティックバージョン(以下Semver)の文字列はドットやハイフンで区切られ、各部分は互いに異なる場面でインクリメントされる、あるいは0にリセットされます。

nbgvを使うとき、バージョンを上げる際の手順は主に2つあります。

リリースによってバージョンを上げる

nbgvのprepare-releaseというサブコマンドによって、現在のバージョン番号に対応した新しいブランチを作成し、同時にmainブランチでは事前に指定しておいたSemverのいずれかの桁をインクリメントさせることができます。

こちらの手順では主にメジャー、マイナー、そして時折はパッチバージョンがインクリメントされます。

この手順について、この後の節「prepare-release のつかいかた」で解説します。

コミットによってバージョンを上げる

プレリリースバージョンを頻繁にリリースしたい場面では、プレリリースを出すたびに新たなブランチが作られるのは不都合かもしれません。nbgvでは、Semverの文字列中にgit heightと呼ばれる数値を埋め込むよう設定することができます。
// ここ、文1と文2の繋がりが不明瞭

git heightは、例えば1.0.1-betaというSemverにおいてパッチバージョンの部分に埋め込んであるとすると、そのリポジトリに新たなコミットが追加されたときにバージョンがインクリメントされ、1.0.2-betaとなります。

git heightの値はversion.jsonファイルに保存されることはなく、gitリポジトリの状態に基づいて決定されます。

この手順について、この後の節「git height のつかいかた」で解説します。

prepare-release のつかいかた🚀

いま、nbgv get-versionコマンドを実行したときの状態が以下の通りだとします。

$ nbgv get-version
Version:                      1.0.2.30851
AssemblyVersion:              1.0.0.0
AssemblyInformationalVersion: 1.0.2-beta+8378bd9a46
NuGetPackageVersion:          1.0.2-beta
NpmPackageVersion:            1.0.2-beta

ここでいま、パッケージのユーザー向けに安定版をリリースしたい状況になったと想像してみましょう。そんなとき、今の設定には問題があります。

バージョン番号を見てみると、-betaというプレリリースバージョンが付いています。安定版のパッケージには通常付けないものなので、外す必要があります。とはいえ、これを外してリリースを終えたなら、また-betaを付けなおして開発を再開したいはずです。

こうしたリリース作業をする際には、nbgv prepare-releaseコマンドを使うと簡単で正確です。さっそく実行してみましょう。

$ nbgv prepare-release
v1.0 branch now tracks v1.0 stabilization and release.
main branch now tracks v1.1-alpha development.

実行した後にgitのブランチを確認してみると、v1.0という名のブランチが作られていることが分かると思います。このブランチをチェックアウトしてから、nbgv get-versionコマンドの結果を確認してみてください。

$ git checkout v1.0
Switched to branch 'v1.0'

$ nbgv get-version
Version:                      1.0.3.41834
AssemblyVersion:              1.0.0.0
AssemblyInformationalVersion: 1.0.3+6aa3f9883a
NuGetPackageVersion:          1.0.3
NpmPackageVersion:            1.0.3

-betaの取り除かれたバージョンが取得できることが分かります。パッケージ番号にはこの値が使えそうですね。

mainブランチはどうなった?

次に、mainブランチに戻ってバージョンを確認してみましょう。

$ git checkout main
Switched to branch 'main'

$ nbgv get-version
Version:                      1.1.2.64794
AssemblyVersion:              1.1.0.0
AssemblyInformationalVersion: 1.1.2-alpha+1afd5eaa24
NuGetPackageVersion:          1.1.2-alpha
NpmPackageVersion:            1.1.2-alpha

マイナーバージョンが上がっていることが分かります。また、mainブランチではプレリリースバージョンはついたままであることが分かります。

……とはいえ、さっきまでbetaだった部分がalphaになってしまっていますね。この挙動を変えるにはversion.jsonを少し書き換えるか、prepare-releaseサブコマンドにオプションを与える必要がありますが、詳しくは公式ドキュメントを読んでみてください。

git height の使い方🚀

いま、nbgv get-versionコマンドを実行したときの状態が以下の通りだとします。

$ nbgv get-version
Version:                      1.1.2.64794
AssemblyVersion:              1.1.0.0
AssemblyInformationalVersion: 1.1.2-alpha+1afd5eaa24
NuGetPackageVersion:          1.1.2-alpha
NpmPackageVersion:            1.1.2-alpha

ここで、リポジトリのルートに適当な空のファイルを作って、コミットしてみてください。その後同様にバージョンを取得すると、異なる結果が得られます。

$ nbgv get-version
Version:                      1.1.3.27754
AssemblyVersion:              1.1.0.0
AssemblyInformationalVersion: 1.1.3-alpha+6a6c9dcf1a
NuGetPackageVersion:          1.1.3-alpha
NpmPackageVersion:            1.1.3-alpha

パッチバージョンの部分がインクリメントされていることが分かります。このようにnbgvでは、コミットが追加されるたびにインクリメントされる値をバージョン番号に埋め込むことができるのです。この値をgit heightといいます。

これはプレリリースバージョンを頻繁にリリースしたい場合に楽できる方法です。バージョンを上げるコマンドを実行する必要はありませんし、新しいブランチが作られることもありません。

プレリリースバージョンを上げたい

ところでSemverでは、1.0-beta.1というふうにプレリリースバージョンの部分に数値を埋め込むことも許されています。実際ぼくの開発時も、パッチバージョンよりも優先度の低い数値として、コミットごとに上がる数値をプレリリースバージョンに使いたい状況でした。

git heightの値を埋め込む位置をプレリリースバージョンの位置にできたなら便利そうです。

git heightの位置はversion.json上で決めることができます。設定ファイルを以下のように書き換えてみましょう。

  • {height}という文字列をプレリリースバージョンの任意の位置に埋め込みます。
  • nuGetPackageVersion/semVerの設定値を追加します。これが必要なのは、-beta.1のようにプレリリースバージョン内で更にドットで区切れるようにするためです。
version.json
{
  "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
-  "version": "1.1-alpha",
+  "version": "1.1-alpha.{height}",
+  "nuGetPackageVersion": {
+    "semVer": 2.0
+  },
  "publicReleaseRefSpec": [
    "^refs/heads/main$",
    "^refs/heads/v\\d+(?:\\.\\d+)?$"
  ],
  "cloudBuild": {
    "buildNumber": {
      "enabled": true
    }
  }
}

version.jsonをリポジトリにコミットすると、バージョンは以下のようになります。

$ nbgv get-version
Version:                      1.1
AssemblyVersion:              1.1.0.0
AssemblyInformationalVersion: 1.1.0-alpha.1+2f9f4433f2
NuGetPackageVersion:          1.1.0-alpha.1
NpmPackageVersion:            1.1.0-alpha.1

設定ファイルで{height}を書いた部分が、現在のgit heightに置き換えられていますね!リポジトリに何らかの変更をコミットして、実際にこの値がインクリメントされるかも確かめてみてください。

これにて、パッチバージョンより優先度の低いプレリリースバージョンに、git heightの値を埋め込むことができました。バージョンのリスト上でも、パッチバージョンより低い優先順位で、かつ昇順に並んでくれるはずです。

パッチバージョンをリリースしたい場合🚀

ここまでで、リリースによってバージョンを上げる方法と、コミットによってバージョンを上げる方法を紹介しました。しかし、前者の方法ではリリース時に上がるのはマイナーバージョンですし、後者の方法ではパッチバージョンをコミットごとに上げることしかできません。

パッチバージョンをリリースごとに上げる方法があると便利そうです。以下のように設定ファイルを書き換えてみてください。

  • versionの値にパッチバージョンも書く
  • release/versionIncrementをbuildに設定する
version.json
{
  "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
  "version": "1.1.0-alpha.{height}",
  "nuGetPackageVersion": {
    "semVer": 2.0
  },
  "publicReleaseRefSpec": [
    "^refs/heads/main$",
    "^refs/heads/v\\d+(?:\\.\\d+)?$"
  ],
  "release": {
    "versionIncrement": "build"
  },
  "cloudBuild": {
    "buildNumber": {
      "enabled": true
    }
  }
}

設定ファイルをコミットして、現在のバージョンを確認します。

$ nbgv get-version -v NuGetPackageVersion
1.1.0-alpha.1

そして、nbgv prepare-releaseを実行してから、改めてバージョンを確認します。

$ nbgv prepare-release
v1.1.0 branch now tracks v1.1.0 stabilization and release.
main branch now tracks v1.1.1-alpha.{height} development.

$ nbgv get-version -v NuGetPackageVersion
1.1.1-alpha.2

1.1.0-alpha.11.1.1-alpha.2 になりましたね!確かにパッチバージョンがインクリメントされました。release/versionIncrementの使い方について詳しくは、公式ドキュメントを見てみてください。

おわりに

Nerdbank.GitVersioningでバージョン番号を管理する方法をご紹介しました。get-versionコマンドで標準出力へバージョン番号を出力できるので、そのまま他のコマンドに流し込んだり、変数に代入したりできるはずです。

バージョン番号の管理ツールとして好みは分かれそうですが、ぼくはかなり気に入りました!現状英語での解説しか見つけられなくて、仕様が把握しづらい部分もあって苦労したので、日本語の記事が誰かの助けになれば……と思い本記事を書いてみました。ぜひ使ってみてください!

ちなみに、GitHub Action版もあります。

余談ですが、Nerdbank.GitVersioningはdotnetチームで開発しているプロダクトのようです。Microsoftがバックに居るならそれなりに長くサポートされるかも?と思ったのも、ぼくが目を付けた理由のひとつです。

Discussion