您的当前位置:首页正文

Learning Gradle

来源:华佗小知识

Java构建工具的发展

Why Gradle

Gradle is an advanced build system as well as an advanced build toolkit allowing to create custom build logic through plugins. Here are some of its features that made us choose Gradle:

  • Domain Specific Language (DSL) based on Groovy, used to describe and manipulate the build logic
  • Build files are Groovy based and allow mixing of declarative elements through the DSL and using code to manipulate the DSL elements to provide custom logic.
  • Built-in dependency management through Maven and/or Ivy.
  • Very flexible. Allows using best practices but doesn’t force its own way of doing things.
  • Plugins can expose their own DSL and their own API for build files to use.
  • Good Tooling API allowing IDE integration.

DSL,领域特定语言,指不像通用目的语言那样目标范围涵盖一切软件问题,而是专门针对某一特定问题的计算机语言,如init.rc,renderscript等。

理解Groovy

Groovy安装

$ brew install groovy

安装完成后可通过如下命令查看:

$ groovy -v
Groovy Version: 2.4.8 JVM: 1.8.0_45 Vendor: Oracle Corporation OS: Mac OS X

Groovy基础语法

  • 可以不同分号结尾。
  • 支持动态类型,即定义变量时可以不指定其类型。
  • 可以使用关键字def定义变量和函数(Groovy推荐)(其实def会改变变量的作用域)。
  • Groovy中的所有事物都是对象。
def var = "Hello Groovy"
println var
println var.class

var = 5
println var
println var.class

输出结果如下:

Hello Groovy
class java.lang.String
5
class java.lang.Integer
  • 单引号中的内容严格对应Java的String,不对$进行转义。
  • 双引号的内容如果有$则会对$表达式先求值()。
  • 三引号可以指示一个多行的字符串,并可以在其中自由的使用单引号和双引号。
def name = 'Jerry'
println 'His name is $name'
println "His name is $name"

def members = """
    'Terry'
    "Larry" """
println "Team member is: " + members

输出结果如下:

His name is $name
His name is Jerry
Team member is:
    'Terry'
    "Larry"
  • 函数定义时返回值和函数参数也可以不指定类型。
  • 未指定返回类型的函数定义必须使用def。
  • 可以不使用return xxx来设置函数返回值,函数最后一行代码的执行结果被设置成返回值,如果定义时指明了类型则必须返回正确的数据类型。
  • 函数调用可以不加括号,构造函数和无参函数除外。
def getValue(name) {// def is must
    name + "'s value is 10"
}
value = getValue "Terry"
println value

结果如下:

Terry's value is 10
  • Java原始数据类型在Groovy中为其对应的包装类型。
  • 类不支持default作用域,且默认作用域为public,如果需要public修饰符,则不用写它。
  • 自动提供足够使用的构造函数(一个无参和带一个Map参数的构造函数,足够)。
  • Groovy动态的为每一个字段都会自动生成getter和setter,并且我们可以通过像访问字段本身一样调用getter和setter。
  • Groovy所有的对象都有一个元类metaClass,我们可以通过metaClass属性访问该元类。通过元类,可以为这个对象增加属性和方法(在java中不可想象)!
  • 常用的集合类有ListMapRange
class Person {
    int id
    String name

    void setId(id) {
        println "setId($id)"
        this.id = id
    }

    String toString() {
        "id=$id, name=$name"
    }
}
merry = new Person()
merry.id = 1    // call merry.setId(1)
merry.setName "Merry"
println merry
println merry.getId().class
jerry = new Person(id: 2, name: "Jerry")
println jerry

运行结果如下:

setId(1)
id=1, name=Merry
class java.lang.Integer
setId(2)
id=2, name=Jerry

下面的例子展示通过metaClass向String对象中动态添加属性和方法:

def msg = "Hello Groovy"
msg.metaClass.upper = { delegate.toUpperCase() }
msg.metaClass.lower = msg.toLowerCase()
println msg.upper()
println msg.lower

运行结果如下:

HELLO GROOVY
hello groovy

Groovy动态性

Groovy动态性示例如下:

class Dog {
    def bark() {
       println 'woof!'
    }
    def sit() {
       println 'sitting!'
    }
    def jump() {
       println 'boing!'
    }
}

def dog = new Dog()
def acts = ['sit','jump','bark']
acts.each {
    dog."${it}"()
}

运行结果如下:

sitting!
boing!
woof!

另一个动态性的例子:

