GitHub Actions からの sbt プラグインの自動公開
本稿は前に書いたTravis-CI からの sbt プラグインの自動公開の GitHub Actions 版だ。
Ólaf さんの olafurpg/sbt-ci-release を使って sbt プラグインのリリースを自動化してみる。sbt-ci-release の README は Sonatype OSS 向けの普通のライブラリのリリースを前提に書かれている。sbt プラグインのリリースに必要な差分以外の詳細は README を参照してほしい。
リリースを自動化することそのものがベスト・プラクティスだが、sbt プラグインのリリースに関連して特に嬉しいことがある。この方法を使うことで Bintray の sbt organization にユーザーを追加せずに、複数人で sbt プラグインのリリース権限を共有することが可能となる。これは仕事でメンテしているプラグインがあるときに便利だ。
step 1: sbt-ci-release
sbt-release を使っている場合は削除する。sbt-ci-release を追加する。
addSbtPlugin("org.foundweekends" %% "sbt-bintray" % "0.6.1")
addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.1.1") // for gpg 2
version.sbt
も削除する。
step 2: -SNAPSHOT version
sbt-dynver を多少抑えて、タグの付いていないコミットで -SNAPSHOT バージョンを使えるようにする:
ThisBuild / dynverSonatypeSnapshots := true
ThisBuild / version := {
val orig = (ThisBuild / version).value
if (orig.endsWith("-SNAPSHOT")) "2.2.0-SNAPSHOT"
else orig
}
step 3: sbt-bintray セッティングを復活させる
プラグインは通常 sbt-bintray を使ってリリースするので、publishTo
を bintray / publishTo
に戻す。publishMavenStyle
を false
にする。
publishMavenStyle := false,
bintrayOrganization := Some("sbt"),
bintrayRepository := "sbt-plugin-releases",
publishTo := (bintray / publishTo).value,
step 4: bintrayReleaseOnPublish オーバーライドの削除
publish が自動的にリリースするようにしてほしいので、現在 bintrayReleaseOnPublish := false
を設定している場合はそれを削除する。
// bintrayReleaseOnPublish := false,
step 5: 新規に GPG キーを作成する
olafurpg/sbt-ci-release の指示に従って新規に GPG キーを生成する。
$ gpg --gen-key
gpg (GnuPG/MacGPG2) 2.2.20; Copyright (C) 2020 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Note: Use "gpg --full-generate-key" for a full featured key generation dialog.
GnuPG needs to construct a user ID to identify your key.
Real name: sbt-avro bot
Email address: eed3si9n@gmail.com
....
public and secret key created and signed.
pub rsa2048 2020-08-07 [SC] [expires: 2022-08-07]
0AC38C6BAD42D5980D8E01A17766C6BECAD5CE7B
uid sbt-avro bot <eed3si9n@gmail.com>
sub rsa2048 2020-08-07 [E] [expires: 2022-08-07]
この公開鍵 ID を LONG_ID
として書き留める:
LONG_ID=0AC38C6BAD42D5980D8E01A17766C6BECAD5CE7B
echo $LONG_ID
gpg --armor --export $LONG_ID
公開鍵を http://keyserver.ubuntu.com:11371/ に届け出る。
step 6: 秘密の設定
https://github.com/<owner>/<repo>/settings/secrets/actions
から秘密を設定する:
BINTRAY_USER
: Bintray ユーザー名BINTRAY_PASS
: Bintray ユーザーの API キーPGP_PASSPHRASE
: さっき作成した GPG 鍵のキーフレーズ。Bash 特殊文字が含まれている場合は'my?pa$$word'
というふうにシングルクォートでくくってやる必要がある。Travis Environment Variables 参照。PGP_SECRET
: base64 エンコードされた秘密鍵。以下のコマンドを実行して得られる:
# macOS
gpg --armor --export-secret-keys $LONG_ID | base64 | pbcopy
# Ubuntu (assuming GNU base64)
gpg --armor --export-secret-keys $LONG_ID | base64 -w0 | xclip
# Arch
gpg --armor --export-secret-keys $LONG_ID | base64 | sed -z 's;\n;;g' | xclip -selection clipboard -i
# FreeBSD (assuming BSD base64)
gpg --armor --export-secret-keys $LONG_ID | base64 | xclip
step 7: 秘密鍵のデコード
最近の Ubuntu ディストロで使われいる gpg 2.2 のために、秘密鍵を時前でデコードする必要がある。 .github/decodekey.sh
というファイルを追加する:
#!/bin/bash
echo $PGP_SECRET | base64 --decode | gpg --batch --import
実行権を付ける:
$ chmod +x .github/decodekey.sh
step 8: GitHub Actions YAML
.github/workflows/ci.yml
を作る。詳細は Setting up GitHub Actions with sbt 参照。
name: CI
on:
pull_request:
push:
schedule:
# 2am EST every Saturday
- cron: '0 7 * * 6'
jobs:
tests:
runs-on: ubuntu-latest
env:
# define Java options for both official sbt and sbt-extras
JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Scala
uses: olafurpg/setup-scala@v10
with:
java-version: "adopt@1.8"
- name: Coursier cache
uses: coursier/cache-action@v5
- name: Build and test
run: |
sbt -v clean scalafmtCheckAll test scripted
rm -rf "$HOME/.ivy2/local" || true
find $HOME/Library/Caches/Coursier/v1 -name "ivydata-*.properties" -delete || true
find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true
find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true
find $HOME/.sbt
リリース用に .github/worflows/release.yml
も作る:
name: Release
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
env:
# define Java options for both official sbt and sbt-extras
JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Scala
uses: olafurpg/setup-scala@v10
with:
java-version: "adopt@1.8"
- name: Coursier cache
uses: coursier/cache-action@v5
- name: Test
run: |
sbt test packagedArtifacts
- name: Release
env:
BINTRAY_USER: ${{ secrets.BINTRAY_USER }}
BINTRAY_PASS: ${{ secrets.BINTRAY_PASS }}
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}
CI_CLEAN: clean
CI_RELEASE: publishSigned
CI_SONATYPE_RELEASE: version
run: |
.github/decodekey.sh
sbt ci-release
step 9: タグ駆動リリース
プラグインをリリースする準備ができたら、コミットにタグを付けて push する。
git tag -a v0.1.0 -m "v0.1.0"
git push origin v0.1.0
GitHub Actions でリリースジョブが開始するはずだ。
gpg 2 に関する備考
sbt-pgp は署名をするときに --passphrase
オプションを使う。ドキュメンテーション によると、新たに --pinentry-mode loopback
を渡す必要がある:
Note that since Version 2.0 this passphrase is only used if the option
--batch
has also been given. Since Version 2.1 the--pinentry-mode
also needs to be set toloopback
.
sbt-pgp 2.1.1 で gpg
コマンドのバージョン番号を検知して --pinetry-mode loopback
オプションを渡すようにした。
sbt-ci-release は --import
を使うが、これは gpg 2.2 で静かに失敗して以下のようなエラーとなって表出する:
gpg: key 24A4616356F15CE1: public key "sbt-something bot <some@example.com>" imported
gpg: key 24A4616356F15CE1/24A4616356F15CE1: error sending to agent: Inappropriate ioctl for device
gpg: error building skey array: Inappropriate ioctl for device
gpg: Total number processed: 1
gpg: imported: 1
gpg: secret keys read: 1
Tag push detected, publishing a stable release
....
[info] gpg: no default secret key: No secret key
[info] gpg: signing failed: No secret key
[error] java.lang.RuntimeException: Failure running 'gpg --batch --pinentry-mode loopback --passphrase *** --detach-sign --armor --use-agent --output /home/runner/work/sbt-projectmatrix/sbt-projectmatrix/target/scala-2.12/sbt-1.0/sbt-projectmatrix-0.7.1-M1.jar.asc /home/runner/work/sbt-projectmatrix/sbt-projectmatrix/target/scala-2.12/sbt-1.0/sbt-projectmatrix-0.7.1-M1.jar'. Exit code: 2
T2313 によると、この回避策は --batch --import
を使うことで、.github/decodekey.sh
はそれを行う。
この辺りの問題に関しては一応何となく存在は知っていたけども自分が使っている Xenial イメージに付いてくる gpg 1.4 はこれらのオプションと互換性が無かったため特にアクションを取ってこなかった。GitHub Action は Bionic を使っていて、これから Focal にも移行するようなのでこれらの問題に皆も遭遇するようになったということだ。