Gradle语法简介

来源:互联网 发布:java算法数据结构 编辑:程序博客网 时间:2024/06/13 22:20

Gradle只是提供了构建项目的一个框架,真正起作用的是Plugin,Gradle在默认情况下为我们提供了许多常用的Plugin,其中包括有构建Java和android项目的Plugin。Gradle不提供内建项目的生命周期管理,只是java Plugin向Project中添加了许多Task,这些Task依次执行,为我们营造了一种如同Maven般项目构建周期

 

Gradle本身的领域对象主要有Project和Task,Project为Task提供了执行上下文,Plugin可以创建extension,也可以向Project中添加Task,一个Task表示一个逻辑上较为独立的执行过程,比如编译Java源代码,拷贝文件,打包dex等,另外,一个Task可以读取和设置Project的Property以完成特定的操作

 

创建一个最简单的Task,内容如下:

 

task helloWorld << {

   println "Hello World!"

}


这里的“<<”表示向helloWorld中加入groovy执行代码Gradle向我们提供了一整套DSLdomain specific language),所以在很多时候我们写的代码似乎已经脱离了groovy,但是在底层依然是执行的groovy比如上面的task关键字,其实就是一个groovy中的方法,而大括号之间的内容则表示传递给task方法一个闭包

 

Gradle将当前目录下的build.gradle文件作为项目的构建文件在上面的例子中,helloWorld是一个DefaultTask类型的对象,这也是定义一个Task时的默认类型,当然我们也可以显式地声明Task的类型,甚至可以自定义一个Task类型比如,我们可以定义一个用于文件拷贝的Task:

 

task copyFile(type: Copy) {

   from 'xml'

   into 'destination'

}

 

以上copyFile将xml文件夹中的所有内容拷贝到destination文件夹中这里的两个文件夹都是相对于当前Project而言的,即build.gradle文件所在的目录

 

Gradle默认提供了几个常用的Task,比如查看Project的Properties、显示当前Project中定义的所有Task等dependenciespropertiestasks),dependencies用于显示Project的依赖信息,projects用于显示所有Project,包括根Project和子Project,而properties则用于显示一个Project所包含的所有Propertyproperties列表中,allprojects表示所有的Project,这里只包含一个根Project,在多项目构建中,它将包含多个Project;buildDir表示构建结果的输出目录;我们自己定义的copyFile和helloWorld也成为了Project中的Property


Gradle的Project从本质上说只是含有多个Task的容器,一个Task表示一个逻辑上的执行单元我们可以通过很多种方式定义Task,所有的Task都存放在Project的TaskContainer


1、调用Project的task()方法创建Task

在使用Gradle时,创建Task最常见的方式便是:task hello1 << { println 'hello1' }

这里的“<<”表示追加的意思,即向hello中加入执行过程。我们还可以使用doLast来达到同样的效果:

task hello2 { doLast { println 'hello2' } }

另外,如果需要向Task的最前面加入执行过程,我们可以使用doFirst

task hello3 { doFirst { println 'hello3' } }

上面的task关键字实际上是一个方法调用,该方法属于ProjectProject中存在多个重载的task()方法。以上我们自定义的3个Task都位于TaskContainer中,Project中的tasks属性即表示该TaskContainer。为此,我们可以新建一个Task来显示这些信息:

task showTasks {

   println tasks.class

   println tasks.size()

} 


2、通过TaskContainer的create()方法创建Task

通过task方法创建的Task都被存放在了TaskContainer中,而Project又维护了一个TaskContainer类型的属性tasks,那么我们完全可以直接向TaskContainer里面添加Task,查看TaskContainer的API文档可以发现,TaskContainer向我们提供了大量重载的create()方法用于添加Task

tasks.create(name: 'hello4') << { println 'hello4' }

 

3、声明Task之间的依赖关系

Task之间是可以存在依赖关系,我们可以在定义一个Task的同时声明它的依赖关系:

task hello5(dependsOn:hello4) << {

    println 'hello5'

}

 

当然,我们也可以在定义Task之后再声明依赖:

task hello6 << {

   println 'hello6'

}

hello6.dependsOn hello5

 

4、配置Task

