この記事は、GitHub Actions Advent Calendar 2024 の14日目の記事です。

GitHub Actions Advent Calendar 2024
https://qiita.com/advent-calendar/2024/github-actions

様々なlinter/formatterをpre-commitで動かし、
また、GitHubActions(pre-commit/actionを使う)でもチェックさせると、
linter/formatterを一箇所で管理できます。

pre-commit
https://pre-commit.com/

pre-commit/action | GitHub
https://github.com/pre-commit/action

GitHubActionsでは、
linter/formatterの出力にProblemMatcherを適用してAnnotationできますが、
出力形式の異なるものをpre-commitでまとめていると、
そのままではProblemMatcherを適用できません。

Problem Matchers | actions/toolkit | GitHub
https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md

この問題に対しては、
pre-commit hookにlog_fileの設定をおこなうことで対処が可能です。

log_file | .pre-commit-config.yaml - hooks | pre-commit
https://pre-commit.com/#config-log_file

このエントリでは、この対処方法の適用手順を説明します。

対象リポジトリの作成

まず対象リポジトリを作成します。
ここでは、flake8とmarkdownlintを導入したリポジトリを作成します。

mkdir problemmatcher-study && cd $_
git init
python -m venv venv
. venv/bin/activate
pip install pyproject-flake8
pip freeze > test-requirements.txt

リポジトリにファイルをcommit、GitHubにpushします。

echo "venv" > .gitignore
git add .
git commit -a -m "initial commit"
git remote add origin ※GitHubリポジトリを指定
git branch -M main
git push -u origin main

linterの適用対象のファイルを作ります。

main.py

import sys
def main():
    pass

README.md

## foo

linterの設定を追加します。

pyproject.toml

[tool.flake8]
exclude = ["venv"]

flake8の動作を確認します。

$ pflake8
./main.py:1:1: F401 'sys' imported but unused
./main.py:2:1: E302 expected 2 blank lines, found 0

ブランチを切り替え、リポジトリにファイルをcommitしておきます。

git switch -c ci
git add .
git commit -a -m "add target files"

pre-commitの設定追加

pre-commitの設定を追加します。

.pre-commit-config.yaml

repos:
  - repo: https://github.com/csachs/pyproject-flake8
    rev: v7.0.0
    hooks:
      - id: pyproject-flake8
  - repo: https://github.com/markdownlint/markdownlint.git
    rev: v0.13.0
    hooks:
      - id: markdownlint

pre-commitの動作を確認します。

$ pre-commit run --files main.py README.md
pyproject-flake8.........................................................Failed
- hook id: pyproject-flake8
- exit code: 1

main.py:1:1: F401 'sys' imported but unused
main.py:2:1: E302 expected 2 blank lines, found 0

yamllint.............................................(no files to check)Skipped
Markdownlint.............................................................Failed
- hook id: markdownlint
- exit code: 1

README.md:1: MD002 First header should be a top level header

リポジトリにファイルをcommitします。

git add .pre-commit-config.yaml
git commit .pre-commit-config.yaml -m "add pre-commit"

GitHubActionsの設定追加

GitHub Actionsの設定を追加します。

.github/workflows/ci.yaml

name: CI
on: pull_request
jobs:
  pre-commit-run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pre-commit/action@v3.0.1

リポジトリにファイルをcommit、GitHubにpushします。

git add .github/workflows/ci.yaml
git commit -a -m "add github ci workflow"
git push origin ci

GitHubのUIからPullRequestをあげて、ステータスチェックが失敗することを確認します。

ProblemMatcherの設定追加

それでは本題のProblemMatcherを設定していきます。

ProblemMatcherの定義flake8用・markdownlint用を追加します。

.github/problem-matcher/flake8.json

{
  "problemMatcher": [
    {
      "owner": "flake8",
      "pattern": [
        {
          "regexp": "^(([^:]+):(\\d+):(\\d+):\\s+.+)$",
          "message": 1,
          "file": 2,
          "line": 3,
          "column": 4
        }
      ]
    }
  ]
}

.github/problem-matcher/markdownlint.json

{
  "problemMatcher": [
    {
       "owner": "markdownlint",
       "pattern": [
         {
           "regexp": "^(([^:]+):(\\d+):\\s+.+)$",
            "message": 1,
            "file": 2,
            "line": 3
        }
      ]
    }
  ]
}

pre-commitでlogfileを出力して、CI側でProblemMatcherを適用するよう設定を変更します。

.pre-commit-config.yaml

repos:
  - repo: https://github.com/csachs/pyproject-flake8
    rev: v7.0.0
    hooks:
      - id: pyproject-flake8
        log_file: /tmp/pre-commit-log-pyproject-flake8
  - repo: https://github.com/markdownlint/markdownlint.git
    rev: v0.13.0
    hooks:
      - id: markdownlint
        log_file: /tmp/pre-commit-log-markdownlint

.github/workflows/ci.yaml

---
name: CI
on: pull_request
jobs:
  pre-commit-run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pre-commit/action@v3.0.1
      - run: |
          echo "::add-matcher::.github/problem-matcher/flake8.json"
          cat /tmp/pre-commit-log-pyproject-flake8 \
            | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g"
          echo "::remove-matcher owner=flake8::"
          echo "::add-matcher::.github/problem-matcher/markdownlint.json"
          cat /tmp/pre-commit-log-markdownlint \
            | sed -r "s/\x1B\]8;;[^\x1B]*\x1B\\\\//g"
          echo "::remove-matcher owner=markdownlint::"          
        if: always()

リポジトリにファイルをcommit、GitHubにpushします。

git add .github/problem-matcher
git commit -a -m "add ci problem matcher"
git push origin ci

GitHubのUIにAnnotationが表示されることが確認できます。

pre-commit-problem-matcher01

以上。