リリース時にのみ行う処理はできるかぎり減らす

GitHub Actionsなどでテストやリリースを自動化していると、様々なトリガーによって異なる処理を行うことはよくあることです。 例えばpushのトリガーではテストやlintを行い、tagをpushしたときはクロスビルドしてリリースする、などです。 これらの処理は内容も頻度も異なるので、ワークフローのファイルを分けることはよくあることだと思います。 私もかつてはこのようにトリガーや実行したい頻度が異なるのだから分けるのは自然なことだと考えていました。 一つのワークフローの中でトリガーによって処理を分岐させるとワークフローが複雑になるし、面倒なことが多いからです。

しかし、最近はリリース時の処理をテストと同じワークフローにまとめる方が良いと考えるようになりました。 リリースする時になって初めて動く処理が多いほど、リリースのワークフローが壊れるリスクが高まるからです。 GitHub Actionsになって、依存するactionの更新や実行するrunnerの更新など、ジョブが外的要因によって壊れることが増えたように感じます。 クロスビルドやDockerイメージのビルドなどリリースする時しか使っていないツールやactionがあると、それらの更新を取り込んだ後の最初のリリースで動かないことに初めて気が付くというリスクがあります。 ワークフローが分かれていると、リリースのコミットやその時の外的要因 (CIの不調とか依存の更新とか) によってテストが落ちるようになったのにリリースされてしまうという懸念もあります。

テストもリリースも一つのワークフローにまとめた上で、リリース時にのみ行う処理はできるかぎり減らすというのが良いと思います。 リリースかどうかの分岐をあちこちに書く羽目になったとしても、です。 クロスビルドは普段からビルドすれば良いですし、Dockerイメージのビルドも常に行っておくべきです。 リリース時には、そういうビルド済みのアーティファクトをダウンロードしてアップロードするだけとか、Dockerイメージをpushするだけとか (docker/build-push-actionならpushオプションでオンオフできる)、そういう処理に限ることが望ましいです。 要するに、リリースのためのビルド処理は、テストと同じ頻度で行いましょうということだと思います。 また、テストが通らなかったらリリースを止めたいという素朴な要件が、ワークフローがまとまっていれば簡単に達成できるのです (GitHub Actionsがワークフロー間の依存を定義しにくいという事情もあるでしょう。workflow_runってみなさん使ってます?)。

私はjqのメンテナをやっているのですが、jqのCIもテストとリリースを一つのワークフローにまとめています。 それでもなお、dependabotによるactions/upload-artifactactions/download-artifactの更新PRが別々に来た時に、他のメンテナが前者のみをマージしてしまい一時的にリリースジョブが壊れてしまったことがあります。 ビルドした実行ファイルをダウンロードしてDockerイメージを作るためにdownload-artifactを使っていたのですが、そのジョブはリリースの時にしか実行していませんでした。 今は、PRの作成時もDockerイメージのビルドを行うようにしています。 github.com

CIリソースを心配されるかもしれませんが、大体のケースではキャッシュを活用すれば節約できますし、リリースジョブが壊れにくくするための必要なコストかなと思っています。 もちろんEnvironmentsの都合などで常には実行できない処理は諦めざるを得ない場面もあるでしょう。 できるだけリリースする時のリスクを減らすための考え方の一つとしてご紹介しました。 他の手としては、リリース用のワークフローは手動でdry runできるようにしておく方法もありそうです (経験上、どんなワークフローでもworkflow_dispatchをつけておいて損はないと思います)。