四、使用Gradle插件

Gradle本身只是一个框架,它的核心部分在构建过程中起的作用实际上很小。真正起作用的步骤来自于插件,比如编译Java代码的功能就是由“java”插件提供。

在本章中,我们会详细价绍如何使用Gradle的插件

4.1 插件能做些什么

插件可以做的事情很多,比如:

  • 扩展Gradle的功能
  • 根据用户的配置来做一些自定义的构建
  • 增加构建多种具体项目的功能,如Android插件

使用插件有许多好处:

  • 促进脚本的复用,可以减少相似的构建脚本
  • 可以更好地组织项目结构
  • 将具体的逻辑封装起来,然后用声明式的方式使用

4.2 插件的类型

4.2.1 脚本插件和二进制插件

Gradle的插件分为两种类型:脚本插件(script plugins)和二进制插件(binary plugins)。

脚本插件 就是额外的构建脚本,脚本插件通常用来对构建过程进行深度配置,同样遵循声明式的思想。脚本插件常常作为另一个脚本文件(即*.gradle) 文件被放置在项目目录中,以本地文件的形式应用插件。虽然脚本插件也可以放置在云端,比如说共享仓库jcenter,但不常用,一般共享的插件都是二进制插件。

二进制插件就是实现了Plugin接口的类,可以用java、kotlin和groovy编写,更容易进行测试,还可以被打包成jar包共享出去。

一个插件项目最开始写的时候通常都是以脚本插件的形式,因为它们更容易编写,当项目变得更有价值之后再被迁移成二进制插件,这样更容易测试以及共享。

4.2.2 核心插件和社区插件

Gradle的插件根据是否内置又分为核心插件社区插件,核心插件是Gradle必要的插件(如java插件),核心插件随着Gradle安装已经解析好了,只需要应用即可;社区插件是共享在社区上的插件,在需要时才被解析到本地。

社区插件会被共享在一些在线仓库中,例如jcenter、Maven仓库等,Gradle还提供了一个专门共享Gradle插件的仓库:Gradle plugin portal,Gradle官方推荐将插件共享在这里。

4.3 Gradle插件的三种实现方式

(本小节仅简单介绍,实现Gradle插件的方法详见“自定义Gradle插件”章节)

4.3.1 脚本插件

可以直接在build.gradle中编写一个实现了org.gradle.api.plugins接口的类, 这个类就是一个插件。另外,这种方式编写的插件就是上文所提的脚本插件

4.3.2 在buildSrc中编写插件类

Gradle提供了一种在现有项目中编写二进制插件的方法,依旧遵循声明式的思想。

二进制插件可以用Java、Kotlin、Groovy多种语言编写,例如使用的是Groovy语言,那么就创建目录rootProjectDir/buildSrc/src/main/groovy,如果是kotlin则为.../kotlin,Groovy则为.../groovy。Gradle会自动编译这些目录下的插件,并且将它们解析,使得在项目的所有构建脚本中都可以应用这些目录下的插件。

4.3.3 独立项目

还可以为你的插件单独建立一个项目,这个项目可以被打包成一个jar包,可以实现更好的复用,还可以分享到在线仓库成为社区插件,给别的项目使用。

4.4 使用插件两步走

使用插件Gradle的插件大致上分为两部,第一步是解析插件,第二步是应用插件。

解析插件 就是找到插件的正确版本,因为Gradle的插件允许有多个版本同时存在,在选择插件时要同时指定插件名和版本。对于二进制插件,因为并不指定它们所在的路径,Gradle要去指定的仓库根据插件名和版本号寻找对应的插件,并解析到本地;而对于脚本插件,它是“自解析”的,因为配置脚本插件时直接指定了它的路径。

应用插件 就是在解析插件完成之后将插件的脚本内容应用到项目的构建中,对于二进制插件,会在此时调用它的apply方法(这个方法定义在Plugin接口中)。

4.5 传统apply语法

使用插件有两种语法,一种是传统的apply语法,另一种是DSL语法。我们先介绍传统的apply语法。

每个插件都有一个id,我们可以通过指定插件的id的方式来应用插件,以应用java插件为例:

//build.gradle
apply plugin: 'java'

也可以直接指定插件的实现类的类名

//build.gradle
apply plugin: JavaPlugin

对于内置插件,我们可以直接通过上述的apply语法应用插件,指定类名时也不需要前缀(org.gradle.api.plugins)以及.class后缀。

但对于社区插件,我们还需要先解析插件。解析插件使用的是buildscript{}语法块,语法规则如下:

//build.gradle
buildscript {
	repositories {
		jcenter()
	}
	dependencies {
		classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:0.4.1'
	}
}
apply plugin: 'com.jfrog.bintray'

