CMake初步学习笔记

CMake是什么

就个人理解,CMake是一个自动化的建构系统,他通过执行CMakeLists.txt中的一系列脚本,来针对指定项目生成一系列构建文件。然后我们就可以使用构建文件来编译并构建项目。

为什么使用CMake

就我目前学习到的知识而言,CMake具有以下优势

  • CMake可以方便我指定用于构建项目的资源与依赖(扫描目录,自动检测);
  • CMake是跨平台的,它生成的构建文件(MakeFile)可以运用于在多个不同的平台上编译项目;

最小化的构建脚本

CMake1中的项目完成了一个A+B程序,以下是CMakeLists.txt中的内容。

1
2
3
4
5
cmake_minimum_required(VERSION 3.10)
set(CMAKE_C_COMPILER "C:/MinGW/bin/gcc.exe")
set(CMAKE_CXX_COMPILER "C:/MinGW/bin/gcc.exe")
project(CMakeAPlusB)
add_executable(a-plus-b a-plus-b.c)
  • cmake_minimum_required(VERSION 3.10)指定了最小的CMake版本,最好在3.10及以上,不然会有警告(貌似是旧版的一些特性不被支持了)。
  • set(CMAKE_C_COMPILER "C:/MinGW/bin/gcc.exe")set(CMAKE_CXX_COMPILER "C:/MinGW/bin/gcc.exe")指定了C文件和C++文件的编译器(我用的是MinGW,以此为例)。set(VAR_NAME VALUE)语句是CMake脚本中的变量设置语句。
  • project(CMakeAPlusB)指定了项目名称。
  • add_executable(a-plus-b a-plus-b.c)指定项目生成可执行文件的构建文件。

执行以下命令行指令来开始编译

1
cmake -B D:\Projects\cmake-summary\CMake1\ -G "MinGW Makefiles"

其中-B指定编译目录,-G指定使用的编译器,CMake GUI也可以完成相应的任务。

执行脚本后,在该目录中会生成MakeFile构建文件,在目录下执行如下命令行指令

1
make

即可编译MakeFile文件,生成a-plus-b.exe


CMake2中的项目完成了一个A+B脚本,并把它编译为了静态库,脚本大意同上。

1
2
3
4
5
cmake_minimum_required(VERSION 3.10)
set(CMAKE_C_COMPILER "C:/MinGW/bin/gcc.exe")
set(CMAKE_CXX_COMPILER "C:/MinGW/bin/gcc.exe")
project(CMakeAPlusBDLL)
add_library(a-plus-b SHARED a-plus-b.c)

之后执行的命令行指令与上方类似,最后会生成a-plus-b.dlla-plus-b.dll.a,前者为动态库文件,后者为静态库文件。若只想要后者的话,将SHARED改为STATIC即可。

静态库相较于动态库而言,被引用时会被全部加载,占用资源大但调用内容时效率有所上升;动态库是被引用后每次调用会动态地在资源中搜索,占用资源相对少而调用效率有所下降。

默认配置目录

以上方CMake1的构建过程为例:

1
cmake -B D:\Projects\cmake-summary\CMake1\ -G "MinGW Makefiles"

使用-B指定编译目录,CMake会检测目录下是否存在CMakeLists.txt文件,如果有,就会开始生成构建文件。

默认构建目录

CMake3的文件夹中,生成的构建文件存放在./build目录下,这是因为我执行了如下指令:

1
cmake -S D:\Projects\cmake-summary\CMake3\ -G "MinGW Makefiles" -B "D:\Projects\cmake-summary\CMake3\build\"

-S指定源文件CMakeLists.txt所在目录,-B指定生成文件所在的目录,就可以指定将文件构建在相应位置。

此外,CMakeLists.txt中多了这么一行:

1
2
3
4
5
6
cmake_minimum_required(VERSION 3.10)
set(CMAKE_C_COMPILER "C:/MinGW/bin/gcc.exe")
set(CMAKE_CXX_COMPILER "C:/MinGW/bin/g++.exe")
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/)
project(CMakeBuildFolder)
add_executable(customBuildFolder main.cpp)

set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/)可以指定可执行文件的生成位置,这里也是在./build目录下。

指定编译后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cmake --G

