如何编写Gradle构建?Gradle脚本编写完整指南

2021年3月8日16:26:29 发表评论 1,164 次浏览

本文概述

前言

编写Gradle构建脚本

我在前一篇文章中讨论了一些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 Java项目构建

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 Android项目构建

总结

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"
    }

}
木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: