Gradle Checkstyle plugin
Jan 8, 2019 06:50 · 846 words · 4 minute read
Возникла необходимость настроить статический анализ кода (checkstyle) для проекта на языке Java. Давайте разберемся!
Из особенностей - проверка кода должна запускаться только при подаче пулл реквеста, а сама задача должна быть легко масштабируема (можно быстро включить checkstyle еще для 50 или 100 других проектов). Итак, приступим.
Для решения задачи будем использовать плагин Checkstyle. Пример схемы (файл checkstyle.xml
) для данного плагина можно взять здесь и, при необходимости, подредактировать под свои нужды.
В предыдущей статье мы уже рассматривали использование инит-скриптов гредла, и, так как нам нужна масштабируемость, настройку статического анализа кода мы тоже сделаем через инит-скрипт. Создаем файл checkstyle.gradle
со следующим содержимым:
allprojects {
afterEvaluate { project ->
project.plugins.withId('checkstyle') {
try {
def _toolVersion = ext.has('toolVersion') ? ext['toolVersion'] : '8.15'
if (System.getenv('BUILD_ENVIRONMENT') ?: 'DEVELOPER' == 'CI') {
println "'checkstyle' plugin common config"
println " |- use toolVersion version: $_toolVersion"
checkstyle {
toolVersion "$_toolVersion"
configFile = file(System.getenv('GRADLE_HOME') + "/checkstyle.xml")
println " |- use checkstyle.xml: " + configFile
println ''
reportsDir = file("${buildDir}/checkstyleReports")
}
checkstyleMain {
source ='src/main/java'
}
checkstyleTest {
source ='src/test/java'
}
} else {
println "'checkstyle' plugin common config"
println " |- use toolVersion version: $_toolVersion"
checkstyle {
toolVersion "$_toolVersion"
configFile = file(System.getenv('HOME') + "/.gradle/checkstyle.xml")
println " |- use checkstyle.xml: " + configFile
println ''
reportsDir = file("${project.buildDir}/checkstyleReports")
}
checkstyleMain {
source ='src/main/java'
}
checkstyleTest {
source ='src/test/java'
}
}
} catch (UnknownPluginException) {
println UnknownPluginException
}
}
}
}
Схема для плагина на машине разработчика должна находиться по пути USER_HOME/.gradle/checkstyle.xml
, а в docker-контейнере, который запускается на CI-сервере, - в GRADLE_HOME/checkstyle.xml
. Добиться этого несложно - в предыдущей статье мы рассматривали как это можно сделать.
Теперь, чтобы в проекте появились задачи для статического анализа кода, достаточно в конфигурации сборки (build.gradle
) добавить соответствующий плагин:
plugins {
id 'checkstyle'
}
После добавления плагина станут доступны задачи checkstyleMain
, checkstyleTest
, checkstyleSourceSet
и, самое главное, check
, которая зависит (включает в себя) все перечисленные ранее.
Для запуска проверки кода на CI-сервере с помощью docker-контейнера достаточно выполнить примерно такую команду:
docker run \
--rm \
-v $(pwd):/project \
-w /project \
myorganization/gradle:5.0-jdk11 gradle clean check
Для описания пайплайна (Pipeline) с задачей checkstyle будем использовать уже хорошо известные нам shared libraries - помним о возможности масштабирования и о принципе DRY (Don’t Repeat Yourself). Расширим базовые требования - добавим в описание пайплайна также отправку комментариев к пулл реквесту с результатами анализа кода, формирование отчета (для доступа по ссылке) и уведомление в slack-канал.
Примечание. Для отправки комментариев в пулл реквесту нам понадобится jenkins-плагин Violation Comments to GitHub. Установить его можно руками или с помощью скрипта 00-install-plugins.groovy
как описано здесь.
Pipeline (файл adsCheckstylePipeline
) в нашем случае будет выглядеть так:
def call(body) {
def pipelineParams = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = pipelineParams
body()
pipeline {
agent any
stages {
stage('Checkstyle') {
steps {
script {
env.REPO_NAME = "${pipelineParams.repoName}"
sh '''
docker run \
--rm \
-v $(pwd):/project \
-w /project \
myorganization/gradle:5.0-jdk11 gradle clean check
'''
env.BUILD_STATUS = "${currentBuild.currentResult}".toLowerCase()
}
step([$class: 'ViolationsToGitHubRecorder',
config: [
gitHubUrl: 'https://api.github.com/',
repositoryOwner: "${GITHUB_PR_SOURCE_REPO_OWNER}",
repositoryName: "${REPO_NAME}",
pullRequestId: "${GITHUB_PR_NUMBER}",
credentialsId: 'token-pr',
createCommentWithAllSingleFileComments: true,
createSingleFileComments: true,
commentOnlyChangedContent: false,
minSeverity: 'WARN',
keepOldComments: false,
commentTemplate: """
Reporter: {{violation.reporter}}{{#violation.rule}}
Rule: {{violation.rule}}{{/violation.rule}}
Severity: {{violation.severity}}
File: {{violation.file}} L{{violation.startLine}}{{#violation.source}}
Source: {{violation.source}}{{/violation.source}}
{{violation.message}}
""",
violationConfigs: [
[ pattern: ".*/checkstyleReports/.*\\.xml\$",
parser: 'CHECKSTYLE',
reporter: 'Checkstyle'
]
]
]
])
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'build/checkstyleReports',
reportFiles: 'main.html',
reportName: 'HTML Report',
reportTitles: ''
])
slackSend(channel: "${pipelineParams.slackChannel}", \
message: "Build '${env.JOB_NAME}' is *${BUILD_STATUS}* \n (${env.BUILD_URL}) \n\n" +
"See report on '${JOB_URL}HTML_20Report/'")
}
}
}
}
}
}
Находящийся в основном проекте Jenkinsfile для запуска checkstyle будет таким:
@Library('jenkins-shared-libraries@master') _
adsCheckstylePipeline {
slackChannel = '@ealebed' // for sending notification in slack
repoName = 'my-test-repo' // GitHub repo name, need for pushing comment to PR
}
Для публикации комментариев с результатами анализа кода плагину необходимо авторизоваться на GitHub. Это делается с помощью строки credentialsId: 'token-pr'
- конечно же, соответствующие креденшелы нужно создать. Можно это сделать вручную - в Интернете множество примеров с картинками, как это сделать, а можно немного “доработать” уже существующие groovy-скрипты по настройке Jenkins (см. цикл статей “Jenkins as a code” - 1, 2, 3, 4).
Я выбрал второй вариант, поэтому мне пришлось добавить в скрипт 01-global-settings.groovy
следующие строки:
...
println("--- Configuring credentials")
Credentials secretText = (Credentials) new StringCredentialsImpl(
CredentialsScope.GLOBAL,
"token-pr", // id token
"GitHub token for PR", // description for this token
Secret.fromString("bw4aeqkmejay.....xip4ln727u0wg7q") // token generated on github: https://github.com/settings/tokens
)
SystemCredentialsProvider.getInstance().getStore().addCredentials(Domain.global(), secretText)
println("--- Configuring GitHubServer")
GitHubServerConfig server = new GitHubServerConfig('token-pr')
server.setManageHooks(true)
server.setApiUrl('https://api.github.com')
server.setClientCacheSize(20)
GitHubPlugin.configuration().getConfigs().add(server)
Осталось создать задачу в Jenkins, где в качестве триггера указать “GitHub Pull Requests”. Trigger Mode в данном случае будет “Cron with Persisted Data”, а Trigger Events - “Pull Request Opened” (конечно, вы можете выбрать другие значения в зависимости от ваших требований). Также в поле Branches to build нужно указать “origin/pull/${GITHUB_PR_NUMBER}/merge”, а в расширенных настройках репозитория в Refspec вписать “+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pull/${GITHUB_PR_NUMBER}/merge”. Опять же, для 1-2 задач это быстрее сделать руками, но если нужно настроить несколько сотен таких задач, то проще доработать groovy-скрипт 05-create-jobs.groovy
из этой статьи.
Больше полезной информации о настройке плагинов Checkstyle (анализ кода), Violation Comments to GitHub (комментарии с результатами чекстайла) и GitHub integration plugin (триггер “GitHub Pull Requests”) можно найти здесь, здесь и здесь.