Melos × マージキューでチーム開発の体験を向上させる

に公開

はじめに

Flutter でマルチパッケージ構成のアプリを開発している場合、複数人での共同作業における開発効率と体験は非常に重要です。
本記事では、Melos を活用したマルチパッケージ構成の Flutter アプリ開発において、GitHub のマージキュー機能を導入することで、開発体験をどのように向上させたかについて共有します。

前提環境

  • Melos を利用したマルチパッケージ構成の Flutter アプリ開発
  • 複数人でのチーム開発環境
  • GitHub でのブランチ保護ルールが適用されている

導入前の課題

私たちのプロジェクトでは、コードの品質を担保するために、ブランチ保護ルールを設定していました。
そのため、プルリクエストをマージする前に以下のような課題がありました。

  • 他のプルリクエストがマージされた場合、自分のプルリクエストに対して「Update branch」が必要になる
  • 差分が少ないプルリクエストでも、都度全体に対して analyzetest などの CI/CD が流れていた
  • その結果、CI の実行時間が長くなり、開発者の待ち時間も増加していた

このような状況は開発のスピードを低下させ、開発者のストレスとなっていました。

提案:GitHub のマージキュー機能を利用する

これらの課題を解決するために、GitHub のマージキュー機能の導入を提案しました。マージキュー機能を使うことで、以下のようなメリットが期待できます。

  • 手動での「Update branch」が不要になる
  • マージの順番が自動的に管理される
  • 複数のプルリクエストが同時に待機状態になれる

しかし、この提案には懸念点もありました。

懸念点

最大の懸念点は、GitHub Actions の実行回数が増えることによるコスト増加でした。

  • 従来は 1 つのプルリクエストに対して最低 1 度だけワークフローが実行されていた
  • マージキューを導入すると、最低でもプルリクエスト作成時のチェックとマージキュー時の 2 回実行されるようになる
  • その分、GitHub Actions の費用が増える可能性がある

ただし、以前から「Update branch」が必要だった状況を考慮すると、単純に費用が 2 倍になるわけではないと予測していました。

折衷案

このような懸念がある中で、次のような折衷案で提案しました。

  • 一定期間マージキューを導入してみる
  • 実際の費用対効果を測定する
  • 結果次第で継続するか元に戻すかを判断する

この方針で進めることにしました。

対応内容

1. Pub workspaces の導入

まず最初に、ビルド時間を短縮するために Pub workspaces を導入しました。
詳細については以前書いた記事を参考にしていただけると幸いです。
https://zenn.dev/yumemi_inc/articles/37717d5be277ee

この対応により、1 ワークフローあたり約 15.6 % の時間削減に成功しました。

2. マージキューの導入

GitHub のマージキュー機能を導入するためには、複数の設定を行う必要がありました。

マージキュー用ワークフロー

既存のプルリクエストチェックワークフローをコピーし、マージキュー用に修正しました。主な変更点は、on セクションを merge_group に変更することです。

# .github/workflows/check-merge-queue.yml
name: Check Merge Queue

# ここが重要:プルリクエストではなくマージキュー用のトリガー
on:
  merge_group:
    
jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-24.04
    # Flutter 環境のセットアップと静的解析を実行するステップ
    # 全パッケージに対して完全な静的解析を実行

  test:
    name: Test
    runs-on: ubuntu-24.04
    # Flutter 環境のセットアップとテストを実行するステップ
    # 全パッケージに対して完全なテストを実行

既存のプルリクエストチェックワークフローと重要な違いは、トリガーが pull_request ではなく merge_group になっている点です。
これにより、マージキューに追加されたプルリクエストに対して実行されるようになります。

また、このワークフローでは全パッケージに対して完全なチェックを実行するように設定しています。これは、マージ前の最終チェックとして重要な役割を果たします。

この設定により、「Update Branch」を手動で行う必要がなくなり、開発者体験が向上しました。

GitHub 設定

また、リポジトリの設定でマージキュー機能を有効にしました。

  1. リポジトリの SettingsGeneralPull Requests セクションへ移動
  2. Allow merge queue オプションにチェックを入れる
  3. 必要に応じて、他のマージオプションも設定

3. プルリクエストチェックワークフローの改善

次に、プルリクエストチェックとマージキューの役割分担を明確にしました。

  • マージキュー: 全体のチェックを担当
  • プルリクエストチェック: デフォルトブランチとの差分のあるパッケージのみに絞ったチェックを実行

Melos の差分パッケージ実行スクリプト

効率的な CI を実現するために、Melos の設定に差分パッケージのみを対象とした実行スクリプトを追加しました。
ワークスペース構成は以下のようになっています。

name: my_flutter_project
publish_to: none

workspace:
  # app
  - app/catalog
  - app/mobile
  - app/shared
  # core
  - core/analytics
  - core/data
  - core/designsystem
  # ... 他のパッケージ

特に重要なのは、以下の Melos スクリプトです。

