自分は Go で CLI ツールを作って配ることが多く、それの配布には GitHub Actions と Homebrew の Taps repository を利用している。このリリース作業は結構面倒になので、極力自動化したい。
リリースの流派は「どこでやるか」「どのようにやるか」などの観点で、いろいろな考え方がある。
- WHERE
- 手元でコマンド一発でぜんぶやる派
-
git push --tags
から CI でやる派
- HOW
- 全部入りツールでエイッてやる(e.g. GoReleaser)
- 「一つのことをうまくやる」ツールを組み合わせる(e.g. goxz + ghr + maltmil)
自分は「CI でやる」「ツールの組み合わせでやる」派。そして、このリリースフローを実現・再利用するため、 CircleCI Orbs と GitHub Actions それぞれに公開している。本記事では、これらの利用・実装コードから CircleCI と GitHub Actions を比べてみたい。
利用者目線
CircleCI Orbs
izumin5210/github-releases および izumin5210/homebrew で実現している。
Go で CLI ツールを書いたときは timakin/go-module や izumin5210/go-crossbuild を組み合わせて良い感じにやってる。
version: 2.1
orbs:
github-release: izumin5210/github-release@0.1.1
homebrew: izumin5210/homebrew@0.1.3
aliases:
filter-release: &filter-release
filters:
branches:
ignore: /.*/
tags:
only: /^v\d+\.\d+\.\d+$/
executors:
default:
docker:
- image: circleci/golang:1.13
workflows:
build:
jobs:
# build something to release...
- github-release/create:
<<: *filter-release
executor: default
context: tool-releasing
requires:
- build
- homebrew/update:
<<: *filter-release
executor: default
context: tool-releasing
requires:
- github-release/create
-
CircleCI の良いところでも悪いところでもある、かなりしっかりした記述
- YAML の Anchor と Alias
を活用すればちょっと見やすくなる
- YAML の Anchor と Alias
- CircleCI の Contexts による、credentials の中央管理
GitHub Actions
izumin5210/homebrew-tap と softprops/action-gh-release で実現する。
Go で CLI ツールを書いたときは izumin5210/action-go-crossbuild を組み合わせて良い感じにやってる。
name: Release
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
# build something to release...
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: './dist/*'
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update Homebrew Formula
uses: izumin5210/action-homebrew-tap@releases/v0
with:
tap: izumin5210/homebrew
token: ${{ secrets.GITHUB_TOKEN }}
tap-token: ${{ secrets.TAP_GITHUB_TOKEN }}
if: startsWith(github.ref, 'refs/tags/')
-
(CircleCI と比べて)スッキリとした記述
- すなおに上から読み下すことができる
-
(自リポジトリであれば)勝手に
secrets.GITHUB_TOKEN
が生えてくる- GitHub Releases へのアップロードだけなら YAML のコピペだけで OK
- 別リポジトリを読み書きしたい場合は、別で AccessToken の発行が必要
実装者目線
CircleCI Orbs
先程の2つは https://github.com/izumin5210/orbs-for-tools でコードを公開している。ShellScript がある程度書けないと基本しんどい(CI の宿命か?)。
-
全部 YAML で書く
- 複雑なものを書くと、どんどん長くなっていく(そしてインデントがよくわからなくなってくる)
-
Pack という機能を使うと YAML の分割は可能
- だが、ファイルをまたぐと Anchor & Alias が効かなくなってしまう…
- 処理実体はだいたい ShellScript を YAML にベタ書きすることになる
GitHub Actions
実装は izumin5210/action-homebrew-tap にある。 Orb と同じく maltmil をうまく使う形。
-
TypeScript で書く
- (ShellScript でもかけるが、Docker コンテナで動かすことになる。pull のオーバーヘッドが意外とでかい…)
- TypeScript には型があるので事前に色々チェックできる
- push して動かしてはじめて変数名の typo に気づく… なんてことが起きない!ステキ!
- 型の恩恵が大きすぎて、 ShellScript にくらべ多少記述が長くなるとか気にならない
-
npm にあるパッケージが利用できる
- GitHub API を叩きたければ @actions/github が使える!など
- actions/toolkit に便利パッケージが置いてある
- もちろん普通に npm に置いてある諸々が利用できる
-
ファイル操作や外部コマンド実行がちょっとしんどい(ShellScript にくらべると)
- maltmil の安定版を GitHub Releases から落としてきて、展開して、パス通して…
- ShellScript だと 2〜3行なのに… みたいな気持ちになること多し
- maltmil の安定版を GitHub Releases から落としてきて、展開して、パス通して…
所感
- 利用者目線では…
- 正直、大した差異は無い
- 結局、他に何やるかによって向いている CI が変わりそう
- e.g.
- 大きい Matrix を組みたい場合は GitHub Actions のほうが簡潔に書ける
- 複雑なワークフローを組む場合は CircleCI のほうが依存グラフを記述しやすい
- CircleCI の Contexts はかなり強力なので、組織とかでは有効かも?
- 実装者目線では…
- TypeScript はもたらされる複雑製よりも型・静的解析の恩恵がかなり強いという印象
- 一方で、ポータビリティを考えると結局ワンバイナリで動くツールをつくって CI 上で使うのが最善かも
- 「CI が落ちてもリリースできる」状態にしたい
- となると、CircleCI のほうが Orb 作るのはラクかも?
- なんなら Orb すらいらなくて、
curl ... | tar xf - && sudo cp ...
をベタ書きすればいい
- なんなら Orb すらいらなくて、
結局、こういう Custom step みたいなものは自分のために作ることが多いので、自分が利用者として CI に何を求めているかによってどちらを選択するのかが変わってくるはず。