本文不会太具体讲编写Gradle插件中用到的API,只是大致梳理一下如何编写一个Gradle插件。
这和是官方对于插件编写的介绍:https://docs.gradle.org/4.3/userguide/custom_plugins.html 。 本文的内容基本是对官方文档的翻译。
编写自定义插件
在Gradle中,插件是用来模块化和重用的组件。我们可以在插件中定义一些常用的方法,以及一些自定义Task
。在build.gradle
中可以使用apply plugin : 'xxx'
来引入一个插件。
我们用的最多的就是 android gradle 插件。
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
}
}
apply plugin: 'com.android.application'
编写一个简单的插件
我们知道在build.gradle
文件中是可以直接写groovy代码的。如果一个插件的功能很简单,我们可以直接把这个插件定义在一个xx.gradle
文件中:
//GreetingPlugin.gradle
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
这个插件很简单,在当前工程下创建一个名为hello
的task。这个任务就是打印一个简单的问候。在工程的build.gradle
我们就可以引用这个插件:
apply from : 'GreetingPlugin.gradle'
运行这个任务:gradle -q hello
。 输出为:Hello from the GreetingPlugin
即定义一个自定义插件我们只需要实现Plugin<Project>
接口。
获取插件的配置
什么是插件的配置呢?比如我们常使用的 android gradle插件:
android {
compileSdkVersion 27
}
这里compileSdkVersion
就是android gradle为android
这个域提供的一些可配置的属性。那么自定义一个插件如何可配置呢?
其实Gradle的Project
关联了一个ExtensionContainer
,ExtensionContainer
中包含所有的插件的设置和属性,我们可以通过Project
的API来添加一个extension object
到ExtensionContainer
中。这样我们就可以在build.gralde
中配置这个extension object
了。如下:
class GreetingPluginExtension { //一个简单的 java bean
String message = 'Hello from GreetingPlugin'
//..当然可以添加更多属性
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
//添加 greeting extension, 在apply插件, 如果ExtensionContainer中就会有greeting这个extension
def extension = project.extensions.create('greeting', GreetingPluginExtension)
project.task('hello') {
doLast {
println extension.message //可以访问到在 build.gradle中配
}
}
}
}
apply plugin: GreetingPlugin
//配置 greeting这个extension
greeting.message = 'Hi from Gradle'
有个疑问: 我们创建的project.extensions.create('greeting', GreetingPluginExtension)
是什么时候放入到ExtensionContainer
?
Gradle的整个构建分为3个阶段: 初始化阶段、配置阶段、执行阶段。 https://www.jianshu.com/p/a45286b08db0
我们自定义的greeting
就是在配置阶段放入到ExtensionContainer
中的。
复杂的自定义配置
当我们想将我们的插件共享给其他人或者我们的插件代码越来越多,我们希望把代码放在一个单独的工程时。我们可以创建一个单独的工程来管理我们的自定义插件。我们可以编译出一个jar
包,或把这个jar
包上传到maven给其他人使用。
步骤也非常简单:
使用gradle构建一个groovy工程,然后依赖gradle api
//build.gradle
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
包含这两个依赖后,我们就可以用它们提供的API来编写我们自定义的gradle插件了。
声明插件的实现类
对于自定义的插件,Gradle有一个约定,我们需要在META-INF/gradle-plugins
提供一个我们插件实现类的声明:
比如我们在src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties
下定义了我们自定义插件的实现类为org.gradle.GreetingPlugin
implementation-class=org.gradle.GreetingPlugin
需要注意: 文件的名字必须是插件的id ,并且官方文档指明,插件的id应该与包名相同。这样可以避免冲突。
编写插件实现
实现很简单,就是把我们上面编写的简单的插件代码,以groovy文件的形式放在我们这个单独的工程中就可以了。
发布插件到maven仓库
我们可以使用maven
插件提供的uploadArchives
来把我们的插件上传到maven
,比如:
apply plugin : 'maven'
uploadArchives {
repositories {
mavenDeployer {
repository(url: 'xxx') {
authentication(userName: 'xx', password: 'xxx')
}
snapshotRepository(url: 'xxx') {
authentication(userName: 'xx', password: 'xxx')
}
pom.project {
artifactId = libArtifactId
version = libVersion
name = libArtifactId
groupId = 'cxxxx'
}
}
}
}
发布插件到本地
如果我们没有maven仓库的话,我们也可以先把插件放到本地,然后依赖本地maven仓库
//plugin.gradle
//定义上传的坐标
group 'com.susion.plugin'
version '0.0.4'
//artifactId 默认为工程名
//把这个插件上传的 “localRepository/libs” 目录下
uploadArchives {
repositories {
flatDir {
name "localRepository"
dir "localRepository/libs"
}
}
}
//主工程的build.gradle
buildscript {
repositories {
flatDir { // 本地的maven仓库
name 'localRepository'
dir "library/localRepository/libs"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.susion.plugin:library:0.0.4' //插件的上传时的坐标
}
}
这样我们就可以在其他的工程中 apply 我们的插件了:
apply plugin: 'plugin id'
使用自定义的插件
将插件发布到maven后,就可以使用自定的插件了。
buildscript {
...
dependencies {
classpath group: 'org.gradle', name: 'customPlugin', version: '1.0-SNAPSHOT'
}
}
apply plugin: 'org.samples.greeting'
为插件提供一个可以配置的DSL
内嵌一个 java bean
前面我们已经知道,我们可以创建一个包含一些简单属性的extension object
(java bean)到ExtensionContainer
。可是如果我们这个java bean
中内嵌一个其他的java bean呢? 那么我们还可以这么简单的在build.gradle
中简单访问内嵌java bean 的属性呢?
答案是不能的,我们需要使用其他一些API来完成这个事情:ObjectFactory
。我么还是直接看官方demo的使用吧:
class Person {
String name
}
class GreetingPluginExtension {
String message
final Person greeter
@javax.inject.Inject
GreetingPluginExtension(ObjectFactory objectFactory) {
greeter = objectFactory.newInstance(Person)
}
void greeter(Action<? super Person> action) {
action.execute(greeter)
}
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// Create the extension, passing in an ObjectFactory for it to use
def extension = project.extensions.create('greeting', GreetingPluginExtension, project.objects)
project.task('hello') {
doLast {
println "${extension.message} from ${extension.greeter.name}"
}
}
}
}
apply plugin: GreetingPlugin
greeting {
message = 'Hi'
greeter {
name = 'Gradle'
}
}
内嵌一个java bean集合
Project.container(java.lang.Class)
可以实例化一个NamedDomainObjectContainer
。传入的这个Class
必须要有一个name
属性,并且必须提供一个含义name
参数的构造函数。NamedDomainObjectContainer
实际上实现了一个Set
。可以理解为一个set集合。
class Book {
final String name
File sourceFile
Book(String name) {
this.name = name
}
}
class DocumentationPlugin implements Plugin<Project> {
void apply(Project project) {
def books = project.container(Book) // 传入book class,返回 NamedDomainObjectContainer, 它是一个set
books.all {
//遍历books中的每一个 Book, 并修改 sourceFile
sourceFile = project.file("src/docs/$name")
}
//将容器添加为extension
project.extensions.add('books', books)
//project.extensions.books = books 向 ExtensionContainer 中添加一个`books`. 它是`NamedDomainObjectContainer<Book>`
}
}
apply plugin: DocumentationPlugin
// Configure the container, books是在插件被apply的时候添加到 ExtensionContainer中的
books {
quickStart { // quickStart作为 Book 的构造函数 name的参数
sourceFile = file('src/docs/quick-start')
}
userGuide {
}
developerGuide {
}
}
task books {
doLast {
books.each { book ->
println "$book.name -> $book.sourceFile"
}
}
}
上面代码可能会对books.all
这个方法有疑问: 看task的执行结果是对每一个 Book都修改了 sourceFile
。 可是刚创建的NamedDomainObjectContainer
,里面并没有对象呀?
我们可以看一下all
这个API,其实它是DomainObjectCollection
的API. NamedDomainObjectContainer
为其子类:
all(Closure action) : Executes the given closure against all objects in this collection, and any objects subsequently added to this collection.
即: 对此集合中的所有对象以及随后添加到此集合的所有对象执行给定的闭包。
了解Gradle的整个构建生命周期之前我们需要先了解一下这些概念:
Gradle项目的组成
先来看一个常见的用Gradle构建的Android的项目(比如这个项目叫Search,主要包含一个搜索library和对library测试的Demo)
Search
--demo
--src
--build.gradle
--library
--src
--build.gradle
--build.gradle
--setting.gradle
相信这个目录对大部分android工程师来说都是挺熟悉的。我们来大致看一下Gradle是如何理解这个目录组织的。
gradle中的工程
其实我们可以认为如果在一个目录下存在build.gradle文件,那么就可以认为这是一个gradle工程(即Project,工程名即目录的名字)。
build.gradle
主要用于对工程进行配置。比如配置版本、依赖、使用的插件等等。
根工程与子工程
上面的Search项目有三个工程: Search、demo、library。从目录结构上看,demo、library好像是Search的子工程,但是如何在gradle中描述这种关系呢?
setting.gradle
setting.gradle
文件是gradle用来初始化整个构建工程树的。默认情况下,如果不配置这个文件,只是在Search目录下执行构建,那么gradle是不会去构建demo和library这两个
项目的。只有在setting.gradle
中配置了这两个工程,它们才算是Search的子工程。才会参与到Search项目的构建。如何配置呢?
如果demo和library项目的目录和settings.gralde
是平级的,那么可以直接这样配置:
include 'demo','library'
gradle会在当前根目录下查找 demo
和library
文件夹,并认为他们是一个工程,将他们加入到构建工程树中。
include 'services:api' -> gradle会去查找 services/api
如果demo和library项目的目录与settings.gradle
不是平级,或者在其他的地方,那么就需要指定这两个工程的目录:
include 'demo','library'
project(':demo').projectDir = new File('dir path')
Task
举一个我们经常运行的命令 : gradle build
这里的build
就是一个task,在gradle中task可以理解为一个操作,比如:打jar包、编译Java代码、上传jar包到maven。除了这些还有一些比较复杂的task,比如build
,它就是由很多task组成的。
一个Project可以包含很多Task。
我们可以在一个工程根目录下运行gradle tasks
查看,这个工程包含哪些task。
Gradle的整个构建生命周期
gradle的构建是在依赖分析完成后才开始的,简单来说就是你配置的所有依赖下载完毕后才会开始构建。在gradle中,整个构建分为3个过程:Initialization、Configuration、Execution。下面就针对这3个阶段简单看一下
Initialization
在初始化阶段,gradle要确定的一件比较重要的事是:这次构建是单工程构建还是多工程。即gradle会去寻找settings.grale
文件。找的大致逻辑是:
- 当前目录查找
settings.grale
文件。查找到后开始构建 - 如果当前目录没有,则找当前目录的父目录有没有,如果有,则按照父目录的settings.grale开始构建
- 如果没有,则如果当前目录存在build.grale,进行单工程构建
确定好是单工程构建还是多工程构建后, 每一个参与构建的工程都会生成一个Project
对象。
不过gradle这种查找settings.gradle
来确定参与构建的工程的机制也存在一个问题:如果我们就是想构建这一个功能呢? 我们可以使用 -u
来使gradle不去查找settings.gradle
gradle -u build
Configuration
在确定了参与构建的工程后,在这个阶段会对每个工程的build.gralde
进行逐行执行,会进行如下任务
- 加载插件
- 加载依赖
- 加载task
- 执行脚本,自定义的插件DSL代码,本身gradle支持的API等
….
Execution
在这个阶段,gradle会根据参与构建的工程,创建这次任务执行的子任务,然后逐一执行这些任务。
关于gradle构建生命周期更细节的一些内容,可以参考官方文档 : https://docs.gradle.org/4.3/userguide/build_lifecycle.html
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/20202,转载请注明出处。
评论0