article

Static Code Analysis Report in GitHub Actions using Danger

Similar like for test results as noted in the previous article, there's no way to display static code analysis reports in the GitHub Action build result.

Instead the comments and checks APIs should be embraced to report results.

One of the best tools to do so I found has been Danger.

Danger runs during your CI process, and gives teams the chance to automate common code review chores. This provides another logical step in your build, through this Danger can help lint your rote tasks in daily code review.

Danger

Danger is not only a great and valueable tool to enforce common code review chores, but also to report these to GitHub or also for example GitLab.

This post will use danger which is built on using ruby. But Danger also exists in various other languages like JS, Swift, Kotlin and Python.

Please see the best guide for your operating system to setup ruby. For macOS I usually prefer brew.

brew install ruby

After installing ruby you will also need bundler.

gem install bundler

To run Danger, you will need a Dangerfile which specifies the actions to be executed by Danger, and a Gemfile which specifies the plugins we use with Danger.

Gemfile

The Gemfile as mentioned specifies the dependencies we want to use with Danger.

For this guide we will use the following. Please create a file called Gemfile in the root of your project:

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem 'danger'
# The dependencies
# Add-on to process checkstyle result files
gem 'danger-checkstyle_format'
# Add-on to process android-lint result files
gem 'danger-android_lint'
# Add-on to process detekt result files
gem 'danger-kotlin_detekt'

Dangerfile

After defining the add-ons to use, create a file named Dangerfile in the root of the project.

This specifies which checks to run, and allows you to build a great set of checks to ensure PRs match your required specifications.

# Only focus on changes for the current DIFF from the active PR
github.dismiss_out_of_range_messages

# Identify if a PR is not yet meant to be merged, by commenting with a warning
has_wip_label = github.pr_labels.any? { |label| label.include? "Engineers at work" }
has_wip_title = github.pr_title.include? "[WIP]"
if has_wip_label || has_wip_title
	warn("PR is marked as Work in Progress")
end

# Ensure the PR is not marked as DO NOT MERGE
fail("PR specifies label DO NOT MERGE") if github.pr_labels.any? { |label| label.include? "DO NOT MERGE" }

# Warn when there is a big PR
warn("Big PR") if git.lines_of_code > 5000

# To support multi module projects, identify defined modules via the settings.gradle
# Open the file `settings.gradle`
File.open("settings.gradle", "r") do |file_handle|
  # Read through every single file
  file_handle.each_line do |setting|
    # Only check lines which include `include`
    if setting.include? "include"
        # read in the module name
        # Note this goes by the assumption that name equals path
        gradleModule = setting[10, setting.length-12]

        # Process check-stlye results
        checkstyleFile = String.new(gradleModule + "/build/reports/checkstyle/checkstyle.xml")
        if File.file?(checkstyleFile)
            checkstyle_format.base_path = Dir.pwd
            checkstyle_format.report(checkstyleFile, inline_mode: true)
        end

        # Process Android-Lint results
        androidLintFile = String.new(gradleModule + "/build/reports/lint-results.xml")
        androidLintDebugFile = String.new(gradleModule + "/build/reports/lint-results-debug.xml")
        if File.file?(androidLintFile) || File.file?(androidLintDebugFile)
            android_lint.skip_gradle_task = true # do this if lint was already run in a previous build step
            android_lint.severity = "Warning"
            if File.file?(androidLintFile)
                android_lint.report_file = androidLintFile
            else
                android_lint.report_file = androidLintDebugFile
            end
            android_lint.filtering = true
            android_lint.lint(inline_mode: true)
        end

        # Process Detekt results
        detektFile = String.new(gradleModule + "/build/reports/detekt.xml")
        if File.file?(detektFile)
            kotlin_detekt.report_file = detektFile
            kotlin_detekt.skip_gradle_task = true
            kotlin_detekt.severity = "warning"
            kotlin_detekt.filtering = true
            kotlin_detekt.detekt(inline_mode: true)
        end
    end
  end
end

The above Dangerfile will not only ensure to not merge a WIP PR, but also parse and report static code analysis checks to the PR.

It's noteworthy that with the above specifications, only reports to actually affected lines are posted. This is done by only reporting problems associated with changes in the DIFF of the current PR. Beside that, also pay close attention to the configuration for the different reporting tasks:

skip_gradle_task - Do this if you ran this task before already

filtering - Only check changes associated to the PRs diff

inline_mode: true - Comment on the code changes in the PR

Please note that the above Dangerfile requires the lint, detekt, checkstyle tasks were executed before running the Danger step. This is not part of this article. You can find a sample if you follow the link to the MaterialDrawer build at the bottom.

GitHub Action

The last step to make use of this newly defined checks is to include it within the workflow. Luckily there is an official action to setup ruby on official GitHub action agents.

- name: Setup Ruby
  uses: ruby/setup-ruby@v1
  with:
    ruby-version: '2.6.3'
    bundler-cache: true

This action will besides setting up ruby, also cache the bundler cache to speed up future builds.

With ruby and bundler available the last piece is to run danger.

- name: Run Danger
  if: github.event_name  == 'pull_request'
  run: |
    # install danger and its dependencies using bundler
    gem install danger
    # execute danger for this PR
    bundle exec danger --dangerfile=Dangerfile --danger_id=danger-pr
  env:
    # the token used by danger to report the results on the PR
    DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Result

After setting up all the above, you can start the workflow and you should get reports similar to:

Danger PR Report
Danger PR Report

A similar setup as in this articles can be found set-up in the MaterialDrawer library.

Feedback

Got thoughts, feedback, improvements, suggestions, or comments?

Let me know @mike_penz