背景
最近一直在做编译优化相关的工作,在工作的过程中才发现自己一直都忽略了一个重要东西,不同于自己写的简单项目,一个成熟的项目都是基于构建系统构建的,如果不了解构建系统也就很难做出什么优化,在自己学习之后也希望通过一些文章将这些内容沉淀下来。希望把这些内容做成一个系列吧,首先先宏观了解构建系统的基本概念,然后再介绍一下常用的构建系统如cmake,gradle和bazel。
什么是构建系统
那么什么是构建系统呢?其可以分为构建和系统,对于系统的定义如下:
系统是由相互作用相互依赖的若干组成部分结合而成的,具有特定功能的有机整体,而且这个有机整体又是它从属的更大系统的组成部分。
那么我们又该如何理解构建呢?
与构建相关联的是编译,编译是将一个源文件转换成一个二进制文件,而构建就是对于编译的安排,在一个大的工程中包含很多源文件,其中可能还包含这复杂的依赖关系,构建就是对于多个编译的合理安排。
构建系统:拥有安排多个编译功能的一个有机整体。
为什么需要构建系统
大多数工程师在学习编程时都是使用非常简单的例子,例子可能就只有一个源文件,所以大多数工程师开始都是直接调用gcc或javac等工具,或者使用IDE中提供的便捷的编译工具,例如以下例子就是将同一目录的源代码转化为二进制文件。
javac *.java
javac非常的智能,可以在当前目录的子目录中查找要导入的代码。但是它找不到文件系统的其他部分中存储的代码(或许是由多个项目共享的库)。它还只知道如何构建Java 代码。大型系统通常涉及使用各种编程语言编写的不同部分,并且这些部分之间具有网络,这意味着单一编译器无法构建整个系统。
当工程日趋复杂,一个简单的编译命令无法满足要求而需要多个编译命令的组合,这时候可能会想想到使用shell脚本来组织编译命令,这些脚本会按正确的顺序构建应用,但是随着工程的进一步膨胀,shell也显的力不从心,会遇到很多问题:
- 构建变得很繁琐。随着系统变得越来越复杂,您在构建脚本上花费的时间几乎与实际代码一样。调试 shell 脚本非常痛苦,并且越来越多的黑客手段层层叠加。
- 很慢。为了确保您不会意外依赖过时的库,需要让构建脚本在每次运行时按顺序构建每个依赖项。所以需要考虑添加一些逻辑来检测哪些部分需要重新构建,但这听起来非常复杂,并且对于脚本来说很容易出错,脚本也会越来越复杂,难以管理。
所以我自己的理解是从最初的gcc,shell到现在完善的构建系统,都是对于编译过程进一步的抽象,
加了一层抽象让编译与构建更加易于理解和维护,底层做的事情是一样的,只是让人更好理解和维护。
构建系统的分类
基于任务的构建系统
在基于任务的构建系统中,基本的工作单元是任务。每个任务都是可以执行任何类型的逻辑的脚本,这些任务会将其他任务指定为必须在其之前运行的依赖项。目前使用的大部分主要构建系统(例如 Ant、Maven、Gradle、Grunt 和 Rake)都是基于任务的。大多数现代构建系统都要求工程师创建描述文件执行方式的构建文件&