5.1 CMake的历史和需求 - Softarchitecture/CMake GitHub Wiki

在开始开发CMake时,项目管理常见的做法是,对于Unix平台使用configure脚本和Makefile文件,对于Windows平台使用Visual Studio工程文件。这种构建系统的二重性使得跨平台开发对许多项目来说变得非常乏味:即使是简单地在一个项目中添加新的源文件都是痛苦的。对开发者而言,显而易见的目标就是拥有一个统一软件构建系统。CMake的开发者们有着通过两种方法来解决这个问题的经验。

一种方法是1999年开发的VTK构建系统。在这个系统中,Unix系统下使用configure脚本,而在Window系统中使用一个叫做pcmaker的可执行文件。pcmaker是一个C程序,它通过解析Unix Makefile文件来生成Windows下的NMake文件。 pcmaker的二进制可执行文件被签入了VTK 的CVS系统仓库中。类似于添加新库这样的几种常见情况需要修改源码,再更新系统仓库的可执行文件。尽管这从某种程度上讲是一个统一的构建系统,但它仍有许多缺点。

开发者们采用过的另一种方法是为TargetJr开发的基于gmake的构建系统。TargetJr是用C++编写的计算机可视化环境,最初在Sun工作站上开发。一开始,TargetJr使用imake构建系统来创建Makefile文件。然而为了满足有些时候Window下的需要,便开发出了gmake构建系统。gmake构建系统同时支持Unix编译器和Windows编译器。在运行gmake之前系统需要设置一些环境变量。没有正确的环境将导致系统产生一些难以调试的错误,特别是终端用户。

这两种方法都有一个严重的缺陷: 它们要求Windows开发者使用命令行。然而,熟练的Windows开发者更倾向于使用集成开发环境(IDE),他们还是会选择手动生成IDE文件然后添加到工程中去,相当于又产生了双构建系统。除了缺乏对IDE的支持,上述两种方法也使得合并软件的项目变得极其困难。比如,VTK中罕有图片加载模块,主要是因为它的构建系统非常难以利用类似libtiff和libjpeg的第三方库。

因此,为了ITK和一般的C++软件,需要开发一个新的软件构建系统。 这个新构建系统必须满足的基本限制条件如下:

  • 唯一的依赖平台: 安装了C++编译器的操作系统
  • 能够生成Visual Studio IDE输入文件
  • 易于创建基本的构建系统的目标文件,包括静态库,共享库/*动态?*/,可执行文件,插件
  • 能够运行构建时的代码生成器
  • 支持源码树和构建树的分离
  • 能够执行系统"自省"(introspection),即能够自动判断目标系统能够做什么和不能够做什么
  • 能够自动扫描C/C++头文件的依赖关系
  • 所有特性对所支持的平台一视同仁
为了避免依赖于第三方软件库和语法分析器,CMake在设计时只考虑了一个主要的依赖:C++编译器(当要构建的是C++代码时,我们可以放心地假设系统中已经安装好C++编译器)。当时,在许多流行的UNIX和Windows操作系统上构建和安装Tcl之类的脚本语言是非常困难的。即便到如今,给超级计算机和没联网的安全计算机安装软件也还是个问题,所以编译第三方软件库一直都是比较困难的。由于软件构建系统是一个基本工具,因此CMake的设计不应再引入其它的依赖关系。这确实限制了CMake提供自己的简单的语言,导致至今仍有人不喜欢CMake。然而,当时最流行的嵌入式语言是Tcl。如果CMake是基于Tcl语言的构建系统,那么它大概不会达到今天这样的流行程度。

生成IDE工程文件的能力是CMake的重要卖点,但这也限制了CMake只能提供本地IDE支持之外的特性。不过,支持本地IDE工程文件的好处完全能弥补它的局限性。尽管这个决定使得CMake的发展变得困难,却令ITK和其他使用CMake的项目的开发更为容易。因为开发者使用自己熟悉的工具,不仅更快乐,效率也更高。允许开发者选择自己偏爱的工具, 项目就能充分利用它最宝贵的资源,即开发者。

所有的C/C++程序都需要以下的一个或多个软件的基本构建单元:可执行文件,静态库,共享库和插件。 CMake必须具备在所有支持的平台上生成这些结果的能力。 虽然所有的平台上都支持生成这些结果, 但不同的平台和不同的编译器会导致编译器选项变化很大。 CMake将实现过程中的复杂性和差异性掩盖在一条条简单的命令之下,从而开发者能够同时在Windows, Unix和Mac上创建这些目标的本地版本。这样,开发人员得以专心于工程本身,而不是在如何编译一个这样的细节上纠结。

代码生成器为构建系统增加了额外的复杂性。最开始,VTK提供了一个系统,它可以通过解析C++头文件,自动地将C++代码封装成Tcl,Python和Java代码,然后自动地生成一个封装层。这要求构建系统先生成一个C/C++程序(封装生成器),然后在编译时运行此程序以生成更多的C/C++源码(特定模块的封装代码)。生成的源码接着将被编译成可执行文件或动态链接库。所有这些过程必须在IDE环境和生成的Makefile中实现。 当开发灵活的跨平台C/C++软件时,很重要的一点是面向功能编程,而不是面向特定的平台。autotool工具支持系统"自省"(introspection),即通过编译少量的代码来检查并存储编译的结果。由于跨平台的需要,CMake也采用了类似系统自省的技术,使得开发者只需针对标准平台编码,而不需要考虑特定的平台。随着编译器和操作系统不断地变化,这个策略对于代码的可移植性非常重要。比如,下面的代码:

    #ifdef linux  
    // do some linux stuff  
    #endif 

对比如下,就显得更脆弱:

    #ifdef HAS_FEATURE  
    // do something with a feature  
    #endif  

另一个CMake早期的需求也来自于autotool: 生成与源码树分开的构建树的能力。这个使从同一个源码树得到的构建类型多样化,同时防止源码树与构建文件之间经常混淆了版本控制系统的冲突。

构建系统一个最更要的功能是对依赖关系的管理能力。如果一个源码文件发生变化,那么所有使用了这个源码文件的生成结果都必须重新构建。对于C/C++代码,被.c和.cpp文件包含的头文件也需要检查部分依赖关系。如果依赖关系理解错误,只有部分修改的代码有可能导致全部重新编译,从而浪费大量时间。

这个新构建系统的所有需求和功能都必须对所有支持的平台一视同仁。CMake需要为开发者提供一个简单的API,以便于在不用了解平台细节的情况下就可以创建复杂的软件系统。事实上,使用CMake的软件只不过是把构建复杂性转移给了CMake开发组。一旦构建工具的愿景随着基本需求而产生,实现的过程则需要灵活的方式。ITK几乎是从第一天开始就需要一个构建系统。而第一个版本的CMake并没有满足陈述在愿景中的所有需求,但是他们已经能够在Windows和Unix下构建软件。

⚠️ **GitHub.com Fallback** ⚠️