BundlerのグループによるGitHub Actions (ruby/setup-ruby@v1) におけるRubocopの実行最適化

# TL;DR

GitHub Actions で RuboCop を実行する際，デフォルトでは Gemfile に記載されている gem を余剰にインストールする必要があり冗長性が生じる．そこでBundlerのグループ機能を使って最小限の gem をインストールし，ジョブの最適化を行う．

具体的な手順は次のとおりである，RuboCopの関連gemを`:rubocop`グループに入れる．`bundle config`コマンドを使用して，`with`と`without`を設定することで，`:default`グループに属するgemを除いて`:rubocop`グループのgemのみをインストールすることができる．GitHub Actionsでは，`ruby/setup-ruby@v1`アクションを使用してRubyを設定し，`bundler-cache`フィールドの値を`false`に設定する．

以上の手順により，`bundle install`の所要時間が20秒程度に短縮された．

# 諸言

CI ツール (GitHub Actions，Circle CI など) で Rubocop を実行する場合，実行環境 (Ubunbu など) に Rubocop gem 群をインストールする必要がある．ただし，デフォルトでは Gemfile に記載された gem (または，`RAILS_ENV`などの環境変数に指定された `test` などのグループに属する gem) がインストールされ，冗長性が生じる．本稿では，Bundler のグループ機能によってインストールする gem を最小化し，CI ジョブの実行最適化を図る．

## Gemfile

本稿では，`rubocop` のほかに，`rubocop-{capybara, factory_bot, performance, rails, rspec}` gem をバンドルしている．これらの gem 群を `rubocop` グループに入れる．

```diff:Gemfile
- group :development, :test do
+ group :development, :test, :rubocop do
  gem "rubocop", require: false
  gem "rubocop-capybara", require: false
  gem "rubocop-factory_bot", require: false
  gem "rubocop-performance", require: false
  gem "rubocop-rails", require: false
  gem "rubocop-rspec", require: false
end
```

Bundler のドキュメントによれば，明示的にグループに属していない gem は， `:default` グループに属している．

> ```Gemfile
> # These gems are in the :default group
> gem 'nokogiri'
> gem 'sinatra'
> ```

https://bundler.io/guides/groups.html#how-to-manage-groups-of-gems

したがって，`:rubocop` グループの gem 群のみをインストールするには，`:default` グループに属す gem 群を除き，`:rubocop` グループの gem 群のみをインストールするよう指定すればよい．
指定する方法には，次の２つがある．

## 1

:::note info
編集後に気づいたが，この方法のほうが良い．
:::

### `BUNDLE_WITH`と `BUNDLE_WITHOUT` を指定する

次のように，環境変数 `BUNDLE_WITH` の値に `rubocop` を， `BUNDLE_WITHOUT` の値に `default` を設定すれば，`:default` グループに属す gem 群を除き，`:rubocop` 群のみをインストールすることができる．

`rubocop` ジョブは次のようになる．

```yaml:ci.yml
# (省略)

jobs:
  rubocop:
    runs-on: ubuntu-latest
    env:
      BUNDLE_WITHOUT: "default development test production"
      BUNDLE_WITH: rubocop
    steps:
    - uses: actions/checkout@v3
    - uses: ruby/setup-ruby@v1  
    - name: Install dependencies
      run: bundle install --jobs 4 --retry 3
    - name: Run rubocop
      run: bundle exec rubocop
```

## 2

### bundle config で `with` と `without` を指定する

次のように，bundle config の `with` の値に `rubocop` を， `without` の値に `default` を設定すれば，`:default` グループに属す gem 群を除き，`:rubocop` 群のみをインストールすることができる．`local` オプションは，設定がローカルに行われるよう指示する．

```
bundle config set --local without default
bundle config set --local with rubocop
```

### ruby/setup-ruby@v1 の `bundler-cache` を偽に設定する

GitHub Actions で Ruby を利用可能するためのアクション [ruby/setup-ruby@v1](https://github.com/ruby/setup-ruby) を用いることにする．
デフォルトでは，ruby/setup-ruby@v1 は Gemfile 及び Gemfile.lock を参照して
 `:default` を除かずに `bundle install` してしまうため，`bundler-cache` フィールドの値を `false` に設定する必要がある． (`bundle` コマンドは，`ruby/setup-ruby@v1` を実行したのちに利用可能になる．)

```yaml
- uses: ruby/setup-ruby@v1  
  with:
    bundler-cache: false
```

### `rubocop` ジョブ

以上から，`rubocop` ジョブは次のようになる．

```yaml:ci.yml
# (省略)

jobs:
  rubocop:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: ruby/setup-ruby@v1  
      with:
        bundler-cache: false 
    - name: Set up Ruby
      run: |
        bundle config set --local without "default development test production"
        bundle config set --local with rubocop
    - name: Install dependencies
      run: bundle install --jobs 4 --retry 3
    - name: Run rubocop
      run: bundle exec rubocop
```

# 結語

これによって，1分以上掛かっていた `bundle install` が 20秒ほどに短縮された．