使用 CMake 替代 Autotools

CMake 是一个跨平台套件,用于构建、测试和打包软件,即使您以前从未使用过构建系统,它也很容易使用。
100 位读者喜欢这篇文章。
GitHub launches Open Source Friday 

Opensource.com

在我的Autotools 介绍中,我演示了如何使用 GNU Autotools 管理代码的构建和打包。它是一个强大且通用的平台,可以轻松集成到许多打包系统中,包括 RPM、APT、pkgsrc 等。它的语法和结构可能令人困惑,但幸运的是,还有其他替代方案,包括开源的 CMake 框架。

CMake 是一个跨平台套件,用于构建、测试和打包软件。它使用简单且文档清晰的语法,因此即使您以前从未使用过构建系统,也很容易上手。

安装 CMake

您的 Linux 系统可能已经安装了 CMake。 如果没有,您可以使用发行版的软件包管理器安装它

$ sudo dnf install cmake

在 Debian 或类似系统上

$ sudo apt install cmake

对于 Mac,您可以使用 MacPortsHomebrew

$ sudo port install cmake

在 Windows 上,您可以使用 Chocolatey 或直接从 CMake 网站下载二进制文件。

CMake 的工作原理

对于想要从源代码构建软件的开发人员或用户来说,CMake 是一种快速简便的编译和安装方法。 CMake 分阶段工作

  1. 首先,在 cmake 步骤中,CMake 扫描主机系统(正在运行它的计算机)以发现默认设置。 默认设置包括支持库的位置以及新软件在系统上的放置位置。
  2. 接下来,您使用系统的 make 命令(通常是 Linux 上的 GNU Make,NetBSD 上的 NetBSD Make 等)来构建应用程序,通常是将人类可读的源代码转换为机器语言。
  3. 最后,在 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 命令,后跟 COPYDESTINATION 参数来告诉 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 参数将数据源标识为目录(而不是 FILESCRIPT 等)。 您正在使用与将数据文件复制到构建位置时使用的变量相同的变量。 此外,必须为 install 提供 TYPEDESTINATION(不能同时提供两者)。 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 这样的大型项目使用户界面非常宝贵。

更多 CMake

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

接下来阅读什么
标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。 他曾在电影和计算机行业工作,通常同时从事这两个行业。

3 条评论

写得好! 但我仍然建议通过 CMake 调用构建和安装,分别是 'cmake --build .' 和 'cmake --install .',而不是直接调用 'make' 和 'make install'。 这更具可移植性,因为它提供了统一的抽象命令,而与使用的生成器无关(例如 'Unix Makefiles' 与 Ninja)。
这种方法对于额外的顶级构建脚本(例如,使用所有正确的参数/定义为项目调用 CMake 的 Python/Bash 脚本)或在将外部打包/依赖项管理系统(如 Conan)集成到项目中时特别有用。

很棒的观点。 我没有想到增加的可移植性。
为了后人,我已经更新了文章,以演示 --build 和 --install 选项。

谢谢,Mariusz!

回复 作者 Mariusz Włodarczyk

我已经使用 autotools 超过 20 年了。 说服我转向 CMake 的原因是它将 750 行 automake/makefile/shell 代码减少到 250 行(包括来自领域特定语言的生成头文件,需要预构建步骤)。 学习一种语言并完成工作,而不是使用 3 种语言,并且不确定接口应该在哪里或您正在为哪种 shell 编写代码。

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.