しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Pipelines で NuGet パッケージのデプロイを自動化

AppVeyor から Azure Pipelines への移行を見越しつつも、ちゃんと NuGet パッケージの発行周りを検証していなかったので Build と Release のパイプラインを組みました。

AppVeyor での自動化については前回書いたエントリを参照してください。

やっていることは AppVeyor と変わらないですが、Azure Pipelines では少しはまるポイントがあります。NuGet のタスクはありますが、テンプレートはないのである程度は手で組む必要はあります。

今回組んだパイプラインは以下のような動作となります。まあ、よくあるやつです。

  • 通常のコミットはビルド(とテスト)だけ行う
  • GitHub で Release が作られた時にはタグ名で nupkg を作成し、NuGet.org にプッシュ
  • タグ名は v1.0.0 のように v から始まるものだけ許可

タグの有無で動作が変わってくる部分が肝です。AppVeyor ではスクリプトで全て書いていたので if で簡単に分岐しましたが、Azure Pipelines ではそのあたり少し工夫します。

具体的にはビルド(とテスト)用と NuGet リリース用に Build のパイプラインを分けてしまう方法と、タスクの実行条件を使ってスキップさせる方法のどちらかを選ぶことになります。

今回はタスクがシンプルだったので、1 つのパイプラインで実行条件を指定する方法にしました。

Build パイプライン

.NET Standard 向けのライブラリを nupkg としてビルドする場合は、単純に .NET Core CLI を使ってビルドとパッケージングを行ってあげれば良いので、タスクはとても単純になります。

今回は以下のような 4 つのタスクを組み合わせて作成しました。build と pack が分かれているのは、通常のコミットではビルドだけ実行したいからです。

f:id:shiba-yan:20190629130844p:plain

dotnet build 以降のタスクには実行条件としてタグの prefix を指定しているので、master などのブランチに対するコミットの場合はスキップされます。

YAML として書き出した場合の全体は以下の通りです。タスクに condition が付いてるのが少し微妙ですが、良い方法が無さそうだったのでこのようにしています。

pool:
  name: Hosted Windows 2019 with VS2019

steps:
- task: DotNetCoreCLI@2
  displayName: 'dotnet build'
  inputs:
    arguments: '--configuration $(BuildConfiguration)'

- powershell: 'echo "##vso[task.setvariable variable=PackageVersion]$($env:Build_SourceBranchName.Substring(1))"'
  displayName: 'PowerShell Script'
  condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))

- task: DotNetCoreCLI@2
  displayName: 'dotnet pack'
  inputs:
    command: pack
    nobuild: true
    versioningScheme: byEnvVar
    versionEnvVar: PackageVersion
    verbosityPack: Normal
  condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: drop'
  condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))

ちなみに間に挟まっている PowerShell Script はタグ名から v prefix を外すための処理です。色々と調べても文字列操作が出来ないっぽかったので、仕方なく PowerShell を使って書いています。

タグ名からバージョン文字列を変数として用意出来れば、後は dotnet pack に任せます。versionEnvVar に用意した変数名を指定すれば、そのバージョンで nupkg を作ってくれます。

これでタスクはタグの有無で動作が変わってくれるので、後は Trigger としてタグを追加します。

f:id:shiba-yan:20190629132430p:plain

YAML を使っている場合はタグ名で Trigger の条件を書けますが、UI からはブランチ名しか指定出来ないので refs/tags/{tagname} というフォーマットで指定します。

これで一通り完成したので、適当にコミットするとビルドだけが走ります。

f:id:shiba-yan:20190628191455p:plain

そして GitHub から新しい Release を作成すると、nupkg 作成まで実行されます。

f:id:shiba-yan:20190628191445p:plain

このパイプラインで作成された nupkg は Artifact に保存されているので、後は Release パイプラインで NuGet へのプッシュを行ってあげるようにします。

Release パイプライン

NuGet へのプッシュは .NET Core CLI の nuget push ではなく、NuGet タスクの push を使います。.NET Core CLI では暗号化された API Key を扱えないので、エラーとなるためです。

f:id:shiba-yan:20190629135202p:plain

作成されたパッケージのプッシュ先は Azure Artifacts だと簡単ですが、NuGet.org や他のリポジトリでは新しく Connection を作成する必要があります。

NuGet.org へのプッシュの場合は API Key を作成して、以下のように Feed URL を設定します。

f:id:shiba-yan:20190629131828p:plain

あまり関係ないですが、NuGet の API Key は期限付きなので安全ですが、更新を忘れそうです。

Release 用のタスクはこれだけで終わりですが、こっちでも Trigger としてタグを指定します。ドロップダウンでは選択できないので、直接入力すれば良いです。

f:id:shiba-yan:20190629131451p:plain

これで GitHub Release を作成すると、自動的に NuGet パッケージのデプロイまで走るようになります。

f:id:shiba-yan:20190629132419p:plain

バージョン名の扱いで少しシンプルさが欠けますが、全体を見ると単純なパイプラインで実現出来ました。

テンプレートである程度までパイプラインが組めるようになれば楽だと思いました。