GITHUB ACTIONS / MONOREPO

差分があるサービスだけbuildする — 汎用パターンで理解する

この課題の価値は、難しいシェルを書くことではありません。変更ファイルを影響サービスへ変換し、その結果だけを後続ジョブへ渡す設計を、どのモノレポでも再利用できる形で理解することです。

TL;DR

  1. detect は変更影響だけを判定し、サービス名のJSON配列を返す。
  2. build はMakefileやクラウド固有includeを見て、自分が実行可能か最終確認する。
  3. AIには「依存マップ」と「5つの確認ケース」を渡して実装させる。

01課題の正体 — diffを実行対象へ変換する

Dockerfile探索もpaths-filterも、入口と出口は同じです。

変更ファイルから差分build対象を作る4段階フロー
入力は変更ファイル、出力はサービス名のJSON配列。実装方法が変わっても契約は同じ。

教材は、Dockerfileのあるディレクトリを候補として集め、git diff の変更ファイルと照合し、build-targets を後続matrixへ渡します。覚えるべきなのはシェルの細部より、このデータ変換です。

変更ファイル
依存ルール
targets JSON
matrix build

AIに任せてよい: glob、JSON化、YAML実装。
人が決める: どの変更がどのサービスへ影響するか。

022つの実装法 — 手製shellとpaths-filter

学習には手製方式、現場の汎用テンプレートには宣言的フィルタが扱いやすい。

手製shellとdorny paths-filterの比較
自動発見と明示的な依存管理のトレードオフ。汎用化では壊れにくい契約を優先する。
観点find + git diffdorny/paths-filter
対象追加Dockerfile追加で候補を自動発見フィルタ名とパスを追加
比較処理fetch範囲・merge-baseを自前管理eventに応じた変更検出をActionへ委譲
JSON出力jq 等で組み立てるchanges 出力をmatrixへ渡せる
共有依存別ロジックが必要各フィルタへ明示しやすい
向く場面教材・自動発見重視現場テンプレート・モノレポ

教材例の境界: HEAD^ HEAD は1コミット比較です。汎用化ではpush内の複数コミットやmerge-baseをどう扱うかを、トリガーとActionの仕様に合わせて決めます。

03なぜMakefile確認をbuild側に残すのか

detectは影響判定、buildは実行能力の確認。段階的に絞ると責務が崩れません。

変更検出とクラウド別buildの責務分離
候補選定を軽く保ち、クラウド固有の条件は実行側へ置く。

質問660の回答では、find-build-targets でMakefileと aws.mk のincludeまで確認すると複雑になり、読みづらくなる点が理由として挙げられています。build側ならAWSは aws.mk、GCPは gcp.mk という固有条件を自然に持てます。

責務置くもの置かないもの
detect変更パス→対象名、JSON出力ECR、認証、make実行
AWS buildMakefile/aws.mk確認、ECR push変更ファイル解析
GCP buildMakefile/gcp.mk確認、Artifact Registry変更ファイル解析

少しrunner時間を使っても、検出ロジックの単純さマルチクラウド拡張性を買う判断です。これは「最安」ではなく「変更しやすさとの総コスト最小化」です。

04汎用化の核心 — 依存マップを宣言する

サービス配下だけでなく、共有コードとbuild定義の波及先を決めます。

共有変更とサービス固有変更の依存マップ
差分検出の品質はglobの巧さではなく、共有依存の定義で決まる。
- uses: dorny/paths-filter@v4
  id: changes
  with:
    filters: |
      slack-metrics:
        - 'services/slack-metrics/**'
        - 'aws.mk'
        - 'go.mod'
        - 'go.sum'
      db-migrator:
        - 'ops/db-migrator/**'
        - 'aws.mk'
        - 'go.mod'
        - 'go.sum'

同じ共有パスを複数フィルタへ含めれば、その変更時に changes 出力が両サービス名を返します。サービス名はbuildだけでなく、migrate・deploy・task definition登録の共通キーにもできます。

方針が必要: workflow変更を全buildのトリガーにするか、workflow構文検証だけにするか。暗黙にせずフィルタへ表します。

05壊れにくいjob契約 — JSON、空配列、status

動くhappy pathだけでなく、何も変わらない時を正しく表現します。

jobs:
  detect:
    outputs:
      targets: ${{ steps.changes.outputs.changes }}

  build:
    needs: detect
    if: needs.detect.outputs.targets != '[]'
    strategy:
      matrix:
        service: ${{ fromJSON(needs.detect.outputs.targets) }}
① 差分なし SKIP[] を返し、build job自体を起動しない。
② 両サービス[A, B] を返し、2つを並列build。
③ Aだけ[A] を返し、Aだけbuild。
④ Bだけ[B] を返し、Bだけbuild。
⑤ shared変更[A, B] を返し、影響先すべてをbuild。

required checkの注意: 下流jobのskipが集約上見落とされる構成があります。Branch Protectionで必須にするなら、if: always() で各job resultを検査する固定名のstatus jobも検討します。

06AIに頼むときの最小プロンプト

コード生成より先に、依存関係と受け入れ条件を渡します。

このモノレポで変更されたサービスだけをbuildしたい。サービス名と影響パスの対応は以下。共有パス変更は全サービス対象。detectは常にJSON配列を出し、空ならbuildをskip。build側でMakefileとクラウド固有includeを確認する。5ケースを満たす最小のworkflow差分を作り、YAML構文とmatrix式を検証して。
AIへ渡す情報渡さないと起きること
サービス一覧と各ルートパス候補の推測漏れ
共有ファイルの波及先必要なrebuildを取りこぼす
push / pull_requestのどちらか比較範囲を誤る
AWS/GCPの実行能力条件存在しないmake targetを呼ぶ
5つの期待結果happy pathだけで「完成」になる

結論: AIでリファクタするのは合理的です。ただし、AIに推測させてはいけないのは依存マップと成功条件です。

結論

  1. 教材の手製方式で原理を理解し、汎用テンプレートでは paths-filter へ置き換えられる。
  2. detectは影響判定、buildは実行能力、deploy/migrateは反映という責務を守る。
  3. AIには依存マップと5ケースを渡す。シェルの巧さではなく契約の明確さが再利用性を作る。