1.4 构建工具
- 当然你可能会问自己,为什么需要另外一个工具来实现项目自动化。用可执行脚本的方式完全可以写一些逻辑,例如shell脚本。回想一下我们之前讨论过的关于项目自动化的目的。你想要一个有一个工具,它能够帮助你创建一个可重复的、可靠地、便携的且不需要手动干预的构建。一个shell脚本可以不是那么容易就能从UNIX系统迁移到Windows系统上的,所以这样就无法满足你的需求了。
1.4.1 什么是构建工具
- 你所需要的是一个可编程的工具,它能够让你以可执行和有序的任务来表达自动化需求。假设你想要编译源代码,将生成的class文件拷贝到某个目录,然后将该目录组装成可交付软件。这个可交付软件可以是ZIP文件,比如,它可以被发布到某一个运行时环境中。图1.4展示了所描述场景中的任务和他们执行的顺序。
- 每个任务都代表着一个工作单元——例如,编译源代码。顺序是非常重要的。如果所需要的class文件没有被编译出来,那么你是不能创建ZIP文件的。因此,编译任务必须先被执行。
有向循环图
- 本质上,任务和它们的相互依赖被模块化成一个有向非循环图(DAG)。DAG是计算机科学里的一种数据结构,包含下面两个元素。
- 节点:一个工作单元;就构建工具而言,它指的是一个任务(例如,编译源代码)。
- 有向边:有向边也叫作箭头,表示节点之间的关系。在这里,箭头表示依赖关系。如果一个任务的定义依赖于另一个任务,那么所依赖的任务就必须先被执行。发生这种情况常常是因为一个任务依赖于另一个任务的输出。这里有个一例子————要执行任务“组装可交付软件”,你需要先执行任务“拷贝class文件到目录”和“编译源代码”。
- 每个节点都知道自己的执行状态。一个节点————表示一个任务————只能被执行一次。例如,如果两个不同的任务都依赖于任务“编译源代码”,那么你只会想要执行一次这个任务。图1.5以DAG图的形式展示了这种场景。
- 你也许已经注意到,在图1.4中,节点与任务显示的方向相反。这是因为顺序由点的依赖决定。作为一个开发人员,你没有必要与DAG图打交道。这个工作是由构建工具来完成的。在本章的后面,你会看到基于Java的构建工具是如何在实践中使用这些概念的。
构建工具的剖析
- 理解构建工具中组件的交互、构建逻辑的实际定义,以及输入和输出的数据是非常重要的。让我们一起探讨一下构建工具中每个元素以及它们的职责。
构建文件
- 构建文件包含了构建所需的配置信息、定义外部依赖,例如第三方类库,还包含了以任务形式实现某个特殊目的的指令和它们的相互依赖关系。图1.6展示了一个描述4个任务和它们之间相互依赖的构建文件。
- 在前面场景中我们讨论的任务————编译源代码、拷贝文件到目录以及组装ZIP文件————都可以定义在构建文件中。在通常情况下,会使用语言来表达逻辑。这就是为什么一个构建文件也叫作
构建脚本
的原因。
构建的输入和输出
- 一个任务会接收一个输入,然后执行一系列步骤,最后产生一个输出。某些任务不需要输入也不需要产生一个必要的输出。在复杂的任务依赖关系中,也许会使用一个依赖任务的输出作为输入。图1.7展示了再任务关系图中输入的消耗和输出的产生。
*下面我们给出的例子遵循这个流程。我们将源代码文件作为输入,将它们编译为class文件,并组装成可交付软件作为输出。编译和组装过程各表示一个任务,只有先编译了源代码,组装可交付软件才有意义。因此两个任务需要保证它们的顺序。
构建引擎
- 构建文件的一步步指令或者规则集必须被翻译成构建工具可以理解的内部模型。构建引擎会在运行时处理构建文件,解析任务之间的依赖,设置好执行所需的全部配置,如图1.8所示。
- 一旦内部模型建立好了,引擎就会按照正确的顺序去执行一系列任务。某些构建工具还允许你通过API去访问这个模型,以便在运行时获取构建信息。
依赖管理器
- 依赖管理器用于处理你在build文件中声名的依赖定义,从工件库(例如,本地文件系统、一个FTP或者HTTP服务器)中解析它们,并使它们对项目可用。
依赖
通常是指外部依赖,一种JAR文件形式的可重用类库(例如,Log4g对日志的支持)。该仓库
就像是依赖的储藏所,通过标识符合描述它们,例如名字和版本号。一个典型的仓库可以是HTTP服务器或者本地文件系统,图1.9展示了依赖管理器在构建工具架构中所处的地位。
- 许多类库还依赖于其他类库,这叫做
传递依赖
。依赖管理器可以通过存储在仓库中的元信息自动地解析传递依赖。但是一个构建工具并不要求提供这样的依赖管理组件。