Introduction
Static analysis is the action of analyzing code without execution. Lint is the most common form of static analysis. The results of lint are warnings and errors for code quality improvement. Linting has evolved to be a crucial part of continuous development.
In this article, we will walk through some concepts of static code analysis on Kotlin Multiplatform Mobile (KMM) Android, how to locally lint, how to integrate linting into a Continuous Integration(CI) flow (using GitHub Actions), and eventually, publish a summary report on every Pull Requests.
Why Lint
Linting is an efficient and quick way to ensure the code conforms to the team’s quality standards. Lint warnings are visible right on the code with supported IDEs, shown with Git Hooks, or automatically commented on the pull request.
Linting is blazingly fast, takes less than 10 seconds, and can be easily integrated into any workflow. The earlier we can find issues with code quality, the quicker we can fix the mistake and lessen the impact on the whole project.
How to Lint
Linting on a KMM project uses the built-in Gradle command:
./gradlew lint
This command will generate lint-results
files, one for each module. The default modules in KMM are androidApp
and shared
, so two reports will be generated.
The settings for ./gradlew lint
can be customized in a lint.xml
file.
For example, to ignore the GradleVersion
warnings from the template, create a lint.xml
in the project’s root with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="GradlePluginVersion" severity="ignore" />
<issue id="GradleDependency" severity="ignore" />
</lint>
Lint with Danger
What is Danger
Linting in continuous integration & continuous development (CI/CD), using just Gradle, will only show the output in the pipeline’s log. This is where Danger comes in. Danger is a tool for commenting on CI/CD’s outputs directly on a pull request’s code changes. Danger makes code review easier and encourages following code conventions more.
💡 Danger only works with CI services and can only post to version control (GitHub, Bitbucket & GitLab) or email. Feel free to skip Danger-related content if your team does not use CI.
Lint with Danger
Set up Danger Instruction
To start using Danger, include Danger in the project’s Gemfile
, and create a file name Gemfile
in the root of the project with the following content:
source "https://rubygems.org"
# Other gems
gem 'danger'
gem 'danger-android_lint'
💡 Danger uses
Ruby
, make sure the CI service hasRuby
pre-installed.
Create a new Dangerfile
at the root of the project. The Dangerfile
contains Danger’s instructions on what to check and comment on.
Add the Android Lint report to the Dangerfile
:
# Android Lint output check
lint_dir = '**/build/reports/lint-result-debug.xml'
Dir[lint_dir].each do |file_name|
android_lint.skip_gradle_task = true
android_lint.report_file = file_name
android_lint.lint(inline_mode: true)
end
This script finds all generated lint-result-debug.xml
in the project and publishes the Android Lint result.
💡 This is a minimum setup for Danger to work with Android Lint. For a comprehensive Danger setup with PR lint, please check out our Android Template for reference.
💡 For a more detailed setup guide, see the instructions on the Danger website.
Set up CI/CD for Danger
Include the following steps to your CI workflow:
- Run Lint to generate the report
./gradlew lint
. - Install Ruby and Danger
bundle install
. - Execute Danger
bundle exec danger
.
Make sure to execute Danger as the last step to ensure all outputs are ready.
The following example workflow is for GitHub Actions:
- name: Run Android Lint
run: ./gradlew lint
- name: Set up Ruby
uses: actions/setup-ruby@v1
with:
ruby-version: '2.7'
- name: Install Bundle and check environment versions
run: bundle install
- name: Run Android Danger
env:
DANGER_GITHUB_API_TOKEN: $
run: bundle exec danger
💡
DANGER_GITHUB_API_TOKEN
is the token from a bot account or a user’s account. Danger will use the included account for commenting on the pull request. There is a detailed instruction in the Danger official tutorial on how to set up one.
This workflow will generate and output the lint report on the pull request.
How to Lint with detekt
What is detekt
detekt is a static code analyzer (linter) for Kotlin. detekt
’s rules cover many rules that Android Lint lacks and can be considered the missing piece in a KMM project.
Using detekt
Add detekt
to the project by adding the dependency inside ./build.gradle.kts
.
buildscript {
...
dependencies {
...
classpath("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.21.0")
}
}
In each of the Kotlin modules, such as shared
and androidApp
, include the detekt
plugin in the build.gradle.kts
.
plugins {
...
id("io.gitlab.arturbosch.detekt")
}
At the end of the module’s build.gradle.kts
, add the source files directories.
detekt {
source = files(
"./"
)
// config = files("./detekt-config.yml") // Use to define Detekt rules.
}
💡 See more detekt configurations from the document.
Run detekt
with the Gradle command ./gradlew detekt
. Each module will have detekt
report files and directory.
detekt with Danger
Showing detekt
report on the pull request is similar to showing Android Lint
report with the support of another danger
gem.
First, add the danger-kotlin_detekt
gem to the Gemfile
.
source "https://rubygems.org"
gem 'danger'
# Other gems
gem 'danger-kotlin_detekt'
Then add the detekt
block to Dangerfile
.
# Other blocks
# ...
# Detekt output check
detekt_dir = "**/build/reports/detekt/detekt.xml"
Dir[detekt_dir].each do |file_name|
kotlin_detekt.skip_gradle_task = true
kotlin_detekt.report_file = file_name
kotlin_detekt.detekt(inline_mode: true)
end
This new block will scan for build/reports/detekt/detekt.xml
in each module and publish detekt’s generated reports on the pull request.
Ensure to include ./gradlew detekt
in the CI workflow as in this GitHub Actions example.
- name: Run Detekt
run: ./gradlew detekt
- name: Run Android Lint
run: ./gradlew lint
...
- name: Run Android Danger
env:
DANGER_GITHUB_API_TOKEN: $
run: bundle exec danger
💡
detekt
’s default configuration can fail the CI pipeline when the number of errors exceeds the threshold. To allowdetekt
to run without failing the pipeline, configure the settingignoreFailures = true
.
// Disable fail on error threshold.
detekt {
source = files(
"./"
)
ignoreFailures = true
}
Conclusion
Linting and code coverage are simple tools that can drastically improve your CI/CD pipeline. But having a summary report for all of them would not be possible without Danger
.
Using Danger
to publish the lint and coverage report to a pull request helps reduce your team’s workload and ensures that the code’s structure follows the agreed format.
detekt
and Danger
are also easy to set up for both legacy and new projects. So if you have yet to try them out, give them a try.
To learn more about code coverage with Danger
, make sure to read the upcoming KMM Android Code Coverage blog post or read about KMM iOS lint and KMM iOS code coverage.