GitHub ActionsでRun rubocop with reviewdogを使ったCIの構築

# はじめに
個人開発でRubyを使って開発しています。
静的解析にはもちろんrubocopを使っているのですが、ローカルで実行するのみでした。
この作業をCIでも実行したいと思い、個人で触ったことのなかったGitHub ActionsとRun rubocop with reviewdogを使ってみることにしました。

# GitHub Actionsとは
公式からの説明は以下のとおりです。
> GitHub Actions は、ソフトウェア開発ライフサイクル内のタスクを自動化するのに役立ちます。 GitHub Actions はイベント駆動型で、指定されたイベントが発生した後に一連のコマンドを実行できます。 たとえば、誰かがリポジトリのプルリクエストを作成するたびに、ソフトウェアテストスクリプトを実行するコマンドを自動的に実行できます。

参照:[GitHub Actions入門](https://docs.github.com/ja/free-pro-team@latest/actions/learn-github-actions/introduction-to-github-actions)

ざっとまとめると、GitHubに備わっているIssueやPull Requestなどの機能をトリガーにして、ビルド、テスト、デプロイなどを実行できるツールとなります。

# Run rubocop with reviewdog
GitHub Actionsでは、アクションとして[Run rubocop with reviewdog](https://github.com/reviewdog/action-rubocop)が提供されています。
これは、Pull Requestが作成されたときに以下2つを実行してくれるアクションです。

- [rubocop](https://github.com/rubocop-hq/rubocop)による静的解析
- [reviewdog](https://github.com/reviewdog/reviewdog)にて解析結果をGitHubに投稿

今回、このアクションを使って静的解析とレビューを行うようにしました。
Run rubocop with reviewdogを使ったGitHub Actionsのyamlは以下のとおりです。

```static_analysis.yml
name: Static Analysis

on: pull_request

jobs:
  rubocop:
    name: Run rubocop
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: rubocop
        uses: reviewdog/action-rubocop@v1
        with:
          rubocop_version: 0.93.0
          github_token: ${{ secrets.GITHUB_TOKEN }}
          rubocop_flags: --config backend/.rubocop.yml backend/
          rubocop_extensions: meowcop
          reporter: github-pr-check
```
Run rubocop with reviewdogにはオプションが指定可能です。
ここで指定したオプションの一部について説明します。
なお、オプション一覧については、Run rubocop with reviewdogの[README](https://github.com/reviewdog/action-rubocop)を確認してください。

- rubocop_flags
  rubocopを実行する際の引数を指定できます。
  ここでは.rubocop.ymlの指定と、rubocopを実行する場所を指定しています。
  
- rubocop_extensions
  rubocopのエクステンションをバージョンの指定と一緒に指定できます。デフォルトでは```rubocop-rails```、```rubocop-performance```、```rubocop-rspec```、```rubocop-i18n```、```rubocop-rake```の最新バージョンが入るようです。
  ここではもともと指定せずに実行していましたが、後述するact実行時に、meowcopがないと怒られていたので、ここで指定しています。

- reporter
  reviewdogのオプションであるreporterを使えます。デフォルトではgithub-pr-checkを使用します。

# act
yamlを作成していくにあたり動作確認と修正を繰り返す必要があります。
GitHub上で確認する方法は、毎回commit、pushしなければならずフィードバックに時間がかかります。
そのためローカルで実行できるツールはないかと探したところ、[act](https://github.com/nektos/act)がありました。
actはGitHub Actionsをローカルで実行できるGolangのツールです。

README通り、brewでインストールします。

```
$ brew install act
```

実行方法は、プロジェクトの.githubフォルダーがあるディレクトリで、actを実行するだけです。

```
$ act
```

actはデフォルトで、pushイベントがあるワークフローを実行します。
特定のイベントを実行したい場合は、引数にそのイベント名をセットすることで実行できます。
例: Pull Requestイベントのワークフローを実行したい場合

```
$ act pull_request
```

またローカルでのGitHub Runnerは、ワークフローのruns-onのイメージに対して、act上でイメージが決まっています。
現在はruns-onが```ubuntu-latest```、```ubuntu-18.04```の場合は```node:12.6-buster-slim```、```ubuntu-16.04```の場合は```node:12.6-stretch-slim```のイメージが使用されます。(なお、runs-onがwindowsやmacosに対しては、actはサポートしていないようです。)
GitHub Runnerのイメージを指定する場合は、```-P runs-onのイメージ名=actでサポートしているイメージ名```としてください。

```
$ act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04
```

今回は、ワークフローのイメージとactで実行するイメージを合わせたかったので、イベント名（pull_request）とイメージを指定し、実行しました。

```
$ act pull_request -P ubuntu-latest=nektos/act-environments-ubuntu:18.04
```

## act実行時のエラー
actを実行すると```Broken pipe @ io_writev - <STDOUT>``` といったエラーが出てしまいました。

```
$ act pull_request -P ubuntu-latest=nektos/act-environments-ubuntu:18.04
[Static Analysis/Run rubocop] 🚀  Start image=nektos/act-environments-ubuntu:18.04
[Static Analysis/Run rubocop]   🐳  docker run image=nektos/act-environments-ubuntu:18.04 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Static Analysis/Run rubocop]   🐳  docker cp src=/Users/masatoyamashita/Data/GitHub/WeatherReport/. dst=/github/workspace
[Static Analysis/Run rubocop] ⭐  Run actions/checkout@v2
[Static Analysis/Run rubocop]   ✅  Success - actions/checkout@v2
[Static Analysis/Run rubocop] ⭐  Run rubocop
[Static Analysis/Run rubocop]   ☁  git clone 'https://github.com/reviewdog/action-rubocop' # ref=v1
[Static Analysis/Run rubocop]   🐳  docker build -t act-reviewdog-action-rubocop-v1:latest /Users/masatoyamashita/.cache/act/reviewdog-action-rubocop@v1
[Static Analysis/Run rubocop]   🐳  docker run image=act-reviewdog-action-rubocop-v1:latest entrypoint=[] cmd=["" "0.93.0" "meowcop" "--config backend/.rubocop.yml backend/" "rubocop" "error" "github-pr-check" "added" "false" "" "."]
| Fetching regexp_parser-1.8.2.gem
| Fetching ast-2.4.1.gem
| Fetching parser-2.7.2.0.gem
| Fetching rainbow-3.0.0.gem
| Fetching rubocop-0.93.0.gem
| Fetching parallel-1.19.2.gem
| Fetching ruby-progressbar-1.10.1.gem
| Fetching unicode-display_width-1.7.0.gem
| Fetching rubocop-ast-0.8.0.gem
| Successfully installed parallel-1.19.2
| Successfully installed ast-2.4.1
| Successfully installed parser-2.7.2.0
| Successfully installed rainbow-3.0.0
| Successfully installed regexp_parser-1.8.2
| Successfully installed rubocop-ast-0.8.0
| Successfully installed ruby-progressbar-1.10.1
| Successfully installed unicode-display_width-1.7.0
| Successfully installed rubocop-0.93.0
| 9 gems installed
| Fetching meowcop-2.15.0.gem
| Successfully installed meowcop-2.15.0
| 1 gem installed
| reviewdog: this is not PullRequest build.
| Broken pipe @ io_writev - <STDOUT>
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/clang_style_formatter.rb:47:in `write'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/clang_style_formatter.rb:47:in `puts'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/clang_style_formatter.rb:47:in `report_line'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/clang_style_formatter.rb:30:in `report_offense'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/clang_style_formatter.rb:12:in `block in report_file'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/clang_style_formatter.rb:12:in `each'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/clang_style_formatter.rb:12:in `report_file'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/progress_formatter.rb:42:in `block in finished'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/progress_formatter.rb:41:in `each'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/progress_formatter.rb:41:in `finished'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/formatter_set.rb:33:in `block (3 levels) in <class:FormatterSet>'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/formatter_set.rb:33:in `each'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/formatter/formatter_set.rb:33:in `block (2 levels) in <class:FormatterSet>'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/runner.rb:95:in `inspect_files'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/runner.rb:47:in `run'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/cli/command/execute_runner.rb:25:in `execute_runner'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/cli/command/execute_runner.rb:17:in `run'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/cli/command.rb:11:in `run'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/cli/environment.rb:18:in `run'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/cli.rb:65:in `run_command'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/cli.rb:72:in `execute_runners'
| /usr/local/bundle/gems/rubocop-0.93.0/lib/rubocop/cli.rb:41:in `run'
| /usr/local/bundle/gems/rubocop-0.93.0/exe/rubocop:13:in `block in <top (required)>'
| /usr/local/lib/ruby/2.6.0/benchmark.rb:308:in `realtime'
| /usr/local/bundle/gems/rubocop-0.93.0/exe/rubocop:12:in `<top (required)>'
| /usr/local/bundle/bin/rubocop:23:in `load'
| /usr/local/bundle/bin/rubocop:23:in `<main>'
[Static Analysis/Run rubocop]   ✅  Success - rubocop
```

Broken pipeについて調べると、長過ぎる出力が問題のようでした。
ログの流れから、rubocopによる指摘が多すぎるからB roken pipe
が起きているのではないかと仮定し、rubocop単体で出力される指摘を1つずつ修正していきました。

再度actを実行したところ、無事成功しました。

```
$ act pull_request -P ubuntu-latest=nektos/act-environments-ubuntu:18.04
[Static Analysis/Run rubocop] 🚀  Start image=nektos/act-environments-ubuntu:18.04
[Static Analysis/Run rubocop]   🐳  docker run image=nektos/act-environments-ubuntu:18.04 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Static Analysis/Run rubocop]   🐳  docker cp src=/Users/masatoyamashita/Data/GitHub/WeatherReport/. dst=/github/workspace
[Static Analysis/Run rubocop] ⭐  Run actions/checkout@v2
[Static Analysis/Run rubocop]   ✅  Success - actions/checkout@v2
[Static Analysis/Run rubocop] ⭐  Run rubocop
[Static Analysis/Run rubocop]   ☁  git clone 'https://github.com/reviewdog/action-rubocop' # ref=v1
[Static Analysis/Run rubocop]   🐳  docker build -t act-reviewdog-action-rubocop-v1:latest /Users/masatoyamashita/.cache/act/reviewdog-action-rubocop@v1
[Static Analysis/Run rubocop]   🐳  docker run image=act-reviewdog-action-rubocop-v1:latest entrypoint=[] cmd=["" "0.93.0" "meowcop" "--config backend/.rubocop.yml backend/" "rubocop" "error" "github-pr-check" "added" "false" "" "."]
| Fetching parallel-1.19.2.gem
| Fetching ast-2.4.1.gem
| Fetching parser-2.7.2.0.gem
| Fetching rainbow-3.0.0.gem
| Fetching regexp_parser-1.8.2.gem
| Fetching rubocop-ast-0.8.0.gem
| Fetching ruby-progressbar-1.10.1.gem
| Fetching unicode-display_width-1.7.0.gem
| Fetching rubocop-0.93.0.gem
| Successfully installed parallel-1.19.2
| Successfully installed ast-2.4.1
| Successfully installed parser-2.7.2.0
| Successfully installed rainbow-3.0.0
| Successfully installed regexp_parser-1.8.2
| Successfully installed rubocop-ast-0.8.0
| Successfully installed ruby-progressbar-1.10.1
| Successfully installed unicode-display_width-1.7.0
| Successfully installed rubocop-0.93.0
| 9 gems installed
| Fetching meowcop-2.15.0.gem
| Successfully installed meowcop-2.15.0
| 1 gem installed
| reviewdog: this is not PullRequest build.
[Static Analysis/Run rubocop]   ✅  Success - rubocop
```

# GitHub上で確認
actによるローカルの確認が終わったのでpushし、GitHub上でGitHub Actionsを走らせたところ、無事成功しました。
![GitHub上の結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406773/c4a6f512-5ba0-49bc-e6f5-8c59347d726a.png)


# 終わりに
GitHub Actions上でreviewdog/action-rubocopを実行しました。
またローカルで実行するためにactを利用しました。
人の手を使わず自動で静的解析を行ってくれてありがたいですね。
こういった自動化はどんどん進めていきたいです。