构建系统#
构建系统介绍#
首先介绍一下 Make,Ant 这些构建系统, 对于理解 gradle 的设计思路有一些帮助。
Make#
Make 是一个古老、广泛使用的经典构建系统,或者是 "任务系统", 任务是 Make 比较重要的概念. 它的格式是这样的:
# Makefile
mytask:
cd /work/project && touch desc.txt
ls /work/project
echo "finish!"
mytask
是任务名称, 任务(Task)由一系列 shell 命令(Command)组成,通过 tab 缩进(不能是空格缩进)来体现层级。
在 Linux 上将这个文件保存为 Makefile, 然后执行 make mytask
命令(make 隐式约定会寻找当前目录下的 Makefile 文件),就会执行 mytask
里面的指令,最后一条指令会输出 "finish"
任务之间也可以有依赖, 下面的 taskZ
依赖了 taskA
、taskB
:
# Makefile
taskA:
echo "I'm task a"
taskB:
echo "I'm task b"
taskZ: taskA taskB
echo "I'm task Z"
执行 make taskZ
会输出:
I'm task a
I'm task b
I'm task z
如果用 make 来做 java 构建,可以这样写:
build:
javac *.java -d build/classes/
cd build/classes && jar cvf mine.jar *.class
Makefile 理念很简单,使用灵活,语法完善,功能强大,几乎是一种完美的构建系统,所以经久不衰。它确定了基于 DAG 任务系统 来实现 构建系统 的基本思路.
Make 内的指令都是 shell 指令,与 shell 深度绑定绑定,所以在 Linux 上使用较多,无法跨平台。
如今,跨平台也不算什么稀罕的东西了。java 就是跨平台成员之一,所以就有人想能不能将指令使用 java 实现,如此一来就能实现一种跨平台的任务系统了。所以就诞生了 Ant。
Ant#
Ant 也是一种通用型构建系统,基本思路与 Make 一致。
Make 只有 Task/Command 两层结构, task 和 task 并没有分别,都可以单独执行,所以维护者可能很难找到哪一个任务是入口, 这方面只能靠公共约定。
Ant 做了一些改进,拥有 Project/Target/Task 三层结构, 其中 Target/Task 分别对应了 Make 的 Task/Command 概念, 这么一来虽然不是纯粹的任务系统,但是更像构建系统了。 也能凸显出上层的 project,而更少关注细节。
下面是 Ant 官网的构建配置入门案例:
<project name="MyProject" default="dist" basedir=".">
<description>
simple example build file
</description>
<!-- set global properties for this build -->
<property name="src" location="src" />
<property name="build" location="build" />
<property name="dist" location="dist" />
<target name="init">
<!-- Create the time stamp -->
<tstamp />
<!-- Create the build directory structure used by compile -->
<mkdir dir="${build}" />
</target>
<target name="compile" depends="init" description="compile the source">
<!-- Compile the Java code from ${src} into ${build} -->
<javac srcdir="${src}" destdir="${build}" />
</target>
<target name="dist" depends="compile" description="generate the distribution">
<!-- Create the distribution directory -->
<mkdir dir="${dist}/lib" />
<!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}" />
</target>
<target name="clean" description="clean up">
<!-- Delete the ${build} and ${dist} directory trees -->
<delete dir="${build}" />
<delete dir="${dist}" />
</target>
</project>
嗯... 看起来有点复杂? 截取其中一小片来看:
<mkdir dir="${dist}/lib" />
<delete dir="${dist}" />
<javac srcdir="${src}" destdir="${build}" />
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}" />
虽然是 xml,不过仍然可以看出来 Make 的影子,
比如上面的 mkdir
delete
javac
等, 与 shell 命令如出一辙, 只不过他们都是 java 实现的。
比如 delete, 就对应了一个 java 函数,它接受一个 dir 参数。
如果希望自定义指令,可以自己写一个 ant 插件,编译一个 jar 包让 ant 加载上去。
另外,Ant 抽象出的 Project/Target/Task 三层概念除了体现在 xml 配置上,在 Ant 中也有对应的 class 定义,例如 Project 是一个 class,绑定了 name/baseDir 等属性,Target 也是一个 class 保留了父 Project 的引用,可以读取上面的属性。
在实践中,要自定义功能写 Ant 插件的场景比较多,每个项目都针对性地写插件还是有些麻烦。有人就想: 为什么不直接在构建脚本里面写 jvm 代码呢? 所以就诞生了 gradle。
Gradle#
它和 Make 类似,也是一种通用构建系统,使用 DAG 组织任务
gradle 也有三层概念: Project/Task/Code, 其中 Task/Code 对应了 Make 的 Task/Command。
gradle 的构建脚本就是 jvm 代码,目前支持使用两种语言编写构建脚本: groovy
/kotlin
. (后面介绍主要使用 kotlin DSL
)
比如这就是一个很简单的 gradle 构建脚本:
// build.gradle.kts
println("hello world")
它只打印了 hello world 。
gradle 的历史以及基本原理算是说完了,后面开始介绍 gradle 的使用方法了。