しばやん雑記

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

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

非常に今更感がある内容なのと、Azure Pipelines の方が便利なんじゃないか疑惑もありますが、AppVeyor を使っているプロジェクトが多いので、やっと真面目に対応しました。

GitHub 上で新しく Release を作成すると、そのタグ名をバージョンにして NuGet パッケージを作成して、デプロイするという流れです。そしてタグ以外の場合はビルドはしますがパッケージは作成しません。

一部 NuGet とは関係ない部分もありますが、AppVeyor を使う場合に注意したい部分です。

Pull Request 作成時に 2 度ビルドされるのを防ぐ

普通に AppVeyor を使い始めると悩むのが、この PR 作成時の 2 度行われるビルドです。

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

ビルドが数秒で終わる場合ならまだマシですが、時間がかかる場合には最悪です。

イマイチ最適な解を見いだせなかったのですが、対象のブランチをデフォルトブランチに絞れば解決します。多くの場合は master なので、master だけビルドするようにします。

branches:
  only:
    - master

この場合でも Pull Request では問題なくビルドされますが、マージ先を master にする必要はあります。

Pull Request でビルド番号を増やさない

これは割とおまけ感ありますが、AppVeyor は Pull Request でのビルド時にはビルド番号を増やさずに、suffix を付けて識別する機能があります。

pull_requests:
  do_not_increment_build_number: true

同じ Pull Request の場合は同一のビルド番号が使われるので、履歴が分かりやすいです。

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

ビルド番号をバージョンに含めている場合などは、PR の雑なコミットで増やしたくないケースもあるかと。

タグが打たれた時だけ nupkg を作る

GitHub でタグが打たれた時は APPVEYOR_REPO_TAG の値が true となるので、それを利用してビルドスクリプトの挙動を変えます。AppVeyor ではビルドに条件を書けないので、PowerShell を使って対応します。

build_script:
  - ps: |
      dotnet build .\NuGetTest\NuGetTest.csproj -c Release
      if ($env:APPVEYOR_REPO_TAG -eq $true) {
          dotnet pack .\NuGetTest\NuGetTest.csproj -c Release --no-build -p:Version=$env:APPVEYOR_REPO_TAG_NAME
      }

これで通常のコミットではビルドだけ実行されますが、タグの場合は nupkg が作成されるようになります。

タグ名をパッケージ生成時に渡すようにしているので、これで NuGet パッケージのバージョンがオーバーライドされます。一緒に --no-build を渡しているので再度ビルドは行われません。

NuGet の API キーを環境変数で渡す

NuGet へのデプロイに必要な API キーを appveyor.yml に含めておくのは、暗号化されていても良い気分がしないのと、キーのローテートの時にコミットするのが面倒だったので、環境変数で渡すことにしました。

AppVeyor の設定から API キーを追加しておきます。鍵ボタンをクリックすると暗号化されます。

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

API キーを使う場合には $(NUGET_API_KEY) というような形で名前を指定します。

deploy:
  - provider: NuGet
    api_key: $(NUGET_API_KEY)
    skip_symbols: true
    artifact: /.*\.nupkg/
    on:
      appveyor_repo_tag: true

そしてデプロイの条件で appveyor_repo_tag: true を指定して、タグの時のみ動くようにします。

完成した appveyor.yml

作成した appveyor.yml の全体は以下の通りです。個別に見ると大したことはしていない簡単な処理です。

version: '{build}'

branches:
  only:
    - master

pull_requests:
  do_not_increment_build_number: true

image:
  - Visual Studio 2017

build_script:
  - ps: |
      dotnet build .\NuGetTest\NuGetTest.csproj -c Release
      if ($env:APPVEYOR_REPO_TAG -eq $true) {
          dotnet pack .\NuGetTest\NuGetTest.csproj -c Release --no-build -p:Version=$env:APPVEYOR_REPO_TAG_NAME
      }

artifacts:
  - path: '**\*.nupkg'

deploy:
  - provider: NuGet
    api_key:
      secure: $(NUGET_API_KEY)
    skip_symbols: true
    artifact: /.*\.nupkg/
    on:
      appveyor_repo_tag: true

ビルドするプロジェクトの指定周りは少し注意が必要です。プロジェクト名を明示的に指定しなくてもパッケージ作成は行えますが、プロジェクトが多い場合には時間がかかったりするので、注意したいところです。

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

テストでビルドしたり、タグを打ってリリースを確認しておきました。問題なくデプロイされています。

長期的には Azure Pipelines への移行を考えていますが、しばらくは AppVeyor を使います。