cmake基础

1.C++工程结构

参考:cxx-pflR1: The Pitchfork Layout (PFL) (csswg.org)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
.
├── 3rdparty -- 所有的第三方库目录
│ ├── external -- 源码依赖的第三方库
│ │ └── CMakeLists.txt
│ └── target -- 头文件/库依赖的第三方库
│ └── catch
│ └── catch.hpp
├── build -- 本地构建目录
│ ├── bin
│ │ ├── Debug
│ │ └── Release
│ ├── docs
│ └── lib
├── CMakeLists.txt -- 根目录cmake配置
├── Doxyfile.in -- Doxygen配置文件
├── include -- 公共头文件
│ └── netapi
│ └── tools
│ └── tools.hpp
├── README.md -- 说明文档
├── src -- 源码目录
│ ├── CMakeLists.txt
│ ├── main.cpp
│ └── tools
│ └── tools.cpp
└── tests -- 测试源码目录
├── CMakeLists.txt
└── tools
└── tools.test.cpp
  • 物理组件组织分为分离和合并两种方式,一般作为库时,可以采用分离头文件的方式,但两种方式都要保证相对路径一致。

  • include目录中的项目名是防止在依赖第三方库时的名字冲突。

  • 每个组件包含一个独立的命令空间,如gno::shapes::circle,可以从这个命名空间中得到文件的目录信息如下:

1
2
3
4
5
6
7
<root>
src/geo/shapes/
circle.cpp
include/<root>/geo/shapes/
circle.hpp
tests/geo/shapes/
circle.test.cpp

如果每个类的函数实现过于复杂,可以根据函数名进行再次拆分,如下所示:

1
2
3
4
5
6
7
8
9
10
geo::shapes::circle -> circumference()
geo::shapes::circle -> area()

<root>
src/geo/shapes/
circle.hpp
circle.cpp
circle.circumference.cpp
circle.area.cpp
circle.test.cpp

2.CMakeLists.txt文件

2.1 根目录

./CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
cmake_minimum_required(VERSION 3.16)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS OFF)

project(netapi LANGUAGES C CXX)

