本页介绍了什么是构建系统、构建系统的作用、为什么要使用 构建系统,以及为什么当您的 组织开始扩容时,编译器和构建脚本不是最佳选择。本页面向对构建系统没有太多 经验的开发者。
什么是构建系统?
从根本上讲,所有构建系统都有一个简单的目的:将 工程师编写的源代码转换为机器可读取的 可执行二进制文件。构建系统不仅适用于人工编写的代码,还允许 机器自动创建 build,无论是用于测试还是用于发布到 生产环境。在拥有数千名工程师的组织中,大多数 build 通常由机器自动触发,而不是由工程师直接触发。
我不能只使用编译器吗?
您可能不会立即意识到需要构建系统。大多数工程师
在学习编码时都不会使用构建系统:大多数人都是从命令行直接调用 gcc 或 javac 等工具,或者在
集成开发环境 (IDE) 中调用等效工具。只要所有源代码都在
同一目录中,这样的命令就可以正常运行:
javac *.java这会指示 Java 编译器获取当前 目录中的每个 Java 源文件,并将其转换为二进制类文件。在最简单的情况下,您只需要这样做 。
但是,一旦代码扩展,问题就会开始出现。javac 非常智能
,可以查找当前目录的子目录以查找要导入的代码
。但它无法找到存储在文件系统的其他部分
(可能是多个项目共享的库)中的代码。它也只知道
如何构建 Java 代码。大型系统通常涉及用
各种编程语言编写的不同部分,这些部分之间存在复杂的依赖关系,
这意味着没有一种语言的编译器可以构建整个系统。
一旦您处理来自多种语言或多个编译 单元的代码,构建代码就不再是一步完成的过程。现在,您必须评估代码依赖于什么 ,并按正确的顺序构建这些部分,可能需要为每个部分使用不同的工具集 。如果任何依赖项发生更改,您必须 重复此过程,以避免依赖过时的二进制文件。对于即使是中等大小的代码库,此过程也会很快变得繁琐且容易出错。
编译器也不知道如何处理外部
依赖项,例如 Java 中的第三方 JAR 文件。如果没有构建系统,
您可以通过从互联网下载依赖项、将其放入硬盘上的lib文件夹中,并配置编译器以从该目录读取
库来管理此问题。随着时间的推移,很难维护这些外部依赖项的
更新、版本和来源。
那么 Shell 脚本呢?
假设您的业余项目一开始很简单,您只需使用编译器即可构建它 ,但您开始遇到之前描述的一些问题 。也许您仍然认为不需要构建系统,并且可以使用一些简单的 Shell 脚本来自动完成繁琐的部分,这些脚本可以按正确的顺序构建内容。这在一段时间内很有帮助,但很快您就会遇到更多问题:
它变得很繁琐。随着系统的复杂性不断提高,您开始花费 几乎与编写实际代码一样多的时间来处理构建脚本。调试 Shell 脚本非常痛苦,并且会不断添加越来越多的 hack。
它很慢。为了确保您没有意外依赖过时的库, 您每次运行构建脚本时,都会让它按顺序构建每个依赖项。您考虑添加一些逻辑来检测哪些部分需要 重建,但对于脚本来说,这听起来非常复杂且容易出错。或者 您考虑每次都指定哪些部分需要重建,但 这样您又回到了原点。
好消息:该发布版本了!最好弄清楚需要传递给 jar 命令的所有参数 以便进行最终构建。并记住 如何上传它并将其推送到中央代码库。并构建和 推送文档更新,并向用户发送通知。嗯, 也许这需要另一个脚本...
灾难!您的硬盘崩溃了,现在您需要重新创建整个 系统。您很聪明,将所有源文件都保留在版本 控制中,但您下载的那些库呢?您能再次找到它们 ,并确保它们与您首次 下载时的版本相同吗?您的脚本可能依赖于在特定位置安装的特定工具 ,您能恢复相同的环境,以便 脚本再次正常运行吗?您很久以前设置的所有那些环境变量呢?您设置这些变量是为了让编译器正常运行,但后来却忘记了它们。
尽管存在这些问题,但您的项目非常成功,因此您可以开始聘用更多工程师。 现在您意识到,不需要发生灾难 之前的问题也会出现:每当有新开发者加入您的团队时,您都需要经历同样痛苦的 引导过程。尽管您尽了最大的努力,但每个人的系统仍然存在细微的差异。通常,在某人的机器上正常运行的内容在另一个人的机器上无法正常运行,并且每次都需要花费几个小时来调试工具路径或库版本,才能找出差异所在。
您决定需要自动执行构建系统。从理论上讲,这就像购买一台新计算机,并将其设置为每晚使用 cron 运行构建脚本一样简单。您仍然需要经历痛苦的 设置过程,但现在您无法获得人脑能够 检测和解决小问题的优势。现在,您每天早上上班时都会看到,昨晚的 build 失败了,因为昨天一位开发者 做出的更改在其系统上正常运行,但在自动构建系统上无法正常运行。每次都是简单的修复,但这种情况经常发生,因此您 每天最终会花费大量时间来发现和应用这些简单的 修复。
随着项目的增长,build 变得越来越慢。有一天,在等待 build 完成时,您悲伤地凝视着正在休假的同事的空闲桌面,并希望有一种方法可以利用所有浪费的计算能力。
您遇到了一个典型的规模问题。对于最多在一两周内处理最多几百行代码的单个开发者(这可能是刚从大学毕业的初级开发者迄今为止的全部经验),您只需要一个编译器。脚本或许可以帮助您更进一步 。但是,一旦您需要协调多个开发者及其 机器,即使是完美的构建脚本也不够,因为很难考虑这些机器的细微差异。此时, 这种简单的方法会失效,您需要投资一个真正的构建系统。