CMake教程

1. 常用的宏

功能
CMAKE_CURRENT_SOURCE_DIR 当前访问的 CMakeLists.txt 文件所在的路径
PROJECT_SOURCE_DIR 使用cmake命令时,后面紧跟的目录,一般是工程的根目录
EXECUTABLE_OUTPUT_PATH 指定输出的目录
LIBRARY_OUTPUT_PATH 库目录
CMAKE_AUTORCC 指定CMake是否会自动处理Qt资源文件

2. 最简单的CMakeLists.txt文件

1
2
3
4
5
6
7
# 指定使用的 cmake 的最低版本
cmake_minimum_required(VERSION 3.8)

project (Master VERSION 0.1 LANGUAGES CXX)

# 可执行程序名 源文件名称
add_executable (app main.cpp)

3. 指定使用的C++标准

1
2
3
4
5
6
7
8
#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)
# 要求编译器必须支持 否则报错
set(CMAKE_CXX_STANDARD_REQUIRED ON)

4. 指定输出路径

1
2
# 指定输出的路径 若不存在会自动生成
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR/bin})

5. 搜索文件

5.1 aux_source_directory函数

在 CMake 中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为:

1
aux_source_directory(< dir > < variable >)
  • dir:要搜索的目录
  • variable:将从dir目录下搜索到的源文件列表存储到该变量中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cmake_minimum_required(VERSION 3.8) 

project (Master VERSION 0.1 LANGUAGES CXX)

# 指定C++版本为C++17
set(CMAKE_CXX_STANDARD 17)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)

# 指定输出的路径
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

# 可执行程序名 源文件名称
add_executable (app ${SRC_LIST})

5.2 file函数

1
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
  • GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
  • GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
1
2
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)

6. 包含头文件

1
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

7. 创建库

1
add_library(<target> <source_files>...)

7.1 制作静态库

1
add_library(calc STATIC ${SRC_LIST})

7.2 制作动态库

1
add_library(calc SHARED ${SRC_LIST})

7.3 指定输出的路径

方式1-适用于动态库

对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录:

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})
方式2-都适用

由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH这个宏对应静态库文件和动态库文件都适用

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})

8. 包含库文件

8.1 链接静态库

1
link_libraries(<static lib> [<static lib>...])

用于设置全局链接库,这些库会链接到之后定义的所有目标上。

  • 参数1:指定出要链接的静态库的名字

  • 参数2-N:要链接的其它静态库的名字

如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:

1
link_directories(<lib path>)
1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})

8.2 链接动态库

1
2
3
4
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

用于指定一个目标(如可执行文件或库)在编译时需要链接哪些库。它支持指定库的名称、路径以及链接库的顺序。

target:指定要加载的库的文件的名字

  • 该文件可能是一个源文件

  • 该文件可能是一个动态库/静态库文件

  • 该文件可能是一个可执行文件

PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC

  • 如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可

  • 动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。

1
2
target_link_libraries(A B C)
target_link_libraries(D A)
  • PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
  • PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
  • INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。

链接系统动态库

动态库的链接和静态库是完全不同的:

  • 静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。

  • 动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存

因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)

target_link_libraries(app pthread)中:

app: 对应的是最终生成的可执行程序的名字
pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。

链接第三方动态库

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库
target_link_libraries(app calc)

建议在 CMake 项目中优先使用 target_link_libraries

9. 日志

在CMake中可以用用户显示一条消息,该命令的名字为message

1
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
  • (无) :重要消息

  • STATUS :非重要消息

  • WARNING:CMake 警告, 会继续执行

  • AUTHOR_WARNING:CMake 警告 (dev), 会继续执行

  • SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤

  • FATAL_ERROR:CMake 错误, 终止所有处理过程

CMake的命令行工具会在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。

CMake警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。

1
2
3
4
5
6
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

10. install

install命令用于将文件或目录安装到指定位置,常用于二进制执行文件、动态库、静态库的部署。

install不是自动执行的,需在命令行执行cmake install

选项 含义
RUNTIME 二进制可执行文件
LIBRARY 动态库
ARCHIVE 静态库
1
2
3
4
install(TARGETS <target_name> 
[RUNTIME | LIBRARY | ARCHIVE] DESTINATION <target_dir>
...
)

GNUInstallDirs标准安装目录

目标类型 GNUInstallDirs变量 内置默认值
RUNTIME ${CMAKE_INSTALL_BINDIR} bin
LIBRARY ${CMAKE_INSTALL_BINDIR} lib
ARCHIVE ${CMAKE_INSTALL_BINDIR} lib
PRIVATE_HEADER ${CMAKE_INSTALL_INCLUDEDIR} include
PUBLIC_HEADER ${CMAKE_INSTALL_INCLUDEDIR} include
FILE_SET (头文件类型) ${CMAKE_INSTALL_INCLUDEDIR} include
1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.0)
project(CALC)

add_executable(app ${PROJECT_SOURCE_DIR}/src/main.c)

include(GNUInstallDirs)
install(TARGETS app
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

11. 添加子目录

1
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

12. 与Qt相关的变量

UIC(用户界面编译器)、MOC(元对象编译器)和RCC(资源编译器)

12.1 自动处理.ui文件文件

1
set(CMAKE_AUTOUIC ON)

启用了CMake的自动UIC支持。当此选项被设置为ON时,CMake将自动检测项目中的.ui文件(Qt Designer创建的用户界面文件),并使用UIC工具将这些文件转换成C++源文件,然后这些源文件会被自动添加到相应的Qt目标(如库或可执行文件)的编译过程中。这样,开发者就不需要手动编写CMake脚本来调用UIC或处理其输出文件。

12.2 自动处理继承QObject的类

1
set(CMAKE_AUTOMOC ON)

启用了CMake的自动MOC支持。当此选项被设置为ON时,CMake将自动检测项目中继承自QObject的类(这些类需要使用MOC进行元对象信息的生成),并调用MOC工具来生成必要的元对象代码。这些生成的代码文件随后会被自动添加到项目的编译过程中。这对于使用Qt信号和槽机制、属性系统、事件系统等特性的类来说是必需的。

12.3 自动处理Qt资源文件

1
set(CMAKE_AUTORCC ON)

启用了CMake的自动RCC支持。RCC是Qt的资源编译器,用于将.qrc文件(Qt资源集合文件)编译成二进制格式,这些二进制资源可以在Qt应用程序中动态加载。当CMAKE_AUTORCC被设置为ON时,CMake会自动检测项目中的.qrc文件,并使用RCC工具将其编译成资源文件,然后将这些资源文件添加到构建过程中。这简化了管理Qt项目中资源文件的过程。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cmake_minimum_required(VERSION 3.16)

project(Master VERSION 0.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 6.5 REQUIRED COMPONENTS Quick)

set(CMAKE_AUTOUIC ON) # 自动处理.ui文件
set(CMAKE_AUTOMOC ON) # 自动处理继承QObject的类
set(CMAKE_AUTORCC ON) # 自动处理Qt资源文件

qt_add_executable(app
main.cpp
res.qrc
)

target_link_libraries(app
PRIVATE Qt6::Quick
)