该存储库提供了一个简单的示例,说明如何设置各种 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 修补版本,将构建系统从 Astyle 的自定义 Makefile 集更改为 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 ()
为了创建自定义 astyle make 目标,我们使用上面的 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
最后,为了验证每个 PR 的代码更改是否符合我们的 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-no except-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
)
Ubuntu 14.04 提供的 CppCheck 版本较旧,并且不能很好地支持 C++11,因此我们从 GitHub 获取特定版本的 CppCheck,允许项目的所有用户使用同一版本。
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,否则它会认为 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 源代码是如何编译的。从那里,他们的网站将提供一种排除目录并查看代码问题的方法。在我们的示例中,我们对 master 的每个更改进行扫描,因为 master 的更改数量很少,但对于每天有大量合并的大型项目,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 测试非常简单,只需编译并运行单元测试,然后运行 Codecov 的 bash 脚本即可。完成后,可以在 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 不同,Collalls 的设置要困难得多。 Codecov 跟踪 git 存储库中的文件,并且只为存储库中的文件生成报告,而 Coveralls 将为它看到的所有文件生成覆盖率报告,包括 CMake 生成的文件。 Coveralls 也没有简单的 bash 脚本来向其服务器报告覆盖率数据,而是需要安装外部 C++ 特定工具来收集 GCOV 数据。由于这些原因,我们必须安装 cpp-coveralls,然后告诉它排除不应该收集的特定文件/目录。
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 许可证的许可。