面向 C++ 的现代 CMake 教程(五)(2)https://developer.aliyun.com/article/1526951
准备覆盖模块
为多个目标添加覆盖是一个有点棘手的过程,因为它包括几个步骤。我们首先介绍两个函数,以启用覆盖跟踪并在构建之间清理陈旧的跟踪文件:
chapter-12/01-full-project/cmake/Coverage.cmake(片段)
function(EnableCoverage target) if (CMAKE_BUILD_TYPE STREQUAL Debug) target_compile_options(${target} PRIVATE --coverage -fno-inline) target_link_options(${target} PUBLIC --coverage) endif() endfunction() function(CleanCoverage target) add_custom_command(TARGET ${target} PRE_BUILD COMMAND find ${CMAKE_BINARY_DIR} -type f -name '*.gcda' -exec rm {} +) endfunction()
前面的函数将在我们到达单独的目标配置(calc_...
和calc_console_...
)时被使用。Coverage
模块还将提供一个生成自定义覆盖目标的函数:
chapter-12/01-full-project/cmake/Coverage.cmake(继续)
function(AddCoverage target) find_program(LCOV_PATH lcov REQUIRED) find_program(GENHTML_PATH genhtml REQUIRED) add_custom_target(coverage-${target} COMMAND ${LCOV_PATH} -d . --zerocounters COMMAND $<TARGET_FILE:${target}> COMMAND ${LCOV_PATH} -d . --capture -o coverage.info COMMAND ${LCOV_PATH} -r coverage.info '/usr/include/*' -o filtered.info COMMAND ${GENHTML_PATH} -o coverage-${target} filtered.info --legend COMMAND rm -rf coverage.info filtered.info WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endfunction()
AddCoverage()
函数在Testing
模块中的AddTests()
函数中被调用。它与第八章中介绍的测试框架略有不同,因为它考虑了目标名称,并将其添加到输出路径以避免任何冲突。
为了生成两个测试目标的报告,我们只需运行两个cmake
命令(在用Debug
构建类型配置项目后):
cmake --build <build-tree> -t coverage-calc_test cmake --build <build-tree> -t coverage-calc_console_test
现在是我们修改之前创建的 Memcheck 模块的时候了(在第九章程序分析工具中)以处理多个目标。
准备 Memcheck 模块
AddTests()
调用了 Valgrind 内存管理报告的生成。我们将从一般设置开始这个模块:
chapter-12/01-full-project/cmake/Memcheck.cmake(片段)
include(FetchContent) FetchContent_Declare( memcheck-cover GIT_REPOSITORY https://github.com/Farigh/memcheck- cover.git GIT_TAG release-1.2 ) FetchContent_MakeAvailable(memcheck-cover)
我们已经熟悉这段代码了;让我们看看将创建适当目标的函数:
chapter-12/01-full-project/cmake/Memcheck.cmake(继续)
function(AddMemcheck target) set(MEMCHECK_PATH ${memcheck-cover_SOURCE_DIR}/bin) set(REPORT_PATH "${CMAKE_BINARY_DIR}/valgrind-${target}") add_custom_target(memcheck-${target} COMMAND ${MEMCHECK_PATH}/memcheck_runner.sh -o "${REPORT_PATH}/report" -- $<TARGET_FILE:${target}> COMMAND ${MEMCHECK_PATH}/generate_html_report.sh -i ${REPORT_PATH} -o ${REPORT_PATH} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endfunction()
为了处理多个目标,REPORT_PATH
变量被设置以存储目标特定的报告路径。然后在此后的命令中使用此变量。
可以通过以下命令生成 Memcheck 报告(这在Debug
构建类型中效果更好):
cmake --build <build-tree> -t memcheck-calc_test cmake --build <build-tree> -t memcheck-calc_console_test
这些都是Testing
模块所使用的模块。我们来看看它是如何使用的。
应用测试场景
为了让测试工作,必须发生几件事情:
- 我们需要为两个目录创建嵌套列表文件并定义测试目标。
- 单元测试需要编写并作为可执行目标准备。
- 这些目标需要调用
AddTests()
。 - 被测试的软件(SUT)需要被修改以启用覆盖率收集。
- 收集的覆盖率应在构建之间清理,以避免段错误。
正如 test/CMakeLists.txt
暗示的那样,我们将创建两个嵌套列表文件来配置我们的测试。再一次,我们将为库提供一个:
chapter-12/01-full-project/test/calc/CMakeLists.txt(片段)
add_executable(calc_test calc_test.cpp) target_link_libraries(calc_test PRIVATE calc_static) AddTests(calc_test) EnableCoverage(calc_obj)
我们也会为可执行文件提供一个:
chapter-12/01-full-project/test/calc_console/CMakeLists.txt(片段)
add_executable(calc_console_test tui_test.cpp) target_link_libraries(calc_console_test PRIVATE calc_console_static) AddTests(calc_console_test) EnableCoverage(calc_console_static)
为了保持简洁,我们将提供尽可能简单的单元测试。一个文件将覆盖库:
chapter-12/01-full-project/test/calc/calc_test.cpp
#include "calc/calc.h" #include <gtest/gtest.h> TEST(CalcTest, SumAddsTwoInts) { EXPECT_EQ(4, Calc::Sum(2, 2)); } TEST(CalcTest, MultiplyMultipliesTwoInts) { EXPECT_EQ(12, Calc::Multiply(3, 4)); }
我们将有一个第二个文件来测试业务代码。为此,我们将使用 FXTUI 库。同样,我们不期望你详细了解这个源代码。本章提供的测试列表只是为了完整:
chapter-12/01-full-project/test/calc_console/tui_test.cpp
#include "tui.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <ftxui/screen/screen.hpp> using namespace ::ftxui; TEST(ConsoleCalcTest, RunWorksWithDefaultValues) { auto component = getTui(); auto document = component->Render(); auto screen = Screen::Create(Dimension::Fit(document)); Render(screen, document); auto output = screen.ToString(); ASSERT_THAT(output, testing::HasSubstr("Sum: 102")); }
这个测试代码简单地将文本界面渲染为默认状态到一个静态屏幕对象,然后将其存储在一个字符串中。为了使测试通过,输出需要包含默认和的子字符串。
现在,我们需要完成剩下的步骤:创建测试目标并准备好它们的源代码后,是时候使用 Testing
模块的 AddTests()
函数将它们注册到 CPack 了。
我们为图书馆这样做:
chapter-12/01-full-project/test/calc/CMakeLists.txt(继续)
# ... calc_test target definition AddTests(calc_test) EnableCoverage(calc_obj)
然后为可执行文件这样做:
chapter-12/01-full-project/test/calc_console/CMakeLists.txt(继续)
# ... calc_console_test target definition AddTests(calc_console_test) EnableCoverage(calc_console_static)
随后,我们指示 SUT 使用 EnableCoverage()
启用覆盖率 instrumentation。注意,在库的情况下,我们必须添加 instrumentation 到 对象库 而不是静态库。这是因为 --coverage
标志必须添加到编译步骤,这发生在 calc_obj
正在构建的时候。
遗憾的是,我们在这里不能添加覆盖率文件的清理,因为 CMake 要求 add_custom_command
钩子必须在目标定义相同的目录中调用。这使我们回到了之前未完成的 src/calc
和 src/calc_console
列表文件。我们需要分别添加 CleanCoverage(calc_static)
和 CleanCoverage(calc_console_static)
(我们首先必须包含 Coverage
模块)。还需要向这些文件添加什么吗?启用静态分析的说明!
添加静态分析工具
我们将业务代码列表文件的继续推迟到现在,这样我们就可以在适当的情况下讨论添加的模块。我们可以在库列表文件中添加一个 CleanCoverage
函数调用和其他一些东西:
chapter-12/01-full-project/src/calc/CMakeLists.txt(继续)
# ... calc_static target definition include(Coverage) CleanCoverage(calc_static) include(Format) Format(calc_static .) include(CppCheck) AddCppCheck(calc_obj) # ... documentation generation
我们也可以将它们添加到可执行文件中:
章节-12/01-full-project/src/calc_console/CMakeLists.cmake (继续)
# ... calc_console_static target definition include(BuildInfo) BuildInfo(calc_console_static) include(Coverage) CleanCoverage(calc_console_static) include(Format) Format(calc_console_static .) include(CppCheck) AddCppCheck(calc_console_static) # ... documentation generation # ... calc_console bootstrap target definition
这些文件现在几乎完成了(正如第二个注释所暗示的,我们还需要添加文档代码,这将在自动文档生成部分完成)。
列表中出现了两个新模块:Format
和CppCheck
。让我们先来看第一个:
章节-12/01-full-project/cmake/Format.cmake
function(Format target directory) find_program(CLANG-FORMAT_PATH clang-format REQUIRED) set(EXPRESSION h hpp hh c cc cxx cpp) list(TRANSFORM EXPRESSION PREPEND "${directory}/*.") file(GLOB_RECURSE SOURCE_FILES FOLLOW_SYMLINKS LIST_DIRECTORIES false ${EXPRESSION} ) add_custom_command(TARGET ${target} PRE_BUILD COMMAND ${CLANG-FORMAT_PATH} -i --style=file ${SOURCE_FILES} ) endfunction()
Format()
函数是第九章《程序分析工具》中描述的格式化函数的完整复制;我们在这里只是重新使用它。
接下来是一个全新的CppCheck
模块:
章节-12/01-full-project/cmake/CppCheck.cmake
function(AddCppCheck target) find_program(CPPCHECK_PATH cppcheck REQUIRED) set_target_properties(${target} PROPERTIES CXX_CPPCHECK "${CPPCHECK_PATH};--enable=warning;--error-exitcode=10" ) endfunction()
这简单又方便。您可能会发现它与 Clang-Tidy 模块有些相似(来自第九章,《程序分析工具》);这是 CMake 的优势——许多概念的工作方式都是一致的。注意传递给cppcheck
的参数:
--enable=warning
– 这指定了我们希望获得警告信息。您可以启用其他检查——具体请参考 Cppcheck 手册(在进一步阅读部分可以找到链接)。--error-exitcode=10
– 这指定了当cppcheck
检测到问题时,我们希望得到一个错误代码。这可以是从1
到255
的任何数字(0
表示成功),尽管有些数字可能被系统保留。
使用非常方便——调用AddCppCheck
将通知 CMake 需要在指定的目标上自动运行检查。
我们已经在src
和test
子目录中几乎创建了所有文件。现在,我们的解决方案可以构建并完全测试。终于到了安装和打包的时候了。
安装和打包
我们回到前一章讨论的主题,并从快速查看设置安装和打包所需的文件开始:
![Figure 12.6 – 配置安装和打包文件的示例
这里只需要文件——大部分工作已经在之前的章节中完成。正如您可能记得的,顶层列表文件包含一个 CMake 模块,它将处理这个过程:
章节-12/01-full-project/CMakeLists.txt (片段)
... include(Install)
我们关注的是安装两个项目:
- 计算库工件:静态库、共享库以及与之相关的头文件和目标导出文件
- 计算控制台可执行文件
包定义配置文件将只引入库目标,因为潜在的消费项目不会依赖于可执行文件。
在配置安装步骤之后,我们将转向 CPack 配置。Install
模块的高级概述如下:
章节-12/01-full-project/cmake/Install.cmake (概览)
# Includes # Installation of Calc library # Installation of Calc Console executable # Configuration of CPack
一切都有计划,所以是时候为库编写一个安装模块了。
面向 C++ 的现代 CMake 教程(五)(4)https://developer.aliyun.com/article/1526954