近来准备学习gradle, 参考书籍Mastering Gradle。 然此书中有些代码语法与现在的gradle版本相比较老,需要进行遗弃与更改。虽然语法更新较快, 但其中的精髓还是值得让人学习。本文是基于Gradle 6.8.1 写的代码。篇幅可能过长,尽量简洁,如有需要,直接搜索关键字阅读相关主题。
1 Gradle 入门
第一行代码:
建立文件命令为build.gradle, 并写以下代码:
task helloGradle {
doLast{
println 'Hello Gradle World!'
}
}
代码中println的意思是打印输出。 敲以下命令:
gradle helloGradle
运行结果:
书中原来的代码是
task helloGradle << {
println 'Hello Gradle World!'
}
但这种方式运行会导致失败,原因是符号 << 早已被gradle弃用。
敲击命令后gradle默认运行当前目录下的build.gradle文件,并可以指定某个task运行。如果不想默认运行build.gradle文件,可以加上-b参数,如下:
gradle -b <buildfile_name> helloGradle
有些task执行过程中可能会抛出异常执行失败,这将会导致后续的task无法继续执行。那么此时我们可以加上–continue参数,表明继续执行后续的task。
gradle helloGradle --continue
有些task不想被执行,可以加入-x参数。
gradle helloGradle -x helloGradle
不想多余信息被打印显示出来,加-q参数
gradle helloGradle -q
其实在我们每次运行gradle的时候,JVM会被运行起来,很多gradle的库会被加载进来。如果启动很频繁,库每次被加载,势必会耗费性能。为了节省时间,可以加上–daemon
gradle helloGradle -q --daemon
如果task之间有依赖关系,可以利用dependsOn参数
task helloGradle {
doLast{
println 'Hello Gradle World!'
}
}
task test(dependsOn: helloGradle){
doLast{
println 'Test case executed!'
}
}
这样运行的时候会先运行helloGradle, 再运行test。
Gradle的运行分为三个阶段
- 初始化阶段(initialization): 决定运行哪个project(因为可能会有多个project在配置文件中),并且为project创建运行实例(project instance) 。
- 配置阶段(configuration): 任何写于task之外的语句都会在这个阶段被执行,task不会被执行,在这个阶段里,task的依赖关系会被形成。
- 执行阶段(execution): 执行task。
Cache管理
Gradle自动去下载所有在build文件里的依赖并将其保存在一个缓存区中,这样的好处是下回在build的时候不需要重新下载。cache的文件地址通常是在<USER_HOME>/.gradle/caches。
2: Groovy 入门
建立第一个groovy文件, 名为GroovyTest.groovy:
println "Hello Groovy"
然后运行命令
groovy GroovyTest.groovy
对于groovy文件,也可以将其预先编译,以后如果需要多次运行这个文件将省去编译时间。敲以下命令进行groovy的编译:
groovyc GroovyTest.groovy
在我本地环境中,将形成GroovyTest.class文件,如果想要运行这个文件,找到groovy-all-*.jar包,执行以下命令:
java -cp %GROOVY_HOME%/embeddable/groovy-all-2.3.1.jar; GroovyTest
命令将会顺利执行。 注意有个";", 千万不要省略,不然会忘记。
关于groovy大部分语法与java相同,容易掌握,不在此赘述,有问题可以找度娘或者google。然有些概念可能需要解释下:
闭包(Closure):
经常能遇见这个概念,可以粗略的将这个概念理解为将某个不在当前范围作用域而在上层范围作用域的变量包裹起来,如下代码:
class ClosurePrint {
def printClosure = {
println value + 10
}
}
def closureFunc = new ClosurePrint().printClosure
def value = 10
closureFunc.delegate = value
closureFunc();
在上面的代码中,变量closureFunc指向类ClosurePrint的函数变量printClosure, 在调用closureFunc()之前, 将value赋值给closureFunc.delegate,这样在调用closureFunc()的时候,类ClosurePrint的函数printClosure()先寻找当前范围是否存在名为value的变量,如果不存在,就在delegate中去寻找,找到后并输出。
Builder
利用groovy的功能builder可以产生复杂的XML树形结构,举个例子,代码如下:
class School {
Student student
String description
}
class Student{
String name
String email
}
School school = new School()
school.student = new Student(name: "Student1",email: "Student1@email.com")
school.setDescription("This is student 1")
def schoolList = school
def builder = new groovy.xml.MarkupBuilder(new FileWriter("schoolList.xml"))
builder.SchoolList{ //SchoolList为根标签节点的名称
for(i in schoolList){
School{ //School为xml标签节点名称
description(i.description)//description为xml标签节点名称
student{//student为xml标签节点名称
name(firstname : i.student.name) //name为xml标签节点名称, firstname为name标签的属性
email(i.student.email)//email为xml标签节点名称
}
}
}
}
利用此代码生成的xml文件如下:
<SchoolList>
<School>
<description>This is student 1</description>
<student>
<name firstname='Student1' />
<email>Student1@email.com</email>
</student>
</School>
</SchoolList>
具体的解析请看代码注释。
3:任务(Task)
一个project由task组成,每个project都有一些自带的property,例如name。还可以利用 ext 关键字给project加上自定义的property。具体请看下面代码:
println name
ext {
start="This is start 1"
}
ext.end = "This is end 1"
task test {
doLast{
println start
println end
}
}
输出结果如下
在上个章节讲过,Gradle的运行分为三个阶段:
- 初始化阶段(initialization): 决定运行哪个project(因为可能会有多个project在配置文件中),并且为project创建运行实例(project instance) 。
- 配置阶段(configuration): 任何写于task之外的语句都会在这个阶段被执行,task不会被执行,在这个阶段里,task的依赖关系会被形成。
- 执行阶段(execution): 执行task。
gradle运行时会按照以上这个顺序执行,举个例子
task test {
doLast{
println "This is execution part"
}
}
println "this is configuration part"
输出为
当我们不想执行task而时可以利用参数–dry-run, 这个不会有执行阶段. 同样-m参数也会起到同样的效果。
gradle test --dry-run
在执行阶段时,在task中引入一个关键字doLast,同时我们还可以引入相应的关键字doFirst, 这个关键字将会doLast前进行执行。举例:
task test {
doLast{
println "do Last"
}
}
test.doFirst {
println "do First"
}
输出
Task的执行顺序,在前面的章节中利用过关键字dependsOn参数,这个参数可以建立起task之间的依赖关系并且确定执行顺序。还有三个参数值得关注:
- shouldRunAfter: 应该在某一个task运行完毕后在运行当前task。
- mustRunAfter: 当同时运行多个task的时候,必须在后一个task运行完毕后在运行当前task, 比关键字shouldRunAfter更加严格些。
- finalyzedBy: 运行完当前task后在运行后一个task。
下面的例子为这三个关键字的用法:
(1..3).each {
task "Task$it" {
doLast {
println "Executing $name"
}
}
}
Task1.dependsOn Task2
Task2.dependsOn Task3
Task3.shouldRunAfter Task1
代码中创建了三个task并且每个task都打印一条语句,三个task的依赖关系是Task1依赖Task2,Task2依赖Task3,Task3 shouldRunAfter Task1. 这里面的关键字用的是shouldRunAfter 而不是mustRunAfter,因此不会像mustRunAfter那样严格,因此这个依赖关系也就没有形成一个环状,当运行:
gradle Task1
输出:
若将shouldRunAfter改为mustRunAfter,
(1..3).each {
task "Task$it" {
doLast {
println "Executing $name"
}
}
}
Task1.dependsOn Task2
Task2.dependsOn Task3
Task3.mustRunAfterTask1
运行结果则不通过:
改变代码:
(1..3).each {
task "Task$it" {
doLast {
println "Executing $name"
}
}
}
Task1.mustRunAfter Task3
运行"gradle Task1"和 "gradle Task1 Task3"的结果如下:
可以发现单独运行Task1的时候不会去运行Task3, 而同时运行Task1和Task3的时候会优先运行Task3。
gradle中也有条件执行语句:onlyIf, 当这个语句中的判断条件为true时才执行相应的task。例如:
ext {
flag = 2
}
task Task1{
doLast{
println "This is task 1"
}
}
Task1.onlyIf {
project.hasProperty('flag') && project.flag==1
}
task Task2{
doLast{
println "This is task 2"
}
}
Task2.onlyIf {
project.hasProperty('flag') && project.flag==2
}
输出:
从这里可以看出,执行Task1还是Task2依赖于project.flag,只有Task2的条件语句满足,所以只有Task2被执行。同时你也可以运行时通过加参数(-P)的方式设置该flag, 例如:
task Task1{
doLast{
println "This is task 1"
}
}
Task1.onlyIf {
project.hasProperty('flag') && project.flag=="1"
}
task Task2{
doLast{
println "This is task 2"
}
}
Task2.onlyIf {
project.hasProperty('flag') && project.flag=="2"
}
这里要记住-Pflag=2 在代码中要用字符串的方式进行匹配,例如:project.flag==“2”, 不要写成project.flag==2
还有一个关键字enable去控制task是否被执行,代码如下:
task Task1{
doLast{
println "This is task 1"
}
}
task Task2{
doLast{
println "This is task 2"
}
}
Task2.enabled = false
Task2.enabled被设置为false, 所以执行gradle的时候Task2不会被触发。
增量构建(incremental build)
默认情况下,gradle build的时候会默认使用增量构建,即有的时候某个task的输入和输出没有发成任何变化(比如读取文件和生成文件),那么这个task不会再次运行,并会显示up-to-date信息。如果想要强制运行,可以加上–rerun-tasks参数。
Gradle内置一些task,例:
- task copyTaskExample(type: Copy)
- task copyWithRenameExample(type: Copy)
- task zipTaskExample(type: Zip)
还有很多类似的task,不在这里一一介绍,举个简单的例子如何应用:
task copyTaskExample(type: Copy) {
from "."
into "./abc"
include('*.xml')
}
当执行完这个task后,当前目录下的所有xml会被赋值到当前目录下名为abc的文件里。还可以自定义task的方式,具体不在这赘述。
4:管理插件(plugin)
插件包含一堆task, property和配置信息。有两种类型的插件
- 脚本插件(script plugin):就是gradle文件,
- 二进制插件(binary plugin): 相当于class文件,这些class文件有一个标识。
脚本插件
想要利用脚本插件需要"apply from:"语法:例如:
在同一个目录下创建两个gradle文件:
build.gradle
apply from: 'anotherbuild.gradle'
task buildTask {
doLast{
println "This is build task"
}
}
anotherbuild.gradle
task anotherBuildTask {
doLast{
println "This is another build task"
}
}
运行:
二进制插件
直接举例:
apply plugin: 'java'
task buildTask {
doLast{
println "This is build task"
}
}
如果想要看这个plugin的所有task,可以输入:
gradle tasks --all
还有许多第三方plugin, 不在此赘述,有需求可以上网搜。这里简单罗列关于java plugin的常用的指令:
gradle classes // 编译在src/main/java下的文件
gradle testClasses // 编译在src/test/java的文件
gradle test // 编译/src/test/java文件并且生成test报告结果
gradle assemble // 生成jar包
gradle build // 执行完整的task,包含上述几个
gradle clean // 清除所有生成的结果
未完待续·······
参考书籍
【1】 Mastering Gradle