В аналізаторі PVS-Studio для мов С та C++ на Linux та macOS, починаючи з версії 7.04, з'явилася тестова можливість перевірити список зазначених файлів. За допомогою нового режиму можна налаштувати аналізатор перевірки коммітів і pull request'ов. У цій статті буде розказано, як налаштувати перевірку списку змінених файлів GitHub-проекту в таких популярних системах CI (Continuous Integration), як Travis CI, Buddy та AppVeyor.
Режим перевірки списку файлів
У версії PVS-Studio 7.04 для Linux і MacOS з'явився режим перевірки списку вихідних файлів. Працює для проектів, збірна система яких дозволяє згенерувати файл
Також режим перевірки списку файлів можна використовувати разом із логом трасування 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'а
Аналіз усього проекту займає чимало часу, тому є сенс у тому, щоб перевіряти лише деяку його частину. Проблема в тому, що потрібно відокремити нові файли від інших проектів.
Розглянемо приклад дерева коммітів із двома гілками:
Давайте уявімо, що коміт 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
Ця команда виведе список помилок у
Тільки ось нам потрібно не тільки вивести помилки, а й повідомити наш сервіс для складання та тестування про наявність проблем. Для цього до конвертера було додано прапор -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 зберігає ім'я репозиторію проекту.
Алгоритм роботи цієї функції:
Travis CI реагує на коди повернення, тому наявність попереджень вкаже сервісу позначити коміт як помилки, що містить.
А тепер розглянемо докладніше цей рядок коду:
git diff --name-only origin/HEAD > .pvs-pr.list
Справа в тому, що Travis CI автоматично злиття гілок під час аналізу pull request:
Тому ми аналізуємо A4, А не B3->A3. Через цю особливість нам потрібно вираховувати різницю з А3, яка якраз і є вершиною гілки з походження.
Залишилася одна важлива деталь - кешування залежностей заголовних файлів від одиниць трансляції, що компилуються (*.c, *.cc, *.cpp і т.д.). Ці залежності аналізатор обчислює при першому запуску в режимі перевірки списку файлів і зберігає потім директорії .PVS-Studio. Travis CI дозволяє кешувати папки, тому ми збережемо дані директорії .PVS-Studio/:
cache:
directories:
- .PVS-Studio/
Цей код потрібно додати до файлу .travis.yml. Ця директорія зберігає різні дані, зібрані після аналізу, які суттєво прискорять подальші запуски аналізу списку файлів або інкрементального аналізу. Якщо цього зробити, то аналізатор фактично щоразу аналізуватиме всі файли.
Приятель
Як і Travis CI,
Насамперед нам необхідно додати нову дію до лінії складання:
Вкажемо компілятор, який використовувався для збирання проекту. Зверніть увагу на docker контейнер, який встановлений у цій дії. Наприклад, для GCC є спеціальний контейнер:
Тепер встановимо 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, то цей код вже вам знайомий, проте тепер з'явився новий етап:
Справа в тому, що тепер ми аналізуємо не результат злиття, а гілки HEAD, з якої робиться pull request:
Тому ми перебуваємо в умовному коментарі 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:
На відміну від Travis CI, нам не потрібно вказувати .pvs-studio для кешування, тому що Buddy автоматично кешує всі файли для подальшого запуску. Тому залишилося останнє — зберегти логін та пароль для PVS-Studio у Buddy. Після збереження змін ми потрапимо назад у Pipeline. Нам потрібно перейти до налаштування змінних та додати логін та ключ для PVS-Studio:
Після цього поява нового pull request'а чи комміта запускатиме перевірку. Якщо коміт містить помилки, Buddy вкаже на це на сторінці pull request'а.
AppVeyor
Налаштування AppVeyor схоже на Buddy, тому що все відбувається у web інтерфейсі і немає потреби додавати файл *.yml до репозиторій проекту.
Перейдемо на вкладку Settings в огляді проекту:
Прокрутимо цю сторінку вниз і увімкнемо збереження кеша для складання pull request'ів:
Тепер перейдемо на вкладку Environment, де вкажемо образ для збирання та необхідні змінні оточення:
Якщо ви прочитали попередні розділи, то ви добре знайомі з цими двома змінними. PVS_KEY и PVS_USERNAME. Якщо ж ні, то нагадаю, що вони потрібні для перевірки ліцензії аналізатора PVS-Studio. Надалі ми зустрінемо їх знову у Bash сценаріях.
На цій сторінці внизу вкажемо папку для кешування:
Якщо ми цього не зробимо, аналізуватимемо замість пари файлів весь проект, але висновок отримаємо за вказаними файлами. Тому важливо запровадити правильну назву директорії.
Тепер настав час скрипту для перевірки. Відкриємо вкладку Tests і оберемо Script:
У цю форму потрібно вставити наступний код:
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_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'ів на сервісі безперервної інтеграції, який не був розглянутий вище, то спочатку переконайтеся, що з кешування у вас не виникне проблем.
Дякую за увагу. Якщо щось не виходить, то сміливо пишіть нам у
Якщо хочете поділитися цією статтею з англомовною аудиторією, прошу використати посилання на переклад: Maxim Zvyagintsev.
Джерело: habr.com