IntelliJ IDEA на сегодня обладает наиболее продвинутым статическим анализатором кода Java, по своим возможностям оставившим далеко позади таких «ветеранов», как Checkstyle и Spotbugs. Её многочисленные «инспекции» проверяют код в различных аспектах, от стиля кодирования до характерных багов.
Однако пока результаты анализа отображаются лишь в локальном интерфейсе IDE разработчика, от них мало пользы для процесса разработки. Статический анализ необходимо выполнять в качестве первого шага конвейера сборки, его результаты должны определять quality gates, а сборка должна фейлиться, если quality gates не пройдены. Известно, что TeamCity CI интегрирован с IDEA. Но даже если вы не используете TeamCity, вы вполне можете попробовать запускать инспекции IDEA в любом другом CI-сервере. Предлагаю посмотреть, как это можно сделать, используя IDEA Community Edition, Jenkins и Warnings NG plugin.
Шаг 1. Запускаем анализ в контейнере и получаем отчёт
Поначалу затея запускать IDE (десктопное приложение!) внутри CI-системы, не имеющей графического интерфейса, может показаться сомнительной и очень хлопотной. К счастью, разработчики IDEA предоставили возможность запускать форматирование кода и инспекции из командной строки. Причём для запуска IDEA в таком режиме не требуется графическая подсистема и эти задачи можно выполнять на серверах с текстовой оболочкой.
Запуск инспекций осуществляется при помощи скрипта bin/inspect.sh
из установочной директории IDEA. В качестве параметров требуются:
- полный путь к проекту (относительные не поддерживаются),
- путь к .xml-файлу с настройками инспекций (обычно находится внутри проекта в .idea/inspectionProfiles/Project_Default.xml),
- полный путь к папке, в которую будут сложены .xml-файлы с отчётами о результатах анализа.
Кроме того, ожидается, что
- в IDE будет настроен путь к Java SDK, иначе анализ работать не будет. Эти настройки содержатся в конфигурационном файле
jdk.table.xml
в папке глобальной конфигурации IDEA. Сама глобальная конфигурация IDEA по умолчанию лежит в домашней директории пользователя, но это местоположение может быть явно задано в файлеidea.properties
. - анализируемый проект должен быть валидным проектом IDEA, для чего на контроль версий придётся закоммитить некоторые файлы, которые обычно игнорируются, а именно:
.idea/inspectionProfiles/Project_Default.xml
— настройки анализатора, они явно будут использованы при запуске инспекций в контейнере,.idea/modules.xml
— иначе получим ошибку 'This project contains no modules',.idea/misc.xml
— иначе получим ошибку 'The JDK is not configured properly for this project',*.iml-файлы
— иначе получим ошибку про не настроенный JDK в модуле.
Хотя обычно эти файлы включают в .gitignore
, они не содержат никакой специфичной для окружения конкретного разработчика информации — в отличие от, например, файла workspace.xml
, где такая информация, как раз, содержится, и потому коммитить его не надо.
Сам собою напрашивается выход запаковать JDK вместе с IDEA Community Edition в контейнер в виде, готовом к «натравливанию» на анализируемые проекты. Выберем подходящий базовый контейнер, и вот какой у нас получится Dockerfile:
FROM openkbs/ubuntu-bionic-jdk-mvn-py3
ARG INTELLIJ_VERSION="ideaIC-2019.1.1"
ARG INTELLIJ_IDE_TAR=${INTELLIJ_VERSION}.tar.gz
ENV IDEA_PROJECT_DIR="/var/project"
WORKDIR /opt
COPY jdk.table.xml /etc/idea/config/options/
RUN wget https://download-cf.jetbrains.com/idea/${INTELLIJ_IDE_TAR} && \
tar xzf ${INTELLIJ_IDE_TAR} && \
tar tzf ${INTELLIJ_IDE_TAR} | head -1 | sed -e 's/\/.*//' | xargs -I{} ln -s {} idea && \
rm ${INTELLIJ_IDE_TAR} && \
echo idea.config.path=/etc/idea/config >> idea/bin/idea.properties && \
chmod -R 777 /etc/idea
CMD idea/bin/inspect.sh ${IDEA_PROJECT_DIR} ${IDEA_PROJECT_DIR}/.idea/inspectionProfiles/Project_Default.xml ${IDEA_PROJECT_DIR}/target/idea_inspections -v2
При помощи опции idea.config.path
мы заставили IDEA искать свою глобальную конфигурацию в папке /etc/idea
, т. к. домашняя папка пользователя в условиях работы в CI — вещь неопределённая и зачастую вовсе отсутствующая.
Так выглядит копируемый в контейнер файл jdk.table.xml
, в котором прописаны пути к OpenJDK, установленной внутри контейнера (за основу может быть взят аналогичный файл из вашей собственной директории с настройками IDEA):
<application>
<component name="ProjectJdkTable">
<jdk version="2">
<name value="1.8" />
<type value="JavaSDK" />
<version value="1.8" />
<homePath value="/usr/java" />
<roots>
<annotationsPath>
<root type="composite">
<root url="jar://$APPLICATION_HOME_DIR$/lib/jdkAnnotations.jar!/" type="simple" />
</root>
</annotationsPath>
<classPath>
<root type="composite">
<root url="jar:///usr/java/jre/lib/charsets.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/deploy.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/access-bridge-64.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/cldrdata.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/dnsns.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/jaccess.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/jfxrt.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/localedata.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/nashorn.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/sunec.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/sunjce_provider.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/sunmscapi.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/sunpkcs11.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/ext/zipfs.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/javaws.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/jce.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/jfr.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/jfxswt.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/jsse.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/management-agent.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/plugin.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/resources.jar!/" type="simple" />
<root url="jar:///usr/java/jre/lib/rt.jar!/" type="simple" />
</root>
</classPath>
</roots>
<additional />
</jdk>
</component>
</application>
Образ в готовом виде доступен на Docker Hub.
Перед тем, как двинуться дальше, проверим запуск анализатора IDEA в контейнере:
docker run --rm -v <путь/к/вашему/проекту>:/var/project inponomarev/intellij-idea-analyzer
Анализ должен успешно отработать, а в подпапке target/idea_inspections должны появиться многочисленные .xml-файлы с отчётами анализатора.
Теперь больше нет никаких сомнений в том, что анализатор IDEA может быть запущен в автономном режиме в любом CI-окружении, и мы переходим ко второму шагу.
Шаг 2. Отображаем и анализируем отчёт
Получить отчёт в виде .xml-файлов — полдела, теперь его нужно сделать человекочитаемым. А также его результаты должны быть использованы в quality gates — логике определения того, проходит или не проходит принимаемое изменение по критериям качества.
В этом нам поможет Jenkins Warnings NG Plugin, релиз которого был сделан в январе 2019 года. С его появлением многие отдельные плагины для работы с результатами статического анализа в Jenkins (CheckStyle, FindBugs, PMD и т. п.) теперь помечены как устаревшие (obsolete).
Плагин состоит из двух частей:
- многочисленных сборщиков сообщений анализаторов (полный список включает в себя все известные науке анализаторы от AcuCobol до ZPT Lint),
- единого для всех них просмотрщика отчётов.
В перечне того, что умеет анализировать Warnings NG, находятся в том числе предупреждения компилятора Java и предупреждения из логов выполнения Maven: хотя они постоянно на виду, их редко когда целенаправленно анализируют. Отчёты IntelliJ IDEA также входят в перечень распознаваемых форматов.
Т. к. плагин новый, он изначально хорошо взаимодействует Jenkins Pipeline. Шаг сборки с его участием будет выглядеть следующим образом (мы просто говорим плагину, какой формат отчёта распознаём и какие файлы следует просканировать):
stage ('Static analysis'){
sh 'rm -rf target/idea_inspections'
docker.image('inponomarev/intellij-idea-analyzer').inside {
sh '/opt/idea/bin/inspect.sh $WORKSPACE $WORKSPACE/.idea/inspectionProfiles/Project_Default.xml $WORKSPACE/target/idea_inspections -v2'
}
recordIssues(
tools: [ideaInspection(pattern: 'target/idea_inspections/*.xml')]
)
}
Интерфейс отчёта выглядит так:
Удобно, что этот интерфейс является универсальным для всех распознаваемых анализаторов. Он содержит интерактивную диаграмму распределения находок по категориям и график динамики изменения количества находок. В гриде внизу страницы можно выполнять быстрый поиск. Единственное, что для испекций IDEA не заработало корректно — возможность браузить код непосредственно в Jenkins (хотя для других отчётов, например Checkstyle, этот плагин умеет это делать красиво). Похоже, это баг парсера отчётов IDEA, который предстоит починить.
Среди возможностей Warnings NG — возможность агрегировать в одном отчёте находки из разных источников и программировать Quality Gates, в том числе — «храповик» по референтной сборке. Некоторая документация по программированию Quality Gates доступна здесь — впрочем, она не полная, и приходится смотреть в исходники. С другой стороны, для полного контроля над происходящим «храповик» можно реализовать и самостоятельно (см. мой предыдущий пост на эту тему).
Заключение
Перед тем, как начать готовить данный материал, я решил поискать: а не писал ли уже кто-нибудь на эту тему на Хабре? Я нашёл лишь интервью 2017 года с lany, где он говорит:
Насколько мне известно, интеграции с Jenkins или maven-плагина нету [...] В принципе, любой энтузиаст мог бы подружить IDEA Community Edition и Jenkins, многие бы от этого только выиграли.
Что же: спустя два года у нас есть Warnings NG Plugin, и наконец-то эта дружба осуществилась!