目录

CMake简介

CMake是一个开源的、跨平台的工具系列,用于构建、测试和打包软件。CMake涵盖了从编译构建到软件发布的所有内容。简单来讲从头到尾的过程看起来是这样的:

/images/2022/03/26/cmake-process.png

配置项目

CMake使用一个叫CMakeLists.txt的文件定义应该构建什么和如何构建,运行什么测试和创建什么包。这个文件是对整个项目的描述,与平台无关。CMake将这个文件变成特定平台构建工具的项目文件。

CMake的一个基本概念是一个项目包含一个源码目录和一个二进制目录。源码目录就是CMakeLists.txt所在的目录,包括所有的源文件。二进制目录是创建由构建产生的所有内容的地方,通常被称为构建目录。可执行程序、库、测试输出和包都是在构建目录中创建。CMake还在构建目录中创建了一个名为CMakeCache.txt的特殊文件,用于存储各种信息,以便在后续运行中重复使用。

当开发者开始在一个项目上工作时,必须决定构建目录与源代码目录之间的关系。基本上有两种方法:源码内构建和源码外构建。

源码内构建

源码目录和构建目录可以使用同一个目录,这被称为源码内构建。这种方式的主要问题是构建过程中生成的中间文件会污染源码目录,同时使得与版本控制系统的工作更加困难。另一个缺点是清理构建输出之前很难得到一个干净的源码树。因此不推荐使用源码内构建。

源码外构建

更可取的安排是源码目录和构建目录使用不同的目录,这被称为源码外构建。它使得源码和构建输出完全分开,从而避免了源码内构建中出现的混杂问题。源码外构建还有一个好处,就是开发者可以为同一个源码目录创建多个构建目录,这样就可以用不同的选项来配置构建,例如调试版本和发布版本等。

生成项目文件

CMake读入CMakeLists.txt文件并在构建目录中创建项目文件。开发者通过选择一个特定的项目文件生成器来选择要创建的项目文件类型。CMake支持一系列生成器,如下表:

Category Generator Examples Multi-config
Visual Studio Visual Studio 15 2017 Yes
Visual Studio 14 2015 Yes
Yes
Xcode Xcode Yes
Ninja Ninja No
Makefiles Unix Makefiles No
MSYS Makefiles No
MinGW Makefiles No
NMake Makefiles No

运行CMake的最基本方法是通过cmake命令行工具,比如:

1
2
3
mkdir build
cd build
cmake -G "Unix Makefiles" ../source

如果省略了-G选项,CMake将根据主机平台选择一个默认的生成器类型。对于所有的生成器类型,CMake将进行一系列的测试并询问系统以确定如何配置项目文件。包括验证编译器是否工作,确定支持的编译器功能集以及其他各种任务。在CMake完成之前,各种信息将被记录下来,成功后会得到如下输出:

1
2
3
-- Configuring done
-- Generating done
-- Build files have been written to: /some/path/build

以上信息说明项目文件的创建实际上包括两个步骤:配置和生成。在配置阶段,CMake读入CMakeLists.txt文件并建立整个项目的内部表示。在这之后,生成阶段会创建项目文件。

当CMake运行完成后,将在构建目录中保存一个CMakeCache.txt文件。CMake使用这个文件来保存细节,这样如果它再次被运行,就可以重复使用第一次计算的信息并加速项目生成。它还允许在运行之间保存开发选项。

运行构建工具

随着项目文件的出现,开发者可以按照他们习惯的方式使用他们选择的构建工具。构建目录将包含必要的项目文件,这些文件可以被加载到IDE中,被命令行工具读取等等。或者cmake可以替开发者调用构建工具,比如:

1
cmake --build /some/path/build --config Debug --target MyApp
  • --build选项指定使用哪个构建目录。
  • 对于多配置生成器,--config选项指定了要构建的配置。单配置生成器将忽略--config选项,而是依赖于执行CMake项目生成步骤时提供的信息。
  • --target选项告诉构建工具构建的目标,如果省略,将使用默认目标。

