Kotaro7750's diary

低レイヤを中心とした技術ブログ、たまに日記

GitHub ActionsでdotfileのCIを回す

以前の記事で、neovimで使うpythonの環境を自動構築するシェルスクリプトを書きましたが、neovim以外でも似たようなことを行うスクリプト群を作成しました。しかし、作ったはいいもののはたしてこれがうまくいくのかということに確証が持てなかったので当然テストしてみたいとなるのですが、実機はもちろん仮想マシン立ててやるのもめんどくさかったので、GitHub Actionsを使ってCIを回してみることにしました。

kotaro7750.hatenablog.com

コード全体は以下にあります。

github.com

GitHub Actionsとは

f:id:Kotaro7750:20200303133312p:plain
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の中に、スクリプトファイル名を値とするリストを定義することで以下のように、独立したジョブが複数実行されます。

f:id:Kotaro7750:20200303133230p:plain
matrixで独立に実行させたジョブ

matrixを使えば、異なるosでテストしたりすることもできます。

注意点

シェルスクリプトがエラーを起こしたときにきちんと終了するように、シェバンには -euをつけましょう。

#!/bin/bash -xeu

これを忘れるとエラーを起こしているのにも関わらず、見た目上は成功しているようになってしまいます。

また、-xもつけると、どのコマンドが実行されたのかわかりやすくなります。

おまけ READMEへのバッジ

よく下のようなテスト結果を示すバッジがREADMEについているところを見たことがあるかもしれません。これはREADMEに以下を埋め込むことで可能です。

f:id:Kotaro7750:20200303133210p:plain
バッジの例

![init script test](https://github.com/{ユーザ名}/{リポジトリ名}/workflows/{ワークフロー名。空白文字は%20で置換}/badge.svg)