LoginSignup
11

More than 1 year has passed since last update.

GitHub Actions を用いて Pull-Request で shellcheck をする方法

Last updated at Posted at 2019-10-02

はじめに

(Ba)sh スクリプトを静的解析してくれる便利なツール shellcheck が、GitHub Actions 標準の実行環境 から使えるので Pull-Request に含まれた (Ba)sh を自動的にチェックをする ワークフローを書きました。

実行結果

image.png

ワークフローのサンプル

:warning: 2022-10-25追記: checkout 時に fetch-depth: 0 オプションを使わない方法を思いついたので以下サンプルを更新しました。2 回 checkout している 理由は GitHub Actions を用いて Pull-Request で変更されたファイル一覧を取り出す方法 に詳細があります。

.github/workflows/static_analysis_on_pr.yml
name: Static analysis

on:
  pull_request:
    types: [opened, synchronize]
    paths: '**.sh'

jobs:
  shellcheck:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
      with:
        ref: ${{ github.event.pull_request.base.sha }}
    - uses: actions/checkout@v3
    - name: Check scripts with shellcheck
      env:
        EXCLUDE_OPTS: SC2086,SC2001
      run: |
        git diff ${{ github.event.pull_request.base.sha }}.. \
          --diff-filter=AM --name-only -- '*.sh' | while read script ; do \
            shellcheck ${EXCLUDE_OPTS:+-e ${EXCLUDE_OPTS}} $script || touch error ; \
        done
        test ! -e error

ポイントの紹介

on.<push|pull_request>.paths でマッチした時のみトリガーする

on:
  pull_request:
    types: [opened, synchronize]
    paths: '**.sh'

今回は (Ba)sh スクリプトのチェックが目的なので、関係のないファイルが更新されたときはワークフローが走らないようにします。push または pull_request では paths定義されているパターンにマッチするファイルが一つでも含まれている時だけワークフローを実行する といった制限を設けることができます。

注意点としては '*'/含めないため paths:'*.sh' としてしまうと、サブディレクトリのスクリプトを更新してもトリガーされません。このあたりの具体例については、最近公開された以下のチートシートがとても参考になります。

また、今回のサンプルでは reopend の時は実行しないように、types: [opened, synchronize] のようにアクティビティタイプに変更しています。こちらは前回の記事の types でワークフローのトリガーを細かく制御する が参考になるかもしれません。

jobs.<job_id>.steps.uses で外部のアクションを参照する

uses: actions/checkout@v3

uses: {owner}/{repo}@{ref} という構文で、パブリックリポジトリとして公開されているアクションを参照することができます。

今回は、以下で紹介されている actions/checkout という便利なアクションを用いました。

このアクションは名前のとおり、ワークスペースにリポジトリを取得した後にチェックアウトしてくれます。ここでポイントなのが、デフォルトでは $GITHUB_SHA にチェックアウトされる点です。Pull-Request における $GITHUB_SHA は他のイベントとは異なり、Pull-Request のブランチからベースブランチに Merge した先頭コミットを指します。

GITHUB_SHA
Last merge commit on the GITHUB_REF branch

つまり、ワークスペースで お試し Merge をして、その先頭コミットにチェックアウトしてくれます。こうすることで「Pull-Request ではうまくいっていたが、いざ Merge してみたら失敗してしまった」ということが避けられるようになっています。 Merge については Actions の実行時ログからも確認することができます。

RUN actions/checkout@v3
git checkout --progress --force refs/remotes/pull/19/merge
(...)
HEAD is now at 5aaad4d Merge eeb2415444ae2c220766235724e679e624ce12d2
into 31bdf0f35c15a591b0b3cf5afa6a754e18bb02d7

更新があった *.sh ファイルだけを取り出して shellcheck をかける

run: |
  git diff ${{ github.event.pull_request.base.sha }}.. \
    --diff-filter=AM --name-only -- '*.sh' | while read script ; do ...

前述の paths: '**.sh'Pull-Request のコミットに 1 つでも .sh が含まれていたらトリガーする という制御なので、.sh 以外のファイルが含まれている場合も考えられます。したがって、更新のあった拡張子が .sh のファイルだけを改めて取り出そうと思います。まず git diff ${{ github.event.pull_request.base.sha }}.. では、ベースブランチの SHA$GITHUB_SHA の差分を得ます。 SHA については以下を参考にしました。

次に、削除やリネームの時はチェックの対象外としたいので --diff-filter=AMA 新規追加と M 変更のみをフィルタしています。最後に --name-only -- '*.sh' で拡張子が .sh にマッチするファイル名を取得し、これらを shellcheck の対象としました。

shellcheck の除外オプション

env:
  EXCLUDE_OPTS: SC2086,SC2001

shellcheck を使おう でも紹介されていますが、実行時に -e CODE1[,CODE2...] とすることで、指定したルールをチェックの対象外とすることができます。今回のサンプルでは、SC2086SC2001 のルールをチェックの対象外としています。

失敗した場合は

image.png

Pull-Request 上ではこのように表示されます。 失敗してしまった場合は Details から Actions の実行時ログを参照して、何故エラーしたかを確認します。

image.png

上記の例では SC2070 というルールに沿っていない点を指摘されているため、コードを修正するか、または 対象外のルール登録をする必要があります。他のルールについては shellcheck/wiki から確認することができます。

おわり

今回 GitHub Actions 標準の実行環境shellcheck が含まれていることに気付いて、このようなチェックの仕組みを作ってみました。 uses: から気軽に使えるように、そのうち Marketplace かパブリックなアクションとして公開してみたいと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11