为什么要使用构建系统?

报告问题 查看源代码

本页介绍了什么是构建系统、它们的作用、为什么您应该使用构建系统,以及为什么随着您的组织开始扩展,编译器和构建脚本并不是最佳选择。适用于在构建系统方面没有太多经验的开发者。

什么是构建系统?

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

我不能只使用编译器吗?

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

javac *.java

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

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

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

此外,编译器对如何处理外部依赖项(例如 Java 中的第三方 JAR 文件)一无所知。如果没有构建系统,您可以通过从互联网下载依赖项并将其保存在硬盘上的 lib 文件夹中,并将编译器配置为从该目录中读取库,来解决此问题。随着时间的推移,维护这些外部依赖项的更新、版本和来源将变得困难。

Shell 脚本呢?

假设您的爱好项目一开始就非常简单,只需使用编译器即可构建,但您开始遇到上述一些问题。也许您仍然认为自己不需要构建系统,并且可以使用一些简单的 Shell 脚本来自动处理繁琐的部分,这些脚本负责按正确的顺序进行构建。这会有所帮助,但很快就会开始遇到更多问题:

  • 变得单调乏味。随着系统变得越来越复杂,您在构建脚本上花费的时间与编写实际代码的时间差不多。调试 shell 脚本非常麻烦,越来越多的黑客手段相互依赖。

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

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

  • 惨了!如果您的硬盘崩溃,现在您需要重新创建整个系统。您足够聪明了,能够将所有源文件控制在版本控制中,但是您下载的库呢?您能否再次找到所有这些步骤,并确保它们的版本与您首次下载时的版本相同?您的脚本可能取决于在特定位置安装的特定工具,您能否恢复相同的环境,以便这些脚本重新正常运行?您很久之前为使编译器正常运行而后忘记的那些环境变量如何?

  • 尽管存在一些问题,但您的项目已经非常成功,您可以开始招聘更多工程师。现在,您已意识到,之前的问题并不难事,每次有新开发者加入您的团队时,您都需要经历同样痛苦的引导过程。即使您已尽最大努力,每个人的系统也仍存在细微差异。通常情况下,适合一个人计算机的内容并不一定适合另一个人,并且每次需要花几个小时调试工具路径或库版本才能找出存在差异之处。

  • 您决定需要实现构建系统的自动化。从理论上讲,这很简单,只需获取一台新计算机,然后将其设置为每晚使用 Cron 运行构建脚本即可。您仍然需要经历痛苦的设置过程,但现在您无法受益于人脑检测和解决一些小问题。现在,每天早上,您都会看到昨晚的构建失败了,因为昨天有一位开发者进行了一项更改,这项更改在他们的系统上有效,但在自动化构建系统上无效。每次都是一个简单的修复,但问题非常频繁,最终您每天都要花费大量时间来发现和应用这些简单的修复。

  • 随着项目的扩大,构建速度会逐渐变慢。有一天,在等待构建完成时,您悲伤地凝视着正在休假的同事的空闲桌面,并希望有办法利用所有浪费的计算能力。

您遇到了一个典型的规模问题。如果单个开发者在最多一两周的时间里编写了最多几百行代码(这可能是刚从大学毕业的初级开发者的全部体验),那么就只需要一个编译器。脚本也许能让您更进一步但是,当您需要在多个开发者及其机器之间进行协调时,即使是完美的构建脚本也是不够的,因为要解释这些机器之间的细微差异就会变得非常困难。至此,这种简单的方法就打破了常规,是时候投资一个真实的构建系统了。