author

koly

gradle简介——从编程的角度

写代码的时候,往往需要一些依赖库,以及一些一般操作,比如代码编译,打包啊。在JAVA的世界里,这些功能的实现经历了ant+ivy,maven,到gradle的过程。最早的时候什么工具都没有,需要一个库,手动下载拷贝。编译,打包,最多也就写个脚本。后来出现了ant和ivy,ant用来执行编译,打包之类的任务,ivy用来管理依赖的库。后面有了maven,通过xml的形式来进行配置,根据配置来管理依赖以及执行任务。接着出现了gradle,实现的功能跟maven差不多,但是在定制化方面可以使用语言进行编程,而不是通过增加maven插件的形式。那么,下面就讲讲一次对gradle的探索过程。

gradle与groovy

gradle与groovy是什么关系呢?首先来看看它们各自是什么。

groovy

先来看看官网定义咯:

Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming.

简单来说,就是一门语言。(我要是只写到这里,你会不会打我…)
多说一点,是一门强大的语言。运行在JVM(Java Virtual Machine)上。学起来简单。可以用来写脚本,写DSL(Domain Specific Language)。

gradle

先来看看官网定义:

We would like to introduce Gradle to you, a build system that we think is a quantum leap for build technology in the Java (JVM) world.

“that”后面的部分不用太在意,就是说我们是JAVA(JVM)世界里面很牛逼的一个构建系统(build system)。就是说,gradle是一个构建系统。

它提供了什么功能呢?

A very flexible general purpose build tool like Ant.
Switchable, build-by-convention frameworks a la Maven. But we never lock you in!
Very powerful support for multi-project builds.
Very powerful dependency management (based on Apache Ivy).
Full support for your existing Maven or Ivy repository infrastructure.
Support for transitive dependency management without the need for remote repositories or pom.xml and ivy.xml files.
Ant tasks and builds as first class citizens.
Groovy build scripts.
A rich domain model for describing your build.

摘几个出来讲讲。这是一个像Ant一样的构建工具(build tool)。对多个项目的构建具有强大支持。基于Apache Ivy的依赖管理。使用Groovy语言来编写构建脚本(build
scripts)。这里的构建脚本是什么意思呢?一般来说,你要对一个项目进行构建,就需要写一些指导构建的文件,比如maven就是一个pom.xml。各种工具通过解析这个文件就可以知道怎么去构建一个项目。这里的构建就包括了依赖的管理,命令的执行。比如下载个依赖包啊,升级个依赖包啊,执行个测试命令啊,执行个打包命令啊。构建脚本就是指这个文件。
这里其实就指出了gradle与groovy的关系:gradle使用groovy来写构建脚本。但是到这里呢,还不够。
在官网的overview中,又介绍了一句”The whole Gradle API is fully Groovy-ized”。就是说gradle的API都是可以直接运行在Groovy环境中的,能够被Groovy直接调用。
最后呢,gradle是开源的,源码在这里:https://github.com/gradle/gradle。gradle的编写语言是Java和Groovy。

gradle当中的概念

由于是从编程的角度,所以什么wrapper,daemon就不提及了。这里主要想看看gradle是怎么使用groovy的。整个探索的过程是起于Gradle Build Language Reference

gradle scripts

gradle scripts就是gralde的脚本文件。有三种,分别是Build script, Init script, Settings script。对应的文件应该是build.gradle, init.gradle, settings.gradle。
gradle scripts是一种configuration scripts。顾名思义,就是说在执行scripts的过程中,会进行一些配置。这些配置呢,是对某个对象的配置。就是说设置一下这个对象的某个属性啊,调用一下这个对象的某些方法啊,等等。比如在build.gradle执行的过程中,实际上就是对一个Project对象的配置。这个对象就叫做script的delegate object。delegate object上的属性和方法都可以在script当中使用。每一个script对应着一个对象,比如一个build.gradle就对应着一个Project对象。
还有,gradle script实现了Script接口,也就是说该接口上的所有方法都可以在gradle script里面调用。
到这里,我就疑惑了。为什么gradle script可以实现接口呢?delegate object跟script本身又是什么关系呢?参考一下Groovy,可能会有些答案。

Groovy当中也有script这一概念,只是跟gradle这里的gradle script可能不太一样。那么,Groovy当中的script概念是什么样的呢?
首先,Groovy作为在JVM上运行的语言,是支持类的。比如你想写一个Hello World,需要:

1
2
3
4
5
class Main {
static void main(String... args) {
println 'Groovy world!'
}
}

此为类,script呢?

1
println 'Groovy world!'

简单来说,就是并不需要类,直接写变量,语句,函数等。这就叫script。(貌似其他语言也是这样定义的呢)
但是,我们知道Groovy是运行在JVM上的。JVM上并没有script这一概念,只有类和对象。那么script想要执行,就必然要变成类,然后实例化为对象。真的是这样的吗?
是的。script会被编译成类。比如上面的Hello World编译之后就变成了:

1
2
3
4
5
6
7
8
9
10
11
import org.codehaus.groovy.runtime.InvokerHelper

class Main extends Script {
def run() {
println 'Groovy world!'
}

static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
}