一个最小的项目

下面是一个最小的、格式良好的CMakeLists.txt文件,产生一个基本的可执行文件。

1
2
3
cmake_minimum_required(VERSION 3.2)
project(MyApp)
add_executable(myExe main.cpp)

上述例子中的每一行都执行了一条内置的CMake命令。在CMake中,命令类似于其他语言的函数调用,支持参数,但不直接返回值。参数之间用空格隔开,并且可以分成多行。

1
2
3
4
5
add_executable(myExe
    main.cpp
    src1.cpp
    src2.cpp
)

命令名称也不区分大小写,所以下面这些都是等同的。

1
2
3
add_executable(myExe main.cpp)
ADD_EXECUTABLE(myExe main.cpp)
Add_Executable(myExe main.cpp)

常见的惯例是命令名称全部使用小写。

管理CMake版本

CMake使用cmake_minimum_required命令指定cmake最低要求版本,它应该放在CMakeLists.txt第一行,以便在其他事情之前检查并确定项目的需求。这个命令做了两件事:

  • 它指定了项目所需的CMake的最小版本。如果在处理CMakeLists.txt文件时,CMake的版本比指定的版本早,它将立即停止并报错误。这确保了在继续进行之前,有一套特定的CMake最小功能可用。
  • 它强制执行策略设置,使CMake行为与指定的版本相匹配。

cmake_minimum_required的格式如下:

1
cmake_minimum_required(VERSION major.minor[.patch[.tweak]])

project命令

每个CMake项目都应该包含一个project命令,它应该出现在cmake_minimum_required后面。该命令最常见选项如下:

1
2
3
4
project(projectName
    [VERSION major[.minor[.patch[.tweak]]]]
    [LANGUAGES languageName ...]
)

项目名称是必需的,只能包含字母、数字、下划线(_)和连字符(-)。VERSION只在CMake 3.0及以后的版本中支持。LANGUAGES参数定义了该项目应启用的编程语言。支持的值包括C、CXX、Fortran、ASM、Java和其他。LANGUAGES NONE表明没有语言被使用。如果没有提供LANGUAGES选项,将默认为C和CXX。CMake 3.0之前不支持LANGUAGES关键字,但是可以使用以下旧形式:

1
project(myProj C CXX)

project命令所做的远不止是填充几个变量。它的一个重要职责是检查每个启用的语言的编译器,确保它们能够成功地编译和链接。这样,编译器和链接器配置的问题就会被很早发现。一旦这些检查通过,CMake就会设置一些变量和属性来控制已启用语言的构建。如果CMakeLists.txt文件没有调用project,或者没有足够早地调用它,CMake将隐含地在内部调用默认语言C和CXX,以确保编译器和链接器为其他依赖它们的命令进行了正确配置。

构建一个基本的可执行文件

add_executable命令告诉CMake从一组源文件中创建一个可执行文件。其基本形式是

1
add_executable(targetName source1 [source2 ...])

当项目被构建时,将在构建目录中创建一个可执行文件,其名称是一个与平台有关的基于目标名称的名字。在一个CMakeLists.txt文件中也可以定义多个不同的可执行文件,只需用不同的目标名称多次调用add_executable命令。如果同一个目标名称在多个add_executable命令中使用,CMake将失败并报错。

注释

CMake遵循与Unix shell脚本类似的注释惯例。任何以#字符开头的行都被视为注释,除了在带引号的字符串中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
cmake_minimum_required(VERSION 3.2)

# We don't use the C++ compiler, so don't let project()
# test for it in case the platform doesn't have one
project(MyApp VERSION 4.7.2 LANGUAGES C)

# Primary tool for this project
add_executable(mainTool
    main.c
    debug.c # Optimized away for release builds
)

# Helpful diagnostic tool for development and testing
add_executable(testTool testTool.c)