
在开发过程中,我喜欢更改编译器,构建模式,依赖版本,执行静态分析,衡量性能,收集覆盖范围,生成文档等。 我真的很喜欢CMake,因为它可以让我做我想做的所有事情。
许多人批评CMake,而且常常是当之无愧的,但是如果您看一下,并不是一切都那么糟糕,而且最近情况还不错 ,并且发展方向是非常积极的。
在本文中,我想说明在CMake系统中组织C ++标头库来获得以下功能有多么简单:
- 组装;
- 自动启动测试;
- 测量代码覆盖率;
- 安装;
- 自动记录
- 在线沙箱生成;
- 静态分析
谁已经了解专业人士和专业人士,谁都可以下载项目模板并开始使用它。
目录内容
- 由内而外的项目
- 项目结构
- 主CMake文件(./CMakeLists.txt)
- 项目信息
- 项目选项
- 编译选项
- 主要目标
- 安装方式
- 测验
- 该文件
- 在线沙箱
- 测试脚本(test / CMakeLists.txt)
- 测试中
- 覆盖范围
- 文档脚本(doc / CMakeLists.txt)
- 在线沙箱的脚本(online / CMakeLists.txt)
- 外部项目
- 组装方式
- 世代
- 组装方式
- 选件
- MYLIB_COVERAGE
- MYLIB_TESTING
- MYLIB_DOXYGEN_LANGUAGE
- 大会目标
- 默认情况下
- mylib单元测试
- 检查
- 覆盖范围
- doc
- 魔盒
- 例子
- 工具
- 静态分析
- 后记
. ├── 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系统。 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()
首先,我们找到具有所需测试框架的软件包(将其替换为您喜欢的软件包)。
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()
发现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 ()
在这里,我们找到第三个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]
阅读有关组装目标的更多信息 。
cmake -S ... -B ... -DMYLIB_COVERAGE=ON [ ...]
包括一个coverage
目标,通过它可以开始通过测试衡量代码的覆盖率。
cmake -S ... -B ... -DMYLIB_TESTING=OFF [ ...]
提供关闭单元测试组件和check
目标的功能。 结果,测试的代码覆盖率测量MYLIB_COVERAGE
关闭(请参阅MYLIB_COVERAGE
)。
另外,如果使用add_subdirectory
将该项目作为子项目连接到另一个项目,则会自动禁用测试。
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
目标。
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
。
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
CMake的 3.13
实际上,仅需要CMake 3.13才能运行此帮助中描述的某些控制台命令。 从CMake脚本的语法角度来看,如果以其他方式调用生成,则3.8版就足够了。
Doctest测试库
可以禁用测试(请参阅 MYLIB_TESTING
)。
氧气
要切换将用于生成文档的语言, MYLIB_DOXYGEN_LANGUAGE
了MYLIB_DOXYGEN_LANGUAGE
选项。
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构建系统造福社会和健康。
→ 下载项目模板