Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
В аналізаторі PVS-Studio для мов С та C++ на Linux та macOS, починаючи з версії 7.04, з'явилася тестова можливість перевірити список зазначених файлів. За допомогою нового режиму можна налаштувати аналізатор перевірки коммітів і pull request'ов. У цій статті буде розказано, як налаштувати перевірку списку змінених файлів GitHub-проекту в таких популярних системах CI (Continuous Integration), як Travis CI, Buddy та AppVeyor.

Режим перевірки списку файлів

ПВС-Студія — це інструмент виявлення помилок і потенційних уразливостей у вихідному коді програм, написаних мовами З, C++, C# і Java. Працює у 64-бітових системах на Windows, Linux та macOS.

У версії PVS-Studio 7.04 для Linux і MacOS з'явився режим перевірки списку вихідних файлів. Працює для проектів, збірна система яких дозволяє згенерувати файл compile_commands.json. Він потрібний для того, щоб аналізатор витягнув інформацію про компіляцію зазначених файлів. Якщо ваша збірна система не підтримує генерацію файлу compile_commands.json, ви можете спробувати згенерувати файл за допомогою утиліти ведмідь.

Також режим перевірки списку файлів можна використовувати разом із логом трасування strace запусків компілятора (pvs-studio-analyzer trace). Для цього вам потрібно буде спочатку провести повне складання проекту і відстежити її, щоб аналізатор зібрав повну інформацію про параметри компіляції всіх фалів, що перевіряються.

