Gradle 脚本基础全攻略

jopen 9年前

原文 http://android.jobbole.com/82027/

1 背景

在开始Gradle之前请务必保证自己已经初步了解了Groovy脚本,特别是闭包规则,如果还不了解Groovy则可以先看 《Groovy脚本基础全攻略》 这一篇博客速成一下Groovy基础,然后再看此文即可。关于Gradle速成干货基础详情也请参考 Gradle官方网站 ,不好意思我太Low了。

Gradle 脚本基础全攻略

Gradle核心是基于Groovy的领域特定语言(DSL,具体概念参见 《Groovy脚本基础全攻略》 ),具有非常好的扩展性,所以不管是简单的独立项目还是大型的多项目构建它都能高效的提高构建任务,尤其对多项目支持是非常牛逼的;Gradle还提供了局部构建功能,譬如构建一个单独子项目时它会构建这个子项目依赖的所有子项目;当然了他对远程仓库和本地库的支持也很到位;哎呀,总之后面你就明白他的牛逼之处了。

既然Gradle核心是Groovy,Groovy本质又是Java,所以很明显可以发现Gradle环境必须依赖JDK与Groovy库,具体如下:

  • JDK版本必须是JDK6以上;
  • 因为Gradle自带Groovy库, 所以已安装的Groovy会被Gradle忽略;

具体Gradle环境配置好了以后如下图:

Gradle 脚本基础全攻略

2 Gradle DSL基础

Gradle的实质是配置脚本,执行一种类型的配置脚本时就会创建一个关联的对象,譬如执行Build script脚本就会创建一个Project对象,这个对象其实就是Gradle的代理对象。下面给出来各种类型Gradle对应的对象类型:

脚本类型 关联对象类型
Build script Project
Init script Gradle
Settings script Settings

Gradle的三种主要对象解释如下:

  • Project对象:每个build.gradle会转换成一个Project对象。
  • Gradle对象:构建初始化时创建,整个构建执行过程中只有这么一个对象,一般很少去修改这个默认配置脚本。
  • Settings对象:每个settings.gradle会转换成一个Settings对象。

可以看见,当我们编写指定类型Gradle脚本时我们可以直接使用关联对象的属性和方法;当然了,每个脚本也都实现了Script接口,也就是说我们也可以直接使用Script接口的属性与方法。

2-1 构建脚本Build script(Project)

在Gradle中每个待编译的工程都是一个Project(每个工程的build.gradle对应一个Project对象),每个Project在构建的时候都包含一系列Task,这些Task中很多又是Gradle的插件默认支持的。

PS:所谓的我们编写Gradle脚本,实质大多数时候都是在编写构建脚本Build script,所以说Project和Script对象的属性和方法等API非常重要。

每一个Project对象和build.gradle一一对应,一个项目在构建时都具备如下流程:

  1. 为当前项目创建一个Settings类型的实例。
  2. 如果当前项目存在settings.gradle文件,则通过该文件配置刚才创建的Settings实例。
  3. 通过Settings实例的配置创建项目层级结构的Project对象实例。
  4. 最后通过上面创建的项目层级结构Project对象实例去执行每个Project对应的build.gradle脚本。

2-2 初始化脚本Init script(Gradle)和设置脚本Settings script(Settings)

Gradle对象:

初始化脚本Init script(Gradle)类似于Gradle的其他类型脚本,这种脚本在构建开始之前运行,主要的用途是为接下来的Build script做一些准备工作。我们如果需要编写初始化脚本Init script,则可以把它按规则放置在USER_HOME/.gradle/相关目录下。譬如:

Gradle 脚本基础全攻略

初始化脚本的Gradle对象代表了Gradle的调运,我们可以通过调用Project对象的getGradle()方法获得Gradle实例对象。

Settings对象:

在对工程进行配置(譬如多项目树构建)时Settings实例与settings.gradle文件一一对应,它用来进行一些项目设置的配置。这个文件一般放置在工程的根目录。譬如:

Gradle 脚本基础全攻略

2-3 Build生命周期

Gradle的构建脚本生命周期具备三大步,如下:

Gradle 脚本基础全攻略

可以看见,生命周期其实和上面构建脚本Build script的执行流程是可以关联上的。有了这个流程图我们接下里详细看下每个过程。

settings.gradle文件:

除了构建脚本文件,Gradle还定义了一个约定名称的设置文件(默认为settings.gradle)。该文件在初始化阶段被执行,对于多项目构建必须保证在根目录下有settings.gradle文件,对于单项目构建设置文件是可选的,不过建议还是写上。

如下是单项目构建的一个例子:

Java

//settings.gradle  println 'This is executed during the initialization phase.'    //build.gradle  println 'This is executed during the configuration phase.'    task configured {      println 'This is also executed during the configuration phase.'  }    task test << {      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.'  }
//settings.gradle  println'This is executed during the initialization phase.'  //build.gradle  println'This is executed during the configuration phase.'  taskconfigured{   println'This is also executed during the configuration phase.'  }  tasktest<<{   println'This is executed during the execution phase.'  }  tasktestBoth{   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.'  }
</div>

运行构建结果:

Java

> gradle test testBoth  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.  :test  This is executed during the execution phase.  :testBoth  This is executed first during the execution phase.  This is executed last during the execution phase.    BUILD SUCCESSFUL    Total time: 1 secs
>gradletesttestBoth  Thisisexecutedduringtheinitializationphase.  Thisisexecutedduringtheconfigurationphase.  Thisisalsoexecutedduringtheconfigurationphase.  Thisisexecutedduringtheconfigurationphaseaswell.  :test  Thisisexecutedduringtheexecutionphase.  :testBoth  Thisisexecutedfirstduringtheexecutionphase.  Thisisexecutedlastduringtheexecutionphase.     BUILD SUCCESSFUL     Total time:1secs
</div>

Gradle多项目构建:

