ハローワールド。
Renovate の PR を 89% 自動マージして楽している話というポストでは、Renovate[1]を自動マージする話が書かれています。
その中で、ビルド結果の差分を測定し差分がなければRenovateによる本番コードへの影響はなしと判断して自動でマージしている、という部分がありました。
これは、ライブラリを含めた全てのコードをWebPackなどでバンドルするフロントエンドでは特に有効なやり方です。
上記記事では、CircleCIを用いて行っていましたが、今回はGithub Actionsで同様のことを行ってみました。
環境
今回は、このブログに対して行ってみました。
結論から言うと、NextのSSGでは使えませんでした。ビルドするたびにファイル名が異なるので。
また、共通化のために、下記のようなComposite Actionを用意しています。下記は単純にyarn(node)をセットアップして、yarn installを行っています。そのときにキャッシュを利用して高速化しています。
name: 'yarn prepare'
description: ''
runs:
using: "composite"
steps:
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
shell: bash
- uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install
shell: bash
差分がないかを確認するアクション
差分がないかを確認するために利用するのは、Github ActionsのArtifactの機能です。これはCircleCIにもありますね。
ざっくり、下記のような戦略を取ります。
- 現在のブランチ(HEAD)でビルドして、ビルドした結果をアップロードします
- 現在のブランチの派生元ブランチ(Renovateの場合はmain|masterしかありませんが)でビルドして、ビルドした結果をアップロードします
- ビルドした2つの結果のハッシュを比較します
Github ActionsのWorkflowではこんな感じです。current-build
が1.にあたり、source-build
が2.にあたります。どちらも成功したら3.にあたるdiff
を実行します。
それぞれの動作は下記になっています。
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: build-diff
'on':
- push
jobs:
current-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/yarn-prepare
- name: yarn build current
env:
CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }}
CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }}
CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }}
NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }}
run: |
NODE_ENV=production yarn build
- name: Upload output file
uses: actions/upload-artifact@v3
with:
name: ${{ github.sha }}-current
path: out
source-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: '0'
- name: switch to source branch
run: git checkout $(git show-branch -a | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}')
- uses: ./.github/actions/yarn-prepare
- name: yarn build source
env:
CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }}
CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }}
CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }}
NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }}
run: |
NODE_ENV=production yarn build
- name: Upload output file
uses: actions/upload-artifact@v3
with:
name: ${{ github.sha }}-source
path: out
diff:
runs-on: ubuntu-latest
needs:
- current-build
- source-build
steps:
- uses: actions/checkout@v3
with:
fetch-depth: '0'
- name: download current export file
uses: actions/download-artifact@v3
with:
name: ${{ github.sha }}-current
path: out-current
- name: download source export file
uses: actions/download-artifact@v3
with:
name: ${{ github.sha }}-source
path: out-source
- name: compress current file
run: tar -cf current.tar out-current/
- name: compress source file
run: tar -cf source.tar out-source/
- name: compare build
run: |
if [ $(sha256sum current.tar | awk '{print $1}') = $(sha256sum source.tar | awk '{print $1}') ]; then
exit 0;
else
exit 1;
fi
それぞれのジョブをそれぞれ見てみます。
現在のブランチのビルド
current-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/yarn-prepare
- name: yarn build current
env:
CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }}
CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }}
CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }}
NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }}
run: |
NODE_ENV=production yarn build
- name: Upload output file
uses: actions/upload-artifact@v3
with:
name: ${{ github.sha }}-current
path: out
ビルドの部分がすごく長いですが、単純な処理となっています。
このブログはHeadless CMSとしてContentfullを採用しており、シークレットをGithub Secretsに登録している関係で、上記のようになっています。
シークレットはComposite Actionで使えないため、ビルドは上記のような感じとなっています。
- name: yarn build current
env:
CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }}
CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }}
CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }}
NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }}
run: |
NODE_ENV=production yarn build
下記がアップロードを行っている処理です。Github Actionsでは、nameのところにコマンドなどを利用して名付けることができなさそうだったので、Githubにもともとある、対象のコミットハッシュのハッシュに-current
をつけた名前でアップロードしています。
アップロード対象は、ビルド結果であるout
フォルダです。
- name: Upload output file
uses: actions/upload-artifact@v3
with:
name: ${{ github.sha }}-current
path: out
派生元のブランチのビルド
source-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: '0'
- name: switch to source branch
run: git checkout $(git show-branch -a | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}')
- uses: ./.github/actions/yarn-prepare
- name: yarn build source
env:
CTF_CDA_ACCESS_TOKEN: ${{ secrets.CTF_CDA_ACCESS_TOKEN }}
CTF_MAIN_AUTHOR_ID: ${{ secrets.CTF_MAIN_AUTHOR_ID }}
CTF_SPACE_ID: ${{ secrets.CTF_SPACE_ID }}
NEXT_PUBLIC_GTM_ID: ${{ secrets.NEXT_PUBLIC_GTM_ID }}
run: |
NODE_ENV=production yarn build
- name: Upload output file
uses: actions/upload-artifact@v3
with:
name: ${{ github.sha }}-source
path: out
大体現在のブランチのビルドと同じですが、違う箇所が少しあります。
今回は対象のコミット以外のコミットも取得する必要があるため、チェックアウト時に下記のように全ての情報をfetchする必要があります。
- uses: actions/checkout@v3
with:
fetch-depth: '0'
続いて、派生元のブランチ(というかコミット)にswitch(というかcheckout)する必要があります。
派生元のブランチを特定する方法は Gitで今のブランチの派生元ブランチを特定する - Qiita を参考にしました。
今回は、派生元のリモートブランチを特定する必要があるので、少しだけコマンドが変わって git show-branch -a
を利用しています。
- name: switch to source branch
run: git checkout $(git show-branch -a | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -1 | awk -F'[]~^[]' '{print $2}')
最後にアップロードのnameが変わっています。上記の理由と同様にコミットハッシュに -source
をつけています。
- name: Upload output file
uses: actions/upload-artifact@v3
with:
name: ${{ github.sha }}-source
path: out
ビルド結果に差分があるかを確認する
上記でビルドを行ったら、最後はそれらの差分を取るだけです。
diff:
runs-on: ubuntu-latest
needs:
- current-build
- source-build
steps:
- uses: actions/checkout@v3
with:
fetch-depth: '0'
- name: download current export file
uses: actions/download-artifact@v3
with:
name: ${{ github.sha }}-current
path: out-current
- name: download source export file
uses: actions/download-artifact@v3
with:
name: ${{ github.sha }}-source
path: out-source
- name: compress current file
run: tar -cf current.tar out-current/
- name: compress source file
run: tar -cf source.tar out-source/
- name: compare build
run: |
if [ $(sha256sum current.tar | awk '{print $1}') = $(sha256sum source.tar | awk '{print $1}') ]; then
exit 0;
else
exit 1;
fi
current-build
とsource-build
の実行を待つために下記のように設定します。
needs:
- current-build
- source-build
続いて、アップロードされたartifactをダウンロードしてきます。nameを指定してダウンロードします。pathを指定すればダウンロードする先も変更できます。
- name: download current export file
uses: actions/download-artifact@v3
with:
name: ${{ github.sha }}-current
path: out-current
- name: download source export file
uses: actions/download-artifact@v3
with:
name: ${{ github.sha }}-source
path: out-source
ダウンロードしたのはフォルダなので、簡単にチェックサムを測れるように、tarアーカイブをしておきます。
- name: compress current file
run: tar -cf current.tar out-current/
- name: compress source file
run: tar -cf source.tar out-source/
最後に、差分を測定し、差分があればエラーとします。
気を利かせて、エラーの場合はどのファイルに差分があるか、みたいなのを表示できればいいですが、今回はとりあえずこれだけです。
sha256sumはubuntuにデフォルトで入っています。結果はhashとファイル名が連結されているので、ハッシュだけを取るためにawkを利用しています。
- name: compare build
run: |
if [ $(sha256sum current.tar | awk '{print $1}') = $(sha256sum source.tar | awk '{print $1}') ]; then
exit 0;
else
exit 1;
fi
最後に
Next.jsでビルド結果を安定させる方法を求む。