放弃 Autotools,选择 CMake

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 步骤中,构建的文件被复制到计算机上相应的 locations(在 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)。
这种方法对于额外的、顶级的构建脚本(例如 Python/Bash 脚本,使用项目的所有正确参数/定义调用 CMake)或在将外部打包/依赖项管理系统(如 Conan)集成到项目中时特别有用。

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

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