此儲存庫提供了一個簡單的範例,說明如何設定各種 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 許可證的許可。