注:阅读本章前建议先回顾第一章中的Projects和tasks概念

3.1 初识build.gradle

在第二章中分析项目结构时我们说过build.gradle这个文件是构建脚本文件,它的本质是在其中定义了一个project和若干tasks

当我们在命令行中输入gradle build(或gradlew build)命令进行项目构建时,Gradle会自动在当前目录下去寻找build.gradle文件,按照里面定义的脚本进行构建。

以一个小demo为例,我们在一个空目录中新建一个文件build.gradle

task hello {
	doLast {
		println 'Hello world!'
	}
}

在当前目录下打开命令行,输入gradle -q hello,会输出以下结果(注:-q参数是为了不让Gradle的日志打印出来,只让我们输出的字符串显示)

> gradle -q hello
Hello world!

在这个demo中,我们在build.gradle中定义了一个任务hello,并在其中添加了一个动作——打印"Hello wold!",然后当我们在命令行执行gradle -q hello命令时就会执行这个task。

3.2 更小的单位——Action

前文说过,一个项目可以有多个Project、每个Project中又有多个Task,Task是原子性的操作。

但一个Task又由多个Action组成,多个Action组成一个Action List ,按顺序执行。

3.1中的doLast函数就是往Task的Action List的末端插入一个Action,相应的还有doFirst函数——往Action List的前端插入一个Action。doLast、doFirst都可以被调用多次

Action从语法的角度来说就是闭包,doLast和doFirst接收的参数也是闭包

3.3 Groovy与Kotlin

Gradle的构建脚本完全支持Groovy和Kotlin两种语言,当用Groovy书写构建脚本时,文件名为build.gradle;用kotlin书写时,文件名为build.gradle.kts

以下是两个例子,分别用两种语言实现同一个功能:

//build.gradle
task upper {
	doLast {
		String someString = 'mY_nAmE'
		println "Original: $someString"
		println "Upper case: ${someString.toUpperCase()}"
	}
}
//build.gradle.kts
tasks.register("upper") {
	doLast {
		val someString = "mY_nAmE"
		println("Original: $someString")
		println("Upper case: ${someString.toUpperCase()}")
	}
}

本文的示例默认用Groovy语言表述

3.4 tasks之间的依赖

有些任务之间可能会有先后关系,这时候就可以用tasks之间的依赖关系来,依赖关系使用dependsOn方法来表示,比如下面的demo就表示taskX的运行依赖于taskY的运行

//build.gradle
task taskX {
	dependsOn 'taskY'
	doLast {
		println 'taskX'
	}
}
task taskY {
	doLast {
		println 'taskY'
	}
}
//build.gradle.kts
tasks.register("taskX") {
	dependsOn("taskY")
	doLast {
		println("taskX")
	}
}
tasks.register("taskY") {
	doLast {
		println("taskY")
	}
}

我们在命令行中输入命令gradle -q taskX得到以下结果:

> gradle -q taskX
taskY
taskX

由结果我们可以看出,Gradle会先执行被依赖的taskY,再去接着执行taskX。
注意:本demo表现了task之间依赖的一个特点,虽然我们在命令行中只指定了要执行taskX并没有指定要执行taskY,但是因为taskX依赖于taskY,所以Gradle会自动地在执行taskX之前去执行taskY。

3.5 动态创建tasks

Groovy和Kotlin所提供的语法特性可以定义多个tasks,比如下面动态创建tasks的例子:

//build.gradle
4.times { counter ->
	task "task$counter" {
		doLast {
			println "I'm task number $counter"
		}
	}
}
//build.gradle.kts
repeat(4) { counter ->
	tasks.register("task$counter") {
		doLast {
			println("I'm task number $counter")
		}
	}
}

运行结果:

> gradle -q task1
I'm task number 1

3.6 在声明之外配置Task

当一个task声明完之后,仍然可以对Task进行配置,Groovy和Kotlin的语法差别较大,详见下面的demo,它们都在四个task声明完之后对task0添加了一个依赖

//build.gradle
4.times { counter ->
	task "task$counter" {
		doLast {
			println "I'm task number $counter"
		}
	}
}
task0.dependsOn task2, task3
//build.gradle.kts
repeat(4) { counter ->
	tasks.register("task$counter") {
		doLast {
			println("I'm task number $counter")
		}
	}
}
tasks.named("task0") { dependsOn("task2", "task3") }

