tl;dr
-
run:
セクションでは一時的なPATHの追加しか出来ない。 - ワークフローに依存関係がある場合、
setup-*
を作ると簡単に解決できる。 - setup-caskを作った!
- 「GitHub Actions」便利なので、みんな使おう!
GitHub Actionsについて
「GitHub Actions」は2018/10/16に発表1され、2019/11/13に正式リリース2されました。
当初はブロックを繋いでワークフローを作っていました1が、現在ではYAMLでのワークフロー定義3になり開発者にとって、再利用しやすく、簡潔にワークフローを定義できるようになりました。
パブリックレポジトリであれば、並列数20でLinux, macOS, Windowsが無料で使え、有効化は .github/workflows/test.yml
にワークフローの定義を書いて push
するだけです。
さらに、体感ですが、Travis CIよりJobの起動が早い気がします。バックエンドとしてGCPでなくAzureを使っている影響かもしれません。
Emacs畑でも、MELPAの管理をしているpurcelさんがActionsでEmacsが使えるようにする、setup-emacsを作成したりと盛り上がっています。
出来上がったもの
完成形を示します。新しいプロジェクトでActionsを使うときは、下記手順を踏むことで簡単に複数バージョンでのCIが走ります。すごい!!
-
下記スニペットを
.github/workflows/test.yml
で保存する。(ファイル)name: Main workflow on: [push] jobs: build: runs-on: ubuntu-latest strategy: matrix: emacs_version: - '26.1' - '26.2' - '26.3' - 'snapshot' include: - emacs_version: 'snapshot' allow_failure: true steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1.1.1 - uses: purcell/setup-emacs@master with: version: ${{ matrix.emacs_version }} - uses: conao3/setup-cask@master - name: Run tests if: matrix.allow_failure != true run: 'make test' - name: Run tests (allow failure) if: matrix.allow_failure == true run: 'make test || true'
-
CommitしてGitHubにPush
-
自動でActionsがキックされ、CIが回る。(ログ)
setup-caskの作成
setup-caskがなぜ必要なのか
今回例に挙げたプロジェクトは Emacs
, Cask
(Python
) に依存しており、CIを回すためにはそれらのソフトウェアをどうにかして動くようにする必要があります。
しかし、EmacsとPythonにはそれぞれ setup-emacs
, setup-python
が用意されていましたが、Cask用の setup-cask
を作っている人がいませんでした。
Caskのインストールは簡単で、 cask/cask
をホームディレクトリに .cask
としてCloneしてPATHを通すだけです。このようにインストールする野良ソフトは多いと思いますが、GitHub Actionsでは動きません。PATHの変更が 一つのステージしか適用されない からです。
確認のため、次の test.yml
でActionsを動かします。
name: Main workflow
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Echo path
run: echo $PATH
- uses: actions/setup-python@v1.1.1
- name: Add path
run: export PATH="$HOME/.cask/bin:$PATH"; echo $PATH
- name: Echo path
run: echo $PATH
ログは次のようになりました。(Raw)
-
Set up job
-
Set up job
-
Run actions/checkout@v1
-
Echo path
/usr/share/rust/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin~
-
Run echo $PATH
/usr/share/rust/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin~
-
Run actions/setup-python@v1.1.1
Added matchers: 'python'. Problem matchers scan action output for known warning or error strings and report these inline.
-
Add path
/home/runner/.cask/bin:/opt/hostedtoolcache/Python/3.8.0/x64/bin:/opt/hostedtoolcache/Python/3.8.0/x64:/usr/share/rust/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
-
Run export PATH="$HOME/.cask/bin:$PATH"; echo $PATH
/home/runner/.cask/bin:/opt/hostedtoolcache/Python/3.8.0/x64/bin:/opt/hostedtoolcache/Python/3.8.0/x64:/usr/share/rust/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
-
Echo path
/opt/hostedtoolcache/Python/3.8.0/x64/bin:/opt/hostedtoolcache/Python/3.8.0/x64:/usr/share/rust/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
-
Run echo $PATH
/opt/hostedtoolcache/Python/3.8.0/x64/bin:/opt/hostedtoolcache/Python/3.8.0/x64:/usr/share/rust/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
-
Complete job
このように Add path
で追加した home/runner.cask/bin/
が次のステージでは消えてしまっています。しかし、 setup-python
によって追加されたPython関連のPATHは次のステージでも保存されていることが分かると思います。
そのため setup-cask
を作成する必要がありました。
setup-caskの作成
今回作成した setup-cask
はこちらに置いてあります。
まずsetup-pythonをフォークし、それとpurcelさんのsetup-emacsと見比べながら作成しました。
なおCask本体はバージョニングされていますが、依存関係のバージョニングはされておらず、常に最新版に依存することになっています。そのためキャッシュ機能を省くことにしました。結果として簡単にPython用の setup-python
からCask用に変換することができました。
本体は src/main.ts
です。CaskのReleaseページからzipを落として、解凍し、PATHを通すだけです。
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as path from 'path';
import * as os from 'os';
async function run() {
try {
const version =
core.getInput('version') == 'snapshot'
? 'master'
: core.getInput('version');
const archive_name =
core.getInput('version') == 'snapshot' ? 'master.zip' : `v${version}.zip`;
const home = os.homedir();
const tmp = os.tmpdir();
core.startGroup('Fetch Cask');
await exec.exec('curl', [
'-L',
`https://github.com/cask/cask/archive/${archive_name}`,
'-o',
`${tmp}/${archive_name}`
]);
await exec.exec('unzip', [`${tmp}/${archive_name}`, '-d', `${tmp}`]);
const options = {recursive: true, force: false};
await io.mv(`${tmp}/cask-${version}`, `${home}/.cask`, options);
core.addPath(`${home}/.cask/bin`);
core.endGroup();
core.startGroup('Install dependency');
await exec.exec('cask', ['--version']);
core.endGroup();
// show Cask version
await exec.exec('cask', ['--version']);
} catch (err) {
core.setFailed(err.message);
}
}
run();
Actionsに依存した処理についてはactions/toolkitにあるライブラリを使用することができます。actions/toolkit
はさらに以下のツールによって構成されています。
core.startGroup
と core.endGroup
で囲んだところのログは折り畳み表示されます。しかしセットアップの最後でバージョンを表示する際はグループで囲わない方が出力が見やすいと思います。
使用例
setup-cask
はEmacsとPythonに依存しています。つまり setup-cask
を実行する前に setup-emacs
と setup-python
を uses:
セクションとして記述する必要があります。
この設計により、 setup-cask
としてはCaskのバージョンのみ受け取ればよく、考えることを減らせます。
Basic testing
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1.1.1
with:
python-version: '3.6'
architecture: 'x64'
- uses: purcell/setup-emacs@master
with:
version: '26.3'
- uses: conao3/setup-cask@master
with:
version: 'snapshot'
- name: Run tests
run: make test
この例ではElispパッケージは次の環境でテストされます。
- Emacs:
26.3
- Python:
3.6 (x64)
- Cask:
snapshot
(HEAD)
Matrix testing
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
emacs_version:
- '26.1'
- '26.2'
- '26.3'
- 'snapshot'
cask_version:
- '0.8.0'
- '0.8.4'
- 'snapshot'
steps:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1.1.1
with:
python-version: '3.6'
architecture: 'x64'
- uses: purcell/setup-emacs@master
with:
version: '26.3'
- uses: conao3/setup-cask@master
with:
version: 'snapshot'
- name: Run tests
run: make test
この例ではElispパッケージは次の環境でテストされます。
- Emacs:
26.1
,26.2
,26.3
- Python:
3.6 (x64)
- Cask:
0.8.0
,0.8.4
,snapshot
(HEAD)
Simplest testing
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1.1.1
- uses: purcell/setup-emacs@master
with:
version: '26.3'
- uses: conao3/setup-cask@master
- name: Run tests
run: make test
この例ではElispパッケージは次の環境でテストされます。
- Emacs:
26.3
- Python:
3.x (x64)
- Cask:
snapshot
(HEAD)
PythonとCaskのバージョンを明示的に指定していません。
明示的に指定しなかった場合の処理は各setupの実装に依存しますが、setup-python
ではVersionとして 3.x
, Architectureとして x64
が指定されたものとして処理されます。 (see setup-python/action.yml)
setup-cask
ではVersionとして snapshot
が指定されたものとして処理されます。 (see setup-cask/action.yml)
setup-emacs
でもバージョン指定を省略したいのですが、PRを出したところ却下されました。そのためEmacsのバージョンは明示的に指定する必要があります。
まとめ
MSの資金力によってどんどんGitHubの機能が増えていきますね。パブリックレポジトリに限定されますが、並列数20でLinux, macOS, Windowsが無料で使えるのは驚異的です。
なお、「GitHub Actions」は発表当初、バッジが用意されていないとしてショックなニュースになっていましたが、現在では用意されています。
さらにShields.ioでも先週バッジリクエストがマージされ、使えるようになりました。
We now support badges for #GitHub Actions, allowing you to display the status of your workflows (e.g. https://t.co/xfHbwoLqpx). Thanks calebcartwright for the implementation! Don't waste a minute, start adding them to your Readmes! 🚀
— Shields (@Shields_io) November 29, 2019
GitHubのバッジより豊富なスタイル指定ができることが特徴です。詳細はshields.ioを参照してください。
また、Patreonでご支援を頂ける方を募集しています。普段はElispパッケージなどを中心にOSS活動をしつつ、学生をしています。ぜひよろしくお願いします。
https://www.patreon.com/conao3
Footnotes
1 GitHubが「GitHub Actions」を発表、開発者が好きな機能を使ってワークフローを自動化 - https://www.atmarkit.co.jp/ait/articles/1810/17/news067.html
2 モバイルアプリ版GitHub発表、GitHub Actions正式リリース、コードアーカイブ…GitHubの多面性が見えたGitHub Universe Keynote - https://codezine.jp/article/detail/11818
3 GitHub Actions now supports CI/CD, free for public repositories - https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/