本文概述
前言
我在前一篇文章中讨论了一些Gradle构建的基本内容,但是后来觉得还不够完整,至少应付一般项目构建问题还有些差距,所以打算再写一下关于Gradle构建的重要问题,于是就有了本文。
使用Gradle构建项目,首先要求我们要有Groovy或Kotlin的基础,因为编写脚本要用到这其中之一的语言。而编写脚本最常见的是围绕任务进行,除了任务其它就是一些简单的配置了,这些配置需要查看相关插件的DSL或Groovy DSL或Gradle自身的DSL。
但是一般情况下,你可能并不需要编写大量的任务,Gradle还提供自定义任务类型,我觉得真是好复杂,对比Maven简直是太过分了!我们可以使用插件避免编写过多的任务,常用的例如构建Java应用程序、Java库和Android项目,所以正常来说,只要用了这些插件,基本是不需要编写过于复杂的逻辑了。
但是我觉得还是需要学一下Groovy,学习如何编写Gradle任务,以免遇到问题手忙脚乱,百度谷歌半天也不知道怎么搞的,其实就是没有这些基础,往往就是这样浪费了很多时间和精力。
编写任务和构建脚本基础
项目、插件和任务
在Gradle构建中,一个独立的库项目、Web应用程序、Jar模块等都是一个项目,一个项目对应一个构建文件,一个构建文件对应一个Project对象,也就是说在构建脚本中编程,Project对象是当前脚本的全局对象,可以直接使用。
任务Task是一个构建的原子操作,例如编译源代码、打包或归档文件、创建JAR、生成Javadoc等,都是一个任务,一个项目的构建脚本可能有多个任务。
插件为构建脚本提供一些已定义的任务或默认属性,例如编译源文件、打包、生成Javadoc等,使用插件可以使我们不用再编写过多复杂的任务,要是你懒——用一个插件就行了,然后其它就是一些简单的配置了。
构建脚本
Gradle构建脚本默认名称为build.gradle,当然你也可以更改默认文件,但是这么偏门的东西我就不讨论了,官方文档中有提到。
构建脚本使用Groovy或Kotlin语言编写,如果你不懂这其中之一的语言,可能看起来会糊里糊涂。
创建任务
Gradle的Project对象有一个tasks对象,这是一个任务容器,保存构建的所有任务。创建任务的方式有以下形式:
使用tasks的register方法:
tasks.register('hello') {
println 'hello configuration'
doLast {
println 'hello task'
}
}
其中’hello’是任务的名称,后面的闭包是任务的动作。
使用task关键字定义一个任务:
task hello {
println 'hello configuration'
doLast {
println 'hello task'
}
}
这是一种常见的形式,也是推荐的形式。
另外我们也可以指定任务的类型,这个类型指定了任务动作闭包的代理delegate,例如:
task hello(type: Copy) {
from 'src'
into buildDir
}
我们也可以在创建任务后使用def定义一个变量来接收,这样我们可以重复方便地使用这个任务。
批量创建任务
我们可以使用N.times{}来批量创建多个任务,例如:
5.times { counter ->
tasks.register("task$counter") {
println "task $counter"
}
}
任务依赖
指定A任务依赖于B任务,执行A的时候,B任务先于A任务执行。任务依赖的作用是使任务按顺序执行,使用task的dependsOn方法指定即可,例如:
5.times { counter ->
tasks.register("task$counter") {
println "task $counter"
}
}
task0.dependsOn 'task2', task3
访问任务
访问任务的方式有多种:
- 通过字符串访问:tasks.named(‘taskName’)
- 使用名称直接访问:taskName
- 作为属性访问:tasks. taskName
- 通过类型访问:tasks.withType(Type)
- 通过路径访问:tasks.getByPath(path),其中path为人物完全限定名。
给任务追加动作
通常doFirst和doLast块为任务的动作,当我们在定义任务后想要继续添加动作,可以通过任务名称直接添加,例如:taskName {do}
默认任务
Gradle若无任务执行,则执行默认任务,默认任务使用defaultTasks方法指定,例如:
5.times { counter ->
tasks.register("task$counter") {
println "task $counter"
}
}
task0.dependsOn 'task2', task3
defaultTasks 'task0'
构建脚本的外部依赖
一般来说我们是添加依赖给项目的,但是如果当前构建脚本需要使用第三方依赖,我们可以在buildscript中添加,具体添加方式请参考Gradle DSL参考文档。
任务效果
用于标记任务的执行状态,Gradle通过对比上一次执行的输入和输出来判定任务的状态,例如在增量构建中,如果输入文件没有更改,那么该构建任务不会执行,控制台显示任务状态为UP-TO-DATE,更多常见的任务状态可参考Gradle文档。
传递参数给任务构造函数
这需要定义一个任务类型,具体步骤为:
- 通过继承DefaultTask自定义任务类型,创建一个DefaultTask的子类。
- 在任务构造函数添加注解@Inject
- 创建任务的时候,在类型后面添加非NULL参数列表,例如:task myTask(type: MyTaskType, p1, P2, P3, …) {}。
任务排序
和dependsOn不同,排序要求A和B都在执行计划的时候,才会应用排序规则,排序的方式有两种:
- must run after:强制排序,使用方式例如:taskB.mustRunAfter(taskA)。
- should run after:可选执行顺序,使用shouldRunAfter方法实现。
添加任务描述
使用task.description,任务描述在执行gradle tasks的时候显示。
跳过任务执行
有四种方式:
- 使用谓词,例如task.onlyIf {}
- 使用StopExecutionException抛出异常
- 启用和禁用任务,task.enabled = /
- 任务超时,task.timeout = /
增量构建
Gradle支持增量构建,但是实现起来非常复杂,需要使用注解指定任务的输入和输出,方式如下:
- 使用getter方法,为输入输出创建类型属性
- 为这些属性添加适当的注解
Project对象
一个构建脚本对应一个project全局对象,支持脚本的所有顶级属性和方法,其它脚本对象还有:
- 设置脚本:Settings
- 初始化脚本:Gradle
Script对象
Gradle将脚本源码编译为实现Script接口的类,该对象也是一个全局对象。
脚本变量
Gradle支持两种变量:
- 本地变量:使用def声明的变量
- 额外属性:Gradle DSL所有对象都可以增加额外属性,使用对象的ext属性添加、读取和设置额外的属性:obj.ext { a b}、obj.ext.a
使用文件
Gradle构建主要是处理项目的文件,所以关于文件处理的API比较重要,文件处理主要包括以下几个方面:
- 复制文件,使用到类型Copy;复制多个文件。
- 创建归档,例如生成zip、jar、war等,任务类型例如Zip、Jar等。
- 解档,使用Copy类型。
- 创建uber或fat jar。
- 创建目录。
- 删除文件或目录,使用Delete类型。
关于以上方面的文件处理基本都要结合任务进行使用,相关的配置应该查看对应的delegate提供的方法或属性,任务有指定类型,则一般delegate就是该类型的对象。
使用插件
插件的作用有:
- 扩展Gradle模型,例如DSL元素
- 按照约定配置项目,例如添加新任务或默认值
- 应用特定的配置,例如添加组织存储库或执行标准
插件是实现了Plugin接口的任何类,可以自定义插件(自定义类),或使用第三方插件。
插件一般有一个ID,它是插件的唯一标识。
应用插件使用plugins{},其中可以使用id、version、apply分别指定插件的ID、版本、是否应用。Plugins可用在构建脚本build.gradle或settings.gradle中,但是不可以用在脚本插件和初始化脚本中。
多项目应用插件的时候可以在根项目的对应插件将apply设置为false,这样子项目则不需要指定版本了。
Gradle的插件
Gradle的构建插件一般有两种:
- 构建应用程序。
- 构建共享库。
Gradle最常用的是Java和Android项目的开发,可用的插件有:
- 构建Java应用程序,其插件ID为application。
- 构建Java库,其插件ID为java-library。
- 构建Android应用程序,在Gradle和Android官方分别提供两种方式,一种是使用plugins指定:id('com.android.application') version '4.1.1',另一种是在buildscript中添加插件依赖:classpath 'com.android.tools.build:gradle:4.0.0'。
总结
Gradle可是真不简单!Gradle构建一般的使用方式是:使用对于平台的插件,编写少量的辅助任务,详细配置脚本(通过参考对应插件的DSL)。
不管如何,不管多么复杂,我还是建议要学一下Groovy,学一下Gradle,这样开发才会更顺利,复制粘贴的程序员有两种:一种是压根不怎么懂,另一种是懂了只是懒得重写了。对于前一种则有些严重,我以前就是,这会造成时间和精力的巨大浪费,一有什么问题就不得了了。
另外还有一个没说的内容,闭包和delegate,简单说下:我们可以给一个闭包增加一个代理delegate对象,这样我们就可以在闭包内访问该对象的方法和属性了,Gradle的配置很多都是类似这样实现的,所以有些方法还是要在闭包内才能访问。
下面是模仿Gradle配置的实现:
class Hello {
static def buildscript(@DelegatesTo(value = BuildScript, strategy = Closure.DELEGATE_FIRST)Closure closure) {
closure.delegate = new BuildScript()
closure.call()
}
static void main(String[] args) {
buildscript {
maven 'central'
dependency 'junit'
}
}
}
class BuildScript {
static def maven(String name) {
println "maven : $name"
}
static def dependency(String name) {
println "dependency : $name"
}
}