GitHub Actions を使って RBS ファイルの更新を自動化する


2025年 07月 03日

rbs_rails や RBS::Inline といった型抽出ツールを使う場合、ソースコードを更新するごとにツールを実行して RBS ファイルを更新する必要があります。

各種ツールのうち、RBS::Inline はファイルの保存時に自動実行するように設定しておくと、透過的に型定義ファイルを更新できます。かなり高速に動作するため、開発体験は特に悪化しません。

RBS::Inline の README では fswatch を使って監視する方法が紹介されていますが、VSCode の場合は拙作の VSCode 拡張 RBS Helper を利用すると、コマンドを起動することなく自動的に実行されます。

RBS helper と RBS::Inline により、自動的に型が抽出されていく様子

一方、rbs_rails やその他の型抽出ツールはもう少しやっかいです。

これらの型抽出ツールは 1回の実行に数十秒〜数分かかるものが多く、コードを編集するたびに実行するには高コストです。実装が一段落し、PR を作成する直前に都度実行すればよいのですが、実行しわすれてコードと型が一致しない状態になりがちです。

また、チームの都合もあります。我々のチームでは、現状では型に興味があるメンバーを中心に型を整えている状態で、プロジェクト全体で型を整えるというルールにはしていません。そのため、開発時のルールを増やしてメンバーの負担を増やすのは避け、GitHub Actions を使って型の抽出を自動化することにしています。

型の便利さを理解してもらい、型に興味を持ってもらおうという太陽と北風作戦を採っているというわけです。

GitHub Actions による型定義の自動更新

GitHub Actions を使った型定義の自動更新は、以下のようなフローで実現します。

この構成では 2種類のジョブを利用します。

  • ソースコードの更新をトリガーとして、型定義を抽出し、その内容をもとに PR を作成するジョブ
  • 型定義の更新 PR をトリガーとして、PR を自動的にマージするジョブ

なお、この構成では CI による型チェックは行いません。

型チェックをパスすることをマージの前提条件としてしまうと、開発者が作った PR が型不足でエラーになってしまうことがあるため、この構成は選択できません。ここでは、ソースコードから型が自動的に抽出されること、そして、抽出された型を使って開発支援 (サジェスト等) を受けられることに主眼をおいています。

GitHub App の作成

今回の構成を実現するには GitHub App を作成します。

通常の GitHub Actions では GitHub Actions が用意したトークンを使いますが、このトークンで作成した PR は GitHub Actions が起動されません。どうやら、 再帰的な GitHub Actions の実行を防ぐための措置 のようです。

この制限があるため、型定義の抽出ジョブが作成した PR をトリガーにして、PR を自動マージするジョブを起動できません。

そこで、GitHub App を使ってこの制限を回避します。GitHub App を使うことで、PR の作成をトリガーにして別のジョブを起動させることができます。ここでは「型定義の抽出」と「PR の自動マージ」というふたつの App を用意します。前者を RBS Updater App、後者を PR Auto Merger App と呼ぶことにします。

なお、PAT (Personal Access Token) を使う方法もありますが、個人のアカウントに依存してしまうため、ここでは GitHub App を使います。

GitHub App の作成の詳細は ドキュメントを参考 にしてください。

今回は以下の設定を施しました。

  • GitHub App name: 任意
  • Homepage URL: 任意
  • Webhook: 不要 (チェックをはずす)
  • Repository permissions
    • Pull request
    • Contents
    • Projects
  • Where can this GitHub App be installed?:
    • Only on this account > Only allow this GitHub App to be installed on the {org_name} account.

ここで作成された GitHub App の ID と、途中でダウンロードできる秘密鍵ファイル (.pem ファイル) は後ほど利用します。手元に保管しておいてください。

リポジトリの設定

続けて、リポジトリの設定も済ませてしまいましょう。

ここでは 2つの設定をします。

  • 自動マージの設定
  • action secrets/variables の設定

自動マージの設定

リポジトリの自動マージを有効にします。

General > Pull Requests > Allow auto-merge にチェックを入れます。

この設定により、自動マージフラグを立てた PR がマージされるようになります。

なお、ブランチルールを設定しておくと自動マージの条件を細かく設定できます。今回の RBS ファイルの自動更新では必須ではないため、説明は省きますが、CI が通っている場合にのみ自動マージするといった条件が設定できます。

Action secrets/variables の設定

GitHub Actions に GitHub App の設定値を渡すために、環境変数および secrets を設定します。

Secrets and variables > Actions を開き、secrets タブと variables タブからそれぞれ以下の項目を追加します。

  • Variables
    • RBS_UPDATER_APP_ID
    • PR_AUTO_MERGER_APP_ID
  • Secrets
    • RBS_UPDATER_PRIVATE_KEY
    • PR_AUTO_MERGER_PRIVATE_KEY

*_APP_ID にはそれぞれの GitHub App の App ID を、*_PRIVATE_KEY には対応する秘密鍵ファイル (.pem ファイル) の中身を設定してください。

Rake タスクの作成

次に、型定義を更新する Rake タスクを作成します。ここで用意した Rake タスクが GitHub Actions から呼び出されます。

我々のチームでは以下のような Rake タスクを用意しています。

# lib/tasks/rbs.rake
return unless Rails.env.development?

require 'rbs_rails/rake_task'

