GitHub ActionsでdotfileのCIを回す
以前の記事で、neovimで使うpythonの環境を自動構築するシェルスクリプトを書きましたが、neovim以外でも似たようなことを行うスクリプト群を作成しました。しかし、作ったはいいもののはたしてこれがうまくいくのかということに確証が持てなかったので当然テストしてみたいとなるのですが、実機はもちろん仮想マシン立ててやるのもめんどくさかったので、GitHub Actionsを使ってCIを回してみることにしました。
コード全体は以下にあります。
GitHub Actionsとは
GitHubが提供しているCI実行環境。無料でも多分ある程度までは使える高性能なCI環境です。
ワークフローという単位で定義し、その中に複数のジョブが実行されるというCIツールとしては一般的な構成かと思います。
ジョブは自分で定義したり、マーケットプレイスでプリセットを探すこともできるので、自由度もかなり高いです。
詳細なドキュメントはこちら
initスクリプトの構成
initスクリプト群はMakeコマンドにより起動され、下のように大きく分けて2つの処理を行っています。
#dotfiles/init/linux/Makefile init: ./install.sh link: ./link.sh nvim-package-update: ./python/update.sh
インストールスクリプト
1つめはインストールです。エントリーポイントはdotfiles/init/linux/install.shで、前述のneovimやzsh、dockerなど使うであろうもののインストール処理を行います。
#dotfiles/init/linux/install.sh #!/bin/sh -xeu sudo apt-get update #zshのインストールと設定 ./zsh.sh #英字キーボード用の設定 sudo apt-get -y install fcitx fcitx-mozc #dockerのインストール ./docker.sh #neovimのインストールと設定 ./nvim.sh #python周り ./python/pyenv.sh #albert ./albert.sh #latex ./latex.sh #リンク作成 ./link.sh
上のようにインストールするものごとに細分化したスクリプトファイルに置くことで、可読性・保守性を向上させ、後述する単独テストを可能にしています。
リンク作成スクリプト
2つめはシンボリックリンク作成です。エントリーポイントはdotfiles/init/linux/link.shで、dotfileに入っている設定ファイルを有効化するためにシンボリックリンクをホームディレクトリに作成しています。
#dotfiles/init/linux/link.sh #!/bin/bash -xeu rm -f ~/.gdbinit ln -s ~/dotfiles/gdb/gdbinit ~/.gdbinit rm -f ~/.gitconfig ln -s ~/dotfiles/git/gitconfig ~/.gitconfig rm -f ~/.latexmkrc ln -s ~/dotfiles/latex/latexmkrc ~/.latexmkrc rm -f ~/.tmux.conf ln -s ~/dotfiles/tmux/tmux.conf ~/.tmux.conf rm -f ~/.config/nvim ln -s ~/dotfiles/nvim ~/.config/nvim rm -f ~/.zshrc ln -s ~/dotfiles/zsh/zshrc ~/.zshrc
現在あるリンクを消した上で新しいリンクを張り直します。(install.shの中でもこのスクリプトは読み込まれているのはちょっと設計ミスかも。分けるか少し検討中。)
CI
スクリプト群を解説したところで、本題のCIに入っていきたいと思います。
ワークフローの定義には、.github/workflows以下のymlファイルを使用します。
今回は、init/linux以下に変更が入ったときと、ワークフロー定義が変わったときにテストしてほしいので以下のように書きます。
#.github/workflows/init-test.yml name: "init script test" on: push: paths: - '.github/workflows/init-test.yml' - 'init/linux/**' #この後にジョブの定義が続く... jobs:
nameでワークフローの名前を定義、on.push.pathsを指定することでこれらのファイルに変更が入ったpushで発火してくれます。
push以外にも、pull requestなど様々な条件で発火でき、詳細は公式ドキュメントを参照してください。
全体テスト
全体テストのジョブは、全体を通して実行してみてエラーが出なければ良いだけなので非常にシンプルです。
#.github/workflows/init-test.yml jobs: init-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: init working-directory: ./init/linux run: make init - name: link working-directory: ./init/linux run: make link
init-testジョブはruns-onで指定したマシンで実行され、stepsで定義されたステップを実行します。
ベースとして使うactionsはcheckoutのみを行うactions/checkout@v2を使用します。
また、それぞれのステップで実行するコマンドをrunで定義します。ここでは上に挙げたMakefileを叩くだけです。
単体テスト
全体テストだけでもいいのですが、今後インストールスクリプトが大きくなってくると、どこでエラーが起こっているのかなどを把握する必要が出てきます。そのため、それぞれのスクリプトごとに単独でテストするジョブも定義していきます。
行うことはそれぞれのスクリプトファイルを単体で実行するだけですが、これを独立して行うためにジョブを大量に作るのは賢くありません。このようなニーズに応えるために、GitHub Actionsにはmatrixという仕組みがあります。
これは、簡単にいうとリスト内の要素を独立に実行してくれるfor文のようなものです。ここではそれぞれのスクリプトファイルを独立に実行したいので以下のように書きます。
#.github/workflows/init-test.yml jobs: unit-test: runs-on: ubuntu-latest strategy: #これをtrueとしてしまうと、どれか一つエラーになると全部強制終了してしまう fail-fast: false #scriptをキーとして、リスト内の要素が値となるジョブが独立して実行される matrix: script: ["./albert.sh", "./docker.sh","./latex.sh","./nvim.sh","./zsh.sh","./python/pyenv.sh"] steps: - uses: actions/checkout@v2 - name: unit-test of ${{matrix.script}} working-directory: ./init/linux run: ${{matrix.script}} #init-testが下に続く...
matrixの中に、スクリプトファイル名を値とするリストを定義することで以下のように、独立したジョブが複数実行されます。
matrixを使えば、異なるosでテストしたりすることもできます。
注意点
シェルスクリプトがエラーを起こしたときにきちんと終了するように、シェバンには -euをつけましょう。
#!/bin/bash -xeu
これを忘れるとエラーを起こしているのにも関わらず、見た目上は成功しているようになってしまいます。
また、-xもつけると、どのコマンドが実行されたのかわかりやすくなります。
おまけ READMEへのバッジ
よく下のようなテスト結果を示すバッジがREADMEについているところを見たことがあるかもしれません。これはREADMEに以下を埋め込むことで可能です。
![init script test](https://github.com/{ユーザ名}/{リポジトリ名}/workflows/{ワークフロー名。空白文字は%20で置換}/badge.svg)