# 定义变量
set(PROJECT_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include")
set(PROJECT_SRC_DIR "${PROJECT_SOURCE_DIR}/src")
set(PROJECT_3RD_TARGET_DIR "${PROJECT_SOURCE_DIR}/3rdparty/target")
set(PROJECT_3RD_EXTERNAL_DIR "${PROJECT_SOURCE_DIR}/3rdparty/external")

# 默认构建类型
if(CMAKE_BUILD_TYPE STREQUAL "")
set(CMAKE_BUILD_TYPE "Release")
endif()
message(STATUS "Current build type is ${CMAKE_BUILD_TYPE}")

# 指定输出目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}")

# 配置Doxygen
find_package(Doxygen)
if (DOXYGEN_FOUND)
message(STATUS "Start to build doxygen documentation")
# 配置目录
set(DOXYGEN_IN ${PROJECT_SOURCE_DIR}/Doxyfile.in)
# 输出目录
set(DOXYGEN_OUT ${CMAKE_BINARY_DIR}/Doxyfile)
# 配置模板文件
configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
# 添加自定义生成命令
add_custom_target( docs ALL
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "STATUS Generating API documentation with Doxygen"
VERBATIM )
else (DOXYGEN_FOUND)
message(STATUS "Dexygen not installed")
endif (DOXYGEN_FOUND)

# 子目录
add_subdirectory(3rdparty/external)
add_subdirectory(src)
add_subdirectory(tests)

2.2 外部依赖目录

external/CMakeLists.txt

1
add_subdirectory(spdlog)

2.3 源文件目录

src/CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# tools库
add_library(tools OBJECT "")
file(GLOB_RECURSE tools_sources CONFIGURE_DEPENDS "${PROJECT_SRC_DIR}/tools/*.cpp" "${PROJECT_INCLUDE_DIR}/netapi/tools/*.hpp")
target_sources(tools PRIVATE "${tools_sources}")
target_include_directories(tools PUBLIC "${PROJECT_INCLUDE_DIR}")

# spdlog库
target_include_directories(tools PUBLIC "${PROJECT_3RD_EXTERNAL_DIR}/spdlog/include")
target_link_libraries(tools PRIVATE spdlog::spdlog)

# main函数
add_executable(netapi main.cpp)
target_link_libraries(netapi PUBLIC tools)
target_include_directories(netapi PUBLIC "${PROJECT_INCLUDE_DIR}")

# 自定义运行命令
add_custom_target(run COMMAND "$<TARGET_FILE:netapi>")

2.4 测试目录

tests/CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enable_testing()

add_executable(
unit_tests
tools/tools.test.cpp
)
# 引入catch2头文件库
target_include_directories(unit_tests PUBLIC "${PROJECT_3RD_TARGET_DIR}")

# 引入待测试的函数
target_include_directories(unit_tests PUBLIC "${PROJECT_INCLUDE_DIR}")
target_link_libraries(unit_tests PRIVATE tools)

add_test(test_all unit_tests)

# 自定义测试命令
add_custom_target(test COMMAND "$<TARGET_FILE:unit_tests>")

3.catch2测试库

基础框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define CATCH_CONFIG_MAIN
#include <catch/catch.hpp>

#include <netapi/tools/tools.hpp>

TEST_CASE("simple1") {
int res = calc(1, 2);
REQUIRE(res == 3);
}

TEST_CASE("simple2") {
int res = calc(3, 4);
REQUIRE(res == 7);
}

4.测试与运行

1
2
3
4
5
6
7
8
9
10
# 配置为发布版
cmake -B build -DCMAKE_BUILD_TYPE=Release
# 生成bin
cmake --build build --parallel 4
# 运行程序
cmake --build build --target run
# 执行测试用例
cmake --build build --target test
# 生成API文档
cmake --build build --target docs

5.常用命令

  • add_executable:添加可执行文件
  • add_library:生成库文件,可选参数(动态库SHARED、静态库STATIC、临时对象OBJECT),ALIAS可以为库取别名
  • target_include_directories:为目标添加头文件路径,使<>可以找到,其中的作用域修饰符控制被链接后是否对链接者可见
  • target_link_libraries:为目标添加库路径
  • add_subdirectory:添加子目录,会依次执行子目录的CMakeLists,并且变量可以向子目录传递

6.导入三方库

6.1 源码依赖

可以使用git拉取源码到指定目录作为git仓库的子模块,如下:

1
2
3
git init
git submodule add -b <分支> <url.git> <path>
eg: git submodule add -b v1.x https://github.com/gabime/spdlog.git 3rdparty/external/spdlog

如果三方库存在自己的CMakeLists.txt则只需在3rdparty/external的CMakeLists.txt中添加子目录,如下:

1
add_subdirectory(spdlog)

之后使用target_include_directories和target_link_libraries即可使用,如下。

1
2
target_include_directories(tools PUBLIC "${PROJECT_3RD_EXTERNAL_DIR}/spdlog/include")
target_link_libraries(tools PRIVATE spdlog::spdlog)

6.2 find_package

从系统默认目录中搜索库的包配置文件OpenCVConfig.cmake(或OpenCV-config.cmake),如果找到了则变量XXX_INCLUDE_DIR和XXX_LIBRARIES分别被赋值为库的头文件和库文件路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
find_package(OpenCV)
# 查找名为OpenCV的包,找不到不会报错,事后通过${OpenCV_FOUND}变量来判断是否找到
find_package(OpenCV REQUIRED)
# 找不到会报错,并终止cmake
find_package(OpenCV REQUIRED COMPONENTS core videoio)
# OpenCV的包里面必须得有OpenCV::core和OpenCV::videoio这两个组件,否则也报错
find_package(OpenCV REQUIRED OPTIONAL_COMPONENTS core videoio)
# 组件找不到不会报错,可以通过${OpenCV_core_FOUND}变量判断是否找到

# 检查是否找到,大多数库找到的话都会设置这个变量
if(OpenCV_FOUND)
message ("OpenCV found")
else()
message (FATAL_ERROR "Cannot find OpenCV")
endif()

# 添加可执行文件
add_executable(third_party_include main.cpp)

# 指定包含路径
target_include_directories( third_party_include
PRIVATE ${OpenCV_INCLUDE_DIR}
)

# 指定库路径
target_link_libraries( third_party_include
PRIVATE
${OpenCV_SYSTEM_LIBRARY}
${OpenCV_FILESYSTEM_LIBRARY}
)

如果库没有被安装在标准目录下,可以采用手动设置变量或设置环境变量值的形式指出正确路径,如下所示:

1
set(OpenCV_DIR "D:/OpenCV")

这种方式适合作者提供配置文件并且已经提前安装到操作系统中的库。

6.3 引入已编译的库

静态库

1
2
3
4
add_library(MyLib STATIC IMPORTED)
set_target_properties(MyLib PROPERTIES
IMPORTED_LOCATION_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/libMyLibRe.a
IMPORTED_LOCATION_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/libMyLibDe.a)

带有依赖项的静态库

1
2
3
4
5
add_library(MyLib2 STATIC IMPORTED)
set_target_properties(MyLib2 PROPERTIES
IMPORTED_LOCATION_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/libMyLib2Re.a
IMPORTED_LOCATION_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/libMyLib2De.a
IMPORTED_LINK_INTERFACE_LIBRARIES MyLib) # <-- dependency is here

动态库

1
2
3
4
5
add_library(MyLib SHARED IMPORTED)
set_property(TARGET MyLib PROPERTY IMPORTED_LOCATION c:/path/to/MyLib.dll)
set_property(TARGET MyLib PROPERTY IMPORTED_IMPLIB c:/path/to/MyLib.lib) # 多了lib信息
add_executable(myexe src1.c src2.c)
target_link_libraries(myexe MyLib)

7.Doxygen注释

7.1 配置文件

doxygen -g命令生成默认Doxyfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 项目名称,将作为于所生成的程序文档首页标题
PROJECT_NAME = "JNZ test project"
# 文档版本号,可对应于项目版本号,譬如 svn、cvs 所生成的项目版本号
PROJECT_NUMBER = "1.0.0"
PROJECT_BRIEF = "xxx"
# 程序文档输出目录
OUTPUT_DIRECTORY = docs

# 程序文档输入目录
INPUT = @PROJECT_SOURCE_DIR@/src \
@PROJECT_SOURCE_DIR@/include

# 程序文档语言环境
OUTPUT_LANGUAGE = Chinese
DOXYFILE_ENCODING = UTF-8
# 只对头文件中的文档化信息生成程序文档
FILE_PATTERNS = *.hpp *.cpp

# 递归遍历当前目录的子目录,寻找被文档化的程序源文件
RECURSIVE = YES
# 如果是制作 C 程序文档,该选项必须设为 YES,否则默认生成 C++ 文档格式
OPTIMIZE_OUTPUT_FOR_C = NO

#提取信息,包含类的私有数据成员和静态成员
EXTRACT_ALL = yes
EXTRACT_PRIVATE = yes
EXTRACT_STATIC = yes
# 对于使用 typedef 定义的结构体、枚举、联合等数据类型,只按照 typedef 定义的类型名进行文档化
TYPEDEF_HIDES_STRUCT = YES
# 在 C++ 程序文档中,该值可以设置为 NO,而在 C 程序文档中,由于 C 语言没有所谓的域/名字空间这样的概念,所以此处设置为 YES
HIDE_SCOPE_NAMES = YES
# 让 doxygen 静悄悄地为你生成文档,只有出现警告或错误时,才在终端输出提示信息
QUIET = YES
# 递归遍历示例程序目录的子目录,寻找被文档化的程序源文件
EXAMPLE_RECURSIVE = YES
# 允许程序文档中显示本文档化的函数相互调用关系
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
REFERENCES_LINK_SOURCE = YES
# 不生成 latex 格式的程序文档
GENERATE_LATEX = NO
# 在程序文档中允许以图例形式显示函数调用关系,前提是你已经安装了 graphviz 软件包
HAVE_DOT = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
#在最后生成的文档中,把所有的源代码包含在其中
SOURCE_BROWSER = YES
#这会在HTML文档中,添加一个侧边栏,并以树状结构显示包、类、接口等的关系
GENERATE_HTML = YES

7.2 注释语法

7.2.1 注释格式

块注释

1
2
3
/**
* ......
*/

行注释

1
2
/// ......
//* ...... */

7.2.2 常用注释命令

命令 说明
@file 指定文件名
@brief 概要信息
@details 详细描述
@todo 将要做的事情,链接到汇总的TODO 列表
@warning 警告信息
@exception 注释异常对象
@bug 缺陷,链接到所有缺陷汇总的缺陷列表
@since {text} 通常用来说明从什么版本、时间写此部分代码
@code 在注释中开始说明一段代码
@endcode 注释中代码段的结束
@pre 用来说明代码项的前提条件
@post 用来说明代码项之后的使用条件
@see 指定对其他代码项的引用链接
@note 开始一个段落,用来描述一些注意事项
@par 开始一个段落,段落名称描述由你自己指定
@include 包含文件
@deprecated 已废弃函数
@fn 函数说明
@param 解释函数参数
@return 描述返回意义
@retval 描述返回值具体意义
@var 变量
@enum 枚举
@struct 结构体
@class

7.2.3 项目整体注释

对于应用程序一般放置在main.cpp中,对于库程序一般放置在较关键但不会被引用的文件中,以下是注释框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**@mainpage  标题
* <table>
* <tr><th>Project <td>工程名
* <tr><th>Author <td>作者名
* <tr><th>Source <td>源码路径
* </table>
* @section 详细描述
* 这里是项目详细描述信息
*
* @section 功能描述
* -# 功能1
* -# 功能2
* -# 功能3
*
* @section 用法描述
* -# 用法1
* -# 用法2
*
* @section 历史更新
* <table>
* <tr><th>Date <th>H_Version <th>S_Version <th>Author <th>Description </tr>
* <tr><td>2022/08/17 <td>1.0 <td>S02010041808171 <td>更新人1 <td>更新内容</tr>
* <tr><td>2022/06/24 <td>1.1 <td>S02010041906241 <td>更新人2 <td>
* -# 更新内容1\n
* -# 更新内容2\n
* </tr>
* </table>
**********************************************************************************
*/

7.2.2 文件注释

1
2
3
4
5
6
7
8
9
/**
* @file 文件名
* @brief 简介
* @details 细节
* @author 作者
* @date 年-月-日
* @version 版本号
* @copyright 版权
*/

7.2.3 函数注释

1
2
3
4
5
6
7
8
9
10
11
/**
* @brief 主函数
* @details 程序唯一入口
*
* @param argc 命令参数个数
* @param argv 命令参数指针数组
* @return 程序执行成功与否
* @retval 0 程序执行成功
* @retval 1 程序执行失败
* @note 这里只是一个简单的例子
*/

cmake基础
http://helloymf.github.io/2022/12/06/cmake-ji-chu/
作者
JNZ
许可协议