能否更上一层楼?探究 CMake 争论

Blog
Author:
Joseph SibonyJoseph Sibony
Published On:
3月 7, 2022
Estimated reading time:
1 minute

C++ 开发人员已经开发得很棒了,但是我们还记得不久之前它还并没有那么不同。C++ 程序员社区中最大的问题之一就是缺乏标准化,这意味着要使用许多不同的工具和流程来实现相同的目标,但往往会产生不同的结果和令人头疼的问题,而且复杂程度也各不相同。制定标准意味着有一个明确的做事方式。但是,如果标准存在争议(至少可以这么说),那么标准化还是一件好事吗?

这个对话围绕 CMake,已有一段时间。可以预见的是,针对这个话题的观点是两极分化的。因此,有些人引用恐怖作家 H.P.Lovecraft 的文章,来描述他们使用 CMake 的经历(就像在这本很好的教程中一样),这也减少了其他人发布这样的模因:

和:

而且,是的,看到图片你明白了。至少可以说,CMake C++ 社区中的一些人感到沮丧。但即便如此,它仍是 C++ 体验的关键部分,那么,我们来深入探讨一下为什么 CMake 如此普遍,以及为什么它在开发人员中仍有争议。

在我们开始之前,先来回顾一下历史

在过去,大多数使用涉及编译多个文件的库或可执行文件的项目都使用 Make。大部分 Unix 系统都捆绑了某些版本的 Make,因此可以很容易地创建 Makefile,定义它们的起始目标,并让 Make 处理其余的事情。

对于程序员来说,只要有一个源代码和一个 Makefile,就可以很简单地输入:

make && make install  

不管成功亦或失败,都很容易处理,因为这个系统是为程序员设计的,也是由程序员设计的,所以它(大部分)运行完美无暇。

当程序员想要跨多个系统编译同一个源文件时,问题就出现了。为了有效地执行此操作,必须单独配置 Makefile。足智多谋的程序员只是简单地将软件工程的基本定理应用于这个问题,并使用 Makefile.in 为该问题添加了一个新的间接层。

这个新层意味着配置脚本是创建特定于每个系统的 Makefile 的脚本。现在,程序员只需输入:

./configure && make && make install 

那问题是?间接导致复杂,这并不总是件好事情。当配置脚本比实际编写的程序大时会发生什么?对一些人来说,解决办法是增加更多间接。但是当间接变得太多时,这个答案又导致了新的问题。这个新发现的复杂性问题意味着,即使是编译短程序也成了一场噩梦,而创建合适的构建系统也成了一件痛苦的事情。因此,CMake 似乎就成了一个受欢迎的解决方案。

深入研究 CMake 争论

CMake 最初是由 Kitware 设计的一个构建系统生成器,可以为 UnixVisual Studio XCode 环境生成 Makefile。它之所以能做到这一点,得益于其单一的 CMakeLists.txt 文件。

使用 CMake 可将项目或库的创建分为两个步骤。首先,使用 CMakeLists.txt 创建标准构建文件。接下来,使用平台的原生工具链实际构建项目。在不拘泥于细节的前提下,这意味着 CMake 本质上是在更大的系统中将 Make 创建为构建系统,并避免了完全依赖 Make 带来的麻烦。这使项目不受传统构建工件的影响,并减少了来自对象文件和其他类似遗留文件的代码污染。这一切听起来都很棒,对吧? 那么,为什么 CMake 仍有争议呢?

好的方面

也许 CMake 提供的最大好处是其宽松的 BSD 许可,该许可允许任何人将其用于构建系统,同时避免供应商锁定。这也意味着微软等公司仍然可以将它与其旗舰 IDE 捆绑在一起。其开源性质的另一个好处是,CMake 在过去 20 年中一直处于持续发展中,在此期间,除了 C++ 之外,它还增加了对大量其他语言的支持。

CMake 还是跨平台的。无论你使用的是什么操作系统(LinuxMacOSWindows),都不需要任何特定配置。你可以跳过 MakefileVisual Studio 项目设置和批处理文件。CMake 会自动处理一切。

你还可以使用 CMake 执行基本的文件操作。当你确实需要与文件系统交互时,CMake 提供了一系列文件命令,让你能够快速有效地处理从读取到复制和传输文件的所有事情。

另一个关键因素(以及和 Make 等以前工具相比,一个重大改进)是 CMake 不再需要你进入命令 shell 来操作它。相反,新版本包括一个可用于所有受支持平台的GUI。这样你就可以在生成构建之前执行诸如配置构建之类的操作,甚至可以使用 Jenkins Azure 管道甚至是 git 工作流等 CI/CD 工具。这使得 CMake 更有可能被采用,并使其更容易在更广泛的开发堆栈中使用。

我们还要提及, CMake 的另一个好处是能够使用缓存来减少运行时的解析时间。平台会创建一个 CMakeCache.txt 文件,每当你重新运行项目时都会读取该文件,并减少加载时间。然而,这也是一个完美的例子,说明了一些开发人员对 CMake 不太感兴趣的原因。那么现在,我们来看看它的另一面。

不太好的方面

我们先聊聊刚才提到的缓存例子吧。对于大多数初学者来说,这种缓存机制会产生问题。通过命令行传递变量时,它会存储在缓存中。在以后使用 CMake 运行时,无论何时访问该变量,都会得到存储在缓存中的值,而系统会忽略通过命令行传递的新变量。为了解决这一问题,我们必须明确将其设为未定义:

cmake -U <先前定义的变量> -D <先前定义的变量>[=新值

很明显,这不是什么大问题,但它确实突显了许多开发人员在使用 CMake 时遇到的一些不便。

另一个常见的怨言(这是主观的,因为它与偏好有关)是 CMake 使用的语法有时不直观。在某些情况下,问题在于条件语句可能看起来更像函数调用;

if(condition)else()endif() 

社区板块和子版块对语法也有类似的怨言,有的甚至抱怨一些简单的事情(比如,设置 (x, “a:b:c”) 应该是一个字符串,但实际上是一个字符串列表)。公正地说,这主要是因为没有一种标准的做事方式,并且必须了解哪种具体方法最有效。

在可用性方面,有人抱怨 CMake 版本之间的向后兼容性。也就是说,问题的核心在于,由于其构建方式,CMake 包含了很多不一定需要并且可能会对项目文件产生负面影响的部件和工件。这其中包括可能导致程序中断,但由于 CMake 的突出而无法删除的(错误)标准方法。

与此密切相关的是与使用 CMake 有关的陡峭学习曲线。由于该语言的发展和扩展方式,积累了大量的脚本,这些脚本虽然在高度特定的情况下有用,但往往会影响代码的整洁,并使其难以与大型项目集成。

结论(?)

对于是否应该使用 CMake,没有真正的答案。请记住,以上内容并不是该语言的完整优劣表。事实上,CMake 变得流行是有充分理由的,尽管有很多抱怨,但它仍然是 C++ 开发人员使用最广泛的工具之一。

事实上,你需要深入了解它(及其历史),才能知道它是否适合你的项目。在很多情况下,你会发现它非常有效——尤其是在你遇到端到端项目的情况下,这使得控制环境和管理的依赖关系变得更加容易。

但是,你需要小心并了解你需要(和不需要)什么,并确保你没有将不良做法和不良代码合并到现有 CMake 文件中,以避免出现问题。你还可以使用我们上面链接的优秀教程来帮你的第一个 CMake 项目导航。无论哪种情况,都值得深入了解它的工作原理及其优势,以充分利用你的 CMake 构建。