多项目构建总是需要指定一个树根,树中的每一个节点代表一个项目,每一个Project对象都指定有一个表示在树中位置的路径;在设置文件中我们还可以使用一套方法来自定义构建项目树。

Java

//分层布局的多项目构建settings.gradle文件  include 'project1', 'project2:child', 'project3:child1'
//分层布局的多项目构建settings.gradle文件  include'project1','project2:child','project3:child1'
</div>

上面例子中把project的路径作为了include方法的参数,譬如上面的’project3:child1’参数就指定了物理路径的project3/child1(project3/child1是相对于多项目根路径的相对路径),这也同时意味着会创建’project3’和’project3:child1’两个project。

Java

//平面布局的多项目构建settings.gradle文件  includeFlat 'project3', 'project4'
//平面布局的多项目构建settings.gradle文件  includeFlat'project3','project4'
</div>

上面例子中includeFlat方法接受目录名作为参数,但是特别注意,这些项目目录必须是根目录的兄弟目录。

当然了,设置文件中创建的多项目树其实是由项目描述符来描述的,我们可以在设置文件中随时修改这些描述符。如下:

Java

//settings.gradle  rootProject.name = 'main'  project(':projectA').projectDir = new File(settingsDir, '../my-project-a')  project(':projectA').buildFileName = 'projectA.gradle'
//settings.gradle  rootProject.name='main'  project(':projectA').projectDir=newFile(settingsDir,'../my-project-a')  project(':projectA').buildFileName='projectA.gradle'
</div>

可以看见,如上例子通过描述符更改名称和项目目录,并且建立了一个项目的文件。

Gradle构建初始化Initialization:

在初始化阶段如果我们在根路径下直接指明settings.gradle文件和相关配置则构建初始化就会直接按照我们的设置去构建项目,如果我们没指明settings.gradle文件则Gradle会以一定的规则去寻找settings.gradle文件,然后依据寻找结果的不同去决定如何构建项目。

3 Gradle构建基础

通过上一章可以知道,每一个Gradle构建都是由一个或多个project构成,每一个project都是由一个或多个tasks构成,每个task的实质其实是一些更加细化的构建(譬如编译class、创建jar文件等)。

任务task基础:

如下例子我们先来直观感受一下task的概念,具体细节后面会探讨:

Java

//创建一个名为build.gradle的文件  task hello {      doLast {          println 'Hello world!'      }  }    //这是快捷写法,用<<替换doLast,后面解释  task hl << {      println 'Hello world!'  }    //创建upper的task,使用Groovy语言编写  task upper << {      String someString = 'mY_nAmE'      println "Original: " + someString      println "Upper case: " + someString.toUpperCase()  }
//创建一个名为build.gradle的文件  taskhello{   doLast{    println'Hello world!'   }  }  //这是快捷写法,用<<替换doLast,后面解释  taskhl<<{   println'Hello world!'  }  //创建upper的task,使用Groovy语言编写  taskupper<<{   StringsomeString='mY_nAmE'   println"Original: "+someString   println"Upper case: "+someString.toUpperCase()  }
</div>

通过如下命令运行构建上面名为hello的task,具体如下:

Java

xxx@XXX:~/$ gradle hello  :hello  Hello world!    BUILD SUCCESSFUL    Total time: 1.037 secs
xxx@XXX:~/$gradlehello  :hello  Hello world!     BUILD SUCCESSFUL     Total time:1.037secs
</div>

可以看见,gradle命令会在当前目录中查找一个叫build.gradle的构建脚本文件,这个构建脚本定义了一个叫做hello的独立task,并且添加了一个action,我们执行了这个task就得到了想要的结果。

在这里再多嘴一句,我们看下task有无action的区别,如下:

Java

//有Action的task  task actionTask << {        println 'I am actionTask'    }    //无Action的task  task noActionTask {        println 'I am noActionTask'    }
//有Action的task  taskactionTask<<{        println'I am actionTask'    }    //无Action的task  tasknoActionTask{        println'I am noActionTask'    }
</div>

一定要记住,在上面这个例子中如果task没有加<<则这个任务在脚本初始化initialization阶段(即无论执行啥task都被执行,具体参见上一章的第一个例子)被执行,如果加了<<则在gradle actionTask后才执行。因为没有加<<则闭包在task函数返回前会执行,而加了<<则变成调用actionTask.doLast(),所以会等到gradle actionTask时执行。

任务task依赖:

我们通过上面task基础感受的例子可以发现,一个build.gradle文件中定义多个task互相没有关系,决定执行的是我们gradle命令后面跟的task名字;那我们要是让他们之间有依赖关系咋办呢?如下:

Java

task taskX(dependsOn: 'taskY') << {      println 'taskX'  }  task taskY << {      println 'taskY'  }
task taskX(dependsOn:'taskY')<<{      println'taskX'  }  tasktaskY<<{      println'taskY'  }
</div>

运行结果如下:

Java

xxx@XXX:~/$ gradle taskX  :taskY  taskY  :taskX  taskX    BUILD SUCCESSFUL    Total time: 1.039 secs
xxx@XXX:~/$gradletaskX  :taskY  taskY  :taskX  taskX     BUILD SUCCESSFUL     Total time:1.039secs
</div>

动态任务task:

我们还可以在Gradle中使用Groovy来创建动态task,如下:

Java

4.times { counter ->      task "task$counter" << {          println "I'm task number $counter"      }  }
4.times{counter->      task"task$counter"<<{          println"I'm task number $counter"      }  }
</div>

运行结果如下:

Java

xxx@XXX:~/$ gradle task1  :task1  I'm task number 1    BUILD SUCCESSFUL    Total time: 1.397 secs
xxx@XXX:~/$gradletask1  :task1  I'mtasknumber1     BUILD SUCCESSFUL     Total time:1.397secs
</div>

使用已存在任务task:

我们除过在上面定义任务task时指明依赖以外还可以通过API为任务加入一个依赖,如下:

Java

4.times { counter ->      task "task$counter" << {          println "I'm task number $counter"      }  }  task0.dependsOn task2, task3
4.times{counter->      task"task$counter"<<{          println"I'm task number $counter"      }  }  task0.dependsOntask2,task3
</div>

运行结果如下:

Java

xxx@XXX:~/$ gradle task0  :task0  I'm task number 2  I'm task number 3  I'm task number 0    BUILD SUCCESSFUL    Total time: 1.397 secs
xxx@XXX:~/$gradletask0  :task0  I'm task number 2  I'mtasknumber3  I'mtasknumber0     BUILD SUCCESSFUL     Total time:1.397secs
</div>

或者我们还可以通过API为任务加入一些新行为,如下:

Java

task hello << {      println 'Hello Earth'  }  hello.doFirst {      println 'Hello Venus'  }  hello.doLast {      println 'Hello Mars'  }  hello << {      println 'Hello Jupiter'  }
taskhello<<{   println'Hello Earth'  }  hello.doFirst{   println'Hello Venus'  }  hello.doLast{   println'Hello Mars'  }  hello<<{   println'Hello Jupiter'  }
</div>

运行结果如下:

Java

xxx@XXX:~/$ gradle hello  :hello  Hello Venus  Hello Earth  Hello Mars  Hello Jupiter    BUILD SUCCESSFUL    Total time: 1.397 secs
xxx@XXX:~/$gradlehello  :hello  Hello Venus  Hello Earth  Hello Mars  Hello Jupiter     BUILD SUCCESSFUL     Total time:1.397secs
</div>

可以发现,doFirst和doLast可以被执行多次,<<操作符实质就是doLast。

任务task短标记:

我们可以通过美元符将一个task作为另一个task的属性,如下:

Java

task hello << {      println 'Hello world!'  }  hello.doLast {      println "Greetings from the $hello.name task."  }
taskhello<<{      println'Hello world!'  }  hello.doLast{      println"Greetings from the $hello.name task."  }
</div>

执行结果如下:

Java

xxx@XXX:~/$ gradle hello  :hello  Hello world!  Greetings from the hello task.    BUILD SUCCESSFUL    Total time: 1.397 secs
xxx@XXX:~/$gradlehello  :hello  Hello world!  Greetings fromthehellotask.     BUILD SUCCESSFUL     Total time:1.397secs
</div>

可以看见,上面脚本中使用的name其实是任务的默认属性, 代表当前任务的名称。

自定义任务task属性:

我们还可以给任务task加入自定义的属性,如下例子:

Java

task myTask {      ext.myProperty = "myValue"  }    task printTaskProperties << {      println myTask.myProperty  }
taskmyTask{      ext.myProperty="myValue"  }     taskprintTaskProperties<<{      printlnmyTask.myProperty  }
</div>

执行结果如下:

Java

xxx@XXX:~/$ gradle printTaskProperties  :printTaskProperties  myValue    BUILD SUCCESSFUL    Total time: 1.397 secs
xxx@XXX:~/$gradleprintTaskProperties  :printTaskProperties  myValue     BUILD SUCCESSFUL     Total time:1.397secs
</div>

定义默认任务task:

Gradle允许在脚本中定义一个或多个默认任务,如下:

Java

defaultTasks 'clean', 'run'    task clean << {      println 'Default Cleaning!'  }    task run << {      println 'Default Running!'  }    task other << {      println "I'm not a default task!"  }
defaultTasks'clean','run'  taskclean<<{   println'Default Cleaning!'  }  taskrun<<{   println'Default Running!'  }  taskother<<{   println"I'm not a default task!"  }
</div>

执行结果如下:

Java

xxx@XXX:~/$ gradle  :clean,run  Default Cleaning!  Default Running!    BUILD SUCCESSFUL    Total time: 1.397 secs
xxx@XXX:~/$gradle  :clean,run  DefaultCleaning!  DefaultRunning!     BUILD SUCCESSFUL     Total time:1.397secs
</div>

4 Gradle依赖管理基础

大多数项目都不是完全独立的,它们需要依赖其他项目进行编译等,Gradle允许你告诉它你项目的依赖关系,以便找到这些依赖关系,并在你的构建中维护这些依赖关系,依赖关系可能需要从远程的Maven等仓库中下载,也可能是在本地文件系统中,或者是通过多项目构建另一个构建,我们称这个过程为依赖解析。

Gradle依赖声明:

关于依赖声明不解释,直接给个例子,如下:

Java