namespace :rbs do
  desc "Install RBS collection"
  task install: :environment do
    sh 'rbs', 'collection', 'install', '--frozen'
  end

  desc "Update RBS files"
  task update: %i[clean update:all validate]

  desc "Clean RBS files"
  task :clean do
    exceptions = [Rails.root.join('sig/gems'),
                  Rails.root.join('sig/handwritten')]
    sig_dirs = Rails.root.glob('sig/*').reject { |path| path.in? exceptions }.map(&:to_s)
    sh 'rm', '-rf', '.gem_rbs_collection/', *sig_dirs
  end

  desc "Validate RBS files"
  task :validate do
    sh 'rbs', '-Isig', 'validate'
  end

  namespace :update do
    task all: %i[
      collection
      prototype
      rbs_rails:generate_rbs_for_path_helpers
      rbs:actionmailer:setup
      rbs:activemodel:setup
      rbs:activerecord:setup
      rbs:config:setup
      rbs:discard:setup
      rbs:shrine:setup

      rbs:activesupport:setup
      subtract
    ]

    task :collection do
      sh 'rbs', 'collection', 'update'
    end

    task :prototype do
      sh 'rbs-inline', '--opt-out', '--output=sig/prototype', '--base=.', 'app', 'config', 'lib'
    end

    task :subtract do
      prototype_path = Rails.root.join('sig/prototype')
      exceptions = [prototype_path,
                    Rails.root.join('sig/gems'),
                    Rails.root.join('sig/handwritten')]
      Rails.root.glob('sig/*').reject { |path| path.in? exceptions }.each do |path|
        sh 'rbs', 'subtract', '--write', path.to_s, "--subtrahend=#{prototype_path}"
      end
    end
  end
end

この Rake タスクは、 RubyKaigi 2023 の pocke さんのトークで紹介されている Rake タスク をベースにしたもので、
さまざまな形抽出ツールを使うよう中身を置き換えたものです。

ここでは以下のような Rake タスクを用意しています。

  • rake rbs:install : ローカル環境に rbs collection をインストールする
  • rake rbs:update : rbs collection を更新し、ソースコードから型定義を抽出し、型定義を検証する
  • rake rbs:clean : 生成した型定義を削除する
  • rake rbs:validate : 型定義の検証する

なお、rbs:update タスクでは以下のようなツールを使って型定義を抽出しています。

これらのツールの詳細についてはここでは触れません。gem の名前が体を表していると思います。使い方については各ツールのドキュメントを参照してください。

GitHub Actions の設定

最後に GitHub Actions を設定します。

先ほど述べたように、ここでは 2種類のジョブを定義します。

  • ソースコードの更新をトリガーとして、型定義を抽出し、その内容をもとに PR を作成するジョブ
  • 型定義の更新 PR をトリガーとして、PR を自動的にマージするジョブ

ひとつめのジョブは次のように定義します。

# .github/workflows/rbs.yml
name: RBS

on:
  push:
    branches:
      - develop

permissions:
  contents: write
  pull-requests: write

jobs:
  rbs:
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:8.0
        ports:
          - "3306:3306"
        env:
          MYSQL_ALLOW_EMPTY_PASSWORD: y
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/create-github-app-token@v2
        id: app-token
        with:
          app-id: ${{ vars.RBS_UPDATER_APP_ID }}
          private-key: ${{ secrets.RBS_UPDATER_PRIVATE_KEY }}
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.4
          bundler-cache: true
      - name: App setup
        run: cp config/env.yml.example config/env.yml
      - name: DB setup
        run: bundle exec rails db:create db:migrate db:seed
      - name: Update types
        run: bundle exec rails rbs:update
      - name: Create a pull request
        uses: peter-evans/create-pull-request@v7
        with:
          add-paths: |
            rbs_collection.lock.yaml
            sig/
          commit-message: "rbs: Update type signatures"
          branch: bot/rbs
          title: "rbs: Update type signatures"
          token: ${{ steps.app-token.outputs.token }}

このジョブでは、先頭で actions/create-github-app-token を使って GitHub App のトークンを取得しています。

そして、先ほど用意した rbs:update タスクを実行して型定義を更新し、 peter-evans/create-pull-request を使って PR を作成しています。

ふたつめのジョブは次のように定義します。

# .github/workflows/auto-merge.yml
name: Auto merge Pull Requests
on: pull_request

jobs:
  rbs:
    runs-on: ubuntu-latest
    if: github.event.pull_request.user.login == 'rbs-updater[bot]' && github.repository == 'org_name/repo_name'
    steps:
      - uses: actions/create-github-app-token@v2
        id: app-token
        with:
          app-id: ${{ vars.PR_AUTO_MERGER_APP_ID }}
          private-key: ${{ secrets.PR_AUTO_MERGER_PRIVATE_KEY }}
      - name: Approve a PR
        run: |
          gh pr review --approve "$PR_URL"
          gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ steps.app-token.outputs.token }}

ここでは、rbs-updater という GitHub App が作成した PR をトリガーにして、PR の自動マージを行っています。
gh pr merge コマンドを使って PR にマージフラグを立てています。

まとめ

GitHub Actions を使って型定義の更新を自動化する方法を紹介しました。

実は PR を作成するところまでは以前から自動化されていましたが、最近までマージは手動で行っていました。自動マージはやればできることはわかっていたのですが、なかなか手を付けられず、つい後回しになっていました。

この設定により、ソースコードを更新するたびに、自動的に型定義が更新されるようになりました。開発者はいつもどおりコードを書き、リポジトリからコードを pull するだけで、最新の型定義が取得できます。


その結果、Steep LSP のサジェストやコードヒント、irb + repl_type_completor によるコード補完など、型による開発支援機能の恩恵をかんたんに受けられます。

ライトに型を導入する人向けの一歩目の構成として、参考にしてみてください。

参考記事

自動マージまわりについては、スマートバンクさんの GitHub Appを使ってDependabotが作るpull requestを自動マージさせる が参考になりました。ありがとうございます。

< 前の記事へ 次の記事へ >