class LightOn
{
    def doing()
    {
       println 'Ligth turning on...'
    }
}

class LightOff
{
    def doing()
    {
       println 'Ligth turning off...'
    }
}

class Switch
{
    def control(action)
    {
       action."doing"()
    }
}
def sh = new Switch()
sh.control(new LightOn())

运行结果如下:

Ligth turning on...

Groovy List

  • List变量由[]定义,其元素可以是任意对象,底层对应Java的List接口。
  • 直接通过索引存取,而且不用担心索引越界,当索引超过当前列表长度,List自动往该索引添加元素。
tmp = ["Jerry", 19 , true]
println "tmp[1] = " + tmp[1]
println tmp[5] == null
tmp[10] = 3.14
println "The size is " + tmp.size
println tmp

运行结果如下:

tmp[1] = 19
true
The size is 11
[Jerry, 19, true, null, null, null, null, null, null, null, 3.14]

Groovy Map

  • Map变量由[:]定义,冒号左边是key,右边是value,key必须是字符串,value可以是任何对象。另外key可以用引号包起来,也可以不用。
  • Map中元素的存取支持多种方法。
def score = "mark"
// id and name are treated as String
// "$score" is also correct
tmp = [id: 1, name: "Jerry", (score): 92]
println tmp
println "id: " + tmp.id
println "name: " + tmp["name"]
tmp.height = 183
println "height: " + tmp.height

运行结果如下:

[id:1, name:Jerry, mark:92]
id: 1
name: Jerry
height: 183

Groovy Range

  • Range是Groovy对List的扩展,由begin值 + .. + end值定义。
  • 不包含end值时使用<。
tmp = 1..5
println tmp
println tmp.from
println tmp.to
tmpWithoutEnd = 1..<5
println tmpWithoutEnd.step(2)
println ""
println ""
println tmpWithoutEnd


tmp = 1.0f..5.0f
println tmp

运行结果如下:

[1, 2, 3, 4, 5]
1
5
[1, 3]


[1, 2, 3, 4]
[1.0, 2.0, 3.0, 4.0, 5.0]

Groovy文档

  • Groovy的API文档位于。
  • 以Range为例,从getter方法我们知道Range有fromto属性,尽管文档中并没有说明。
T   getFrom()
The lower value in the range.
T   getTo()
The upper value in the range.

