Featured image of post CMake 学习记录

CMake 学习记录

Learning CMake

前言絮絮叨叨

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)
  • cmake_minimum_required 指定使用 CMake 的最低版本号

  • project 指定项目名称

  • add_executable 用来生成可执行文件,需要指定生成可执行文件的名称和相关源文件。


构建指令

 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}
                           )
  • configure_file: 用于将一个输入文件复制到输出文件,同时可以根据 CMake 中定义的变量对内容进行替换。通常用于生成配置文件(如头文件、资源文件等)。

  • target_include_directories: 用于为指定的目标(如库或可执行文件)设置头文件的搜索路径。

看眼效果,首先建一个 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() 语法

  • 引入子目录

    • 它会加载指定子目录中的 CMakeLists.txt 文件。

    • 子目录可以定义自己的目标(例如库或可执行文件)或设置构建选项。

  • 共享作用域

    • 子目录中的变量、目标、设置等会影响主目录,反之亦然(除非特别限定作用域)
  • 支持递归调用

    • 子目录可以进一步包含其他子目录,从而实现递归组织项目。

这个指令用于将库链接到目标(库或可执行文件)。

1
target_link_libraries(<target> [PRIVATE | PUBLIC | INTERFACE] <libraries...>)
  • <target>:要链接库的目标名字(之前通过 add_libraryadd_executable 定义)。
  • [PRIVATE | PUBLIC | INTERFACE]
    • PRIVATE:链接库仅对当前目标可见,其他目标无法继承。
    • PUBLIC:当前目标和依赖此目标的其他目标都可以访问该库。
    • INTERFACE:仅依赖此目标的其他目标可以访问该库,当前目标自身不会链接。
  • <libraries...>:需要链接的库,可以是目标名称(如 MyLib)、系统库(如 pthread)或文件路径。
1
target_link_libraries(MyApp PRIVATE MyLib pthread)

这会将静态库 MyLib 和系统库 pthread 链接到 MyApp 可执行文件。


参考资料

知乎文章:CMake 良心教程,教你从入门到入魂
B 站:比飞鸟贵重的多_HKL 教程

Licensed under CC BY-NC-SA 4.0
最后更新于 Dec 26, 2024 01:37 +0800
comments powered by Disqus
本博客已稳定运行
使用 Hugo 构建
主题 StackJimmy 设计