面向 C++ 的现代 CMake 教程(五)(3)

简介: 面向 C++ 的现代 CMake 教程(五)

面向 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模块所使用的模块。我们来看看它是如何使用的。

应用测试场景

为了让测试工作,必须发生几件事情:

  1. 我们需要为两个目录创建嵌套列表文件并定义测试目标。
  2. 单元测试需要编写并作为可执行目标准备。
  3. 这些目标需要调用 AddTests()
  4. 被测试的软件SUT)需要被修改以启用覆盖率收集。
  5. 收集的覆盖率应在构建之间清理,以避免段错误。

正如 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/calcsrc/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

这些文件现在几乎完成了(正如第二个注释所暗示的,我们还需要添加文档代码,这将在自动文档生成部分完成)。

列表中出现了两个新模块:FormatCppCheck。让我们先来看第一个:

章节-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检测到问题时,我们希望得到一个错误代码。这可以是从1255的任何数字(0表示成功),尽管有些数字可能被系统保留。

使用非常方便——调用AddCppCheck将通知 CMake 需要在指定的目标上自动运行检查。

我们已经在srctest子目录中几乎创建了所有文件。现在,我们的解决方案可以构建并完全测试。终于到了安装和打包的时候了。

安装和打包

我们回到前一章讨论的主题,并从快速查看设置安装和打包所需的文件开始:

![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

相关文章
|
8天前
|
C++
Clion CMake C/C++程序输出乱码
Clion CMake C/C++程序输出乱码
10 0
|
9天前
|
存储 算法 编译器
C++ 函数式编程教程
C++ 函数式编程学习
|
9天前
|
存储 编译器 开发工具
C++语言教程分享
C++语言教程分享
|
9天前
|
存储 编译器 C++
|
1月前
|
C++ 存储 索引
面向 C++ 的现代 CMake 教程(一)(5)
面向 C++ 的现代 CMake 教程(一)
46 0
|
1月前
|
缓存 存储 C++
面向 C++ 的现代 CMake 教程(一)(4)
面向 C++ 的现代 CMake 教程(一)
45 0
|
1月前
|
C++ 缓存 存储
面向 C++ 的现代 CMake 教程(一)(3)
面向 C++ 的现代 CMake 教程(一)
44 0
|
1月前
|
缓存 C++ Windows
面向 C++ 的现代 CMake 教程(一)(2)
面向 C++ 的现代 CMake 教程(一)
57 0
|
1月前
|
C++ 容器 Docker
面向 C++ 的现代 CMake 教程(一)(1)
面向 C++ 的现代 CMake 教程(一)
67 0
|
5天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。