Groovy闭包

  • 英文叫,是Groovy中非常重要的一个数据类型或者说一种概念了。
  • 闭包,是一种数据类型,它代表了一段可执行代码或方法指针。定义格式为:
    • def 闭包对象 = { parameters -> code }
    • def 闭包对象 = { code } // 参数个数少于2个时可以省略->符号
    • `def 闭包对象 = reference.&methodName
  • 闭包的调用方式:
    • 闭包对象.call(参数)
    • 闭包对象(参数)
  • 闭包只有一个参数时,可省略书写参数,在闭包内使用it变量引用参数。
  • 闭包可以作为函数返回值,函数参数,可以引用闭包外部定义的变量,可以实现接口方法。
def person = { id, name ->
    println "id=$id, name=$name"
}
person(1, "Jerry")
person.call(2, "Larry")
person 3, "Merry"

Closure resume = { "resume" }
println resume()

def hello = "Hello"
def length = hello.&length
println "The length is " + length()

def greeting = { "$hello, $it" }
// equals: greeting = { it -> "Hello, $it" }
println greeting('Groovy')

def exit = { -> "exit" }
// exit(1) <= wrong!!

def members = ["Jerry", "Larry", "Merry"]
members.each {
    println "Hello $it"
}

def welcome(name) {
    return {
        println "Welcome $name"
    }
}
println welcome("Terry")

运行结果如下:

id=1, name=Jerry
id=2, name=Larry
id=3, name=Merry
resume
The length is 5
Hello, Groovy
Hello Jerry
Hello Larry
Hello Merry
closure$_welcome_closure6@73a1e9a9

如何确定闭包的参数?查看API文档。

Groovy DSL

  • Command Chain: 链式调用既可以省略圆括号,又可以省略”.”号。
  • 闭包作为函数调用的最后一个参数时,可以拿到圆括号外面。
// equivalent to: turn(left).then(right)
turn left then right

// equivalent to: take(2.pills).of(chloroquinine).after(6.hours)
take 2.pills of chloroquinine after 6.hours

// equivalent to: paint(wall).with(red, green).and(yellow)
paint wall with red, green and yellow

// with named parameters too
// equivalent to: check(that: margarita).tastes(good)
check that: margarita tastes good

// with closures as parameters
// equivalent to: given({}).when({}).then({})
given { } when { } then { }

一个例子:

def name(name) {
    println "name: $name"
    return this
}
def age(age) {
    println "age: $age"
    return this
}
def action(String tips, Closure c) {
    println "begin to $tips"
    c()
    println "end"
    return this
}

name "Jerry" age 18
// name("Jerry").age(18)

name "Herry"
age 22
action("eat") {
    println "eating..."
}

运行结果如下:

name: Jerry
age: 18
name: Herry
age: 22
begin to eat
eating...
end

Groovy脚本

Groovy脚本是什么?我们通过一个例子来看一下。

// variables.groovy

def x = 1 // or int x = 1

def printx() {
    println x
}

printx() // failed

运行结果如下:

Caught: groovy.lang.MissingPropertyException: No such property: x for class: variables
groovy.lang.MissingPropertyException: No such property: x for class: variables
    at variables.printx(variables.groovy:5)
    at variables.run(variables.groovy:8)

为何会出现找不到属性x?我们来看看反编译groovy运行时生成的.class文件。使用如下命令在-d指定的目录生成.class文件,然后使用JD-GUI查看:

groovyc –d classes variables.groovy
variables.class

可以看到:

  • XXX.groovy被转换成XXX类,它从Script类派生。
  • 每一个脚本都会生成一个static main函数。这样,当我们groovy XXX.groovy的时候,其实就是用调用虚拟机去执行这个main函数。
  • 如果脚本定义了函数,则函数会被定义在XXX类中。
  • 脚本中的其他代码都会放到run函数中。

所以变量x是在是在run函数中定义的局部变量,当然无法在printx()函数的访问。那要如何才能实现printx()函数访问变量x呢?有两种方式可以实现。
第一种方式:

x = 1 // replace def x = 1 or int x = 1

def printx() {
    println x
}

printx()

对应的.class文件反编译代码:


另一种方式:
import groovy.transform.Field;

@Field x = 1 // <= def x = 1 or int x = 1

def printx() {
    println x
}

printx()

对应的.class文件反编译代码:

Gradle介绍

Gradle被认为是Java世界构建工具的一次飞跃,它提供:

  • 一个非常灵活通用的构建工具。
  • 对多项目构建提供强大支持。
  • 强大的依赖管理机制。
  • 完美支持已有的Maven和Ivy仓库。
  • 支持依赖传递管理。
  • 脚本编写基于Groovy。
  • 丰富的描述构建的领域模型。

Why Groovy

We think the advantages of an internal DSL (based on a dynamic language) over XML are tremendous when used in build scripts. There are a couple of dynamic languages out there. Why Groovy? The answer lies in the context Gradle is operating in. Although Gradle is a general purpose build tool at its core, its main focus are Java projects. In such projects the team members will be very familiar with Java. We think a build should be as transparent as possible to all team members.

In that case, you might argue why we don't just use Java as the language for build scripts. We think this is a valid question. It would have the highest transparency for your team and the lowest learning curve, but because of the limitations of Java, such a build language would not be as nice, expressive and powerful as it could be. [1] Languages like Python, Groovy or Ruby do a much better job here. We have chosen Groovy as it offers by far the greatest transparency for Java people. Its base syntax is the same as Java's as well as its type system, its package structure and other things. Groovy provides much more on top of that, but with the common foundation of Java.

For Java developers with Python or Ruby knowledge or the desire to learn them, the above arguments don't apply. The Gradle design is well-suited for creating another build script engine in JRuby or Jython. It just doesn't have the highest priority for us at the moment. We happily support any community effort to create additional build script engines.

Gradle基本概念

gradle-delegates

Gradle生命周期

gradle-build-phasesM

Gradle用户手册中关于Gradle生命周期的描述如下:


gradle-lifecycle

Gradle生命周期的例子如下:

// settings.gradle
println 'This is executed during the initialization phase.'

// build.gradle in root directory
println 'This is executed during the configuration phase.'

task configured {
    println 'This is also executed during the configuration phase.'
}
task test {
    doLast {
        println 'This is executed during the execution phase.'
    }
}
task testBoth {
    doFirst {
      println 'This is executed first during the execution phase.'
    }
    doLast {
      println 'This is executed last during the execution phase.'
    }
    println 'This is executed during the configuration phase as well.'
}

执行结果如下:

This is executed during the initialization phase.
This is executed during the configuration phase.
This is also executed during the configuration phase.
This is executed during the configuration phase as well.

Gradle生命周期Hook

gradle-lifecycle-phases-hook

以项目评估结束事件为例说明:

// build.gradle in root directory
afterEvaluate {
    println "Project $name has been evaluated."
}

gradle.afterProject {
    println "Project $it.name is evaluated."
}

gradle.addProjectEvaluationListener(new ProjectEvaluationListener() {
    void afterEvaluate(Project project, ProjectState state) {
        println "Project $project.name was evaluated."
    }
    void beforeEvaluate(Project project) {
    }
})

执行结果如下:

Project tutorial was evaluated.
Project tutorial is evaluated.
Project tutorial has been evaluated.

Gradle核心组件

Gradle组件

Settings组件

  • setting.gradle同级的gradle.properites文件定义的属性。
  • 用户Gradle目录里gradle.properties定义的属性。
  • 通过命令行-P参数定义的属性。

示例如下:

// gradle.properties in project root directory
componentized=true

// settings.gradle
if (componentized) {
    include ':components'
}

执行结果如下:

Project tutorial was evaluated.
Project tutorial is evaluated.
Project tutorial has been evaluated.
Project components was evaluated.
Project components is evaluated.

Project组件

5大范围动态属性


project-5-property-scopes

5大范围动态方法


project-5-method-scopes

Ext属性示例:

// build.gradle in root directory
ext {
    gradleVersion = "3.4"
    isSnapshot = true
}
ext.javaVersion = "1.7"

task addProperty(dependsOn: clean) {
    project.ext.prop3 = true
}
if (isSnapshot) {
    println "GradleVersion: $gradleVersion"
    println "JavaVersion: $javaVersion"
    println "Prop3: ${prop3}"
}

// build.gradle in components directory
println "GradleVersion in components: $gradleVersion"
println "JavaVersion in components: ${rootProject.ext.javaVersion}"

执行结果如下:

GradleVersion: 3.4
JavaVersion: 1.7
Prop3: true
GradleVersion in components: 3.4
JavaVersion in components: 1.7

依赖管理

  • 解决依赖。依赖指使得项目能够正常编译或运行的输入组件。
    • 依赖配置:包括项目内依赖和外部依赖,并以配置分组。
    • 仓库配置:Gradle会在配置的仓库地址查找外部依赖。
  • 发布产物。产物指项目编译生成的或需要上传到中心的仓库的输出产物。
    • 配置发布仓库。通常插件会定义好项目的产物,我们不必关心,但必须告诉Gradle将产物发布到何处,,Ivy仓库,等。
dependency-types

依赖管理示例:

// In this section you declare where to find the dependencies of your project
repositories {
    jcenter()
    jcenter {
        url 
    }
    mavenCentral()
    maven {
        url 
    }
    ivy {
        url "../local-repo"
    }
}

dependencies {
    compile 'com.google.guava:guava:20.0'
    compile project(':components')
    compile fileTree(dir: 'libs', include: '*.jar')
    testCompile group: 'junit', name: 'junit', version: '4.+'
}

Task组件

4大范围动态属性


task-4-property-scopes

Task使用示例:

// build.gradle in root project
task hello1 {
    doLast {
        println "Hello1 Task"
    }
}
task hello2 << {
    ext.prop2 = "prop2"
    println "Hello2 Task"
}
hello2.dependsOn hello1
task(hello3, dependsOn: hello2) {
    doFirst {
        println "Prop2: ${hello2.ext.prop2}"
        println "Hello3 Task"
    }
}
tasks.create('hello4').dependsOn('hello3')
hello4.doLast {
    println "Hello4 Task"
}

运行结果如下:

:hello1
Hello1 Task
:hello2
Hello2 Task
:hello3
Prop2: prop2
Hello3 Task
:hello4
Hello4 Task

Plugin组件

  • Build script:插件自动被编译并被include到classpath中,build script外不可用。
  • buildSrc project:buildSrc子项目自动被编译并被include到classpath中,整个项目可用,其他项目不可用。
  • Standalone project:可以发布jar或发布到公共仓库,其他项目可用。

Gradle Wrapper目录结构

gradle-wrapper-in-project

用户主目录下Gradle相关文件说明如下:


gradle-wrapper-in-home

Android Plugin for Gradle


参考