为什么要使用构建系统?

报告问题 查看来源 Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

本页面讨论了什么是 build 系统、build 系统的作用、为什么要使用 build 系统,以及为什么当组织开始扩大规模时,编译器和 build 脚本不是最佳选择。它适用于对 build 系统不太熟悉的开发者。

什么是 build 系统?

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

我不能只使用编译器吗?

您可能不会立即意识到需要构建系统。大多数工程师在学习编码时不会使用构建系统:他们大多会先直接从命令行调用 gccjavac 等工具,或者在集成开发环境 (IDE) 中调用等效工具。只要所有源代码都在同一目录中,这样的命令就可以正常运行:

javac *.java

此命令指示 Java 编译器获取当前目录中的每个 Java 源文件,并将其转换为二进制类文件。在最简单的情况下,您只需要使用此功能即可。

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

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

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

Shell 脚本呢?

假设您的业余项目最初非常简单,您只需使用编译器即可构建它,但您开始遇到之前描述的一些问题。也许您仍然认为自己不需要构建系统,并且可以使用一些简单的 shell 脚本来自动化处理繁琐的部分,这些脚本可以按正确的顺序构建内容。这在一段时间内很有帮助,但很快您就会遇到更多问题:

  • 这会让人感到厌烦。随着系统变得越来越复杂,您开始花费几乎与实际代码一样多的时间来处理 build 脚本。调试 shell 脚本非常痛苦,而且越来越多的 hack 相互叠加。

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

  • 好消息:是时候发布了!最好弄清楚需要传递给 jar 命令的所有实参,以便进行最终 build。并记住如何上传并将其推送到中央代码库。并构建和推送文档更新,以及向用户发送通知。嗯,也许需要另一个脚本...

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

  • 尽管存在这些问题,但您的项目非常成功,因此您可以开始聘用更多工程师。现在,您意识到,即使没有灾难,之前的问题也会出现,因为每当有新开发者加入您的团队时,您都需要经历同样痛苦的引导过程。尽管您尽了最大努力,但每个人的系统仍存在细微差异。通常情况下,在某人的机器上可行的操作在另一人的机器上却不可行,每次都需要花费数小时来调试工具路径或库版本,才能找出差异所在。

  • 您决定需要自动化构建系统。从理论上讲,这很简单,只需购买一台新电脑,然后设置该电脑每晚使用 cron 运行您的 build 脚本即可。您仍然需要完成繁琐的设置流程,但现在无法再借助人脑来检测和解决小问题。现在,每天早上上班时,您都会看到昨晚的 build 失败了,因为昨天有位开发者做了一项更改,该更改在其系统上有效,但在自动化 build 系统上无效。每次都是简单的修复,但这种情况太频繁了,以至于您每天都要花费大量时间来发现和应用这些简单的修复。

  • 随着项目规模的扩大,构建速度会越来越慢。有一天,您在等待 build 完成时,悲伤地凝视着正在休假的同事的空闲桌面,希望能够利用所有这些浪费的计算能力。

您遇到了一个典型的规模问题。对于最多只编写几百行代码、工作时间最多只有一两周的单个开发者(这可能是刚从大学毕业的初级开发者迄今为止的全部经验),编译器就足够了。脚本或许能让您走得更远。但是,一旦您需要协调多位开发者及其机器,即使是完美的 build 脚本也不够,因为很难考虑到这些机器的细微差异。此时,这种简单的方法会失效,需要投资于真正的构建系统。