apply plugin: 'java'    repositories {      mavenCentral()  }    dependencies {      compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'      testCompile group: 'junit', name: 'junit', version: '4.+'  }
apply plugin:'java'  repositories{   mavenCentral()  }  dependencies{   compilegroup:'org.hibernate',name:'hibernate-core',version:'3.6.7.Final'   testCompilegroup:'junit',name:'junit',version:'4.+'  }
</div>

Gradle依赖配置:

在Gradle中依赖可以组合成configurations(配置),一个配置简单地说就是一系列的依赖,通俗说也就是依赖配置;我们可以使用它们声明项目的外部依赖,也可以被用来声明项目的发布。下面我们给出几种Java插件中常见的配置,如下:

  • compile
    用来编译项目源代码的依赖;
  • runtime
    在运行时被生成的类需要的依赖,默认项,包含编译时的依赖;
  • testCompile编译测试代码依赖,默认项,包含生成的类运行所需的依赖和编译源代码的依赖;
  • testRuntime运行测试所需要的依赖,默认项,包含上面三个依赖;

各种各样的插件支持许多标准的配置,我们还可以定义自己的配置。

Gradle外部依赖:

我们可以用Gradle声明许多种依赖,其中有一种是外部依赖(external dependency),它是在当前构建之外的一种依赖,一般存放在远程(譬如Maven)或本地的仓库里。如下是一个外部依赖的例子:

Java

dependencies {      compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'  }
dependencies{      compilegroup:'org.hibernate',name:'hibernate-core',version:'3.6.7.Final'  }
</div>

可以看见,引用一个外部依赖需要用到group、name、version属性。上面的写法还有一种简写,如下规则:

Java

group:name:version
group:name:version
</div>

这是一个简写的例子:

Java

dependencies {      compile 'org.hibernate:hibernate-core:3.6.7.Final'  }
dependencies{      compile'org.hibernate:hibernate-core:3.6.7.Final'  }
</div>

Gradle仓库:

有了上面的外部依赖,你指定会想Gradle是咋找到那些外部依赖文件的。其实Gradle会在一个仓库(repository)里找这些依赖文件,仓库其实就是很多依赖文件的集合服务器, 他们通过group、name、version进行归类存储,好在Gradle可以解析好几种不同的仓库形式(譬如Maven等),但是Gradle默认不提前定义任何仓库,我们必须手动在使用外部依赖之前定义自己的仓库。

下面是一个使用MavenCentral仓库的例子:

Java

repositories {      mavenCentral()  }
repositories{      mavenCentral()  }
</div>

这是一个使用远程Maven仓库的例子:

Java

repositories {      maven {          url "http://repo.mycompany.com/maven2"      }  }
repositories{      maven{          url"http://repo.mycompany.com/maven2"      }  }
</div>

这是一个使用本地文件系统里库的例子:

Java

repositories {      ivy {          // URL can refer to a local directory          url "../local-repo"      }  }
repositories{      ivy{          // URL can refer to a local directory          url"../local-repo"      }  }
</div>

当然了,一个项目可以有好几个库,Gradle会根据依赖定义的顺序在各个库里寻找它们,在第一个库里找到了就不会再在第二个库里找它了,否则在第二个库找。

Gradle发布artifacts:

依赖配置也可以用来发布文件,我们可以通过在uploadArchives任务里加入仓库来完成。下面是一个发布到Maven 库的例子,Gradle将生成和上传pom.xml,如下:

Java

apply plugin: 'maven'    uploadArchives {      repositories {          mavenDeployer {              repository(url: "file://localhost/tmp/myRepo/")          }      }  }
apply plugin:'maven'     uploadArchives{      repositories{          mavenDeployer{              repository(url:"file://localhost/tmp/myRepo/")          }      }  }
</div>

5 Gradle命令

多任务调用命令:

Java

gradle task1 task2 [...]
gradle task1task2[...]
</div>

排除任务命令:

Java

gradle -x task1 task2 [...]
gradle-xtask1task2[...]
</div>

失败后继续执行构建命令:

只要有任务调用失败Gradle默认就会中断执行,我们可以使用–continue选项在一次调用中不中断执行,然后发现所有失败原因。

简化任务名命令:

当我们调用某个任务时如果名字太长我们可以采用简化操作,但是必须保证可以唯一区分出该任务的字符,譬如:

Java

//简写  gradle -x t1  //替换  gradle -x task1
//简写  gradle-xt1  //替换  gradle-xtask1
</div>

选择执行构建命令:

调用gradle命令默认会构建当前目录下的build.gradle文件,我们可以使用-b参数选择其他目录的构建文件且当使用此参数时settings.gradle将不会生效。如下:

Java

//选择文件构建subdir/myproject.gradle  task hello << {      println "using build file '$buildFile.name' in '$buildFile.parentFile.name'."  }
//选择文件构建subdir/myproject.gradle  taskhello<<{      println"using build file '$buildFile.name' in '$buildFile.parentFile.name'."  }
</div>

执行过程:

Java

xxx@XXX:~/$ gradle -b subdir/myproject.gradle hello  :hello  using build file 'myproject.gradle' in 'subdir'.    BUILD SUCCESSFUL    Total time: 1.397 secs
xxx@XXX:~/$gradle-bsubdir/myproject.gradlehello  :hello  using buildfile'myproject.gradle'in'subdir'.     BUILD SUCCESSFUL     Total time:1.397secs
</div>

此外我们还可以使用-p参数来指定构建的目录,譬如在多项目构建中可以用-p替代-b参数。如下执行过程:

Java

xxx@XXX:~/$ gradle -p subdir hello  :hello  using build file 'build.gradle' in 'subdir'.    BUILD SUCCESSFUL    Total time: 1.397 secs
xxx@XXX:~/$gradle-psubdirhello  :hello  using buildfile'build.gradle'in'subdir'.     BUILD SUCCESSFUL     Total time:1.397secs
</div>

获取构建信息:

  • gradle projects命令:列出子项目名称列表。
  • gradle tasks命令:列出项目中所有任务。
  • gradle help –task someTask命令:可以显示指定任务的详细信息。
  • gradle dependencies命令:列出项目的依赖列表,所有依赖会根据任务区分,以树型结构展示。

6 编写Gradle脚本

Gradle是以Groovy语言为基础,基于DSL语法的自动化构建工具,一个构建脚本能够包含任何Groovy语言元素,每个脚本都是UTF-8编码的文件。

6-1 Project对象API

前面我们说过,Gradle在构建脚本中定义了一个project,对于构建脚本中每个project其实Gradle都创建了一个 Project类型的对象来关联,当构建脚本执行时它会去配置所关联的Project对象;构建脚本中每个被调用的方法和属性都委托给了当前Project对象。

如下我们看一个使用Project属性的例子:

Java

println name  println project.name
println name  println project.name
</div>

上面两个println语句的输出是一样的;由于name属性没有在当前脚本中定义,所以可以像第一个那样使用自动委托 ,通常我们使用第二中写法。

Project对象提供了一些标准的属性,我们可以在构建脚本中很方便的使用他们,如下:

Name Type Default Value
project Project Project实例对象
name String 项目目录的名称
path String 项目的绝对路径
description String 项目描述
projectDir File 包含构建脚本的目录
build File projectDir/build
group Object 未具体说明
version Object 未具体说明
ant AntBuilder Ant实例对象

具体关于Project的方法详情参阅Project的API文档。这里我们给出Project的apply方法的一个例子,如下:

Java

//加载一个gradle文件  apply from: rootProject.getRootDir().getAbsolutePath() + "/common.gradle"
//加载一个gradle文件  apply from:rootProject.getRootDir().getAbsolutePath()+"/common.gradle"
</div>

6-2 Script对象API

当Gradle执行一个脚本时它会将这个脚本编译为实现了Script的类(在上篇博客 《Groovy脚本基础全攻略》 Groovy的本质编译class代码那块有介绍),也就是说所有的属性和方法都是在Script的接口中声明。

6-3 Gradle对象API

关于Gradle对象的详细属性和API介绍 点我即可 。这里直接给出一个使用Gradle对象的例子,如下:

Gradle 脚本基础全攻略

6-4 Gradle变量声明

在Gradle脚本中有两种类型的变量可以声明,如下:

  • 局部变量
  • 扩展变量

局部变量使用关键字def声明,它只在声明的地方可见,如下:

Java

def dest = "dest"        task copy(type: Copy) {            form "source"            into dest        }
    defdest="dest"         taskcopy(type:Copy){            form"source"            intodest         }
</div>

在Gradle中所有被增强的对象可以拥有自定义属性(譬如projects、tasks、source sets等),使用ext扩展块可以一次添加多个属性。如下:

Java

apply plugin: "java"    ext {      springVersion = "3.1.0.RELEASE"      emailNotification = "build@master.org"  }    sourceSets.all { ext.purpose = null }    sourceSets {      main {          purpose = "production"      }      test {          purpose = "test"          }      plugin {          purpose = "production"      }  }    task printProperties << {      println springVersion      println emailNotification      sourceSets.matching { it.purpose == "production" }.each { println it.name}  }
apply plugin:"java"  ext{   springVersion="3.1.0.RELEASE"   emailNotification="build@master.org"  }  sourceSets.all{ext.purpose=null}  sourceSets{   main{    purpose="production"   }   test{    purpose="test"    }   plugin{    purpose="production"   }  }  taskprintProperties<<{   printlnspringVersion   printlnemailNotification   sourceSets.matching{it.purpose=="production"}.each{printlnit.name}  }
</div>

上面我们用一个ext扩展块向Project对象添加两个扩展属性,当这些扩展属性被添加后,它们就像预定义的属性一样可以被读写。

6-5 Gradle中Groovy使用

这个没啥说的,具体可以参考 《Groovy脚本基础全攻略》 这篇博客,里面有详细介绍。我们这里粗略总结回忆一下即可:

  • Groovy会自动将一个属性的引用转换为相应的getter/setter方法。
  • Groovy调用方法时圆括号可有可无。
  • Groovy为List和Map集合提供了一些操作捷径,譬如apply plugin:’java’中的plugin:’java’其实就是Groovy中的Map,apply是一个方法,省略了括弧而已。

哎呀,详细的还是去看前一篇博客吧。

7 Gradle文件操作基础

实际使用Gradle过程中大多数时候需要操作文件,好在Gradle给我们提供了一些API来快捷处理。

定位文件:

我们可以使用Project.file()方法来定位一个文件获取File对象(详情参考Project的API),如下:

Java

//相对路径  File configFile = file('src/config.xml')  //绝对路径  File configFile = file(configFile.absolutePath)  //项目路径的文件对象   File configFile = file(new File('src/config.xml'))
//相对路径  File configFile=file('src/config.xml')  //绝对路径  File configFile=file(configFile.absolutePath)  //项目路径的文件对象   File configFile=file(newFile('src/config.xml'))
</div>

可以从Project的API发现file()方法能够接收任何形式的对象参数,它会将参数值转换为一个绝对文件对象,通常我们可以传一个String或File实例;如果传的路径是绝对路径,则会被直接构造为一个文件实例,否则会被构造为项目目录加上传递目录的文件对象;当然了,file()方法还能识别URL(譬如file:/some/path.xml等)。

文件集合:

文件集合其实是一组文件,Gradle使用FileCollection接口表示文件集合,Gradle API中许多类都实现了这个接口,譬如dependency configurations等。获取FileCollection实例的一种方法是Project.files(),我们可以传递任何数量的对象参数。如下:

Java

FileCollection collection = files('src/file1.txt',                                    new File('src/file2.txt'),                                    ['src/file3.txt', 'src/file4.txt'])
FileCollection collection=files('src/file1.txt',                                    newFile('src/file2.txt'),                                    ['src/file3.txt','src/file4.txt'])
</div>

使用迭代操作还能将其转换为其他的一些类型,同时我们还可以使用+操作将两个文件集合合并,使用-操作对一个文件集合做减法。如下例子:

Java

// 对文件集合进行迭代  collection.each {File file ->      println file.name  }    // 转换文件集合为其他类型  Set set = collection.files  Set set2 = collection as Set  List list = collection as List  String path = collection.asPath  File file = collection.singleFile  File file2 = collection as File    // 增加和减少文件集合  def union = collection + files('src/file3.txt')  def different = collection - files('src/file3.txt')
// 对文件集合进行迭代  collection.each{Filefile->   printlnfile.name  }  // 转换文件集合为其他类型  Set set=collection.files  Set set2=collectionasSet  List list=collectionasList  Stringpath=collection.asPath  File file=collection.singleFile  File file2=collectionasFile  // 增加和减少文件集合  def union=collection+files('src/file3.txt')  def different=collection-files('src/file3.txt')
</div>

我们也可以向files()方法传递闭包或者可回调的实例参数,当查询集合的内容时就会调用它,然后将返回值转换为一些文件实例,返回值可以是files()方法支持的任何类型的对象。如下例子:

Java

task list << {      File srcDir        // 使用闭合创建一个文件集合      collection = files { srcDir.listFiles() }        srcDir = file('src')      println "Contents of $srcDir.name"      collection.collect { relativePath(it) }.sort().each { println it }        srcDir = file('src2')      println "Contents of $srcDir.name"      collection.collect { relativePath(it) }.sort().each { println it }  }
tasklist<<{   FilesrcDir   // 使用闭合创建一个文件集合   collection=files{srcDir.listFiles()}   srcDir=file('src')   println"Contents of $srcDir.name"   collection.collect{relativePath(it)}.sort().each{printlnit}   srcDir=file('src2')   println"Contents of $srcDir.name"   collection.collect{relativePath(it)}.sort().each{printlnit}  }
</div>

文件树:

文件树可以代表一个目录树结构或一个ZIP压缩文件的内容,FileTree继承自FileCollection,所以我们可以像处理文件集合一样处理文件树,使用Project.fileTree()方法可以得到FileTree实例,它会创建一个基于基准目录的对象。如下:

Java

//以一个基准目录创建一个文件树  FileTree tree = fileTree(dir: 'src/main')    // 添加包含和排除规则  tree.include '**/*.java'  tree.exclude '**/Abstract*'    // 使用路径创建一个树  tree = fileTree('src').include('**/*.java')    // 使用闭合创建一个数  tree = fileTree('src') {      include '**/*.java'  }    // 使用map创建一个树  tree = fileTree(dir: 'src', include: '**/*.java')  tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])  tree = fileTree(dir: 'src', include: '**/*.java', exclude: '**/*test*/**')    // 遍历文件树  tree.each {File file ->      println file  }    // 过滤文件树  FileTree filtered = tree.matching {      include 'org/gradle/api/**'  }    // 合并文件树A  FileTree sum = tree + fileTree(dir: 'src/test')    // 访问文件数的元素  tree.visit {element ->      println "$element.relativePath => $element.file"  }
//以一个基准目录创建一个文件树  FileTree tree=fileTree(dir:'src/main')  // 添加包含和排除规则  tree.include'**/*.java'  tree.exclude'**/Abstract*'  // 使用路径创建一个树  tree=fileTree('src').include('**/*.java')  // 使用闭合创建一个数  tree=fileTree('src'){   include'**/*.java'  }  // 使用map创建一个树  tree=fileTree(dir:'src',include:'**/*.java')  tree=fileTree(dir:'src',includes:['**/*.java','**/*.xml'])  tree=fileTree(dir:'src',include:'**/*.java',exclude:'**/*test*/**')  // 遍历文件树  tree.each{Filefile->   printlnfile  }  // 过滤文件树  FileTree filtered=tree.matching{   include'org/gradle/api/**'  }  // 合并文件树A  FileTree sum=tree+fileTree(dir:'src/test')  // 访问文件数的元素  tree.visit{element->   println"$element.relativePath => $element.file"  }
</div>

我们还可以使用ZIP或TAR等压缩文件的内容作为文件树,Project.zipTree()和Project.tarTree()方法可以返回一个FileTree实例。如下:

Java

// 使用路径创建一个ZIP文件  FileTree zip = zipTree('someFile.zip')    // 使用路径创建一个TAR文件  FileTree tar = tarTree('someFile.tar')    //TarTree可以根据文件扩展名得到压缩方式,如果我们想明确的指定压缩方式则可以如下操作  FileTree someTar = tarTree(resources.gzip('someTar.ext'))
// 使用路径创建一个ZIP文件  FileTree zip=zipTree('someFile.zip')     // 使用路径创建一个TAR文件  FileTree tar=tarTree('someFile.tar')     //TarTree可以根据文件扩展名得到压缩方式,如果我们想明确的指定压缩方式则可以如下操作  FileTree someTar=tarTree(resources.gzip('someTar.ext'))
</div>

指定输入文件:

Gradle中有些对象的属性可以接收一组输入文件,譬如JavaComplile任务的source属性(定义编译的源文件)。如下:

Java

//使用一个File对象设置源目录  compile {      source = file('src/main/java')  }    //使用一个字符路径设置源目录  compile {      source = 'src/main/java'  }    //使用一个集合设置多个源目录  compile {      source = ['src/main/java', '../shared/java']  }    //使用FileCollection或者FileTree设置源目录  compile {      source = fileTree(dir: 'src/main/java').matching {include 'org/gradle/api/**'}  }    //使用闭包设置源目录  compile {      source = {          // Use the contents of each zip file in the src dir          file('src').listFiles().findAll {it.name.endsWith('.zip')}.collect { zipTree(it) }      }  }    compile {      //使用字符路径添加源目录      source 'src/main/java', 'src/main/groovy'      //使用File对象添加源目录      source file('../shared/java')      //使用闭包添加源目录      source { file('src/test/').listFiles() }  }
//使用一个File对象设置源目录  compile{   source=file('src/main/java')  }  //使用一个字符路径设置源目录  compile{   source='src/main/java'  }  //使用一个集合设置多个源目录  compile{   source=['src/main/java','../shared/java']  }  //使用FileCollection或者FileTree设置源目录  compile{   source=fileTree(dir:'src/main/java').matching{include'org/gradle/api/**'}  }  //使用闭包设置源目录  compile{   source={    // Use the contents of each zip file in the src dir    file('src').listFiles().findAll{it.name.endsWith('.zip')}.collect{zipTree(it)}   }  }  compile{   //使用字符路径添加源目录   source'src/main/java','src/main/groovy'   //使用File对象添加源目录   sourcefile('../shared/java')   //使用闭包添加源目录   source{file('src/test/').listFiles()}  }
</div>

复制文件:

我们可以使用复制任务(Copy)进行文件复制操作,复制任务扩展性很强,它可以过滤复制文件的内容,使用复制任务要提供想要复制的源文件和一个目标目录,如果要指定文件被复制时的转换方式则可以使用复制规则,复制规则是一个CopySpec接口的实现,我们使用CopySpec.from()方法指定源文件,CopySpec.into()方法指定目标目录即可。如下:

Java

task copyTask(type: Copy) {      from 'src/main/webapp'      into 'build/explodedWar'  }    task anotherCopyTask(type: Copy) {      //复制src/main/webapp目录下的所有文件      from 'src/main/webapp'      //复制一个单独文件      from 'src/staging/index.html'      //复制一个任务输出的文件      from copyTask      //显式使用任务的outputs属性复制任务的输出文件      from copyTaskWithPatterns.outputs      //复制一个ZIP压缩文件的内容      from zipTree('src/main/assets.zip')      //指定目标目录      into { getDestDir() }  }    task copyTaskWithPatterns(type: Copy) {      from 'src/main/webapp'      into 'build/explodedWar'      include '**/*.html'      include '**/*.jsp'      exclude { details -> details.file.name.endsWith('.html') &&                           details.file.text.contains('staging') }  }    task copyMethod << {      copy {          from 'src/main/webapp'          into 'build/explodedWar'          include '**/*.html'          include '**/*.jsp'      }  }    //在复制时重命名文件  task rename(type: Copy) {      from 'src/main/webapp'      into 'build/explodedWar'      //使用闭包映射文件名      rename { String fileName ->          fileName.replace('-staging-', '')      }      // 使用正则表达式映射文件名      rename '(.+)-staging-(.+)', '$1$2'      rename(/(.+)-staging-(.+)/, '$1$2')  }
task copyTask(type:Copy){   from'src/main/webapp'   into'build/explodedWar'  }  task anotherCopyTask(type:Copy){   //复制src/main/webapp目录下的所有文件   from'src/main/webapp'   //复制一个单独文件   from'src/staging/index.html'   //复制一个任务输出的文件   fromcopyTask   //显式使用任务的outputs属性复制任务的输出文件   fromcopyTaskWithPatterns.outputs   //复制一个ZIP压缩文件的内容   fromzipTree('src/main/assets.zip')   //指定目标目录   into{getDestDir()}  }  task copyTaskWithPatterns(type:Copy){   from'src/main/webapp'   into'build/explodedWar'   include'**/*.html'   include'**/*.jsp'   exclude{details->details.file.name.endsWith('.html')&&         details.file.text.contains('staging')}  }  taskcopyMethod<<{   copy{    from'src/main/webapp'    into'build/explodedWar'    include'**/*.html'    include'**/*.jsp'   }  }  //在复制时重命名文件  task rename(type:Copy){   from'src/main/webapp'   into'build/explodedWar'   //使用闭包映射文件名   rename{StringfileName->    fileName.replace('-staging-','')   }   // 使用正则表达式映射文件名   rename'(.+)-staging-(.+)','$1$2'   rename(/(.+)-staging-(.+)/,'$1$2')  }
</div>

文件同步任务:

同步任务(Sync)继承自复制任务(Copy),当执行时会复制源文件到目标目录,然后从目标目录删除所有非复制文件。如下:

Java

task libs(type: Sync) {      from configurations.runtime      into "$buildDir/libs"  }
task libs(type:Sync){      fromconfigurations.runtime      into"$buildDir/libs"  }
</div>

创建归档文件:

使用归档任务可以创建Zip、Tar、Jar、War、Ear等归档文件,如下:

Java

apply plugin: 'java'    task zip(type: Zip) {      from 'src/dist'      into('libs') {          from configurations.runtime      }  }
apply plugin:'java'     task zip(type:Zip){      from'src/dist'      into('libs'){          fromconfigurations.runtime      }  }
</div>

关于文件操作的其他请参考API文档。

8 Gradle插件

8-1 Gradle插件概述

插件基础:

关于Gradle支持的插件可以 点我搜索 。其实Gradle的核心只是一个空空的框架,所谓的Gradle构建便捷脚本其实都是由插件提供支持的,插件添加了新的任务。在Gradle中一般有两种类型的插件,如下:

  • 脚本插件
    是额外的构建脚本,它会进一步配置构建,通常会在构建内部使用。脚本插件可以从本地文件系统或远程获取,如果从文件系统获取则是相对于项目目录,如果是远程获取则是由HTTP URL指定。
  • 二进制插件
    是实现了Plugin接口的类,并且采用编程的方式来操纵构建。

插件需要通过Project.apply()方法完成声明应用,相同的插件可以应用多次。如下例子:

Java

//脚本插件  apply from: 'other.gradle'    //二进制插件  apply plugin: 'java'
//脚本插件  apply from:'other.gradle'     //二进制插件  apply plugin:'java'
</div>

插件还可以使用插件ID,插件的id作为给定插件的唯一标识符,我们可以给插件注册一个缩写字符的id。譬如下面例子:

Java

//通过Java插件的id进行引用  apply plugin: JavaPlugin
//通过Java插件的id进行引用  apply plugin:JavaPlugin
</div>

使用构建脚本块应用插件:

我们可以向构建脚本中加入插件的类路径然后再应用插件和使用插件的任务,如下:

Java

//通过Java插件的id进行引用  apply plugin: JavaPlugin
//通过Java插件的id进行引用  apply plugin:JavaPlugin
</div>

Gradle插件拓展:

可以看见,Gradle其实是依托于各种插件壮大的,譬如Java插件用来构建Java工程,Android插件用来构建打包Android工程,我们只需要选择合适的插件即可,插件会为我们提供丰富的任务用来快捷处理构建,具体详情参考各插件API即可。

8-2 Gradle的Java插件构建实例

上面说了,插件是Gradle的扩展,它会通过某种方式配置我们的项目(譬如加入一些task);Gradle自带许多插件,我们也可以编写自己的插件然后开源.,Java 插件就是这样的一个插件,该插件已经给项目定义了默认的参数(譬如Java源文件位置),所以通常我们不需要在脚本中加入太多东西。

单个基础Java项目构建:

Java

//把Java插件加入到项目中,也就是许多预定制的任务被自动加入到了项目里  apply plugin: 'java'
//把Java插件加入到项目中,也就是许多预定制的任务被自动加入到了项目里  apply plugin:'java'
</div>

加入上面插件以后Gradle默认希望能在src/main/java路径下找到源代码,在 src/test/java路径下找到测试代码,任何src/main/resources路径的文件都会被包含在JAR文件里,任何src/test/resources路径的文件都会被加入到classpath中以运行测试代码,所有的输出文件将会被创建在构建目录里,JAR文件存放在 build/libs文件夹里。

加入Java插件后我们可以通过gradle tasks命令来列出项目的所有任务,这样就可以知道Java插件添加了哪些task。常用的task如下:

  • build task
    当运行gradle build命令时Gradle将会编译和测试你的代码,并且创建一个包含类和资源的JAR文件。
  • clean task
    当运行gradle clean命令时Gradle将会删除build生成的目录和所有生成的文件。
  • assemble task
    当运行gradle assemble命令时Gradle将会编译并打包代码,但是并不运行单元测试。
  • check task
    当运行gradle check命令时Gradle将会编译并测试你的代码,其他的插件会加入更多的检查步骤。

单个具有外部依赖的Java项目构建:

当然了,一个Java项目可能会有许多外部依赖(即调用第三方JAR),为了在项目里引用这些 JAR包,我们需要告诉Gradle去哪里找他们,好在Gradle支持许多仓库,这些仓库可以被用来提取或者放置依赖,我们可以很方便的从这些仓库中取得第三方Jar包。如下:

Java

//加入Maven仓库  repositories {      mavenCentral()  }
//加入Maven仓库  repositories{      mavenCentral()  }
</div>

接着加入一些编译阶段来自于mavenCentral仓库的依赖,如下:

Java

dependencies {      //编译阶段      compile group: 'commons-collections', name: 'commons-collections', version: '3.2'      //测试编译阶段      testCompile group: 'junit', name: 'junit', version: '4.+'  }
dependencies{      //编译阶段      compilegroup:'commons-collections',name:'commons-collections',version:'3.2'      //测试编译阶段      testCompilegroup:'junit',name:'junit',version:'4.+'  }
</div>

定制构建项目:

Java插件给项目加入了一些属性,这些属性已经被赋予了默认的值且已经够我们日常使用了,如果我们觉得这些默认属性不好也可以自己修改。如下:

Java

//定制 MANIFEST.MF 文件  sourceCompatibility = 1.5  version = '1.0'  jar {      manifest {          attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version      }  }
//定制 MANIFEST.MF 文件  sourceCompatibility=1.5  version='1.0'  jar{      manifest{          attributes'Implementation-Title':'Gradle Quickstart','Implementation-Version':version      }  }
</div>

默认Java插件加入的任务是常规性的任务,但是我们可以定制任务,譬如我们可以设置一个任务的属性、在任务中加入行为、改变任务的依赖、完全重写一个任务等。如下:

Java

//测试阶段加入一个系统属性  test {      systemProperties 'property': 'value'  }
//测试阶段加入一个系统属性  test{      systemProperties'property':'value'  }
</div>

关于哪些属性是可用的问题,我们可以使用gradle properties命令列出项目的所有属性。

发布JAR文件:

通常JAR文件需要在某个地方发布,我们可以通过Gradle方便的进行发布,譬如下面例子将发布到一个本地的目录,如下:

Java

//uploadArchives task  uploadArchives {      repositories {         flatDir {             dirs 'repos'         }      }  }
//uploadArchives task  uploadArchives{      repositories{         flatDir{             dirs'repos'         }      }  }
</div>

多Java项目构建:

在Gradle中为了定义一个多项目构建我们需要创建一个设置文件(settings.gradle),设置文件放在源代码的根目录,它用来指定要包含哪个项目且名字必须叫做settings.gradle。如下例子:

Java

//多项目工程结构树:  multiproject/    api/    services/webservice/    shared/    //多项目构建settings.gradle文件  include "shared", "api", "services:webservice", "services:shared"
//多项目工程结构树:  multiproject/    api/    services/webservice/    shared/     //多项目构建settings.gradle文件  include"shared","api","services:webservice","services:shared"
</div>

对于大多数多项目构建有一些配置对所有项目都是通用的,所以我们将在根项目里定义一个这样的通用配置(配置注入技术 configuration injection)。 根项目就像一个容器,subprojects方法遍历这个容器的所有元素并且注入指定的配置。如下:

Java

//多项目构建通用配置  subprojects {      apply plugin: 'java'      apply plugin: 'eclipse-wtp'        repositories {         mavenCentral()      }        dependencies {          testCompile 'junit:junit:4.11'      }        version = '1.0'        jar {          manifest.attributes provider: 'gradle'      }  }
//多项目构建通用配置  subprojects{   applyplugin:'java'   applyplugin:'eclipse-wtp'   repositories{      mavenCentral()   }   dependencies{    testCompile'junit:junit:4.11'   }   version='1.0'   jar{    manifest.attributesprovider:'gradle'   }  }
</div>

可以看见,上面通用配置把Java插件应用到了每一个子项目中。

我们还可以在同一个构建里加入项目之间的依赖,这样可以保证他们的先后关系。如下:

Java

//api/build.gradle  dependencies {      compile project(':shared')  }
//api/build.gradle  dependencies{      compileproject(':shared')  }
</div>

至此基础的Java插件使用就OK了,深入的请自行查看API。

4 Gradle基础总结

到此Gradle的基础知识就完全介绍完了,我们对Gradle的框架也有了一个直观的认识。其实编写Gradle无非也就是对类的属性和方法进行调运操作,至于如何调运操作依据具体插件而异,核心的生命周期和几个对象实例搞明白基本上就能驾驭Gradle脚本了,其他的无非就是熟练度和API查找。