CMake和C ++-永远的兄弟

永远的友谊


在开发过程中,我喜欢更改编译器,构建模式,依赖版本,执行静态分析,衡量性能,收集覆盖范围,生成文档等。 我真的很喜欢CMake,因为它可以让我做我想做的所有事情。


许多人批评CMake,而且常常是当之无愧的,但是如果您看一下,并不是一切都那么糟糕,而且最近情况还不错 ,并且发展方向是非常积极的。


在本文中,我想说明在CMake系统中组织C ++标头库来获得以下功能有多么简单:


  1. 组装;
  2. 自动启动测试;
  3. 测量代码覆盖率;
  4. 安装;
  5. 自动记录
  6. 在线沙箱生成;
  7. 静态分析

谁已经了解专业人士和专业人士,谁都可以下载项目模板并开始使用它。


目录内容


  1. 由内而外的项目
    1. 项目结构
    2. 主CMake文件(./CMakeLists.txt)
      1. 项目信息
      2. 项目选项
      3. 编译选项
      4. 主要目标
      5. 安装方式
      6. 测验
      7. 该文件
      8. 在线沙箱
    3. 测试脚本(test / CMakeLists.txt)
      1. 测试中
      2. 覆盖范围
    4. 文档脚本(doc / CMakeLists.txt)
    5. 在线沙箱的脚本(online / CMakeLists.txt)
  2. 外部项目
    1. 组装方式
      1. 世代
      2. 组装方式
    2. 选件
      1. MYLIB_COVERAGE
      2. MYLIB_TESTING
      3. MYLIB_DOXYGEN_LANGUAGE
    3. 大会目标
      1. 默认情况下
      2. mylib单元测试
      3. 检查
      4. 覆盖范围
      5. doc
      6. 魔盒
    4. 例子
  3. 工具
  4. 静态分析
  5. 后记


由内而外的项目



项目结构


. ├── CMakeLists.txt ├── README.en.md ├── README.md ├── doc │  ├── CMakeLists.txt │  └── Doxyfile.in ├── include │  └── mylib │  └── myfeature.hpp ├── online │  ├── CMakeLists.txt │  ├── mylib-example.cpp │  └── wandbox.py └── test ├── CMakeLists.txt ├── mylib │  └── myfeature.cpp └── test_main.cpp 

我们将主要讨论如何组织CMake脚本,因此将对其进行详细分析。 每个人都可以直接在project-template页面上看到其余文件。



主CMake文件(./CMakeLists.txt)



项目信息


首先,您需要请求正确版本的CMake系统。 CMake在不断发展,团队签名,在不同条件下的行为正在发生变化。 为了使CMake能够立即了解我们对他的要求,我们需要立即确定对他的要求。


 cmake_minimum_required(VERSION 3.13) 

然后,我们指定项目,其名称,版本,使用的语言等(请参阅 project )。


在这种情况下,我们指定CXX语言(即C ++),以便CMake不会紧张,也不会寻找C语言编译器(默认情况下,CMake包含两种语言:C和C ++)。


 project(Mylib VERSION 1.0 LANGUAGES CXX) 

在这里,您可以立即检查我们的项目是否作为子项目包含在另一个项目中。 这将在将来极大地帮助您。


 get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY) 


项目选项


我们提供两种选择。


第一个选项MYLIB_TESTING关闭单元测试。 如果我们确定一切都与测试保持一致,并且例如仅希望安装或打包我们的项目,则这可能是必要的。 或者我们的项目被包含为子项目-在这种情况下,我们项目的用户对运行我们的测试不感兴趣。 您不测试使用的依赖项吗?


 option(MYLIB_TESTING "  " ON) 

此外,我们将单独使用一个选项MYLIB_COVERAGE来测试测试的代码覆盖率,但是它将需要其他工具,因此您需要显式启用它。


 option(MYLIB_COVERAGE "    " OFF) 


编译选项


当然,我们很酷,再加上程序员,所以我们希望编译器提供最大程度的编译时间诊断。 没有一只鼠标会滑过。


 add_compile_options( -Werror -Wall -Wextra -Wpedantic -Wcast-align -Wcast-qual -Wconversion -Wctor-dtor-privacy -Wenum-compare -Wfloat-equal -Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wsign-conversion -Wsign-promo ) 

我们还将禁用扩展程序以完全符合C ++语言标准。 默认情况下,它们包含在CMake中。


 if(NOT CMAKE_CXX_EXTENSIONS) set(CMAKE_CXX_EXTENSIONS OFF) endif() 


主要目标


我们的库仅由头文件组成,这意味着我们没有任何静态或动态库形式的文件。 另一方面,要在外部使用我们的库,您需要安装它,需要能够在系统中找到它并将其连接到您的项目,同时这些相同的标头以及可能的其他一些标头属性。


为此,我们创建一个接口库。


 add_library(mylib INTERFACE) 

将标头绑定到我们的前端库。


CMake的现代,时尚,年轻化使用意味着标题,属性等。 通过一个单一目的传输。 因此,只要说target_link_libraries(target PRIVATE dependency)就足够了,并且与dependency目标相关联的所有标头都可用于属于目标target源。 并且不需要[target_]include_directories 。 下面将在分析CMake脚本进行单元测试时对此进行演示。


所谓的也值得关注 -: $<...>


此命令将所需的标头与前端库相关联,如果我们的库连接到同一CMake层次结构中的目标,则目录${CMAKE_CURRENT_SOURCE_DIR}/include标头将与之关联,如果我们的由于该库已安装在系统上,并使用find_package连接到另一个项目,因此include目录相对于安装目录的标头将与该库相关联。


 target_include_directories(mylib INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> ) 

设置语言标准。 当然,最后。 同时,我们不仅包含标准,而且还将其分发给将使用我们库的人员。 这是由于set属性具有INTERFACE类别(请参见target_compile_features命令 )而实现的。


 target_compile_features(mylib INTERFACE cxx_std_17) 

我们为我们的库创建一个别名。 此外,为了美观,他将处于特殊的“名称空间”中。 当不同的模块出现在我们的库中,并且我们彼此独立地连接时,这将很有用。 例如,在Boost中


 add_library(Mylib::mylib ALIAS mylib) 


安装方式


在系统中安装标题。 这里的一切都很简单。 我们说带有所有标题的文件夹应位于相对于安装位置的include目录中。


 install(DIRECTORY include/mylib DESTINATION include) 

接下来,通知构建系统我们希望能够在第三方项目中调用find_package(Mylib)并获取目标Mylib::mylib


 install(TARGETS mylib EXPORT MylibConfig) install(EXPORT MylibConfig NAMESPACE Mylib:: DESTINATION share/Mylib/cmake) 

