В этом репозитории представлен простой пример настройки различных служб CI, а также интеграции инструментов анализа в эти службы. Эти инструменты следует использовать как часть комплексного процесса разработки программного обеспечения (SDP), а также их можно использовать в качестве стартового шаблона для любого приложения C или C++. Используются следующие инструменты CI, обеспечивающие поддержку тестирования для Windows, Cygwin, Linux и macOS.
Выполняются следующие проверки:
Следующие реальные проекты используют различные эти методы как часть своей SDP:
Хотя этот репозиторий можно запустить на большинстве систем, ниже перечислены поддерживаемые платформы и их зависимости:
sudo apt-get install git build-essential cmake
setup-x86_64.exe -q -P git,make,gcc-core,gcc-g++,cmake
Установите следующие пакеты:
Чтобы скомпилировать и установить этот пример, используйте следующие инструкции:
git clone https://github.com/ainfosec/ci_helloworld.git
mkdir ci_helloworld/build
cd ci_helloworld/build
cmake ..
make
make test
git clone https://github.com/ainfosec/ci_helloworld.git
mkdir ci_helloworld/build
cd ci_helloworld/build
cmake -G "NMake Makefiles" ..
nmake
nmake test
git clone https://github.com/ainfosec/ci_helloworld.git
mkdir ci_helloworld/build
cd ci_helloworld/build
cmake -G "Visual Studio 15 2017 Win64" ..
msbuild ci_helloworld.sln
ctest
git clone https://github.com/ainfosec/ci_helloworld.git
mkdir ci_helloworld/build
cd ci_helloworld/build
cmake ..
make
make test
Ниже представлено описание всех инструментов анализа, которые были интегрированы в службы CI, используемые в этом проекте, включая объяснение того, как они работают.
CI настроен на проверку отсутствующей документации с помощью doxygen. В отличие от большинства инструментов анализа, используемых в этом проекте, для doxygen нет цели make, вместо этого он запускается с помощью doxygen вручную с помощью следующего скрипта:
- doxygen .doxygen.txt
- |
if [[ -s doxygen_warnings.txt ]]; then
echo "You must fix doxygen before submitting a pull request"
echo ""
cat doxygen_warnings.txt
exit -1
fi
Этот сценарий запускает doxygen для исходного кода, и все предупреждения помещаются в файл с именем doxygen_warnings.txt
. Если этот файл пуст, это означает, что анализ doxygen пройден, и весь код документирован на основе настроек в файле конфигурации .doxygen.txt
. Если эти файлы не пусты, тест завершается неудачей и печатаются предупреждения, сгенерированные doxygen.
git diff --check
предоставляет простой способ определить, когда в репозиторий были проверены ошибки пробелов, а также проверить, когда символы новой строки в конце файла отсутствуют или содержат слишком много. Более подробную информацию об этой проверке можно найти здесь. Эта проверка чрезвычайно полезна для разработчиков, когда PR содержат модификации, не связанные с их конкретными изменениями.
- |
if [[ -n $(git diff --check HEAD^) ]]; then
echo "You must remove whitespace before submitting a pull request"
echo ""
git diff --check HEAD^
exit -1
fi
Эта проверка просто запускает git diff --check
, который возвращает ошибку, если проверка не удалась. В этом случае разница отображается для просмотра пользователем.
Форматирование исходного кода — отличный способ сохранить единообразный внешний вид кода. Проблема с форматированием исходного кода заключается в том, что, если все его не используют, PR разработчиков будут содержать изменения, не связанные с их конкретными изменениями, или, что еще хуже, попытки периодически исправлять форматирование исходного кода будут уничтожать историю git вашего репозитория каждый раз, когда вы форматируете исходный код. Поэтому, если необходимо использовать форматирование исходного кода, его следует проверять при каждой разнице кода, чтобы убедиться в правильности форматирования.
В этом примере мы используем Astyle, но формат Clang также подойдет. Чтобы упростить поддержку Astyle, мы предоставляем цель make, которая позволяет разработчику форматировать свой исходный код, просто запустив make format
. Для этого нам нужно сначала получить Astyle:
list ( APPEND ASTYLE_CMAKE_ARGS
"-DCMAKE_INSTALL_PREFIX= ${CMAKE_BINARY_DIR} "
)
ExternalProject_Add(
astyle
GIT_REPOSITORY https://github.com/Bareflank/astyle.git
GIT_TAG v1.2
GIT_SHALLOW 1
CMAKE_ARGS ${ASTYLE_CMAKE_ARGS}
PREFIX ${CMAKE_BINARY_DIR} /astyle/ prefix
TMP_DIR ${CMAKE_BINARY_DIR} /astyle/tmp
STAMP_DIR ${CMAKE_BINARY_DIR} /astyle/stamp
DOWNLOAD_DIR ${CMAKE_BINARY_DIR} /astyle/download
SOURCE_DIR ${CMAKE_BINARY_DIR} /astyle/src
BINARY_DIR ${CMAKE_BINARY_DIR} /astyle/ build
)
Эта логика cmake использует ExternalProject_Add для автоматической загрузки Astyle, компиляции его для вашей платформы и установки в каталог сборки, чтобы его можно было использовать нашей пользовательской целью make. Обратите внимание, что мы используем нашу собственную исправленную версию Astyle, которая для простоты меняет систему сборки с пользовательского набора Makefiles Astyle на систему сборки CMake.
list ( APPEND ASTYLE_ARGS
--style=1tbs
--lineend=linux
-- suffix =none
--pad-oper
--unpad-paren
--break-closing-brackets
--align-pointer= name
--align-reference= name
--indent-preproc-define
--indent-switches
--indent-col1-comments
--keep-one-line-statements
--keep-one-line-blocks
--pad-header
--convert-tabs
--min-conditional-indent=0
--indent=spaces=4
--close-templates
--add-brackets
--break-after-logical
${CMAKE_SOURCE_DIR} / include /*.h
${CMAKE_SOURCE_DIR} /src/*.cpp
${CMAKE_SOURCE_DIR} / test /*.cpp
)
if ( NOT WIN32 STREQUAL "1" )
add_custom_target (
format
COMMAND ${CMAKE_SOURCE_DIR} /bin/astyle ${ASTYLE_ARGS}
COMMENT "running astyle"
)
else ()
add_custom_target (
format
COMMAND ${CMAKE_SOURCE_DIR} /bin/astyle.exe ${ASTYLE_ARGS}
COMMENT "running astyle"
)
endif ()
Чтобы создать нашу собственную цель make astyle, мы используем приведенный выше код CMake. Это указывает CMake на полученный двоичный файл astyle в зависимости от платформы и предоставляет astyle параметры форматирования и исходные файлы, специфичные для этого проекта.
- cmake -DENABLE_ASTYLE=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make format
- |
if [[ -n $(git diff) ]]; then
echo "You must run make format before submitting a pull request"
echo ""
git diff
exit -1
fi
Наконец, чтобы проверить при каждом запросе запроса на то, что изменение кода соответствует нашей конфигурации Astyle, мы добавляем приведенный выше код в наш сценарий Travis CI. Это создаст нашу цель make format
и выполнит ее для форматирования кода. Если make format
форматирует код, будет создан diff, для обнаружения которого можно использовать git diff
. Если различия не созданы, это означает, что весь исходный код соответствует нашей конфигурации Astyle и тест пройден.
Clang Tidy обеспечивает статический анализ. Поддержка этого инструмента начинается с добавления следующего в файл CMakeLists.txt:
set (CMAKE_EXPORT_COMPILE_COMMANDS ON )
Это указывает CMake записывать все инструкции компиляции, используемые для компиляции вашего проекта, включая флаги и определения. Clang Tidy будет использовать эту информацию для статического анализа вашего проекта так же, как он был скомпилирован. Преимуществом этого подхода является значительное повышение точности. Основным недостатком этого подхода является то, что Clang Tidy не умеет статически анализировать файлы, которые не отображаются в базе данных компиляции (например, файлы заголовков). По этой причине, если вы хотите проанализировать заголовок, он должен быть включен в исходный файл, включенный в базу данных компиляции.
list ( APPEND RUN_CLANG_TIDY_BIN_ARGS
-clang-tidy-binary ${CLANG_TIDY_BIN}
-header- filter =.*
-checks=clan*,cert*,misc*,perf*,cppc*,read*,mode*,-cert-err58-cpp,-misc-noexcept-move-constructor
)
add_custom_target (
tidy
COMMAND ${RUN_CLANG_TIDY_BIN} ${RUN_CLANG_TIDY_BIN_ARGS}
COMMENT "running clang tidy"
)
Наконец, Clang Tidy имеет собственную цель make, чтобы упростить его использование. Здесь мы сообщаем скрипту run-clang-tidy-4.0.py
, какой двоичный файл clang tidy использовать, а также какие проверки выполнять и какие заголовочные файлы включать (и это все). Мы отключаем тест -cert-err58-cpp, поскольку он запускает код из catch.hpp, и тест -misc-noException-move-constructor, поскольку в версии 4.0 он все еще содержит ошибки. Обратите внимание, что мы выбираем конкретную версию Clang Tidy, что важно, поскольку каждая новая версия Clang Tidy исправляет ошибки и добавляет новые проверки, что приводит к разным результатам в зависимости от того, какую версию вы используете.
- cmake -DENABLE_CLANG_TIDY=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make tidy > output.txt
- |
if [[ -n $(grep "warning: " output.txt) ]] || [[ -n $(grep "error: " output.txt) ]]; then
echo "You must pass the clang tidy checks before submitting a pull request"
echo ""
grep --color -E '^|warning: |error: ' output.txt
exit -1;
else
echo -e " 33[1;32mxE2x9Cx93 passed: 33[0m $1";
fi
В Travis CI мы включаем Clang Tidy и сохраняем вывод в файл. Если этот файл содержит «предупреждение» или «ошибку», мы не проходим тест и выводим проблемы, о которых сообщил Clang Tidy, пользователю для исправления. Это гарантирует, что каждый PR был статически проверен.
CppCheck — еще один инструмент статического анализа.
list ( APPEND CPPCHECK_CMAKE_ARGS
"-DCMAKE_INSTALL_PREFIX= ${CMAKE_BINARY_DIR} "
)
ExternalProject_Add(
cppcheck
GIT_REPOSITORY https://github.com/danmar/cppcheck.git
GIT_TAG 1.79
GIT_SHALLOW 1
CMAKE_ARGS ${CPPCHECK_CMAKE_ARGS}
PREFIX ${CMAKE_BINARY_DIR} /external/cppcheck/ prefix
TMP_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/tmp
STAMP_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/stamp
DOWNLOAD_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/download
SOURCE_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/src
BINARY_DIR ${CMAKE_BINARY_DIR} /external/cppcheck/ build
)
Версия CppCheck, предоставляемая Ubuntu 14.04, устарела и плохо поддерживает C++11, поэтому мы берем конкретную версию CppCheck с GitHub, позволяя всем пользователям проекта использовать одну и ту же версию.
list ( APPEND CPPCHECK_ARGS
--enable=warning,style,performance,portability,unusedFunction
--std=c++11
--verbose
--error-exitcode=1
-- language =c++
-DMAIN=main
-I ${CMAKE_SOURCE_DIR} / include
${CMAKE_SOURCE_DIR} / include /*.h
${CMAKE_SOURCE_DIR} /src/*.cpp
${CMAKE_SOURCE_DIR} / test /*.cpp
)
add_custom_target (
check
COMMAND ${CMAKE_BINARY_DIR} /bin/cppcheck ${CPPCHECK_ARGS}
COMMENT "running cppcheck"
)
Затем мы добавляем специальную цель для нашего недавно созданного приложения CppCheck, сообщая CppCheck включить все его проверки (за исключением педантических предупреждений) и проверить все наши исходные файлы. Обратите внимание, что CppCheck должен знать, что MAIN=main, иначе он будет думать, что основная функция не выполнена, и нам нужно сообщить CppCheck об ошибке с кодом ошибки, отличным от 0, чтобы Travis CI сообщал о неудачном тесте, если какой-либо из проверки не проходят.
- cmake -DENABLE_CPPCHECK=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make check
Запустить тест Travis CI так же просто, как включить CppCheck и запустить пользовательскую цель make.
Coverity Scan — еще один инструмент статического анализа, который очень хорошо помогает обнаруживать труднообнаружимые структурные проблемы в вашем коде. Если у вас есть доступ к Coverity Scan, стоит добавить его в свой SDP.
- os : linux
env :
- TEST="Coverity Scan"
addons :
apt :
sources :
- ubuntu-toolchain-r-test
packages :
- gcc-6
- g++-6
coverity_scan :
project :
name : " ainfosec/ci_helloworld "
description : " A simple example of how to setup a complete CI environment for C and C++ "
notification_email : [email protected]
build_command_prepend : " cmake -DCMAKE_CXX_COMPILER=g++-6 .. "
build_command : " make "
branch_pattern : master
script :
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-
Coverity Scan также очень прост в настройке. Приведенный выше тест Travis CI представляет собой вырезание и вставку с их веб-сайта после регистрации вашего проекта. Все, что нам нужно сделать, это скомпилировать исходный код, который сообщает Coverity Scan, как компилируется исходный код. После этого на их веб-сайте будут доступны средства для исключения каталогов и выявления проблем с кодом. В нашем примере мы выполняем сканирование при каждом изменении в мастер-файле, так как количество изменений в мастер-файле невелико, но в крупных проектах с большим количеством слияний в день Coverity Scan предлагает использовать для сканирования специальную ветку Coverity_scan. Если это сделано, необходимо настроить ночное сканирование, захватив ветку master и каждую ночь помещая ее в ветку Coverity_scan. Таким образом, проблемы с Coverity Scan можно быстро выявить.
Codecov — мощный, но простой в настройке инструмент покрытия.
if (ENABLE_COVERAGE)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -g " )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -O0" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fprofile-arcs" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -ftest-coverage" )
set ( CMAKE_EXE_LINKER_FLAGS " ${CMAKE_EXE_LINKER_FLAGS} --coverage" )
endif ()
Чтобы настроить покрытие, мы должны включить поддержку GCOV в нашем компиляторе (предполагается GCC или Clang). Как только эта поддержка будет включена, запуск make test
будет генерировать статистику покрытия, которую Codecov может проанализировать, чтобы предоставить вам отчет о том, какой код прошел или не прошел модульное тестирование.
- cmake -DENABLE_COVERAGE=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make test
- cd ..
- bash <(curl -s https://codecov.io/bash)
Тест Travis CI так же прост, как компиляция и запуск модульных тестов, а затем запуск bash-скрипта Codecov. Как только это будет сделано, результаты можно будет увидеть на сайте Codecov.
Комбинезоны — еще один инструмент защиты.
if (ENABLE_COVERAGE)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -g " )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -O0" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fprofile-arcs" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -ftest-coverage" )
set ( CMAKE_EXE_LINKER_FLAGS " ${CMAKE_EXE_LINKER_FLAGS} --coverage" )
endif ()
Как и Codecov, GCOV должен быть включен.
- pip install --user git+git://github.com/eddyxu/cpp-coveralls.git
- cmake -DENABLE_COVERAGE=ON -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- make test
- cd ..
- |
coveralls --build-root build --gcov-options '-lp'
-e build/external
-e build/include
-e build/CMakeFiles/3.8.0
-e build/CMakeFiles/feature_tests.c
-e build/CMakeFiles/feature_tests.cxx
В отличие от Codecov, Coveralls настроить намного сложнее. Codecov отслеживает, какие файлы находятся в вашем репозитории git, и генерирует отчеты только для файлов в репозитории, в то время как Coveralls будет генерировать отчеты о покрытии для всех файлов, которые он видит, включая файлы, созданные CMake. У Coveralls также нет простого bash-скрипта для передачи данных о покрытии на свой сервер, вместо этого требуется установка внешнего специального инструмента C++ для сбора данных GCOV. По этим причинам мы должны установить cpp-comoveralls, а затем указать ему исключить определенные собираемые файлы/каталоги, которые не должны быть собраны.
Google Sanitizers — это инструмент динамического анализа, включенный в GCC и Clang/LLVM.
if (ENABLE_ASAN)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -g" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -O1" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fuse-ld=gold" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fsanitize=address" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fsanitize=leak" )
endif ()
if (ENABLE_USAN)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fuse-ld=gold" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fsanitize=undefined" )
endif ()
if (ENABLE_TSAN)
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fuse-ld=gold" )
set ( CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} -fsanitize=thread" )
endif ()
Каждое дезинфицирующее средство необходимо запускать изолированно, поэтому у нас есть один тест на каждую группу дезинфицирующих средств. Флаги для каждого набора можно найти на странице Google GitHub, а также в документации по использованию Clang.
- cmake -DENABLE_ASAN= ON -DCMAKE_CXX_COMPILER= "g++-6" ..
- make
- make test
Для каждого теста мы включаем конкретную проверку и модульные тесты, и если проверка не удалась, модульный тест завершится с кодом завершения, отличным от 0, в результате чего Travis CI не пройдет тест. Следует отметить, что каждая новая версия GCC и Clang имеет лучшую поддержку, и поэтому, как и в случае с некоторыми другими инструментами, вам следует придерживаться определенной версии.
Valgrind — еще один инструмент динамического анализа, обеспечивающий обнаружение утечек.
set (MEMORYCHECK_COMMAND_OPTIONS " ${MEMORYCHECK_COMMAND_OPTIONS} --leak-check=full" )
set (MEMORYCHECK_COMMAND_OPTIONS " ${MEMORYCHECK_COMMAND_OPTIONS} --track-fds=yes" )
set (MEMORYCHECK_COMMAND_OPTIONS " ${MEMORYCHECK_COMMAND_OPTIONS} --trace-children=yes" )
set (MEMORYCHECK_COMMAND_OPTIONS " ${MEMORYCHECK_COMMAND_OPTIONS} --error-exitcode=1" )
Самый простой способ выполнить Valgrind — использовать встроенную поддержку CMake, поскольку она будет обрабатывать логику ошибок за вас. По этой причине нам нужно указать CMake, какие флаги передать Valgrind. В этом случае мы включаем все его проверки и сообщаем Valgrind выйти с кодом выхода, отличным от 0, чтобы в случае неудачной проверки Travis CI не прошел тест.
- cmake -DCMAKE_CXX_COMPILER="g++-6" ..
- make
- ctest -T memcheck
Чтобы запустить тест, все, что нам нужно сделать, это скомпилировать код и запустить модульные тесты с помощью ctest, включив режим memcheck.
Этот проект распространяется по лицензии MIT.