为什么要使用构建系统?

本页面介绍了什么是构建系统、它们的用途、为什么应使用构建系统,以及随着您的组织开始扩大规模,为什么编译器和构建脚本不是最佳选择。它适用于在构建系统方面不太熟悉的开发者。

什么是构建系统?

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

不能直接使用编译器吗?

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

javac *.java

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

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

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

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

Shell 脚本怎么样?

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

  • 就会变得乏味。随着系统变得越来越复杂,您在构建脚本上花费的时间几乎与在真实代码上一样多。调试 shell 脚本非常困难,而且层层叠放的黑客手段也越来越多。

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

  • 好消息:您现在可以发布新版本了!最好先弄清楚需要传递给 jar 命令的所有参数,才能完成最终构建。并记住上传和推送至中央代码库的方法。构建并推送文档更新,并向用户发送通知。嗯,这可能需要其他脚本...

  • 惨了!您的硬盘会崩溃,现在需要重新创建整个系统。您足够聪明,可以对所有源文件进行版本控制,但是您下载的库呢?您能否重新找到所有这些内容,并确保它们的版本与首次下载的版本相同?您的脚本可能依赖于在特定位置安装的特定工具。您能否恢复相同的环境,以使这些脚本重新运行?您很久之前为使编译器能正常工作而后却忘记了自己设置的环境变量,该怎么办?

  • 尽管存在一些问题,但您的项目已经足够成功,您可以开始聘用更多工程师。现在,您意识到前面的问题不会造成灾难,每次有新开发者加入您的团队时,您都需要经历同样的艰难引导流程。尽管您尽了最大努力,但每个人的系统仍存在细微差异。很多时候,一个人机器上适用的方法并不适用于另一个人,并且每次都需要花费几个小时的调试工具路径或库版本才能找出差异所在。

  • 您决定需要自动执行构建系统。从理论上讲,这非常简单,只需获得一台新计算机并将其设置为每天晚上使用 cron 运行构建脚本即可。您仍然需要完成艰难的设置过程,但现在您将无法受益于人脑检测和解决小问题。现在,每天早上当您进来时,您都会看到昨晚的构建失败,因为昨天开发者做出了一项更改,该更改在他们的系统上有效,但对自动化构建系统无效。每次解决问题都很简单,但问题经常发生,您每天都要花大量时间来发现和应用这些简单的解决方法。

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

您遇到了规模化的典型问题。如果一个开发者需要最多编写几百行代码,且最多持续一两周(可能是刚刚毕业的初级开发者到目前为止所具备的全部经验),那么您只需使用编译器即可。脚本可能会让您做得更深入一些。但是,当您需要在多个开发者及其机器之间进行协调时,即使是完美的构建脚本是不够的,因为很难解释这些机器中的细微差异。至此,这种简单的方法就失效了,是时候投资一个真正的构建系统了。