一个Task除了执行操作之外,还可以包含Property,其中有Gradle为每个Task默认定义的Property,比如description,logger等。另外,每一个特定的Task类型还可以含有特定的Property,比如Copy的from和to等当然,我们还可以动态地向Task中加入额外的Property在执行一个Task之前,我们通常都需要先设定Property的值,Gradle提供了多种方法设置Task的Property值。

首先,我们可以在定义Task的时候对Property进行配置:

task hello7 << {

   description = "this is hello7"

   println description

}

我们还可以通过闭包的方式来配置一个已有的Task:

task hello8 << {
   println description
}

hello8 {
   description = "this is hello8"
}

需要注意的是,对hello8的description设置发生在创建该Task之后,在执行“gradle hello8”时,命令行依然可以打印出正确的“this is hello8”,这是因为Gradle在执行Task时分为两个阶段,首先是配置阶段,然后才是实际执行阶段所以在执行hello8之前,Gradle会扫描整个build.gradle文档,将hello8的description设置为“this is hello8,然后执行hello8

 

我们还可以通过调用Task的configure()方法完成Property的设置:

task hello9 << {

   println description

}

hello9.configure {

   description = "this is hello9"

}

 

task showDescription1 << {

   description = 'this is task showDescription'

   println description

}

 

task showDescription2 << {

   println description

}

showDescription2.description = 'this is task showDescription'

 

task showDescription3 << {

   println description

}

showDescription3 {

   description = 'this is task showDescription'

}

 

对于每一个Task,Gradle都会在Project中创建一个同名的Property,所以我们可以将该Task当作Property来访问,showDescription2便是这种情况另外,Gradle还会创建一个同名的方法,该方法接受一个闭包,我们可以使用该方法来配置Task,showDescription3便是这种情况

 

要读懂Gradle,我们首先需要了解Groovy语言中的两个概念,一个Groovy中的bean概念,一个是Groovy闭包的delegate机制

Groovy中的Bean机制,即Groovy动态为每一个字段都会自动生成getter和setter,并且我们可以通过像访问字段本身一样调用getter和setter,比如:

class GroovyBeanExample {

   private String name

}

def bean = new GroovyBeanExample()

bean.name = 'this is name'

println bean.name

 

GroovyBeanExample只定义了一个私有的name属性,并没有getter和setter。但是在使用时,我们可以直接对name进行访问,无论读还是写

 

另外,Gradle大量地使用了Groovy闭包的delegate机制。delegate机制可以使我们将一个闭包中的执行代码的作用对象设置成任意其他对象比如:

 

class Child { private String name }

class Parent {

   Child child = new Child();

   void configChild(Closure c) {

      c.delegate = child

      c.setResolveStrategy = Closure.DELEGATE_FIRST

      c()

   }

}

def parent = new Parent()

parent.configChild { name = "child name" }

println parent.child.name

 

configChild()方法中,我们将该方法接受的闭包的delegate设置成了child,然后将该闭包的ResolveStrategy设置成了DELEGATE_FIRST在调用configChild()时,所跟闭包中代码被代理到了child上,即闭包实际上是在child上执行的。此外,闭包的ResolveStrategy在默认情况下是OWNER_FIRST,即它会先查找闭包的owner

在使用Gradle时,我们build.gradle文件中直接调用task(),apply()和configuration()等方法,这是因为在没有说明调用对象的情况下,Gradle会自动将调用对象设置成当前Project。比如调用apply()方法和调用project.apply()方法的效果是一样的。查看Gradle的Project文档,你会发现这些方法都是Project类的方法

对于configurations方法该方法会将闭包的delegate设置成ConfigurationContainer,然后在ConfigurationContainer上执行闭包中的代码。dependencies方法会将所跟闭包的delegate设置成DependencyHandler

Project定义了configure(Object object,Closure configureClosure)方法,该方法是专门用来配置对象的(比如Task),它会将configureClosure的delegate设置成object,之后configureClosure中的执行代码其实是在object上执行的。和Groovy Bean一样,delegate机制的一个好处是可以增加所创建DSL的可读性


