注:阅读本章前建议先回顾第一章中的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
除了给它们添加依赖,还可以通过doFirst
、doLast
方法给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
参考资料