运行结果:

> gradle -q task0
I'm task number 2
I'm task number 3
I'm task number 0

除了给它们添加依赖,还可以通过doFirstdoLast方法给task添加Action,以及其它的配置操作。

3.7 Task的额外属性

Task除了在声明时可以定义属性之外,Gradle还提供了一个叫 “额外属性” 的东西来让我们给task再添加一些自定义属性。在Groovy中使用ext命名空间来声明额外属性
注意: 额外属性在task内部按键值对存储

//build.gradle.kts
task myTask {
	ext.myProperty = "myValue"
}
task printTaskProperties {
	doLast {
		println myTask.myProperty
	}
}
//build.gradle.kts
tasks.register("myTask") {
	extra["myProperty"] = "myValue"
}
tasks.register("printTaskProperties") {
	doLast {
		println(tasks["myTask"].extra["myProperty"])
	}
}

运行结果:

> gradle -q printTaskProperties
myValue

3.8 默认Task

Gradle允许添加默认Task,当执行gradle命令而不指定task时就会执行这些默认的Tasks。

注意:gradle命令指定了task时,默认的Task除非被依赖,否则不会执行。

//build.gradle
defaultTasks 'clean', 'run'
task clean {
	doLast {
		println 'Default Cleaning!'
	}
}
task run {
	doLast {
		println 'Default Running!'
	}
}
task other {
	doLast {
		println "I'm not a default task!"
	}
}
//build.gradle.kts
task("clean") {
	doLast {
		println("Default Cleaning!")
	}
}
tasks.register("run") {
	doLast {
		println("Default Running!")
	}
}
tasks.register("other") {
	doLast {
		println("I'm not a default task!")
	}
}

执行结果:

> gradle -q
Default Cleaning!
Default Running!
> gradle -q other
I'm not a default task!

3.9 配置阶段与执行阶段

Gradle运行构建脚本时有配置(Configuration)阶段和执行(Execution)阶段,先配置后执行。

当配置阶段执行完了之后,Gradle就知道哪些tasks将要被执行,Gradle给我们提供了一个hook的能力,在两个阶段之间执行一些操作。

下面的demo将根据release这个task是否将会被执行做出不同操作,这个demo具有实际意义,代表我们在开发时debug和release的两种情况。

//build.gradle
task distribution {
	doLast {
		println "We build the zip with version=$version"
	}
}
task release {
	dependsOn 'distribution'
	doLast {
		println 'We release now'
	}
}
gradle.taskGraph.whenReady { taskGraph ->
	if (taskGraph.hasTask(":release")) {
		version = '1.0'
	} else {
		version = '1.0-SNAPSHOT'
	}
}
tasks.register("distribution") {
	doLast {
		println("We build the zip with version=$version")
	}
}
tasks.register("release") {
	dependsOn("distribution")
	doLast {
		println("We release now")
	}
}
gradle.taskGraph.whenReady {
	version =
		if (hasTask(":release")) "1.0"
		else "1.0-SNAPSHOT"
}

执行结果:

> gradle -q distribution
We build the zip with version=1.0-SNAPSHOT
> gradle -q release
We build the zip with version=1.0
We release now

3.10 外部依赖

如果你的构建脚本需要使用一些外部的依赖,比如说需要一些开源库来执行某些操作时,可以将它们的classpath添加至脚本中,使用buildscript方法

//build.gradle
import org.apache.commons.codec.binary.Base64
buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath group: 'commons-codec', name: 'commons-codec', version:'1.2'
	}
}
task encode {
	doLast {
		def byte[] encodedString = new Base64().encode('hello world\n').getBytes()
		println new String(encodedString)
	}
}
//build.gradle.kts
import org.apache.commons.codec.binary.Base64
buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		"classpath"(group = "commons-codec", name = "commons-codec", version= "1.2")
	}
}
tasks.register("encode") {
	doLast {
		val encodedString = Base64().encode("hello world\n".toByteArray())
		println(String(encodedString))
	}
}

输出结果:

> gradle -q encode
aGVsbG8gd29ybGQK

参考资料

UserGuide