Generators
The following generators are available on this platform (* marks default):
Visual Studio 17 2022 = Generates Visual Studio 2022 project files.
Use -A option to specify architecture.
Visual Studio 16 2019 = Generates Visual Studio 2019 project files.
Use -A option to specify architecture.
Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
Optional [arch] can be "Win64" or "ARM".
Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.
Optional [arch] can be "Win64" or "ARM".
Borland Makefiles = Generates Borland makefiles.
...

如上所示,执行cmake -G后,会展示所有可用的编译器列表,使用-G命令指定需要的编译后端即可,例如下面的指令:

1
cmake -S D:\Projects\cmake-summary\CMake3\ -G "MinGW Makefiles" -B "D:\Projects\cmake-summary\CMake3\build\"

设置变量并使用

正如上方所提到的,set(VAR_NAME VALUE)语句是CMake脚本中的变量设置语句,那么以下语句等价:

1
2
set(CMAKE_C_COMPILER "C:/MinGW/bin/gcc.exe")
set(CMAKE_CXX_COMPILER "C:/MinGW/bin/g++.exe")
1
2
3
set(MINGW_COMPILER "C:/MinGW/bin")
set(CMAKE_C_COMPILER "${MINGW_COMPILER}/gcc.exe")
set(CMAKE_CXX_COMPILER "${MINGW_COMPILER}/g++.exe")

要引用变量,使用${VAR_NAME}的格式即可。

CMake –build 与 –target

正如上方所提到的,对于生成的MakeFile文件,用make命令可以生成相关的可执行文件或库。实际上,如果我们使用NinjaVisual Studio等其它编译器生成构建文件,得到的就会是.ninja.sln构建文件,此时就必须使用xcodebuildmsbuild等其它的命令进行编译。如果我们还要配置生成参数的话,不同的生成命令配置方式还不一样。

为了方便跨平台,所以,我们可以用以下命令来代替make

1
cmake --build .

如果我们使用的是MakeFile,此命令相当于在本目录.执行了make

我们还可以用--target来指定生成目标:

1
2
3
4
5
6
7
8
9
10
11
12
cmake --build . --target help

The following are some of the valid targets for this Makefile:
... all (the default if no target is provided)
... clean
... depend
... edit_cache
... rebuild_cache
... customBuildFolder
... main.obj
... main.i
... main.s

target后加入参数,来构建指定的目标,如果不指定,就全部构建。

清除构建目录并重新构建

根据个人的一些实践,当我们将一个已经完成了构建并顺利生成了CMakeCache.txt的项目文件从一个目录移动到另外一个目录的时候,我们必须将CMakeCache.txt删除,并重新使用cmake命令构建MakeFile,否则在执行相关命令时会提示目录不符合。

常用内置变量

引用内置变量的格式和引用自定义变量的没有区别,都是${VAR_NAME}

我自己用过的内置变量:

  • ${CMAKE_CURRENT_SOURCE_DIR} CMakeLists.txt所在的根目录
  • ${CMAKE_C_COMPILER} C编译器位置(不能设置成CPP的!)
  • ${CMAKE_CXX_COMPILER} CPP编译器位置(不能设置成C的!)
  • ${EXECUTABLE_OUTPUT_PATH} 输出可执行文件的位置
  • ${LIBRARY_OUTPUT_PATH} 输出库的位置

有篇写这个的博客:https://www.cnblogs.com/Braveliu/p/15827857.html,我觉得这个总结的挺全面。

资源管理

参见CMake4项目,可以使用一些指令来包含资源文件与头文件,并在项目中引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
cmake_minimum_required(VERSION 3.10)
set(CMAKE_C_COMPILER "C:/MinGW/bin/gcc.exe")
set(CMAKE_CXX_COMPILER "C:/MinGW/bin/g++.exe")
project(CMakeShooter)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/build/)
# 设置输出目录
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
# 指定头文件(可以做include了)
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 用file语句查找资源文件(可以先进行方法声明再引用)
# 这个玩意还有一个可用的首个参数GLOB_RECURSE,这个可以递归搜索所有的内部目录
add_executable(CMakeShooter main.cpp ${MAIN_SRC})
# 将主文件和资源文件一起编译

使用include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)可以将指定目录下的所有文件作为头文件添加到项目中,这样就可以使用include引用相应的文件。

使用file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)可以按照格式扫描指定目录,将所有符合格式的文件存放到数组变量中。

add_executable()可以一次性添加多个文件,只需要把多个变量写在参数里就行。