「CMake」是一个跨平台的C++项目管理解决方案,它可以帮助用户方便地管理大型C++项目,支持生成不同平台的Makefile。本系列文章可以帮助你快速掌握CMake,所有内容来源于Mastering Cmake

本篇文章为该系列的第一篇。

1.1 安装Cmake

在使用CMake之前,当然你需要在你的操作系统上安装它。在很多操作系统的包管理软件中你基本都能找到CMake的发行版,比如Cygwin,Debian,FreeBSD,Mac的MacPort、HomeBrew等。

如果你的操作系统没有对应的CMake版本,你可以到CMake的下载页面去下载与你的计算机架构相符的Cmake预编译版本。或者你也可以下载CMake的源码自行编译安装,只需要参考源码根目录的Readme.txt文件并按照提示进行编译即可。

1.2 目录结构

CMake在构建一个项目时有两个主要的目录:源码目录和二进制目录。源码目录下存放的是项目的源代码和CMakeLists文件,二进制目录通常命名为build,CMake会将对象文件,库,可执行文件存放其中。CMake不会更改除了build目录以外的任何文件。

源代码无关(Out-of-source)的构建,指的是源代码和二进制目录分离,我们强烈推荐这种结构。如果源码和二进制代码目录是混合的,那么称为源代码相关(In-source)的构建,CMake同样也是支持的。源代码无关的构建能够让我们更加简单地维护和清理代码树,并且允许快速删除构建所产生的所有文件。构建树和源码树的分离也可以让我们更加简单地为同一颗源码树生成多个版本的构建。当你想根据不同的选项来产生多个版本的构建时,这种源代码和二进制代码分离的方式将大有裨益。

1.3 CMake的基本使用

CMake通过使用大量的本地部署工具,可以将一个或多个CMakeLists文件作为输入,并生成项目文件或者对应的Makefile。

典型的CMake处理过程如下:

  1. 项目由一个或多个CMakeLists文件所定义
  2. CMake配置并生成该项目
  3. 用户使用他们喜欢的本地部署工具来构建该项目

接下来我们详细描述每一个步骤的细节。

1.4 CMakeLists文件

CMakeLists文件(我们通常省略它的.txt后缀)是一个纯文本文件,包含了使用CMake语言编写的项目的描述。CMake语言由一系列的注释、命令和变量又称。你可能奇怪为什么CMake决定定义它自己的语言而不是使用一些现有的,诸如Python,Java或者Tcl等语言。主要的原因是CMake的开发者不想让CMake需要额外的工具才能运行。如果依赖于其他某一种语言,那么所有的CMake用户都需要先安装它,甚至可能必须安装某一特定的版本。

1.4.1 Hello, CMake!

让我们考虑一个最简单的CMakeLists文件。要将一个源文件编译为可执行文件,CMakeLists文件应该包含以下内容:

cmake_minimum_required(VERSION 3.20)
project(Hello)
add_executable(Hello Hello.c)

顶层CMakeLists文件的第一行应当总是cmake_minimum_required。这条命令指定了项目所要求的CMake的最低版本,也使得CMake能够向后兼容。

顶层CMakeLists文件的下一行应该是project命令。这条命令设置了项目名称以及其他的一些选项,比如语言或者版本。

一旦CMakeLists文件调用了project命令,那么对于该项目下的所有目录,CMake都会生成一个顶级(top-level)的Makefile或者IDE项目文件。该项目会包含CMakeLists文件中的所有目标(targets)和由add_subdirectory命令指定的子目录。如果add_subdirectory命令使用了EXCLUDE_FROM_ALL选项,生成的项目将不会出现在顶级Makefile或IDE项目文件中;这对于生成在主构建过程中没有影响的子项目非常有用。如果一个项目有很多示例,可以使用这个特性为每个示例运行一次CMake生成构建文件,而又不将这些示例作为正常构建过程的一部分。

最后,使用add_executable命令来向项目中添加一个可执行文件。在这个例子中,源码目录中有两个文件:CMakeLists.txt和hello.c。

下一节我们讨论如何使用CMake GUI和命令行界面来配置和构建一个项目。

1.5 配置和生成

创建CMakeLists文件后,CMake会在一个缓存文件中处理这个文本文件,用户可以编辑CMakeLists文件或用CMake gui或ccmake指定缓存值并重新配置。接下来,CMake使用缓存项在用户所需的构建系统中生成项目(例如Makefile或Visual Studio解决方案)。

1.5.1 运行CMake GUI

CMake包含了一个基于Qt编写的用户界面,可以在大多数平台上使用,包括UNIX,Mac OSX和Windows。cmake-gui包含在了CMake源码中,但是你需要在你的操作系统中安装Qt来构建它。

在Windows上,可执行文件名为cmake-gui.exe,它应该在“程序文件”下的“开始”菜单中。在您的桌面上也可能有一个快捷方式,或者如果您从源代码构建CMake,它将在构建目录中。对于UNIX和Mac用户,可执行文件名为cmake-gui,可以在安装CMake可执行文件的地方找到它。

如你使用的是Mac OS和最新版的homebrew,你可以使用brew install cmake --cask来安装cmake-gui。--cask选项表示让homebrew直接使用预编译好的应用包(.dmg/.pkg),而不是下载源码编译安装。