如果我们将Gradle的Task看作一个黑盒子,那么我们便可以抽象出输入和输出的概念,一个Task对输入进行操作,然后产生输出。比如,在使用java插件编译源代码时,输入即为Java源文件,输出则为class文件。如果多次执行一个Task时的输入和输出是一样的,那么我们便可以认为这样的Task是没有必要重复执行的

为了解决这样的问题,Gradle引入了增量式构建的概念。在增量式构建中,我们为每个Task定义输入(inputs)和输入(outputs),如果在执行一个Task时,如果它的输入和输出与前一次执行时没有变化,那么Gradle会认为该Task是最新的(UP-TO-DATE),因此Gradle将不予执行一个Task的inputs和outputs可以是一个或多个文件,可以是文件夹,还可以是Project的某个Property,甚至可以是某个闭包所定义的条件

每个Task都拥有inputs和outputs属性,他们的类型分别为TaskInputs和TaskOutputs。在下面的例子中,我们展示了这么一种场景:名为combineFileContent的Task从sourceDir目录中读取所有的文件,然后将每个文件的内容合并到destination.txt文件中。让我们先来看看没有定义Task输入和输出的情况:

task combineFileContentNonIncremental {

   def sources = fileTree('sourceDir')

   def destination = file('destination.txt')

   doLast {

      destination.withPrintWriter { writer ->

         sources.each { source ->

            writer.println source.text

         }

      }

   }

}

多次执行“gradle combineFileContentNonIncremental”时,整个Task都会反复执行,即便在第一次执行后我们已经得到了所需的结果。如果该combineFileContentNonIncremental是一个繁重的Task,那么多次重复执行势必造成没必要的时间耗费

这时,我们可以将sources声明为该Task的inputs,而将destination声明为outputs,重新创建一个Task如下:

 

task combineFileContentIncremental {

   def sources = fileTree('sourceDir')

   def destination = file('destination.txt')

   inputs.dir sources

   outputs.file destination

   doLast {

      destination.withPrintWriter { writer ->

         sources.each {source ->

            writer.println source.text

         }

      }

   }

}

当首次执行combineFileContentIncremental时,Gradle会完整地执行该Task。但是紧接着再执行一次,命令行显示:

:combineFileContentIncremental UP-TO-DATE

BUILD SUCCESSFUL

Total time: 2.104 secs

combineFileContentIncremental被标记为UP-TO-DATE,表示该Task是最新的,Gradle将不予执行。在实际应用中,你将遇到很多这样的情况,因为Gradle的很多插件都引入了增量式构建机制

如果我们修改了inputs中的任何一个文件Gradle又会重新执行,因为此时的Task已经不再是最新的了。对于outputs,我们还可以使用upToDateWhen()方法来决定一个Task的outputs是否为最新的,该方法接受一个闭包作为检查条件


设置和读取Project的Property是使用Gradle的一个很重要的方面。比如,很多Plugin都会向Project中加入额外的Property,在使用这些Plugin时,我们需要对这些Property进行赋值Gradle在默认情况下已经为Project定义了很多Property,其中比较常用的有:

project:Project本身

name:Project的名字

path:Project的绝对路径

description:Project的描述信息

buildDir:Project构建结果存放目录

version:Project的版本号

我们首先设置Project的version和description属性,再定义showProjectProperties以打印这些属性:

version = 'this is the project version'

description = 'this is the project description'

task showProjectProperties << {

   println version

   println project.description

}

在打印description时,我们使用了project.description而不是直接使用description原因在于,Project和Task都拥有description属性,而定义Task的闭包将delegate设置成了当前的Task,故如果直接使用description,此时打印的是showProjectProperties的description,而不是Project的,所以我们需要显式地指明project

Gradle还为我们提供了多种方法来自定义Project的Property,其中之一是build.gradle文件中定义Property


build.gradle文件中向Project添加额外的Property时,我们并不能直接定义,而是应该通过ext来定义如果要添加一个名为property1的Property,我们应该:

ext.property1 = "this is property1"

 另外,我们也可以通过闭包的方式:

ext {

   property2 = "this is property2"

}

在定义了Property后,使用这些Property时我们则不需要ext,而是可以直接访问:

task showProperties << {

   println property1

   println property2

}

事实上,任何实现了ExtensionAware接口的Gradle对象都可以通过这种方式来添加额外的Property,比如Task也实现了该接口