里面的东西不用细看,这里只需要知道:script会被编译成一个类,然后script当中的代码会在这个类中的某个地方得到执行。
由于script会被编译成一个类,所以自然也可以实现接口了。那么script中的delegate object又是什么呢?是不是指类中的一个属性呢?
Groovy中,script没有一个delegate的概念,但是closure却有一个。先开条分线,看看closure是什么。
官方定义:

A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope.

简单来说,closure是一段代码,这段代码可以接受参数,能够返回一个值,并且这段代码可以被赋给一个变量。同时,这段代码也可以应用定义在其外层的变量。这样看来,跟一个匿名函数非常像。可以暂时想象成一个匿名函数。
closure的定义:

1
{ [closureParameters -> ] statements }

其中中括号的部分是可选的。就是说类似{ item++ }{ -> item++ }{ println it }都是closure。
当然,由于groovy是跑在JVM上的语言,所以closure最后也会变成一个对象。实际上,closure是groovy.lang.Closure类的一个对象。closure其他的特点暂不必说,先看看它的delegate是什么样的。由于closure是一个对象,所以自然也可以有自己的属性:

this corresponds to the enclosing class where the closure is defined
owner corresponds to the enclosing object where the closure is defined, which may be either a class or a closure
delegate corresponds to a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined

前两个this和owner暂不说,delegate属性,指向了一个第三方对象。所有在closure中没有主体的方法调用或者属性都会到这个第三方对象中去找。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
String name
}
class Thing {
String name
}

def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')


def upperCasedName = { delegate.name.toUpperCase() }
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'

上面,upperCasedName是一个closure,然后里面的delegate是其的一个属性。之后将upperCaseName的delegate指向了对象p,然后里面的delegate.name实际上就在p对象上去找了。

关于closure的delegate,关键的一点就是,没有定义的方法或者属性会去closure的delegate object上去找。这种机制就跟上面提到的”delegate object上的属性和方法都可以在script当中使用。”行为上一致了。至于其背后的delegate object是怎么实现的,是不是跟closure的delegate方式一致,且听下回分解。

还有一点是,closure的这种到其他地方去找函数或者属性的行为称为delegation strategy。默认的strategy是Closure.OWNER_FIRST,首先会在closure上的owner找,然后回去closure上的delegate找。还有比如Closure.DELEGATE_FIRST,Closure.DELEGATE_ONLY之类。

到这里,小结一下:

  • gradle的script是一个对象,该对象实现了com.gradle.Script接口,该接口上的所有方法都可以在script中调用。
  • gradle的每种script有一个对应的delegate object,在script中可以使用该delegate object的属性和方法。比如build.gradle的delegate object就是一个Project对象。而settings.gradle的delegate object就是一个Settings对象。具体可以在settings.gralde中访问settings,在build.gradle中访问project对象。
  • 在调用delegate object上的方法和属性的时候,实际也是在对delegate object进行配置。比如设置这个对象的某个属性什么的。

lifecycle

来自官网,gradle配置Project对象的生命周期:

  • Create a Settings instance for the build.
  • Evaluate the settings.gradle script, if present, against the Settings object to configure it.
  • Use the configured Settings object to create the hierarchy of Project instances.
  • Finally, evaluate each Project by executing its build.gradle file, if present, against the project. The projects are evaluated in breadth-wise order, such that a project is evaluated before its child projects.

task

gradle里面,执行的代码段通过task这种形式组织起来。比如对于JAVA,compile,install都是task,然后各自完成一些事情,比如编译,打包。

task的定义

有两种方式,
使用task keyword。这里的keyword很奇怪,task并不是Groovy的keyword,应该是gradle自己定义的,怎么定义的这里先不展开。就看看怎么使用:

1
2
3
4
5
task myTask
task myTask { configure closure }
task myType << { task action }
task myTask(type: SomeType)
task myTask(type: SomeType) { configure closure }

第二种方式是使用TaskContainer.create函数,这里的TaskContainer是一个接口,不是一个类,所以:

1
TaskContainer.create(taskName, {closure})

上面那样用,是不行的,不能直接调用接口上的函数。得有一个实现。在build.gradle里面可以:

1
2
project.getTasks().create(taskName, {closure})
project.getTasks().create taskName, closure

build.gradle里面使用project.getTasks(),这个函数会返回一个TaskContainer的实例,所以就可以调用这上面的值了。注意在省略括号的情况下,参数之间需要加上逗号。

task 后面的closure

task后面传入的closure,有两个特点。第一是这个closure的delegate是这个task对象;第二是这个closure的参数是task对象。所以有两种方式去获取这个task对象:

1
2
3
task oneTask { t ->
print t
}

1
2
3
task oneTask {
println delegate
}

task对象上的属性和方法可以查看具体文档

查文档

有一些文档是应该看看的,去熟悉一下可以使用的方法及属性,比如Project的文档:https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#buildscript(groovy.lang.Closure))

总结

不要把gradle当成maven,记住,gradle是可以直接写代码的,直接写代码的,直接写代码的。然后,得知道可以使用的函数和属性从哪里来,也就说需要使用的时候去哪里找。
所谓的DSL,其实就是Groovy类中的函数或者属性,通过delegate这种机制,可以直接在gradle scirpt里面调用这些函数,然后这些函数又可以接受一个closure作为参数,于是看起来就变得像配置文件了。此为编程语言的配置化伪装。