melos:
  scripts:
    # 未コミットの差分パッケージを処理するスクリプト
    gen:diff:head:
      description: 未コミットの差分パッケージのみ build_runner と l10n の生成コマンドを実行します。
      steps:
        - gen:build:diff:head
        - gen:l10n:diff:head
    gen:build:diff:head:
      description: 未コミットの差分パッケージのみ build_runner を使用してコードを生成します。
      run: dart run build_runner build -d
      exec:
        orderDependents: true
      packageFilters:
        dependsOn: build_runner
        diff: "" # --diff=HEAD のワークアラウンド https://github.com/invertase/melos/issues/759
    gen:l10n:diff:head:
      description: 未コミットの差分パッケージのみ多言語対応のためのローカライゼーションファイルを生成します。
      run: flutter gen-l10n
      exec:
        orderDependents: true
      packageFilters:
        dependsOn: flutter_localizations
        diff: "" # --diff=HEAD のワークアラウンド https://github.com/invertase/melos/issues/759

    # メインブランチとの差分パッケージを処理するスクリプト
    gen:diff:main:
      description: main ブランチと差分のあるパッケージのみ build_runner と l10n の生成コマンドを実行します。
      steps:
        - gen:build:diff:main
        - gen:l10n:diff:main
    gen:build:diff:main:
      description: main ブランチと差分のあるパッケージのみ build_runner を使用してコードを生成します。
      run: dart run build_runner build -d
      exec:
        orderDependents: true
      packageFilters:
        dependsOn: build_runner
        diff: origin/main...HEAD
    gen:l10n:diff:main:
      description: main ブランチと差分のあるパッケージのみ多言語対応のためのローカライゼーションファイルを生成します。
      run: flutter gen-l10n
      exec:
        orderDependents: true
      packageFilters:
        dependsOn: flutter_localizations
        diff: origin/main...HEAD

これらのスクリプトでは、2 種類の差分実行オプションを用意しています。

  1. gen:diff:head - 未コミットの変更に対してのみ実行(ローカル開発用)
  2. gen:diff:main - main ブランチとの差分に対して実行(プルリクエスト開発用)

packageFiltersdiff オプションによって差分のあるパッケージのみを特定し、orderDependents: true によって依存関係の順序を考慮した実行が可能になっています。

注目すべき点として、gen:diff:head では Melos の既知の問題(Issue #759)に対するワークアラウンドとして、空の diff 値を指定し、実行時に --diff=HEAD オプションを追加するよう設計されています。

GitHub Actions ワークフローの実装

これらの Melos スクリプトを活用し、プルリクエストチェックワークフローでは Melos の差分実行機能を直接利用して、変更のあったパッケージのみでテストや解析を実行するように設定しました。

# .github/workflows/pr-check.yml
name: PR Check

on:
  pull_request:
    branches: [ main ]

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-24.04
    steps:
    # ... 他のステップ

  test:
    name: Test
    runs-on: ubuntu-24.04
    steps:
    # ... 他のステップ

  check_generated_files:
    name: Check Generated Files
    runs-on: ubuntu-24.04
    steps:
      # ... 他のステップ

      - name: Generate code for affected packages
        run: melos run gen:diff:main

      - name: Check for uncommitted generated files
        run: git diff --exit-code

開発者はローカル環境でも同様のコマンドを使用することができ、差分のあるパッケージのみに対して効率的に作業を行えるようになりました。

# 未コミットの変更に対して(ローカル作業中)
melos run gen:diff:head

# main ブランチとの差分に対して(プルリクエスト作業中)
melos run gen:diff:main

これらの改善により、プルリクエストチェックでは変更のあったパッケージとその依存先のみをチェックするようになり、1 回あたり約 56.6 % の実行時間短縮を達成しました。

導入前後の費用比較

当初は費用増加を懸念していましたが、実際に一定期間運用して測定した結果、全体で むしろ 28% ほど費用削減 に成功しました。

この削減の主な理由は以下の 3 点です。

  1. Pub workspaces の導入による依存関係解決の高速化
  2. プルリクエストチェックを差分のあるパッケージに絞ったことによる実行時間の短縮
  3. 手動での「Update branch」が不要になったことで、重複した CI 実行が減少

まとめ

GitHub のマージキュー機能と Melos の機能を組み合わせることで、以下のような効果が得られました。

  • 開発者の手動操作(Update branch)が減少し、開発体験が向上
  • CI の実行時間が短縮され、フィードバックが早く得られるようになった
  • 予想に反して、GitHub Actions 費用が削減された
  • 差分パッケージのみを対象とした効率的なワークフローにより、開発プロセス全体が高速化

マルチパッケージ構成の Flutter アプリ開発においては、このような CI/CD 環境の最適化が、開発効率と品質の両方を高める鍵となります。特に、Melos のスクリプト機能を活用することで、ローカル開発環境と CI 環境の両方で一貫した差分パッケージ処理が可能になり、効率的な開発ワークフローを実現できます。

皆さんのプロジェクトでも、今回の事例が参考になれば幸いです。

参考リンク

GitHubで編集を提案
Ishinova株式会社

Discussion