为什么要使用构建系统?

报告问题 查看源代码 每夜 build · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

本页介绍了构建系统的定义、用途、使用原因,以及在组织开始扩大规模时,为什么编译器和构建脚本不是最佳选择。适用于没有太多构建系统经验的开发者。

什么是构建系统?

从根本上讲,所有构建系统都有一个简单明了的用途:将工程师编写的源代码转换为可供机器读取的可执行二进制文件。构建系统不仅适用于由人编写的代码;它们还允许机器自动创建 build,无论是用于测试还是用于发布到生产环境。在拥有数千名工程师的组织中,大多数 build 通常由系统自动触发,而不是由工程师直接触发。

我不能直接使用编译器吗?

构建系统的需求可能并不明显。大多数工程师在学习编码时不会使用构建系统:大多数人开始时会直接从命令行调用 gccjavac 等工具,或在集成开发环境 (IDE) 中调用等效工具。只要所有源代码都在同一目录中,如下所示的命令就可以正常运行:

javac *.java

这会指示 Java 编译器将当前目录中的每个 Java 源文件转换为二进制类文件。在最简单的情况下,只需执行这些操作即可。

不过,代码一旦扩展,复杂性就会随之而来。javac 足够智能,可以查看当前目录的子目录,以查找要导入的代码。但它无法找到存储在文件系统其他部分(可能是多个项目共享的库)中的代码。它也只知道如何构建 Java 代码。大型系统通常涉及使用各种编程语言编写的不同部分,这些部分之间存在复杂的依赖关系网,这意味着任何单一语言的编译器都无法构建整个系统。

一旦您要处理多种语言或多个编译单元的代码,构建代码就不再是一项单步流程。现在,您必须评估代码依赖于哪些部分,并按照正确的顺序构建这些部分,可能需要为每个部分使用一组不同的工具。如果任何依赖项发生变化,您必须重复此过程,以免依赖于过时的二进制文件。对于中等大小的代码库,此过程很快就会变得繁琐且容易出错。

此外,编译器也不知道如何处理外部依赖项,例如 Java 中的第三方 JAR 文件。如果没有构建系统,您可以通过以下方式进行管理:从互联网下载依赖项,将其粘贴到硬盘上的 lib 文件夹中,并将编译器配置为从该目录读取库。随着时间的推移,这些外部依赖项的更新、版本和来源难以维护。

Shell 脚本怎么样?

假设您的业余爱好项目一开始足够简单,您只需使用编译器即可构建它,但您开始遇到之前所述的一些问题。也许您仍然认为自己不需要构建系统,并且可以使用一些简单的 shell 脚本来自动执行繁琐的部分,这些脚本会按照正确的顺序处理构建事宜。这在短期内会有所帮助,但很快您就会遇到更多问题:

  • 这会很繁琐。随着系统变得越来越复杂,您开始花费与处理真实代码一样多的时间来处理构建脚本。调试 shell 脚本很麻烦,因为越来越多的黑客会层层叠加。

  • 速度很慢。为确保您不会意外依赖于过时库,您可以让 build 脚本在每次运行时按顺序构建每个依赖项。您考虑添加一些逻辑来检测哪些部分需要重新构建,但这对于脚本来说听起来非常复杂且容易出错。或者,您考虑指定每次需要重新构建哪些部分,但又回到了原点。

  • 好消息:现在可以发布了!最好先确定您需要传递给 jar 命令的所有参数,以便生成最终 build。并记住如何上传该文件并将其推送到中央存储库。并构建和推送文档更新,以及向用户发送通知。嗯,可能需要另一个脚本来解决这个问题...

  • 糟糕!您的硬盘崩溃了,现在您需要重新创建整个系统。您很聪明,将所有源文件都纳入了版本控制,但下载的库呢?您能否重新找到它们,并确保它们的版本与首次下载时相同?您的脚本可能依赖于在特定位置安装特定工具 - 您能否恢复相同的环境,以便脚本再次正常运行?您很久以前为了让编译器正常运行而设置的所有环境变量,后来却忘记了,那该怎么办?

  • 尽管存在问题,但您的项目非常成功,您可以开始招聘更多工程师了。现在,您已经意识到,之前出现的问题并非灾难性的,因为每当有新开发者加入您的团队时,您都需要经历同样痛苦的引导流程。尽管您已尽最大努力,但每个用户的系统之间仍会存在细微差异。通常,某个用户的机器上可正常运行的代码在另一个用户的机器上无法正常运行,而且每次都需要花费数小时来调试工具路径或库版本,才能找出差异所在。

  • 您决定需要自动化构建系统。从理论上讲,只需购买一台新电脑并将其设置为使用 cron 每天晚上运行构建脚本,即可轻松实现此目的。您仍然需要经历痛苦的设置流程,但现在您无法再利用人脑来检测和解决小问题。现在,每天早上上班时,您都会发现昨晚的 build 失败了,因为昨天有位开发者进行了一项更改,该更改在其系统上有效,但在自动化 build 系统上无效。每次都是简单的修复,但问题出现的频率非常高,以至于您每天都要花费大量时间来发现和应用这些简单的修复。

  • 随着项目的增长,构建速度会越来越慢。某天,在等待 build 完成时,您悲哀地看着正在休假的同事闲置的桌面,并希望能找到一种方法来利用所有浪费的计算能力。

您遇到了典型的规模问题。对于单个开发者,如果他只需要处理最多几百行代码,并且最多只需要一两周的时间(这可能就是刚刚从大学毕业的初级开发者的全部经验),那么只需使用编译器即可。脚本或许可以帮您更进一步。但是,一旦您需要协调多个开发者及其机器,即使是完美的构建脚本也不足以应付,因为很难考虑到这些机器之间的细微差异。此时,这种简单的方法就行不通了,您需要投资于真正的构建系统。