下一个咒语应理解如下。 当我们在第三方项目中调用find_package(Mylib 1.2.3 REQUIRED)时,在这种情况下,已安装库的实际版本将与1.2.3版本不兼容,CMake将自动生成错误。 也就是说,您无需手动遵循版本。


 include(CMakePackageConfigHelpers) write_basic_package_version_file("${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) install(FILES "${PROJECT_BINARY_DIR}/MylibConfigVersion.cmake" DESTINATION share/Mylib/cmake) 


测验


如果使用适当的选项显式关闭了测试,或者如果我们的项目是子项目,即使用add_subdirectory连接到另一个CMake项目,则在层次结构中我们将走得更远,描述生成和运行测试的命令的脚本不会启动。


 if(NOT MYLIB_TESTING) message(STATUS "  Mylib ") elseif(IS_SUBPROJECT) message(STATUS "Mylib     ") else() add_subdirectory(test) endif() 


该文件


如果是子项目,也不会生成文档。


 if(NOT IS_SUBPROJECT) add_subdirectory(doc) endif() 


在线沙箱


同样,该子项目也将没有在线沙箱。


 if(NOT IS_SUBPROJECT) add_subdirectory(online) endif() 


测试脚本(test / CMakeLists.txt)



测试中


首先,我们找到具有所需测试框架的软件包(将其替换为您喜欢的软件包)。


 find_package(doctest 2.3.3 REQUIRED) 

我们使用测试创建可执行文件。 通常,我只将main功能直接位于其中的文件添加到可执行二进制文件中。


 add_executable(mylib-unit-tests test_main.cpp) 

稍后会添加描述测试本身的文件。 但这不是必需的。


 target_sources(mylib-unit-tests PRIVATE mylib/myfeature.cpp) 

我们连接依赖关系。 请注意,我们仅target_include_directories所需target_include_directories CMake目标target_include_directories到二进制文件,而未调用target_include_directories命令。 测试框架和Mylib::mylib的标头以及构建参数(在我们的情况下,这是C ++语言标准)与这些目标一起爬网。


 target_link_libraries(mylib-unit-tests PRIVATE Mylib::mylib doctest::doctest ) 

最后,创建一个虚拟目标,其“程序集”等效于运行测试,并将该目标添加到默认程序集中( ALL属性负责此操作)。 这意味着默认情况下,程序集会启动测试的启动,也就是说,我们将永远不会忘记运行它们。


 add_custom_target(check ALL COMMAND mylib-unit-tests) 


覆盖范围


接下来,如果指定了相应选项,则启用代码覆盖率测量。 我不再赘述,因为它们与衡量覆盖率的工具比与CMake的联系更多。 唯一重要的是要注意,根据结果,将创建一个coverage目标,通过它可以方便地开始测量覆盖率。


 find_program(GCOVR_EXECUTABLE gcovr) if(MYLIB_COVERAGE AND GCOVR_EXECUTABLE) message(STATUS "    ") target_compile_options(mylib-unit-tests PRIVATE --coverage) target_link_libraries(mylib-unit-tests PRIVATE gcov) add_custom_target(coverage COMMAND ${GCOVR_EXECUTABLE} --root=${PROJECT_SOURCE_DIR}/include/ --object-directory=${CMAKE_CURRENT_BINARY_DIR} DEPENDS check ) elseif(MYLIB_COVERAGE AND NOT GCOVR_EXECUTABLE) set(MYLIB_COVERAGE OFF) message(WARNING "       gcovr") endif() 


文档脚本(doc / CMakeLists.txt)


发现Doxygen


 find_package(Doxygen) 

接下来,我们检查用户是否设置了language变量。 如果是,那就不要碰,如果没有,那就用俄语。 然后配置Doxygen系统文件。 在配置过程中,所有必需的变量(包括语言)都到达了那里(请参见 configure_file )。


然后,我们创建doc目标,这将开始生成文档。 由于生成文档并不是开发过程中的最大需求,因此默认情况下不会实现目标,因此必须明确启动它。


 if (Doxygen_FOUND) if (NOT MYLIB_DOXYGEN_LANGUAGE) set(MYLIB_DOXYGEN_LANGUAGE Russian) endif() message(STATUS "Doxygen documentation will be generated in ${MYLIB_DOXYGEN_LANGUAGE}") configure_file(Doxyfile.in Doxyfile) add_custom_target(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) endif () 


在线沙箱的脚本(online / CMakeLists.txt)


在这里,我们找到第三个Python并创建一个wandbox目标,该目标将生成一个与Wandbox服务API匹配的请求并将其发送。 作为响应,出现了指向完成的沙箱的链接。


 find_program(PYTHON3_EXECUTABLE python3) if(PYTHON3_EXECUTABLE) set(WANDBOX_URL "https://wandbox.org/api/compile.json") add_custom_target(wandbox COMMAND ${PYTHON3_EXECUTABLE} wandbox.py mylib-example.cpp "${PROJECT_SOURCE_DIR}" include | curl -H "Content-type: application/json" -d @- ${WANDBOX_URL} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS mylib-unit-tests ) else() message(WARNING "  -    python 3- ") endif() 


外部项目


现在考虑如何使用这一切。



组装方式


与CMake装配系统上的任何其他项目一样,该项目的装配也包括两个阶段:



世代


 cmake -S // -B /// [ ...] 

如果以上命令由于CMake的旧版本而无法使用,请尝试省略-S
 cmake // -B /// [ ...] 

有关选项的更多信息



项目组装


 cmake --build /// [--target target] 

阅读有关组装目标的更多信息



选件



MYLIB_COVERAGE


 cmake -S ... -B ... -DMYLIB_COVERAGE=ON [  ...] 

包括一个coverage目标,通过它可以开始通过测试衡量代码的覆盖率。



MYLIB_TESTING


 cmake -S ... -B ... -DMYLIB_TESTING=OFF [  ...] 

提供关闭单元测试组件和check目标的功能。 结果,测试的代码覆盖率测量MYLIB_COVERAGE关闭(请参阅MYLIB_COVERAGE )。


另外,如果使用add_subdirectory将该项目作为子项目连接到另一个项目,则会自动禁用测试。



MYLIB_DOXYGEN_LANGUAGE


 cmake -S ... -B ... -DMYLIB_DOXYGEN_LANGUAGE=English [  ...] 

doc目标生成的文档语言切换为指定的语言。 有关可用语言的列表,请参阅Doxygen系统网站


默认情况下,启用俄语。



大会目标



默认情况下


 cmake --build path/to/build/directory cmake --build path/to/build/directory --target all 

如果未指定目标(相当于all目标),则收集所有可能的内容,并调用check目标。



mylib单元测试


 cmake --build path/to/build/directory --target mylib-unit-tests 

编译单元测试。 默认启用。



检查


 cmake --build /// --target check 

运行收集的(如果尚未收集的话)单元测试。 默认启用。


另请参见mylib-unit-tests



覆盖范围


 cmake --build /// --target coverage 

使用gcovr程序分析正在运行(如果尚未运行的话)的单元测试,以覆盖代码。


涂层废气看起来像这样:


 ------------------------------------------------------------------------------ GCC Code Coverage Report Directory: /path/to/cmakecpptemplate/include/ ------------------------------------------------------------------------------ File Lines Exec Cover Missing ------------------------------------------------------------------------------ mylib/myfeature.hpp 2 2 100% ------------------------------------------------------------------------------ TOTAL 2 2 100% ------------------------------------------------------------------------------ 

仅当MYLIB_COVERAGE时,目标才可用。


另请参阅check



doc


 cmake --build /// --target doc 

开始使用Doxygen系统生成代码文档。



魔盒


 cmake --build /// --target wandbox 

服务的响应如下所示:


 { "permlink" : "QElvxuMzHgL9fqci", "status" : "0", "url" : "https://wandbox.org/permlink/QElvxuMzHgL9fqci" } 

为此,请使用Wandbox服务。 我不知道它们有多少橡胶服务器,但我认为不应滥用这一机会。



例子


调试模式下的项目组装,具有覆盖率测量


 cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DMYLIB_COVERAGE=ON cmake --build /// --target coverage --parallel 16 

项目安装无需预先组装和测试


 cmake -S // -B /// -DMYLIB_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/// cmake --build /// --target install 

由指定的编译器以发布模式构建


 cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-8 -DCMAKE_PREFIX_PATH=///// cmake --build /// --parallel 4 

英文文件生成


 cmake -S // -B /// -DCMAKE_BUILD_TYPE=Release -DMYLIB_DOXYGEN_LANGUAGE=English cmake --build /// --target doc 


工具


  1. CMake的 3.13


    实际上,仅需要CMake 3.13才能运行此帮助中描述的某些控制台命令。 从CMake脚本的语法角度来看,如果以其他方式调用生成,则3.8版就足够了。


  2. Doctest测试


    可以禁用测试(请参阅 MYLIB_TESTING )。


  3. 氧气


    要切换将用于生成文档的语言, MYLIB_DOXYGEN_LANGUAGEMYLIB_DOXYGEN_LANGUAGE选项。


  4. Python 3解释器


    自动生成在线沙箱




静态分析


借助CMake和一些出色的工具,您可以以最少的身体移动提供静态分析。


Cppcheck


CMake内置了对Cppcheck静态分析工具的支持


为此,请使用选项CMAKE_CXX_CPPCHECK


 cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_CPPCHECK="cppcheck;--enable=all;-I///include" 

之后,静态分析将在每次编译和重新编译源代码时自动启动。 您无需执行任何其他操作。


lang


使用出色的scan-build工具,您还可以进行两次静态分析:


 scan-build cmake -S // -B /// -DCMAKE_BUILD_TYPE=Debug scan-build cmake --build /// 

在这里,与Cppcheck的情况不同,您需要每次通过scan-build运行程序集。



后记


CMake是一个功能强大且灵活的系统,可让您实现各种口味和颜色的功能。 并且,尽管有时语法尚待改进,但魔鬼仍然不如他被涂上的那么可怕。 使用CMake构建系统造福社会和健康。




下载项目模板

Source: https://habr.com/ru/post/zh-CN461817/


All Articles