1.5.2 运行ccmake curses界面

在大多数的UNIX平台上,如果支持curses库,那么CMake会提供一个名为ccmake的可执行文件。这个界面是基于终端的文本应用,非常类似于cmake-gui。要运行ccmake,切换到你的二进制目录,然后运行ccmake,传入你的源码路径。这样会启动一个文本程序界面。

简短的说明显示在窗口的底部。如果你按c键,它会配置项目。您应该总是在更改缓存中的值之后进行配置。要更改值,使用箭头键选择缓存条目,并按enter键编辑它们。布尔值将通过回车键切换。设置好所有值后,可以按g键生成Makefiles并退出。你也可以按h来寻求帮助,按q来退出,按t来切换高级缓存条目的查看。

1.5.3 在命令行中运行CMake

在命令行中,cmake可执行文件可以用来生成一个项目构建系统。对于那些具有很少甚至很少选项的系统来说,命令行再合适不过了。但对于一些大型项目,我们更推荐你使用ccmake或者cmake-gui。为了使用cmake构建一个项目,请首先创建并进入你的二进制文件目录,然后运行cmake命令并指定源代码的路径。如果要传递选项,可以试用-D标志。不像cmake或者cmake-guicmake命令配置和生成的步骤是连续的。

1.5.4 在CMake中指定编译器

在某些系统上,你可能有多个编译器可供选择,或者你的编译器可能位于非标准(non-standard)的位置。在这些情况下,你需要向CMake指定所需编译器的位置。有三种方法来指定:生成器可以指定编译器;可以设置环境变量;或者可以设置缓存项。有些生成器绑定到特定的编译器;例如,Visual Studio 19生成器总是使用Microsoft Visual Studio 19编译器。对于基于Makefile的生成器,CMake将尝试一系列常见的编译器,直到找到一个可用的。

编译器列表优先使用环境变量,但也可以在CMake运行之前设置。cc环境变量指定C编译器,cxx指定C++编译器。比如你也可以直接使用-DCMAKE_CXX_COMPILER=cl来指定编译器。一旦cmake已经运行并且选择了编译器,如果你想要修改编译器选择,可以在一个空的二进制目录中重新运行cmake

编译器和链接器的选项同样可以通过环境变量来设置。设置LDFLAGS会初始化链接器标志的缓存值,CXXFLAGSCFLAGS会分别初始化CMAKE_CXX_FLAGSCMAKE_C_FLAGS

1.5.5 构建配置

构建配置允许以不同的方式生成项目,用于调试、优化或任何其他特殊标志。CMake默认支持DebugReleaseMinSizeRelRelWithDebInfo配置。Debug会设置基本的调试标志。Release开启了基本的优化。MinSizeRel的标志可以生成最小的目标代码,但不一定是最快的代码。RelWithDebInfo生成一个优化的版本同时带有调试信息。

CMake处理配置的方式略有不同,这取决于所使用的生成器。尽可能地遵循本地构建系统的约定。这意味着在使用Makefile和使用Visual Studio项目文件时,配置会以不同的方式影响构建。

Visual Studio IDE支持构建配置的概念。Visual Studio中的默认项目通常具有调试和发布配置。从IDE中,您可以选择构建调试,并且将使用调试标志构建源码文件。IDE将所有二进制文件放入以当前的配置命名的目录中。这为项目带来了额外的复杂性,这些项目的构建程序需要作为构建过程的一部分运行。请查看CMAKE_CFG_INTDIR变量和自定义命令一阶来了解如何处理这个问题。CMAKE_CONFIGURATION_TYPES变量用于告诉CMake应该将哪个配置放到工作空间中。

使用基于Makefile的生成器时,只有一个配置可以在CMake运行期间激活,并且由CMAKE_BUILD_TYPE变量制定。如果该变量为空,那么在构建时将不会添加任何标志。如果该变量设置为一个配置的名称,那么一些变量和规则(如CMAKE_CXX_FLAGS_<ConfigName>)会被添加到编译器的编译执行时,Makefile不会使用特定配置的子目录和对象文件。要同时构建debug和release版本,用户需要创建多个构建目录,并使用前文提到的CMake的源码无关的特性,并且为不同的构建分别设置CMAKE_BUILD_TYPE变量。比如:

# With source code in the directory MyProject
# to build MyProject-debug create that directory, cd into it and
ccmake ../MyProject -DCMAKE_BUILD_TYPE=Debug
# the same idea is used for the release tree MyProject-release
ccmake ../MyProject -DCMAKE_BUILD_TYPE=Release

1.6 构建你的项目

运行CMake后,你的项目就已经准备好编译了。如果你的目标生成器时基于Makefile的,你可以进入项目的二进制目录下并输入make来编译。如果你的生成文件是IDE的配置文件比如Visual Studio,你可以启动IDE加载项目文件了。

另一个选项是在命令行中使用cmake--build选项。这个选项可以让你在命令行中编译你的项目。

这就是如何安装并在一个简单项目上使用CMake的全过程。接下来的文章中,我们将深入CMake的更多细节并学习如何在一个复杂的软件项目中使用它。