DEV Community

CharlieTap
CharlieTap

Posted on

Making PR checks pretty by example

If you've worked on a project with any form of continuous integration before, then you'll likely be familiar with the concept of checks that run when you submit your code to version control.

For those that aren't aware, typically tasks are run in response to submission/updates of code, these tasks can run tests, linting checks and a plethora of other code verifications. In most setups, branches are usually protected by a pull review process, which runs checks on proposed code changes before they are reviewed and potentially merged into the codebase.

In this post I'm going to walk you through setting up a simple continuous integration task using GitHub Actions, which runs the popular Ktlint library and adds the output back onto the Pull Request in a way that's easy to digest using a tool called ReviewDog.

Ktlint meets Gradle


Whilst it's possible to run Ktlint from CLI, I prefer to introduce it in the form of a Gradle task. Providing it in this way allows all the configuration (albeit none in this case) to rest within the project itself, guaranteeing you and your teammates will have the same output when running it. Another benefit is that you can hook this task into the existing android linting task, just saving you one less thing to run in your CLI.

My task of choice:

kotlinter

This particular plugin is very performant, it uses gradles incremental build system, and parallelises the linting work using the Gradle Worker API, making it very fast if you have a multi module workspace. Ultimately these checks are run often, so saving 10-15 seconds adds up over the course of a year.

I won't cover the setup here, you can follow the link above or even checkout my example application to support this blog post which is found at the bottom.

Once you think you are setup, run the following task to verify:

gradle lintKotlin

If you have any errors appear, there exists another task which may be able to automatically correct them for you:

gradle formatKotlin

Be aware there are certain things this task will be unable to fix for you and therefore you'll have to rectify them yourself using the feedback you get.

Making this task run on Pull Requests


To run any workflow in GitHub Actions you simply place a yaml file in the folder .github/workflows relative to the project root with the instructions you want to run.

For example we could put the following:

.github/workflows/code-quality.yml

name: Code Quality
on: [pull_request]
jobs:
  ktlint:
    name: Run ktlint
    runs-on: ubuntu-latest

    steps:
      - name: Clone repo
        uses: actions/checkout@master
        with:
          fetch-depth: 1

      - name: Ktlint
        run: gradlew lintKotlin
Enter fullscreen mode Exit fullscreen mode

This would suffice, in running the linter and providing a check on PR which would validate the code. However we want to do something which produces more meaningful feedback like this:

Image description

...

The pretty part ...


So we know how to:

  • Run ktlint with Gradle
  • Run the Gradle task from CI

Now we need to figure out how to use review dog to comment on the pull request with the results of the gradle lint task.

So it's important to understand the output of that linting task. By default, kotlinter will create report files, one for each module you run it on. You'll find these reports inside the modules generated build folder (i.e if you run it on app, you'll find the report in app/build/...). Whilst its possible to aggregate these files and pass their contents onto review dog, it also writes any errors it finds to stderr. With a little bit shell scripting you can tailor this output ready to be piped into ReviewDog and save yourself the task of aggregating all of the reports.

So lets look at the shape we need the listing report to be in for review dog to accept it:

"reviewdog accepts any compiler or linter result from stdin and parses it with scan-f like 'errorformat', which is the port of Vim's errorformat feature."

In simple terms, each line we feed into standard input of review dog must have a particular format, we can dictate that format to some extent, but ultimately we have to conform to errorformat.

For example if we tell review dog through the command line argument "efm" to expect the format:

%f:%l:%c:%t: %m

Review dog will the expect each line to be in the following format:

{file}:{line number}:{column number}:{error type}: {message}

Thankfully the stderr from the lintKotlin task is largely already in this form.

We just need to filter for the lines we care about

grep 'Lint error > '

Tweak them to replace the text 'Lint error > ' with the error type which in this case can simply be 'e' for error.

sed 's/ Lint error > /e: /g'

You can read more about the types and meanings of the format substitutions here

Et Voila

.github/scripts/check.sh

#!/usr/bin/env bash

./gradlew lintKotlin 2>&1 | grep 'Lint error > ' | sed 's/ Lint error > /e: /g' | reviewdog  \
-name="ktlint" \
-reporter=github-pr-review \
-efm="%f:%l:%c:%t: %m" \
-level=error \
-filter-mode=nofilter \
-fail-on-error
Enter fullscreen mode Exit fullscreen mode

Something to be aware of here, if you want scripts to be executable when they are run inside of CI, you'll need to instruct git of the executable permission. I.e git update-index --chmod=+x .github/scripts/check.sh

Putting it all together


Lets update the yaml file from earlier to look like the following:

.github/workflows/code-quality.yml


name: Code Quality
on: [pull_request]
jobs:
  ktlint:
    name: Run ktlint
    runs-on: ubuntu-latest

    steps:
      - name: Clone repo
        uses: actions/checkout@master
        with:
          fetch-depth: 1

      - name: Install Review Dog
        uses: reviewdog/action-setup@v1
        with:
          reviewdog_version: latest

      - name: Ktlint
        env:
          REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.github_token }}
        run: .github/scripts/check.sh

Enter fullscreen mode Exit fullscreen mode

Without going into too much detail, we added a new job to install review dog, and another to run our bash script we wrote earlier (You don't have to configure the secret, this secret is included for you).

I've put an example project here for anyone that gets stuck and wants to copy this across, you'll also see the PR mentioned in the post on this repo.

Top comments (0)