里面还有两个子块:

  • repositories{}语法块,用于指定仓库,有以下常用选项:
  • mavenLocal():本地Maven仓库( ${user.home}/.m2/repository )
  • mavenCentral():中央Maven仓库( http://repo1.maven.org/maven2 )
  • maven { url 'https://...' }:可用于Maven私服、镜像服务器等
  • ivy {url "../local-repo"}:本地的ivy仓库
  • ivy {url "http://repo.mycompany.com/repo"}:远程的ivy仓库
  • google():google仓库(https://maven.google.com)
  • dependencies{}语法块,用于指定要使用的插件,由classpath关键字指定,格式为:classpath 'group:name:version'

4.6 plugins DSL语法

Gradle鼓励将Gradle插件共享在它的Gradle plugin portal上,对于上面的插件Gradle还提供了简洁的应用插件的语法,这就是“plugins DSL"语法。

因为解析插件和应用插件通常都同时使用,所以plugins DSL语法默认同时对插件进行解析和应用,核心插件和社区插件的使用方式略有不同,核心插件只需要指定插件名,以java插件为例:

//build.gradle
plugins {
	id 'java'
}
//build.gradle.kts
plugins {
	java
}

社区插件还需要指定版本

//build.gradle
plugins {
	id 'com.jfrog.bintray' version '0.4.1'
}
//build.gradle.kts
plugins {
	id("com.jfrog.bintray") version "0.4.1"
}

DSL语法存在着一些限制,首先是语法规则比较严格,这也是出于声明式的思想,语法规则如下:

//build.gradle
plugins {
	id «plugin id» ①
	id «plugin id» version «plugin version» [apply «false»] ②
}
//① 适用于核心插件,或者已经解析好的插件、或者buildSrc中的插件
//② 适用于社区插件,因为DSL语法默认同时解析和应用插件,加上apply «false»可以只解析不应用
//apply false的具体用法见后续章节

而且DSL语法只能用于构建脚本中,不能用于脚本插件中,也不能用于settings.gradleinit script,不过Gradle官方说会在之后的版本移除这种限制(笔者当前版本是5.6.3)

4.7 Plugin Management语法块

4.7.1 基本语法

pluginManagement{}语法块是专门用于管理整个项目插件的,只能出现在settings.gradle文件或”初始化脚本“中,并且在settings.gradle文件中pluginManagement{}必须是文件中的第一个块。

语法格式如下(限于篇幅,仅以groovy举例,kotlin语法见手册P313):

//build.gradle
pluginManagement {
	plugins {
	}
	resolutionStrategy {
	}
	repositories {
	}
}
//init.gradle
settingsEvaluated { settings ->
	settings.pluginManagement {
		plugins {
		}	
		resolutionStrategy {
		}
		repositories {
		}
	}
}
4.7.2 设置插件的来源仓库

默认情况下,plugins DSL语法会从Gradle plugin portal去解析插件,但许多插件开发者喜欢使用私有的Maven或Ivy仓库,为了指定使用的插件仓库,Gradle提供了repositories{}语法块,放在pluginManagement语法块中使用,用于管理整个项目使用的插件仓库。

语法规则如下(以本地路径为例,如果使用网络资源就将路径替换成http url):

//build.gradle
pluginManagement {
	repositories {
		maven {
			url '../maven-repo'
		}
		gradlePluginPortal()
		ivy {
			url '../ivy-repo'
		}
	}
}
//注:指定的仓库顺序就是Gradle寻找插件顺序的优先级,本例中会先
//去maven仓库尝试解析所需插件,如果找不到再去gradlePluginPortal
//如果再找不到,最后再去ivy仓库
4.7.3 插件版本管理

Gradle提供了plugins{}语法块,用于pluginManagement语法块中,用于统一指定整个仓库使用的某个插件的版本。

使用这种方式管理插件的版本有一些好处,例如在多projects构建中很有用,因为在多projects构建中统一每个子project依赖的插件版本很有必要;另外,前面提过,DSL语法在build.gradle有严格的限制,但是在settings.gradle中语法却比较宽松,这允许它从gradle.properties文件中去获取版本。

在根目录下的settings.gradle指定了插件的版本后,在build.gradle中只需要指定插件的id即可,插件版本配置在gradle.properties中。

使用示例如下:

//settings.gradle
pluginManagement {
	plugins {
		id 'org.gradle.sample.hello' version "${helloPluginVersion}"
	}
}
//build.gradle
plugins {
	id 'org.gradle.sample.hello'
}
//gradle.properties
helloPluginVersion=1.0.0