前言絮絮叨叨
CMake 目前对我而言最大的意义就是告诉我,如果一个东西如果你不用,那最好挑中午的时候学,因为早晚要忘。这次重新倒腾博客,想了想决定把 CMake 作为第一篇文章,希望以后学了的东西都能用上(笑。
准备环境
构建工具:Download | CMake
编译工具:Downloads - MinGW-w64
基本语法
首先是建一个 CMakeLists.txt
文件,然后让我们看下最基本的一个项目需要哪些指令
1
2
3
4
5
6
7
|
cmake_minimum_required(VERSION 3.15)
# set the project name
project(Tutorial)
# add the executable
add_executable(Tutorial tutorial.cpp)
|
构建指令
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 当前路径下创建一个 build 目录
mkdir build
# 进入 build 目录
cd build
# 使用 MinGW 构建项目
# cmake -G"MinGW Makefiles" ..
# 直接使用 windows 默认环境 msvc 构建项目
# 因为 CMakeLists 文件在上级目录,这里用 .. 指定
cmake ..
# build
cmake --build .
|
CMakeLists 优化
add_executable(Tutorial tutorial.cpp)
中的项目名称可以直接用 ${PROJECT_NAME}
代替,这样修改项目名称后不用再修改此指令。
类似的,tutorial.cpp
源文件名称可以用 set(SRC_LIST a.cpp b.cpp c.cpp)
这种形式替代,优化后的文件内容如下:
1
2
3
4
5
6
7
8
9
|
cmake_minimum_required(VERSION 3.15)
# set the project name
project(Tutorial)
SET(SRC_LIST tutorial.cpp)
# add the executable
add_executable(${PROJECT_NAME} ${SRC_LIST})
|
这里注意到 CMake 语法似乎习惯性的大写变量的所有字母。
接下来是两个新的指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
cmake_minimum_required(VERSION 3.15)
# set the project name and version
project(Tutorial VERSION 1.0.2)
configure_file(TutorialConfig.h.in TutorialConfig.h)
set(SRC_LIST tutorial.cpp)
# add the executable
add_executable(${PROJECT_NAME} ${SRC_LIST})
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
)
|
看眼效果,首先建一个 TutorialConfig.h.in
文件:
1
2
3
4
5
|
// TutorialConfig.h.in
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define Tutorial_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
当使用 CMake 构建项目后,会在 build 中生成一个 TutorialConfig.h
文件,内容如下:
1
2
3
4
5
|
// TutorialConfig.h
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
#define Tutorial_VERSION_PATCH 2
|
接着就可以直接在 tutorial.cpp
里去 include 这个 TutorialConfig.h
了,like so
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// tutorial.cpp
#include <iostream>
#include <TutorialConfig.h>
int main(int argc, char *argv[])
{
if (argc < 2)
{
// report version
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
}
|
构建运行后的结果:
1
2
3
|
PS D:\Dev\CMakeStudy\Demo\build> .\Debug\Tutorial.exe
D:\Dev\CMakeStudy\Demo\build\Debug\Tutorial.exe Version 1.0
Usage: D:\Dev\CMakeStudy\Demo\build\Debug\Tutorial.exe number
|
类似的,也可以给编译时间打上时间戳
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
cmake_minimum_required(VERSION 3.15)
# set the project name and version
project(Tutorial VERSION 1.0.2)
string(TIMESTAMP COMPILE_TIME %Y%m%d-%H%M%S)
configure_file(TutorialConfig.h.in TutorialConfig.h)
set(SRC_LIST tutorial.cpp)
# add the executable
add_executable(${PROJECT_NAME} ${SRC_LIST})
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
)
|
查阅了一下 string()
指令的用法,发现功能挺强大的,主要体现在其支持非常多的子指令,格式类似这样 string(<sub-command> <args...>)
。
这里用到的 TIMESTAMP
就是其中一个子指令,COMPILE_TIME
表示接收 TIMESTAMP
的结果,%Y%m%d-%H%M%S
则是供 TIMESTAMP
使用的格式化字符串
修改下 TutorialConfig.h.in
文件
1
2
3
4
5
6
|
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define Tutorial_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define TIMESTAMP @COMPILE_TIME@
|
cmake build 生成的 TutorialConfig.h
会增加一个时间宏
1
2
3
4
5
6
|
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
#define Tutorial_VERSION_PATCH 2
#define TIMESTAMP 20241223-022712
|
然后就可以在代码里调用了。
如果需要指定 C++ 标准的话,可以在 CMakeLists 里加上这两个指令
1
2
3
4
5
6
7
8
|
cmake_minimum_required(VERSION 3.15)
# set the project name and version
project(${PROJECT_NAME} VERSION 1.0)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
添加库
库自己的 CMakeLists 里要加上这句
1
2
|
# MathFunctions/CMakeLists.txt
add_library(MathFunctions mysqrt.cpp)
|
顶级 CMakeLists 里面要也要加上指定子目录的指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(${PROJECT_NAME} tutorial.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC MathFunctions)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/MathFunctions
)
|
add_library() 语法:
这个指令用于定义一个库目标。库可以是静态库、共享库或模块库。
1
|
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] <source_files...>)
|
-
<name>
:库的名字。
-
[STATIC | SHARED | MODULE]
:指定库的类型:
- STATIC:静态库(如
.a
或 .lib
)。
- SHARED:动态库(如
.so
或 .dll
)。
- MODULE:非链接的动态库(通常用于插件系统)。
-
[EXCLUDE_FROM_ALL]
:如果指定,库不会默认参与 all
目标的构建。
-
<source_files...>
:库的源码文件。
1
|
add_library(MyLib STATIC mylib.cpp mylib.h)
|
这会创建一个名为 MyLib
的静态库。
add_subdirectory() 语法
-
引入子目录
-
共享作用域
- 子目录中的变量、目标、设置等会影响主目录,反之亦然(除非特别限定作用域)
-
支持递归调用
- 子目录可以进一步包含其他子目录,从而实现递归组织项目。
target_link_libraries() 语法
这个指令用于将库链接到目标(库或可执行文件)。
1
|
target_link_libraries(<target> [PRIVATE | PUBLIC | INTERFACE] <libraries...>)
|
<target>
:要链接库的目标名字(之前通过 add_library
或 add_executable
定义)。
[PRIVATE | PUBLIC | INTERFACE]
- PRIVATE:链接库仅对当前目标可见,其他目标无法继承。
- PUBLIC:当前目标和依赖此目标的其他目标都可以访问该库。
- INTERFACE:仅依赖此目标的其他目标可以访问该库,当前目标自身不会链接。
<libraries...>
:需要链接的库,可以是目标名称(如 MyLib
)、系统库(如 pthread
)或文件路径。
1
|
target_link_libraries(MyApp PRIVATE MyLib pthread)
|
这会将静态库 MyLib
和系统库 pthread
链接到 MyApp
可执行文件。
参考资料
知乎文章:CMake 良心教程,教你从入门到入魂
B 站:比飞鸟贵重的多_HKL 教程