Запускаємо інспекції IntelliJ IDEA на Jenkins

IntelliJ IDEA на сьогодні володіє найбільш просунутим статичним аналізатором коду Java, який за своїми можливостями залишив далеко позаду таких «ветеранів», як Контрольний стиль и 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):

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')]
    )
}

Інтерфейс звіту виглядає так:

Запускаємо інспекції IntelliJ IDEA на Jenkins

Зручно, що цей інтерфейс є універсальним для всіх аналізаторів, що розпізнаються. Він містить інтерактивну діаграму розподілу знахідок за категоріями та графік динаміки зміни кількості знахідок. У гриді внизу сторінки можна виконувати швидкий пошук. Єдине, що для спектаклів IDEA не запрацювало коректно - можливість браузити код безпосередньо в Jenkins (хоча для інших звітів, наприклад Checkstyle, цей плагін вміє це робити красиво). Схоже, це баг парсера звітів IDEA, який належить полагодити.

Серед можливостей Warnings NG — можливість агрегувати в одному звіті знахідки з різних джерел та програмувати Quality Gates, у тому числі «храповик» з референтної збірки. Деяка документація щодо програмування Quality Gates доступна тут — втім, вона не повна, і доводиться дивитись у вихідники. З іншого боку, для повного контролю над тим, що відбувається, «храповик» можна реалізувати і самостійно (див. мій попередній пост на цю тему).

Висновок

Перед тим, як почати готувати цей матеріал, я вирішив пошукати: а чи не писав уже хтось на цю тему на Хабре? Я знайшов лише інтерв'ю 2017 року с лені, де він каже:

Наскільки мені відомо, інтеграції з Jenkins або maven-плагіна немає […] У принципі, будь-який ентузіаст міг би подружити IDEA Community Edition і Jenkins, багато хто б від цього тільки виграв.

Що ж: через два роки ми маємо Warnings NG Plugin, і нарешті ця дружба здійснилася!

Джерело: habr.com

Додати коментар або відгук