semantic-release による継続的デリバリー(CD)の最適な設計を考える
これはなに
semantic-release と GitHub を使ってソフトウェアパッケージの継続的デリバリー(CD)を構築するうえで留意すべきポイントと、それらを踏まえた最適なリリースワークフローを考えるものです。
背景情報
一般的に Node.js といったソフトウェアパッケージのリリースプロセスには、次のバージョン番号の決定、CHANGELOGやリリースノートの生成、パッケージの公開など、多くのステップが必要です。 semantic-releaseは、Semantic Versioning(SemVer2)に基づいてこれらのリリース・プロセスを自動化するツールですが、これを用いて CD を設計する場合には対象とするリポジトリー(≒ プロジェクト)の開発戦略に応じて考慮すべき点がいくつかあります。以下は本稿で前提となるリポジトリーの開発戦略です。
1.トランクベース開発
トランクベース開発とは、すべての開発者がトランクと呼ばれる単一のメインブランチ上で作業し、機能開発に必要な作業をより細かいタスクに分け、短期間に頻繁にメインブランチにコミットするという開発手法です(トランクへのコミットは直接ではなく Pull Request経由で行う)。
2. Squash and Merge
作業ブランチ上のコミット履歴をひとかたまりに圧縮してからトランクにマージする戦略です。圧縮したものは1つの新しいコミットとしてトランクにマージされます。これによりトランク上のコミット履歴は、各作業ブランチに相当する Pull request (PR) と 1:1 でマッピングされるようになります。
上記2つのポイントを前提とした開発リポジトリーに semantic-release を導入しても適切に運用できるワークフローを設計する必要があります。
リリースワークフローを実施するブランチとタイミングについて考える
先述の2点を前提とする場合、以下を考慮する必要があります。
- いつ: リリースワークフローを実施するタイミング
- どこ: リリースワークフローを実施するブランチ
トランクベース開発の場合、原則としてブランチはトランクと作業ブランチの2種類しか存在せず、全ての PR はトランクにしか向かいません。これを踏まえたうえで、リリースフローをいつ(タイミング)・どこ(ブランチ)で実施するかを考えます。
案1.トランクへのマージをトリガーにして実施
トランクへのマージをリリースワークフロー実施のトリガーとします。新機能や改修を実装してトランクにマージするたびにリリースワークフローが走ることとなります。それら機能実装における変更差分が1つの PR に収まる規模であるうちは、この方法が最もシンプルかつ妥当な方法となります。
ただし、1つの機能実装が PR を複数に分割したくなるくらいの規模となった場合、この方法は破綻します。
例えば1つの新機能を実装するために PR を10回に分割したくなったとします。その場合、9回目までの PR は機能実装の途中段階であるため、これらの時点でリリースするわけにいきません。つまり、最後の10回目がその機能実装の最後の PR であることを識別し、これがマージされて初めてリリースワークフローが実施されねばならないということです。よってトランクへのマージをトリガーにすることは非現実的であるといえます。
案2. リリースブランチへのマージをトリガーにして実施
リリース用ブランチを別途用意し、トランクからそちらへの PR を作成、マージをトリガーにしてワークフローを実施するというものです。ここで作成する PR には前回のリリース時点からの差分が累積されており、これらをまとめてリリース用ブランチにマージしてリリースワークフローに乗せるというものです。トランクにいくらマージしてもワークフローは実施されないため、開発者は大規模な機能実装を複数の PR に分割することが可能となります。
問題は、リリース用ブランチへマージする際は Squash and Merge でなく Create Merge にせねばならないことです。さもないと、リリース用ブランチとトランクとの間でコミット履歴に整合性が取れずコンフリクト状態とみなされて2回目以降のマージができなくなってしまします。
また、semantic-release によるリリースタグ、リリースノート、CHANGELOG の生成および package.json
の version
フィールド更新は、リリース用ブランチ上で行われます。すなわちそれはCHANGELOG と package.json
のアップデート差分がトランクの履歴に表れないことを意味し、これを解消するためにリリース用ブランチの最新コミットを cherry-pick してトランクに取り込む作業が追加で必要となります。
全ての起点となるトランク、リリースに特化したブランチとで責務が分かれることで整理はされますが、その分ワークフローが複雑化することに留意せねばなりません。
案3. 手動(workflow_dispatch) によるトリガーで実施
先述の案はすべてブランチへのマージをトリガーにしたものでしたが、そうではなく GitHub Actions の workflow_dispatch イベントをトリガーにしてリリースワークフローを実施します。開発者はトランクベースで機能開発を進め、任意のタイミングでリリースワークフローを手動実行できます。
また、トランクでリリースワークフローを実施できることで、 semantic-release によるアップデート差分がトランク以外のブランチに発生する問題も解消されます。
name: Release
on:
workflow_dispatch:
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
# ...
workflow_dispatch
を実行する UI。
結論: 案3 が最適
上記の3つの案を比較した結果、案3 が最も適切であると考えます。semantic-release はあらゆるリリースワークフロー自動化ツールの中でも特に「自動化」に重きをおいていますが、トランクベース開発において semantic-release によるリリースワークフローを適切に運用するためには、リリースワークフローのトリガーを開発者が任意で操作できることが重要です。これにより、開発者はリリースワークフローを適切なタイミングで実行でき、機能開発やリリースの粒度を適切にコントロールできます。
参考文献
Discussion