🪟

paths-filter を使ってワークフローのジョブをスキップする

2023/12/25に公開

マージ前にベースブランチの変更の取り込みを必須化

手ぶら登園 では GitHub を使って開発を行っており、品質向上のために、マージ前にベースブランチの変更の取り込みを必須化しました。
必須化の経緯や方法等はアドベントカレンダーの 12 日目の記事で書いているので、気になった方はご覧ください。

CI には GitHub Actions を使っていて、主にバックエンドのテストを行っているので、マージ前にベースブランチの変更の取り込みを必須化する際のステータスチェックとしてもテストを選択しました。「ベースブランチの変更を取り込み済み」かつ「テストが通っている」ならばマージできるという設定です。

マージできなくなる問題

ドキュメント系の変更や HTML や CSS のみの変更の場合に、バックエンドのテストは不要です。
そのため、CI の実行時間の節約や開発体験を考慮して paths-ignore によって、特定のケースでワークフローを起動しない設定にしていました。

設定イメージ.yml
on:
  push:
    branches: [develop, main]
    paths-ignore:
      - 'ignore-path1/**'
      - 'ignore-path2/**'
  pull_request:
    branches: ["**"]
    paths-ignore:
      - 'ignore-path1/**'
      - 'ignore-path2/**'

この状態で前述の「ベースブランチの変更を取り込み済み」かつ「テストが通っている」ならばマージできるという設定をしました。すると、paths-ignore にマッチした場合に、ワークフローが起動しないので、結果としてステータスチェックをパスできずにマージできなくなる問題が起こりました。

ワークフローは実行しつつも、ジョブをスキップする

ワークフローレベルでスキップするのではなく、ジョブレベルでスキップすれば、ステータスチェックも通ります。そのため、解決策としては paths-ignore と同等のことをテストジョブの if でやればいいわけです。

既存の if に paths-ignore と同等の制御を加えると、条件が複雑になって可読性が悪いので、paths-filter を使いました。
paths-filter はワークフロー組み込みの paths と似たことをジョブレベル、ステップレベルでできるようにするライブラリです。

README を見ながら、以下のように設定するとシンプルな記述でジョブの実行制御ができるようになり、マージできない問題も解消しました!

設定イメージ.yml
on:
  push:
    branches: [develop, main]
  pull_request:
    branches: ["**"]

jobs:
  need-ci:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    outputs:
      paths: ${{ steps.filter.outputs.paths }}
    permissions:
      pull-requests: read
      contents: read
    steps:
      - name: Checkout
        if: github.event_name == 'push'
        uses: actions/checkout@v4

      - name: Filter paths
        uses: dorny/paths-filter@v2
        id: filter
        with:
          base: ${{ github.ref }}
	  # paths-ignore の代わりとして使うので、test を実行したいファイルのみをピックアップする
	  # 変更したファイルがいずれかにマッチすれば true
          filters: |
            paths:
              - 'test-required-path1'
              - 'test-required-path2/**'
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs:
      - need-ci
    if: (!failure()) && (needs.need-ci.outputs.paths == 'true')

    steps:
      - name: Checkout
        if: github.event_name == 'push'
        uses: actions/checkout@v4
    # 以下省略
BABYJOB テックブログ

Discussion