在我的Autotools 介绍中,我演示了如何使用 GNU Autotools 管理代码的构建和打包。它是一个强大且通用的平台,可以轻松集成到许多打包系统中,包括 RPM、APT、pkgsrc 等。它的语法和结构可能令人困惑,但幸运的是,还有其他替代方案,包括开源的 CMake 框架。
CMake 是一个跨平台套件,用于构建、测试和打包软件。它使用简单且文档清晰的语法,因此即使您以前从未使用过构建系统,也很容易上手。
安装 CMake
您的 Linux 系统可能已经安装了 CMake。 如果没有,您可以使用发行版的软件包管理器安装它
$ sudo dnf install cmake
在 Debian 或类似系统上
$ sudo apt install cmake
对于 Mac,您可以使用 MacPorts 或 Homebrew
$ sudo port install cmake
在 Windows 上,您可以使用 Chocolatey 或直接从 CMake 网站下载二进制文件。
CMake 的工作原理
对于想要从源代码构建软件的开发人员或用户来说,CMake 是一种快速简便的编译和安装方法。 CMake 分阶段工作
- 首先,在
cmake
步骤中,CMake 扫描主机系统(正在运行它的计算机)以发现默认设置。 默认设置包括支持库的位置以及新软件在系统上的放置位置。 - 接下来,您使用系统的
make
命令(通常是 Linux 上的 GNU Make,NetBSD 上的 NetBSD Make 等)来构建应用程序,通常是将人类可读的源代码转换为机器语言。 - 最后,在
make install
步骤中,构建的文件被复制到计算机上相应的位置(在cmake
阶段检测到)。
这看起来很简单,当您使用 CMake 时,它确实很简单。
CMake 的可移植性
CMake 在设计时就考虑了可移植性。 虽然它不能使您的项目跨所有 POSIX 平台工作(这取决于您,作为程序员),但它可以确保您标记为安装的文件安装到已知平台上最合理的位置。 并且由于像 CMake 这样的工具,高级用户可以轻松自定义和覆盖任何非最佳值,以适应其系统的需求。
使用 CMake,您只需知道哪些文件需要安装到哪个大致位置。 它会处理所有其他事情。 不再有在任何未经测试的操作系统上都会崩溃的自定义安装脚本。
打包
与 Autotools 一样,CMake 也得到了很好的支持。 将带有 CMake 的项目交给发行版打包人员,无论他们是打包 RPM、DEB 还是 TGZ(或其他任何格式),他们的工作都简单而直接。 打包工具了解 CMake,因此可能不需要任何修补、黑客攻击或调整。 在许多情况下,将 CMake 项目整合到管道中可以实现自动化。
如何使用 CMake
要开始将 CMake 用于您的项目,您只需在项目目录中创建一个 CMakeLists.txt
文件。 首先,声明 CMake 的最低必需版本以及项目标题和版本。 CMake 努力尽可能长时间地保持兼容性,但是您使用它并关注其开发越多,您就越了解您依赖的功能。
cmake_minimum_required(VERSION 3.10)
project(Hello VERSION 1.0)
您可能已经注意到,CMake 的语法是一个命令,后跟括号中的参数。 大写的 VERSION
字符串不是任意的或仅仅是为了样式; 它们是 project
命令的有效参数。
在继续之前,生成一个用 C 或 C++ 编写的示例 hello world
应用程序。 为了简单起见,我编写了六行 C 代码并将其保存为 hello.c
(以匹配我在 CMakeLists.txt
中列出的可执行文件)
#include <stdio.h>
int main() {
printf("Hello open source\n");
return 0;
}
但请不要误会,CMake 的用途不仅仅限于 C 和 C++。 它可以处理任意文件,并具有大量可用的命令,因此它可以帮助您维护多种不同形式的项目。
CMake 网站记录了所有有效的内置命令及其可用参数,因此无论您想做什么,都可以轻松找到您需要的功能。 这是一个简单的示例,但是您需要的下一个命令是必不可少的——您必须为 CMake 定义您正在构建的代码
add_executable(Hello hello.c)
这会将您的编译后的二进制文件的名称设置为 Hello
,因此在功能上,它与在终端中使用 -o Hello
运行 gcc
相同。
在一个复杂的项目中,您可能同时拥有库和可执行文件。 您可以使用 add_library
命令添加库。
在您设置了要构建并标记为安装的文件之后,您必须告诉 CMake 用户安装您的应用程序后,最终产品应该放在哪里。
在这个简单的示例中,只有一个东西被标记为安装,因此您只需要在 CMakeLists
中添加一个 install
行。 install
命令接受一些参数,但在这种情况下,所有必要的就是 TARGETS
参数,后跟要安装的文件名
install(TARGETS Hello)
向 CMake 项目添加文件
一个软件项目很少只向其用户交付代码。 通常还有一些额外的数据,例如手册页或信息页、示例项目或配置文件。 您可以使用类似于包含编译文件的工作流程,在 CMake 项目中包含任意数据:首先,将文件添加到 CMakeLists.txt
,然后描述如何安装它。
例如,要将名为 assets
的目录包含在您的示例应用程序中,您可以使用 file
命令,后跟 COPY
和 DESTINATION
参数来告诉 CMake 将您的附加文件复制到您的可分发包中
file(COPY assets DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
${CMAKE_CURRENT_BINARY_DIR}
是一个特殊的内置 CMake 变量,表示 CMake 当前正在处理的目录的路径。 换句话说,您的任意数据被复制到构建目录(在您运行 cmake
之后,这一点会更加清楚,所以请注意稍后再次出现这种情况)。
由于数据目录往往是拥挤的地方(如果您不相信我,请查看 /usr/share
),因此为了每个人的利益,您应该为自己的项目创建一个子目录,最好带有版本控制。 您可以通过在 CMAKE_CURRENT_BINARY_DIR
中指定一个新目录来完成此操作,使用您选择的项目名称,后跟一个以您的项目命名的特殊变量以及您在项目声明中设置的 VERSION
file(COPY assets DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Hello-${Hello_VERSION}")
定义安装位置
您已经定义了构建过程的文件,因此现在您必须告诉 CMake 在安装过程中将其放在哪里。 就像您的主可执行文件一样,这使用了 install
命令
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Hello-${Hello_VERSION}" TYPE DATA)
这里有一些新的参数。 DIRECTORY
参数将数据源标识为目录(而不是 FILE
或 SCRIPT
等)。 您正在使用与将数据文件复制到构建位置时使用的变量相同的变量。 此外,必须为 install
提供 TYPE
或 DESTINATION
(不能同时提供两者)。 TYPE
参数指定通用文件类型,该类型放置在适合目标系统的位置。 在 Linux 上,除非用户或打包人员定义了不同的数据位置,否则 TYPE DATA
目录通常放置在 /usr/local/share
或 /usr/share
中。
这是像 CMake 这样的优秀构建系统强大的地方之一。 您不必担心文件最终会放在哪里,因为您知道用户可以提醒 CMake 他们首选的默认值,并且 CMake 将构建代码以使其工作。
运行 CMake
CMake 有几个界面。 您可以从终端作为命令或交互式应用程序使用它,也可以使用其图形用户界面 (GUI) 前端。 我倾向于使用终端命令,但我同样喜欢其他用户体验(它们绝对比在 Makefiles 中搜索晦涩的变量来重新定义要好得多)。
对于任何构建过大量开源 C++ 项目的人来说,第一步是创建一个 build
目录,更改到该目录,然后运行 cmake ..
命令。 我是一个懒惰的打字员,所以我将我的构建目录命名为 b
,但您可以使用对您来说最有意义的任何名称
$ mkdir b
$ cd b
$ cmake ..
-- The C compiler identification is GNU 11.1.1
-- The CXX compiler identification is GNU 11.1.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /var/home/seth/demo-hello/b
$
这或多或少等同于经典 ./configure; make; make install
咒语中的 ./configure
。 查看您的构建目录会发现 CMake 生成了几个新文件来帮助您的项目组合在一起。 有一些 CMake 数据、一个常规 Makefile(对于免费的代码来说是 247 行,但对于复杂的项目来说要多得多)以及包含与此示例应用程序一起分发的任意非编译数据的 Hello-1.0
数据目录
$ ls
CMakeCache.txt
CMakeFiles
Makefile
Hello-1.0
cmake_install.cmake
接下来,您可以构建。 您可以使用 CMake 使用 --build
选项来执行此操作,使用当前构建目录作为源目录
$ cmake --build .
Scanning dependencies of target Hello
[ 50%] Building C object CMakeFiles/Hello.dir/hello.c.o
[100%] Linking C executable Hello
[100%] Built target Hello
或者您可以运行 make
命令。 这会读取 CMake 生成的 Makefile
。 在此示例中,Make 的默认操作是编译其目标 hello.c
$ make
Scanning dependencies of target Hello
[ 50%] Building C object CMakeFiles/Hello.dir/hello.c.o
[100%] Linking C executable Hello
[100%] Built target Hello
$
正如您可能期望的那样,Hello
二进制可执行文件现在存在于您的当前构建目录中。 因为它是一个简单的独立应用程序,所以您可以运行它以进行测试
$ ./Hello
Hello open source
$
最后,您可以使用 --install
选项进行安装。 因为我不希望我的简单“hello world”应用程序实际安装到我的系统上,所以我将 --prefix
选项设置为将 CMake 的目标从根目录 (/
) 重定向到 /tmp
中的子目录
$ cmake --install . --prefix /tmp/hello/
-- Install configuration: ""
-- Installing: /tmp/dist-hello/usr/local/bin/Hello
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0/assets/file0
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0/assets/file1
或者,您可以运行 make install
来调用 Makefile 的安装操作。 同样,为了避免在我的系统上安装演示应用程序,我在本示例中设置了 DESTDIR
变量,以将安装目标重定向到 /tmp
中的子目录
$ mkdir /tmp/dist-hello
$ make install DESTDIR=/tmp/dist-hello
[100%] Built target Hello
Install the project...
-- Install configuration: ""
-- Installing: /tmp/dist-hello/usr/local/bin/Hello
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0/assets/file0
-- Installing: /tmp/dist-hello/usr/local/share/Hello-1.0/assets/file1
输出确认了其操作,并且应用程序已安装。
快速自定义
CMake 的安装前缀 (CMAKE_INSTALL_PREFIX
变量) 默认为 /usr/local
,但是当您使用 -D
选项运行 cmake
时,可以自定义任何 CMake 变量
$ cmake -DCMAKE_INSTALL_PREFIX=/usr ..
$ make install DESTDIR=/tmp/dist-hello
$ make install DESTDIR=/tmp/dist-hello
[100%] Built target Hello
Install the project...
-- Install configuration: ""
-- Installing: /tmp/dist-hello/usr/bin/Hello
-- Installing: /tmp/dist-hello/usr/share/Hello-1.0
-- Installing: /tmp/dist-hello/usr/share/Hello-1.0/assets/file0
-- Installing: /tmp/dist-hello/usr/share/Hello-1.0/assets/file1
CMake 使用的任何变量都可以通过这种方式自定义。
交互式 CMake
CMake 的交互模式是配置安装环境的一种友好且有用的方法。 要求您的用户了解您的项目使用的所有可能的 CMake 变量有点过分,因此 CMake 交互式界面是他们发现自定义选项而无需查看 Makefiles 和 CMakeLists 的一种简便方法。
要调用交互式 CMake 会话,请使用 ccmake
命令。 对于这个简单的示例项目来说,没有什么可看的,但是像数字音频工作站 Rosegarden 这样的大型项目使用户界面非常宝贵。

(Seth Kenlon,CC BY-SA 4.0)
更多 CMake
CMake 还有很多很多内容。 作为一名开发人员,我喜欢 CMake 的简单语法和广泛的文档、可扩展性和便捷性。 作为用户,我欣赏 CMake 的友好和有用的错误消息和用户界面。 如果您的项目尚未使用构建系统,请查看 CMake。 您以及任何稍后尝试打包您的应用程序的人都不会后悔的。
3 条评论