Анализ коммитов и Pull Requests в Travis CI, Buddy и AppVeyor
В данной статье описана проверка Pull Requests на Linux и macOS. Проверка Pull Requests на Windows описана в соответствующем разделе документации утилит PVS-Studio_Cmd и CLMonitor (подраздел "Задание отдельных файлов для проверки").
Проверка Pull Requests доступна только при наличии Enterprise лицензии. Вы можете запросить пробную Enterprise лицензию здесь.
Режим проверки списка файлов
В PVS-Studio, начиная с версии 7.04, для Linux и macOS существует режим проверки списка исходных файлов. Работает это для проектов, сборочная система которых позволяет сгенерировать файл 'compile_commands.json'. Он нужен для того, чтобы анализатор извлёк информацию о компиляции указанных файлов. Если ваша сборочная система не поддерживает генерацию файла 'compile_commands.json', вы можете попробовать сгенерировать такой файл с помощью утилиты Bear.
Также режим проверки списка файлов можно использовать вместе с логом трассировки 'strace' запусков компилятора (pvs-studio-analyzer trace). Для этого вам нужно сначала провести полную сборку проекта в режиме трассировки, чтобы анализатор собрал полную информацию о параметрах компиляции всех файлов.
Однако у такого варианта есть существенный недостаток - нужно будет либо производить полную трассировку сборки всего проекта при каждом запуске, что само по себе противоречит идее быстрой проверки коммита. Либо, если закэшировать сам результат трассировки, последующие запуски анализатора могут оказаться неполными, если после трассировки поменяется структура зависимостей исходных файлов (например, в один из исходных файлов будет добавлен новый #include).
Поэтому мы не рекомендуем использовать режим проверки списка файлов с логом трассировки для проверки коммитов или Pull Requests. В случае, если вы можете делать при проверке коммита инкрементальную сборку, рассмотрите возможность использовать режим инкрементального анализа.
Список исходных файлов для анализа сохраняется в текстовой файл и передаётся анализатору с помощью параметра '-S':
pvs-studio-analyzer analyze ... -f build/compile_commands.json -S check-list.txt
В этом файле указываются относительные или абсолютные пути к файлам, причем каждый новый файл должен быть на новой строке. Текст, который не является путем к файлу с исходным кодом, игнорируется. Это может быть полезно для комментирования, если файлы указываются вручную. Однако зачастую список файлов будет сгенерирован во время анализа в CI, например, это могут быть файлы из коммита или Pull Request.
Для корректной работы режима проверки списка файлов, при первом запуске анализа будет сгенерирован файл зависимостей всех исходных файлов проекта от заголовочных. Необходимость такого файла - особенность анализа C/C++ файлов. В дальнейшем файл зависимостей закэшируется и будет обновляться анализатором автоматически.
Для того, чтобы сгенерировать или обновить файл зависимостей без запуска анализа, можно воспользоваться флагом '--regenerate-depend-info' с ключом 'skip-analysis':
pvs-studio-analyzer analyze ... -f build/compile_commands.json \
–-regenerate-depend-info skip-analysis
Если по каким-либо причинам требуется проанализировать списков файлов или проект целиком, принудительно обновив при этом кэш зависимостей, то можно воспользоваться флагом '--regenerate-depend-info' с ключом 'run-analysis':
pvs-studio-analyzer analyze ... -S source_files \
-f build/compile_commands.json \
--regenerate-depend-info run-analysis
Теперь с помощью этого режима можно быстро проверять новый код до попадания его в основную ветку разработки. Чтобы система проверки реагировала на наличие предупреждений анализатора, в утилите 'plog-converter' существует флаг '--indicate-warnings':
plog-converter ... --indicate-warnings ... -o /path/to/report.tasks ...
С этим флагом конвертер вернёт ненулевой код, если в отчёте анализатора присутствуют предупреждения. По коду возврата можно заблокировать прекоммит хук, коммит или Pull Request, а сгенерированный отчёт анализатора вывести на экран или отправить по почте.
Общие принципы анализа Pull Request
Анализ всего проекта занимает достаточно много времени, поэтому есть смысл в том, чтобы проверять только некоторую его часть. Проблема в том, что нужно отделить новые файлы от остальных файлов проекта.
Рассмотрим пример дерева коммитов с двумя ветками:

Представим, что коммит 'A1' содержит достаточно большое количество кода, который уже проверили. Немного ранее была создана ветка 'hotfix' от коммита 'A1' и были изменены какие-то файлы.
Как видно из примера, после 'A1' в ветку 'master' произошло ещё два коммита. Как только ветка 'hotfix' готова к слиянию, открывается 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 (стандартный поток вывода сообщений об ошибках).
Помимо вывода ошибок, часто бывает полезно сообщить сервису для сборки и тестирования о наличии проблем. Для этого в 'plog-converter' необходимо добавить флаг '-W' ('--indicate-warnings'). При наличии хоть одного предупреждения анализатора, код возврата утилиты 'plog-converter' изменится на 2, что, в свою очередь, сообщит CI сервису о наличии потенциальных ошибок в файлах Pull Request.
Travis CI
Конфигурация выполнена в виде файла '.travis.yml'. Для удобства советуем вынести всё в отдельный bash-скрипт с функциями, которые будут вызваны из файла '.travis.yml' ('bash имя_скрипта.sh имя_функции').
Добавим необходимый код в скрипт на 'bash'. В секции 'install' пропишем следующее:
install:
- bash .travis.sh travis_install
Откроем файл '.travis.sh' и добавим установку анализатора в функцию 'travis_install()':
travis_install() {
wget -q -O - https://files.pvs-studio.com/etc/pubkey.txt \
| sudo apt-key add -
sudo wget -O /etc/apt/sources.list.d/viva64.list \
https://files.pvs-studio.com/etc/viva64.list
sudo apt-get update -qq
sudo apt-get install -qq pvs-studio
}
Добавим в секцию 'script' запуск анализа:
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 CI самостоятельно объявляет переменные окружения '$TRAVIS_PULL_REQUEST' и '$TRAVIS_BRANCH':
- '$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', которая как раз и является вершиной ветки из 'origin'.
Осталась одна важная деталь - кэширование зависимостей заголовочных файлов от компилируемых единиц трансляции (*.c, *.cc, *.cpp и т.д.). Эти зависимости анализатор вычисляет при первом запуске в режиме проверки списка файлов и сохраняет затем в директории '.PVS-Studio'. Travis CI позволяет кэшировать папки, поэтому мы сохраним данные директории '.PVS-Studio':
cache:
directories:
- .PVS-Studio/
Этот код нужно добавить в файл '.travis.yml'. Эта директория хранит различные данные, собранные после анализа, которые существенно ускорят последующие запуски анализа списка файлов или инкрементального анализа. Если этого не сделать, то анализатор фактически каждый раз будет анализировать все файлы.
Buddy
Как и Travis CI, Buddy предоставляет возможность автоматизированной сборки и тестирования проектов, которые хранятся на GitHub. В отличие от Travis CI, он настраивается в веб интерфейсе (поддержка bash имеется), поэтому нет необходимости хранить файлы конфигурации в проекте.
В первую очередь необходимо добавить новое действие в линию сборки:

Укажем компилятор, который использовался для сборки проекта. Обратите внимание на docker контейнер, который установлен в этом действии. Например, для GCC есть специальный контейнер:

Теперь установим PVS-Studio и необходимые утилиты:

Добавим в редактор следующие строки:
apt-get update && apt-get -y install wget gnupg jq
wget -q -O - https://files.pvs-studio.com/etc/pubkey.txt | apt-key add -
wget -O /etc/apt/sources.list.d/viva64.list \
https://files.pvs-studio.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
Если вы прочитали раздел, посвященный Travis-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 Requests:

Теперь перейдем на вкладку 'Environment', где укажем образ для сборки и необходимые переменные окружения:

Если вы прочитали предыдущие разделы, ты вы хорошо знакомы с этими двумя переменными – 'PVS_KEY' и 'PVS_USERNAME'. Они необходимы для проверки лицензии анализатора PVS-Studio.
На этой же странице внизу укажем папку для кэширования:

Без этой настройки будет анализироваться весь проект вместо пары файлов, но вывод получим по указанным файлам. Поэтому важно ввести правильное название директории.
Теперь настало время скрипта для проверки. Откроем вкладку 'Tests' и выберем 'Script':

В эту форму нужно вставить следующий код:
sudo apt-get update && sudo apt-get -y install jq
wget -q -O - https://files.pvs-studio.com/etc/pubkey.txt \
| sudo apt-key add -
sudo wget -O /etc/apt/sources.list.d/viva64.list \
https://files.pvs-studio.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' переменной '$PWD' необходимо для корректной работы анализатора, поскольку AppVeyor модифицирует переменную для своих служебных целей в другое значение.
А дальше всё, как и раньше:

Теперь рассмотрим следующий фрагмент:
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' - имя пользователя и репозиторий проекта.