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:


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):


jdk.table.xml
<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, и наконец-то эта дружба осуществилась!

Комментарии (2)


  1. sshikov
    02.05.2019 09:46
    +1

    Спасибо, хорошая идея! Было бы неплохо вообще отвязать инспекцию от файлов конфигурации. Правда, тогда скорее всего потребуется анализ pom.xml, или что там у нас в качестве описания проекта имеется?


    1. IvanPonomarev Автор
      02.05.2019 23:28

      Да, это было бы очень неплохо. Необходимость коммитить кучу IDE-specific файлов напрягает!

      В Gradle есть вот такая штучка, создающая .iml и прочие файлы на основе проекта, но я Gradle не пользуюсь и насколько это работает — сказать не могу. Судя по всему, был и такой maven-idea-plugin, но он лет шесть уже как не поддерживается. В общем, есть ещё, куда копать в этой области.