Однак такий варіант має суттєвий недолік — потрібно буде або здійснювати повне трасування складання всього проекту при кожному запуску, що саме по собі суперечить ідеї швидкої перевірки комміту. Або якщо закешувати сам результат трасування, наступні запуски аналізатора можуть виявитися неповними, якщо після трасування зміниться структура залежностей вихідних файлів (наприклад, до одного з вихідних файлів буде додано новий #include).

Тому ми не рекомендуємо використовувати режим перевірки списку файлів із логом трасування для перевірки коммітів або pull request'ів. У разі, якщо ви можете робити під час перевірки комміту інкрементальне складання, розгляньте можливість використовувати режим інкрементального аналізу.

Список вихідних файлів для аналізу зберігається до текстового файлу і передається аналізатору за допомогою параметра -S:

pvs-studio-analyzer analyze ... -f build/compile_commands.json -S check-list.txt

У цьому файлі вказуються відносні або абсолютні шляхи до файлів, причому кожен новий файл має бути на новому рядку. Допустимо вказувати як імена файлів для аналізу, а й різний текст. Аналізатор побачить, що це файл, і проігнорує рядок. Це може бути корисним для коментування, якщо файли вказуються вручну. Однак найчастіше список файлів буде згенерований під час аналізу в CI, наприклад це можуть бути файли з комміту або pull request'а.

Тепер за допомогою цього режиму можна швидко перевіряти новий код до влучення його в основну гілку розробки. Щоб система перевірки реагувала на наявність попереджень аналізатора в утиліту plog-converter додано прапор -indicate-warnings:

plog-converter ... --indicate-warnings ... -o /path/to/report.tasks ...

З цим прапором конвертер поверне ненульовий код, якщо у звіті аналізатора є попередження. За кодом повернення можна заблокувати прекомміт хук, коміт або pull request, а згенерований звіт аналізатора вивести на екран, розшарити або надіслати поштою.

Примітка. Під час першого запуску аналізу списку файлів буде проаналізовано весь проект, т.к. аналізатору необхідно згенерувати файл залежностей вихідних файлів проекту від заголовних файлів. Це особливість аналізу C та C++ файлів. Надалі файл залежностей можна закешувати і він оновлюватиметься аналізатором автоматично. Перевага перевірки коммітів при використанні режиму перевірки списку файлів перед використанням інкрементального аналізу полягає в тому, що потрібно кешувати тільки цей файл, а не об'єктні файли.

Загальні принципи аналізу pull request'а

Аналіз усього проекту займає чимало часу, тому є сенс у тому, щоб перевіряти лише деяку його частину. Проблема в тому, що потрібно відокремити нові файли від інших проектів.

Розглянемо приклад дерева коммітів із двома гілками:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio

Давайте уявімо, що коміт A1 містить досить велику кількість коду, який вже перевірили. Трохи раніше ми зробили гілку від комміту A1 та змінювали якісь файли.

Ви, звичайно, помітили, що після A1 відбулося ще два коміти, але це були також злиття інших гілок, адже ми ж не комітімо в майстер. І ось настав час, коли виправлення готовий. Тому виник pull request на злиття B3 и A3.

Само собою, можна було б перевірити весь результат їхнього злиття, але це було б занадто довго і невиправдано, оскільки було змінено лише кілька файлів. Тому ефективніше проаналізувати лише змінені.

Для цього отримаємо різницю між гілками, перебуваючи в HEAD гілки, з якої хочемо злити в master:

git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list

$MERGE_BASE ми докладно розглядатимемо пізніше. Справа в тому, що далеко не кожен CI сервіс дає необхідну інформацію про базу для злиття, тому щоразу доводиться вигадувати нові способи отримання цих даних. Це буде детально описано нижче в кожному з описаних веб-сервісів.

Отже, ми отримали різницю між гілками, а точніше – список імен файлів, які були змінені. Тепер нам потрібно віддати файл .pvs-pr.list (ми в нього перенаправили висновок вище) аналізатору:

pvs-studio-analyzer analyze -j8 
                            -o PVS-Studio.log 
                            -S .pvs-pr.list

Після аналізу нам потрібно конвертувати файл логів (PVS-Studio.log) у зручний для сприйняття формат:

plog-converter -t errorfile PVS-Studio.log --cerr -w

Ця команда виведе список помилок у stderr (стандартний потік виведення повідомлень про помилки).

Тільки ось нам потрібно не тільки вивести помилки, а й повідомити наш сервіс для складання та тестування про наявність проблем. Для цього до конвертера було додано прапор -W (-indicate-warnings). За наявності хоч одного попередження аналізатора, код повернення утиліти plog-converter зміниться на 2, що, своєю чергою, повідомить CI сервісу про наявність потенційних помилок у файлах pull request'а.

Тревіс К.І.

Конфігурація виконана у вигляді файлу .travis.yml. Для зручності раджу винести все в окремий bash-скрипт із функціями, які будуть викликані з файлу .travis.yml (bash имя_скрипта.sh имя_функции).

Додаватимемо необхідний код у скрипт на битиТаким чином ми отримаємо більший функціонал. У секції встановлювати напишемо таке:

install:
  - bash .travis.sh travis_install

Якщо у вас були якісь інструкції, то можете перенести їх у скрипт, прибравши дефіси.

Відкриємо файл .travis.sh та додамо установку аналізатора у функцію travis_install():

travis_install() {
  wget -q -O - https://files.viva64.com/etc/pubkey.txt 
    | sudo apt-key add -
  sudo wget -O /etc/apt/sources.list.d/viva64.list 
    https://files.viva64.com/etc/viva64.list
  
  sudo apt-get update -qq
  sudo apt-get install -qq pvs-studio 
}

Тепер додамо до секції сценарій запуск аналізу:

script:
  - bash .travis.sh travis_script

І в bash-скрипті:

travis_script() {
  pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY
  
  if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
    git diff --name-only origin/HEAD > .pvs-pr.list
    pvs-studio-analyzer analyze -j8 
                                -o PVS-Studio.log 
                                -S .pvs-pr.list 
                                --disableLicenseExpirationCheck
  else
    pvs-studio-analyzer analyze -j8 
                                -o PVS-Studio.log 
                                --disableLicenseExpirationCheck
  fi
  
  plog-converter -t errorfile PVS-Studio.log --cerr -w
}

Цей код потрібно запустити після складання проекту, наприклад, якщо у вас була збірка на CMake:

travis_script() {
  CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
  cmake $CMAKE_ARGS CMakeLists.txt
  make -j8
}

Вийде так:

travis_script() {
  CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
  cmake $CMAKE_ARGS CMakeLists.txt
  make -j8
  
  pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY
  
  if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
    git diff --name-only origin/HEAD > .pvs-pr.list
    pvs-studio-analyzer analyze -j8 
                                -o PVS-Studio.log 
                                -S .pvs-pr.list 
                                --disableLicenseExpirationCheck
  else
    pvs-studio-analyzer analyze -j8 
                                -o PVS-Studio.log 
                                --disableLicenseExpirationCheck
  fi
  
  plog-converter -t errorfile PVS-Studio.log --cerr -w
}

Напевно, ви вже звернули увагу на вказані змінні оточення $TRAVIS_PULL_REQUEST и $TRAVIS_BRANCH. Travis CI оголошує їх самостійно:

  • $TRAVIS_PULL_REQUEST зберігає номер pull request'а або falseякщо це звичайна гілка;
  • $TRAVIS_REPO_SLUG зберігає ім'я репозиторію проекту.

Алгоритм роботи цієї функції:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Travis CI реагує на коди повернення, тому наявність попереджень вкаже сервісу позначити коміт як помилки, що містить.

А тепер розглянемо докладніше цей рядок коду:

git diff --name-only origin/HEAD > .pvs-pr.list

Справа в тому, що Travis CI автоматично злиття гілок під час аналізу pull request:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Тому ми аналізуємо A4, А не B3->A3. Через цю особливість нам потрібно вираховувати різницю з А3, яка якраз і є вершиною гілки з походження.

Залишилася одна важлива деталь - кешування залежностей заголовних файлів від одиниць трансляції, що компилуються (*.c, *.cc, *.cpp і т.д.). Ці залежності аналізатор обчислює при першому запуску в режимі перевірки списку файлів і зберігає потім директорії .PVS-Studio. Travis CI дозволяє кешувати папки, тому ми збережемо дані директорії .PVS-Studio/:

cache:
  directories:
    - .PVS-Studio/

Цей код потрібно додати до файлу .travis.yml. Ця директорія зберігає різні дані, зібрані після аналізу, які суттєво прискорять подальші запуски аналізу списку файлів або інкрементального аналізу. Якщо цього зробити, то аналізатор фактично щоразу аналізуватиме всі файли.

Приятель

Як і Travis CI, Приятель надає можливість автоматизованого складання та тестування проектів, що зберігаються на GitHub. На відміну від Travis CI, він налаштовується у веб-інтерфейсі (підтримка bash є), тому немає необхідності зберігати файли конфігурації в проекті.

Насамперед нам необхідно додати нову дію до лінії складання:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Вкажемо компілятор, який використовувався для збирання проекту. Зверніть увагу на docker контейнер, який встановлений у цій дії. Наприклад, для GCC є спеціальний контейнер:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Тепер встановимо PVS-Studio та необхідні утиліти:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Додамо до редактора наступні рядки:

apt-get update && apt-get -y install wget gnupg jq

wget -q -O - https://files.viva64.com/etc/pubkey.txt | apt-key add -
wget -O /etc/apt/sources.list.d/viva64.list 
  https://files.viva64.com/etc/viva64.list

apt-get update && apt-get -y install pvs-studio

Тепер перейдемо на вкладку Run (перша іконка) і до відповідного поля редактора додамо наступний код:

pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY

if [ "$BUDDY_EXECUTION_PULL_REQUEST_NO" != '' ]; then
  PULL_REQUEST_ID="pulls/$BUDDY_EXECUTION_PULL_REQUEST_NO"
  MERGE_BASE=`wget -qO - 
    https://api.github.com/repos/${BUDDY_REPO_SLUG}/${PULL_REQUEST_ID} 
    | jq -r ".base.ref"`

  git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck 
                              -S .pvs-pr.list
else
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck
fi

plog-converter -t errorfile PVS-Studio.log --cerr -w

Якщо ви читали розділ, присвячений Travs-CI, то цей код вже вам знайомий, проте тепер з'явився новий етап:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Справа в тому, що тепер ми аналізуємо не результат злиття, а гілки HEAD, з якої робиться pull request:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Тому ми перебуваємо в умовному коментарі B3 і нам потрібно отримати різницю з A3:

PULL_REQUEST_ID="pulls/$BUDDY_EXECUTION_PULL_REQUEST_NO"
  MERGE_BASE=`wget -qO - 
    https://api.github.com/repos/${BUDDY_REPO_SLUG}/${PULL_REQUEST_ID} 
    | jq -r ".base.ref"`
git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list

Для визначення A3 скористаємося API GitHub:

https://api.github.com/repos/${USERNAME}/${REPO}/pulls/${PULL_REQUEST_ID}

Ми використовували такі змінні, які надає Buddy:

  • $BUDDY_EXECUTION_PULL_REQEUST_NO - Номер pull request'а;
  • $BUDDY_REPO_SLUG — поєднання імені користувача та репозиторію (наприклад max/test).

Тепер збережемо зміни, скориставшись кнопкою внизу, та включимо аналіз pull request:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
На відміну від Travis CI, нам не потрібно вказувати .pvs-studio для кешування, тому що Buddy автоматично кешує всі файли для подальшого запуску. Тому залишилося останнє — зберегти логін та пароль для PVS-Studio у Buddy. Після збереження змін ми потрапимо назад у Pipeline. Нам потрібно перейти до налаштування змінних та додати логін та ключ для PVS-Studio:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Після цього поява нового pull request'а чи комміта запускатиме перевірку. Якщо коміт містить помилки, Buddy вкаже на це на сторінці pull request'а.

AppVeyor

Налаштування AppVeyor схоже на Buddy, тому що все відбувається у web інтерфейсі і немає потреби додавати файл *.yml до репозиторій проекту.

Перейдемо на вкладку Settings в огляді проекту:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Прокрутимо цю сторінку вниз і увімкнемо збереження кеша для складання pull request'ів:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Тепер перейдемо на вкладку Environment, де вкажемо образ для збирання та необхідні змінні оточення:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Якщо ви прочитали попередні розділи, то ви добре знайомі з цими двома змінними. PVS_KEY и PVS_USERNAME. Якщо ж ні, то нагадаю, що вони потрібні для перевірки ліцензії аналізатора PVS-Studio. Надалі ми зустрінемо їх знову у Bash сценаріях.

На цій сторінці внизу вкажемо папку для кешування:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Якщо ми цього не зробимо, аналізуватимемо замість пари файлів весь проект, але висновок отримаємо за вказаними файлами. Тому важливо запровадити правильну назву директорії.

Тепер настав час скрипту для перевірки. Відкриємо вкладку Tests і оберемо Script:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
У цю форму потрібно вставити наступний код:

sudo apt-get update && sudo apt-get -y install jq

wget -q -O - https://files.viva64.com/etc/pubkey.txt 
  | sudo apt-key add -
sudo wget -O /etc/apt/sources.list.d/viva64.list 
  https://files.viva64.com/etc/viva64.list

sudo apt-get update && sudo apt-get -y install pvs-studio

pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY

PWD=$(pwd -L)
if [ "$APPVEYOR_PULL_REQUEST_NUMBER" != '' ]; then
  PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER"
  MERGE_BASE=`wget -qO - 
    https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} 
    | jq -r ".base.ref"`

  git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck 
                              --dump-files --dump-log pvs-dump.log 
                              -S .pvs-pr.list
else
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck
fi

plog-converter -t errorfile PVS-Studio.log --cerr -w

Звернімо увагу на наступну частину коду:

PWD=$(pwd -L)
if [ "$APPVEYOR_PULL_REQUEST_NUMBER" != '' ]; then
  PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER"
  MERGE_BASE=`wget -qO - 
   https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} 
   | jq -r ".base.ref"`

  git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck 
                              --dump-files --dump-log pvs-dump.log 
                              -S .pvs-pr.list
else
  pvs-studio-analyzer analyze -j8 
                              -o PVS-Studio.log 
                              --disableLicenseExpirationCheck
fi

Досить специфічне надання значення команди pwd змінної, яка повинна зберігати це значення за умовчанням, здається дивним на перший погляд, проте я зараз все поясню.

Під час налаштування аналізатора AppVeyor я зіткнувся з вкрай дивною поведінкою аналізатора. З одного боку, все працювало правильно, але аналіз не запускався. Я витратив чимало часу, щоб помітити, що ми знаходимося в директорії /home/appveyor/projects/testcalc/, а аналізатор впевнений у тому, що ми знаходимося в /opt/appveyor/build-agent/. Тоді я зрозумів, що змінна $PWD трохи бреше. Тому вручну оновив її значення перед запуском аналізу.

А далі все, як і раніше:

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio
Тепер розглянемо наступний фрагмент:

PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER"
MERGE_BASE=`wget -qO - 
  https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} 
  | jq -r ".base.ref"`

У ньому ми отримуємо різницю між гілками, з яких оголошено pull request. Для цього нам потрібні такі змінні оточення:

  • $APPVEYOR_PULL_REQUEST_NUMBER – номер pull request;
  • $APPVEYOR_REPO_NAME — ім'я користувача та репозиторій проекту.

Висновок

Звичайно, ми не розглянули всі з можливих сервісів безперервної інтеграції, проте всі вони мають вкрай схожу один з одним специфіку роботи. Крім кешування, кожен сервіс робить свій «велосипед», тому завжди все по-різному.

Десь, як у Travis-CI, пара рядків коду та кешування працює бездоганно; десь, як у AppVeyor, потрібно просто вказати папку в налаштуваннях; але десь потрібно створювати унікальні ключі та намагатися переконати систему дати тобі можливість перезаписати закешований фрагмент. Тому, якщо ви хочете налаштувати аналіз pull request'ів на сервісі безперервної інтеграції, який не був розглянутий вище, то спочатку переконайтеся, що з кешування у вас не виникне проблем.

Дякую за увагу. Якщо щось не виходить, то сміливо пишіть нам у підтримку. Ми підкажемо та допоможемо.

Аналіз коммітів та pull request'ів у Travis CI, Buddy та AppVeyor за допомогою PVS-Studio

Якщо хочете поділитися цією статтею з англомовною аудиторією, прошу використати посилання на переклад: Maxim Zvyagintsev. Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using PVS-Studio.

Джерело: habr.com

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