×

Gradle自定义Plugin(上)

96
呆萌狗和求疵喵
2016.05.11 23:13* 字数 5801

这篇文章讲给大家带来gradle打包系列中的高级用法-自己动手编写gradle插件。我们平常在做安卓开发时,都会在android这个插件提供的功能内使用,大部分情况下,配置好这个插件就够了,但是有时候我们想做一些额外的拓展,比如对build输出的Apk进行上传。当然通过在工程中添加额外的task就可以了,但是如果把这个功能做成插件,就会更加通用。那今天我们就从groovy基础语法开始,剖析一下gradle的原理,最后教大家如何自定义Gradle插件。

这篇文章里面会涉及到Groovy的语法,gradle的基础知识等等,我不会专门去讲,但是凡是在与本次主题相关的概念都会给大家梳理一下。

在Gradle构建中,我们需要在build.gradle文件或者settings.gradle里面写上自己的配置,这些配置语句,看起来是符合某个标准的,就像SQL语句那样,我们如果要查询就得用Select语句,然后按照Select支持的语法来书写。我们如果想对gradle的构建过程进行配置就得遵循Gradle支持的语法。这样在某个领域通用的语言就叫做DSL(Domain specific languange ), 直译就是领域专用语言。

Martin Fowler将DSL分为两类:外部DSL和内部DSL。外部DSL是一种独立的可解析的语言,举一个最常见的是例子,SQL,它专注于数据库的操作。内部DSL是通用语言所暴露的用来执行特定任务的API,它利用语言本身的特性,将API以特殊的形式(或者格式)暴露出来的,就像gradle,它是基于groovy的,groovy是是一种通用语言,但是gradle基于groovy的语法,构建了自己的一套DSL,我们在配置gradle时,必须首先遵循groovy的语法,其次还必须遵循Gradle 的DSL标准。

闲话少说,要自己动手编写Gradle插件,必须首先对Groovy的语法有一定的了解,尤其是Groovy中的闭包语法。

Groovy essentials

Groovy是一种基于JVM的语言,了解groovy的一些语法,对于理解并用活gradle有很大的帮助

Groovy语法特性一:方法的输入参数优化

groovy中定义的函数,如果至少有一个参数,在调用的时候可以省略括号。比如这样

def func(String a){
  println(a)
}
func 'hello'

在gradle有大量省略括号调用函数的例子,比如

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
    ....
}

比如这里compileSdkVersion 和 buildToolsVersion 其实就是调用了同样名字的两个函数,在AndroidStudio里面可以点进去查看函数实现

当然如果某个函数没有参数,那就不能省略括号,否则会当成一个变量使用

Groovy语法特性二:闭包

闭包(Closure)是groovy中一个很重要的概念,而且在gradle中广泛使用。what is closure? 简而言之,闭包是一个可执行的代码块,类似于C语言中的函数指针。在很多动态类型语言中都有广泛的使用,java8 中也有类似的概念:lambda expression,但是groovy中的闭包和java8中的lambda表达式相比又有很多的不同之处。稍后会提到,先来学习一下groovy中的闭包

def listener = { e -> println "Clicked on $e.source" }

此时listener就是一个闭包,

闭包参数

闭包参数的构成有三个元素,

  • 一个可选的参数类型
  • 一个可选的参数默认值
  • 参数名称
def xx = {
  int a=2 ->
   a + b
}

闭包对象

grooy中闭包是Closure类型的实例,比如上面的闭包我们又可以定义为:

Closure xx = {
  int a=2 ->
   a + b
}

而且你可以制定一个可选的返回类型

 Closure<Integer> xx = {
   int a=2 ->
    a + b
 }

隐含参数
如果闭包内没有生命任何参数,没有->, 那么闭包内置会定义一个隐含参数it

def greeting = { "Hello, $it!" }
闭包代理(Closure delegate)

闭包区别lambda表达式的一个显著的区别在与groovy中的闭包可以指定代理。

实际上在闭包内,可以拿到三个对象:

  • This 对应于定义闭包时包含他的class,可以通过getThisObject或者直接this获取
class EnclosedInInnerClass {
   class Inner {
       Closure cl = { this }                               
   }
   void run() {
       def inner = new Inner()
       assert inner.cl() == inner                          
   }
}
  • owner 对应于定义闭包时包含他的对象,可以通过getOwner或者直接owner获取
class EnclosedInInnerClass {
  void run() {
     def nestedClosures = {
         def cl = { owner }                               
         cl()
     }
     assert nestedClosures() == nestedClosures            
 }
}
  • delegate 闭包对象可以指定一个第三方对象作为其代理,用于函数调用或者属性的指定,可以通过getDelgate或者delegate属性获取

    class Person {
        String name
    }
    class Thing {
        String name
    }
    
    def p = new Person(name: 'Norman')
    def t = new Thing(name: 'Teapot')
    
    def upperCasedName = { delegate.name.toUpperCase() }
    
    upperCasedName.delegate = p
    assert upperCasedName() == 'NORMAN'
    upperCasedName.delegate = t
    assert upperCasedName() == 'TEAPOT'
    

代理策略

如果在闭包内,没有明确指定属性或者方法的调用是发生在this, owner,delegate上时,就需要根据代理策略来判断到底该发生在谁身上。有如下几种代理策略:

  • Closure.OWNER_FIRST 默认的策略,如果属性或者方法在owner中存在,调用就发生在owner身上,否则发生在delegate上

  • Closure.DELEGATE_FIRST 跟owner_first正好相反

  • Closure.OWNER_ONLY 忽略delegate

  • Closure.DELEGATE_ONLY 忽略owner

  • Closure.TO_SELF 调用不发生在owner或delegate上,只发生在闭包内

class Person {
    String name
    def pretty = { "My name is $name" }             
    String toString() {
        pretty()
    }
}
class Thing {
    String name                                     
}

def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')

#调用发生在owner
assert p.toString() == 'My name is Sarah'           
p.pretty.delegate = t                               
assert p.toString() == 'My name is Sarah'

#调用发生在delegate
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'

groovy中的闭包还有很多种高级的使用方法,但是在gradle中比较少用到,你可以点击这个链接 Groovy闭包 去了解。

Groovy语法特性三:类的Property

Groovy中的class和java中的Class区别不大,值得我们关注的区别是,如果类的成员变量没有加任何权限访问,则称为Property, 否则是Field,filed和Java中的成员变量相同,但是Property的话,它是一个private field和getter setter的集合,也就是说groovy会自动生成getter setter方法,因此在类外面的代码,都是会透明的调用getter和setter方法


 class Person {
    String name
    void name(String name) {
        this.name = "Wonder$name"       
    }
    String wonder() {
        this.name                       
    }
}
def p = new Person()
p.name = 'Marge'                      //调用setter方法                  
assert p.name == 'Marge'             //调用getter方法    
p.name('Marge')                         
assert p.wonder() == 'WonderMarge'

我们在gradle脚本中经常会看到这样的属性

//gradle扩展
ext{
    name 'haha'
}

这里name属性实际上就是调用了setName方法,并且利用了之前说的省略括号的特性,实际上就是 调用了setName('haha')
还有我们常见的在project中创建task的方法

task taskName << {
    doSomeThing
}

实际上就是调用了task(taskName).leftShift(closure),这里又扯出来Groovy中的两个语法特性,一个是操作符重载,这个早C++里面用到很多,这个语法特性可以让你的类,可以像基本类型一样使用普通的操作符,比如 + - * / 等,只需要你重载了这些函数。


class Bucket {
    int size

    Bucket(int size) { this.size = size }

    Bucket plus(Bucket other) {                     
        return new Bucket(this.size + other.size)
    }
}

def b1 = new Bucket(4)
def b2 = new Bucket(11)
assert (b1 + b2).size == 15   //b1 和b2 可以直接相加

上面那个task的例子,就是因为task类重载了leftShift,因此可以使用<< 操作符

//Project类
Task task(String var1) throws InvalidUserDataException;
//Task类
Task leftShift(Closure var1);

task例子中用到的另外一个语法特性是Command Chains, 这个特性不仅可以省略函数调用中的括号,而且可以省略,连续函数调用中的. 点号, 比如
a(b).c(d) 这里a c是函数, b,d是函数参数, 就可以缩写为a b c d。这个特性强大之处在于不仅适用于单个参数类型函数,而且适用于多个参数类型的函数,当参数类型为闭包时同样适用, 比如这样

task xxxxx  doLast { println "task doLast 1"}  doLast { println "task doLast 2"}

这么看起来是不是跟sql语句就有点儿像了,哈哈,DSL。

上面所提到的那些groovy的语法特性,构成了Gradle中DSL的基础,理解了这些就能理解groovy是如何为gradle dsl提供支撑的。

说完了groovy的这些基础语法,我们再来学习一下gradle的一些基础知识,这样就不难理解。

Gradle Basics

gradle的语法和入门指导,可以从gradle的官网上找到,如果需要像教科书那样的教程,完成可以去官网上学习,我们这里只会从中提炼出一些核心知识,了解了这些核心知识,我认为就足够了。

我们在AndroidStudio中创建基于Gradle的project时,会默认生成一个多项目结构的Gradle工程,像下面这样

Gradle muti-project structure

对于这个project,我们能对构建过程做出改变的,就只能发生在这些.gradle文件中,这些文件称为Build Script构建脚本。对于Gradle中的构建脚本,你一方面可以理解为配置文件,每一种类型脚本文件都是对某一中类型的构建对象进行配置。但另一方面你可以把每个脚本理解为一个Groovy闭包,这样我们在执行构建脚本时,就是在执行每一个闭包函数,只不过每个闭包所设置的delegate不一样

Paste_Image.png

Build Script

Gradle中共有三种类型的构建脚本:build script, init script, setting script,分别对应三种类型的Gradle构建对象。

我们先来说说这三种不同的构建脚本:

Init Script

init script我们大部分人并不常用,但是它确实可以配置,你可以通过如下方式指定init script

  1. 通过在命令行里指定gradle参数 -I 或者--init-script <pathToInit.gradle>
  2. USER_HOME/.gradle 或者 USER_HOME/.gradle/init.d目录下,放置init.gradle文件
  3. 将init.gradle放置在GRADLE_HOME/init.d/目录下

init.gradle脚本中,我们可以对当前的build做出一些全局配置,比如全局依赖,何处寻找插件等。

initscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'org.apache.commons', name: 'commons-math', version: '2.0'
    }
}

Settings script

Settings.gradle文件在项目创建于rootProject根目录下,因为settings.gradle对于多项目工程来说是必须的,然而对于单项目project来说不是必须的。Settings脚本的Delegate是Settings对象,因此我们可以瞅瞅Settings类里面都有哪些函数

Settings类

我们可以看到有常见的include 函数 和 includeFlat函数, 因为我们最常做的事情就是在settings脚本里面写上include ':app'这样的函数调用语句,根据groovy的语法,他就是在gradle生成的settings对象调用函数 include('app')
include接受的参数是一个string数组,因此include后可以加很多参数,这个函数的意义就是:指明那些子project参与这次gradle构建

Build Script

build.gradle脚本文件使我们最常见的,绝大部分配置工作发生在这里,因为它的Delegate就是Project,如果是多项目结构,则build.gradle文件分为两种,rootProject下的build.gradle文件,subProject下的build.gradle文件,每个build.gradle文件执行时的代理project都不一样。

project中有很多函数,比如我们最常见的apply, dependencies等

//apply一个插件或者脚本
void apply(Map<String, ?> options);
//配置当前project的依赖
void dependencies(Closure configureClosure);
//配置当前脚本的classpath
void buildscript(Closure configureClosure);

这些函数都可以在AndroidStudio里点击到Project里面去一探究竟,如果理解了groovy的闭包,相信不会再对build.gradle里面那些配置感到神秘,他们也只是在某个对象上调用了某个函数,传入了某个闭包或者其他对象作为参数

至于这里面提到的gradle, project, settings对象何时生成,我们可以通过了解gradle的build lifecycle了解到

Build life cycle

Gradle最终的目的是运行gradle中的task(task我们后面会单独介绍)

gradle <sometask>

在gradle运行的过程中,gradle会经历三个阶段

  1. Initialization 在这个阶段,gradle会首先生成gradle对象和settings对象,然后执行init.gradle中脚本,再执行settings.gradle中的脚本,根绝settings.gradle给每个项目生成一个project对象

  2. Configuration 在这个阶段,gradle会运行参与本次构建的所有project中的build.gradle文件,这个阶段完成之后,每个project中的所有task以及相互关系就确定了

  3. Execution 执行阶段,gradle会根据传给它的task名字运行指定的task

监听器

我们还可以在上述build lifecycle中添加额外的监听器,监听某件事情的完成,这样我们就可以对gradle的正常运行流程做干预,比如我们想监听某个subProject 配置完毕,打印日志。我们可以这样

gradle.afterProject { project ->
    println('Project ' + project + '  has evaluated')
}

这段代码通常需要添加在rootProject的build.gradle脚本中,也可以添加到subProject中(只要能通过gradle获取到Gradle对象),但是这个监听器的安装需要发生在subProject在evaluated时,因此如果前面已经有project参与过evaluate,就不会得到监听。
还可以通过如下方法监听:

allprojects {
    afterEvaluate { project ->
        if (project.hasTests) {
            println "Adding test task to $project"
            project.task('test') << {
                println "Running tests for $project"
            }
        }
    }
}

监听task的创建
可以在build.gradle中通过如下方式监听task的创建

tasks.whenTaskAdded { task ->
    task.ext.srcDir = 'src/main/java'
}

监听整个task关系图的创建

gradle.taskGraph.whenReady {
    
}

监听某个task的执行

//监听整个build完毕
gradle.buildFinished {
    
}
//监听某个task开始执行,结束执行
gradle.taskGraph.addTaskExecutionListener(new TaskExecutionListener() {
    @Override
    void beforeExecute(Task task) {

    }

    @Override
    void afterExecute(Task task, TaskState state) {

   }
})

任意类型监听器

/**
 * Adds the given listener to this build. The listener may implement any of the given listener interfaces:
 *
 * <ul>
 * <li>{@link org.gradle.BuildListener}
 * <li>{@link org.gradle.api.execution.TaskExecutionGraphListener}
 * <li>{@link org.gradle.api.ProjectEvaluationListener}
 * <li>{@link org.gradle.api.execution.TaskExecutionListener}
 * <li>{@link org.gradle.api.execution.TaskActionListener}
 * <li>{@link org.gradle.api.logging.StandardOutputListener}
 * <li>{@link org.gradle.api.tasks.testing.TestListener}
 * <li>{@link org.gradle.api.tasks.testing.TestOutputListener}
 * <li>{@link org.gradle.api.artifacts.DependencyResolutionListener}
 * </ul>
 *
 * @param listener The listener to add. Does nothing if this listener has already been added.
 */
public void addListener(Object listener);

可以通过gradle.addListener(listener) 传入一个gradle支持的listener类型。上面addListener的注释中列出了它支持的监听器类别,你的listener可以任意实现其中的接口来满足你的需求。

创建Task

创建一个简单的task的语法为

task <taskName> << {
    
}

将groovy语法的时候,我们提到过,这句话应该这么理解:

  1. 首先调用project的task方法,传入一个taskName,返回一个task
  2. 调用task的leftShift 方法 传入一个closure,根据leftShift的解释,我们知道这个闭包将添加到task的action list里去。在任务执行的时候运行

有时候,我们可能会错写成

task <taskName> {
    
}

少了这个<< 操作符,意思就大不一样了,这个时候调用的函数为

//Project类
Task task(String name, Closure configureClosure);

这时,第二个参数closure用来配置task,在task创建的时候执行,而不是在task执行的时候运行。不过我们可以在这个闭包内配置task的一些属性。

//指定Copy类型task的属性
task copyDocs(type: Copy) {
   from 'src/main/doc'
   into 'build/target/doc'
}

当然我们还可以这样指定task的行为:

task exampleTask {
    doLast{
        
    }
}

task exampleTask doLast{
  
}

因为Task的doLast函数的作用和<<操作符一样。

task的类型

task name(type: Type){
    
    doLast{

     }
}

gradle内置为我们生成了很多task类型,比如Copy,Delete,可以点击链接 查看gradle内置的task类型列表,如果创建task时没有指定type,则他默认是DefaultTask类型。我们还可以创建自己的task类型,我们在稍后就会讲到。

我们还可以可以指定task之间的依赖关系, 通过dependsOn, mustRunAfter, shouldRunAfter来指定。 还可以指定task的分组group, 如果不指定,将会出现在other里面。

自定义Gradle插件

我们先来看看gradle默认会生成哪些task,我们从app中删除以下code:

apply plugin: 'com.android.application'

android{
    ...
}  
dependencies{
  ....
}

再次运行gradlew app:tasks 会发现

Paste_Image.png

只有9个task,而且全都出现在help分组中,很多task都不见了,很容易才想出, 这些task都是android application插件生成的。我们能使用Gradle构建Android 工程,一切都基于这个插件。这个插件从android这个扩展中读取了我们的配置,生成了一些列构建android 所需要的任务。

如果我们想生成自己的Task,执行额外的任务呢?

我们需要扩展,首先我们需要扩展任务的类型,其次我们需要编写我们自己的插件,这两者并不是紧密联系的,有时候我们编写插件的时候,也许并不需要扩展任务类型,有时候我们并不需要编写额外的插件,只需要新写一种任务类型就能达到我们的目的。但大部分时候,这两者我们都需要,我们还需要实现我们自己的Extension,有了Extension我们就能从build.gradle脚本中读取我们需要的属性。因此我们需要做的事情是:

对Gradle进行拓展

接下我们看看怎么利用AndroidStudio编写我们自己的Gradle插件。AS默认不会为我们生成Groovy插件项目的结构,我们需要自己构建出来。首先新建一个Android Project, 在app subProject中src/main/下新建groovy和resources目录,删除其他目录,并修改build.gradle文件,像这样

apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

在groovy木下新建HelloPlugin.groovy文件在resources目录下新建path:
META-INF/gradle-plugins/com.junli.HelloPlugin.properties,最终目录结构看起来就像这样:


Gradle插件项目结构

这里的com.junli.HelloPlugin最终会出现在

apply plugin: 'com.junli.HelloPlugin'

我们开始编写HelloPlugin.groovy


package com.junli

import org.gradle.api.Plugin
import org.gradle.api.Project

class HelloPlugin implements Plugin<Project>
{

    @Override
    void apply(Project project) {
        project.task('helloPluginTask') {
            group 'junli'
            doLast {
                println 'hell i am a task in plugin'
            }
        }
    }
}

我们定义了一个类HelloPlugin,让其继承自gradle api中的Plugin<Project> ,实现了apply接口函数,apply函数会在build.gradle中调用apply plugin: 'xxx'时调用,我们的HelloPlugin插件的实现很简单,我们在project中创建了一个名叫helloPluginTask的task,分组为junli, action为打印一串字符串.

为了插件能成功apply, 我们还需要在META-INF/gradle-plugins/com.junli.helloPlugin.properties中写入

implementation-class=com.junli.HelloPlugin

这句话指定了插件类的位置。

我们如何将其apply到自己的工程中去呢?,首先我们必须说明的是,插件可以以三种形式存在:

  1. 在我们构建项目的build.gradle脚本中直接编写

  2. 在我们构建项目的rootProjectDir/buildSrc/src/main/groovy 目录下

  3. 以单独的project存在

在这里我们采用的是第三种形式,这种形式的好处在于可以将插件打包发布,并很好的分享给使用者。

OK,那我们改如何分享呢?,我们可以先来学习如何在发布到本地,这样我们机器上的其他工程就能在指定目录下找到我们的插件。

要将插件发布,我们需要利用maven-publish插件

apply plugin: 'maven-publish'

publishing{
    publications {
        mavenJava(MavenPublication) {
            from components.java

            groupId 'com.junli'
            artifactId 'helloPlugin'
            version '0.1'

        }
    }

    repositories{
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url "../repo"
        }
    }
}

应用了插件maven-publish后,我们事先extension publishing,并在publications下添加我们需要发布的配置,这里我们添加了一个mavenJava,这个名字可以随意命名。其他配置还有

  • from 指定需要发布的组件,目前仅有两种java 和 web

  • groupId、artifactId、version,这三者组成了插件使用者在声明依赖时的完整语句 groupId:artifactId:version

  • 默认打包时只会包含编译过的jar包,我们还可以将源代码和javadoc打包发布,可以通过artifact指定:

task javadocJar(type: Jar, dependsOn: groovydoc) {
    classifier = 'javadoc'
    from "${buildDir}/javadoc"
}

task sourcesJar(type: Jar) {
    from sourceSets.main.allSource
    classifier = 'sources'
}

mavenJava(MavenPublication) {
    from components.java

     artifact sourcesJar
     artifact javadocJar

    groupId 'com.junli'
    artifactId 'helloPlugin'
    version '0.1'

}

这样我们运行gradlew app:tasks就可以看到创建了如下tasks:

publishing tasks

有两种publish任务,publish 和 publishToMavenLocal,

publish任务依赖于所有的mavenPublication的generatePomFileFor任务和publishxxxPublicationToMavenRepository,意思是将所有的mavenPublication发布到指定的repository,

publishToMavenLocal依赖于所有的mavenPublication的generatePomFileFor和publishxxxTomavenLocal任务,意思是讲所有的mavenPublication发布到本地的m2 repository。

我们运行

gradlew app:publish

我们会在rootProjectDir的repo目录下看到我们生成的repo:

本地maven repo

我们看到了目录结构为 groupId/artifactId/version, 每个版本下面有我们打包生成的jar包,每个jar包的校验文件(md5和sha1),以及pom文件。pom文件是一个xml结构的文件,用来描述maven项目:包括配置文件;开发者需要遵循的规则,缺陷管理系统,组织和licenses,项目的url,项目的依赖性,以及其他所有的项目相关因素。

应用插件

我们改如何应用插件呢?因为我们在插件已经publish到本地的目录中,因此我们可以在本地直接使用,我们在项目先新建一个子项目MyLibrary,为了使用插件我们需要修改build.gradle文件

apply plugin: 'com.junli.HelloPlugin' //应用插件

buildscript {
    repositories {
        maven {
            url uri('../repo') //插件所在的目录
        }
    }

    dependencies {
        classpath 'com.junli:helloPlugin:0.1' //添加依赖
    }
}

首先添加依赖,依赖寻找的repo需要添加我们之前发布的目录,然后应用插件.

我们运行graldew mylibrary:tasks可以看到mylibrary项目中生成了一个新的task:

Junli tasks

helloPluginTask

处与junli分组下
运行任务打印出

hell i am a task in plugin

通过这样一个简单的例子,我们了解了如何编写插件和应用插件,接下来我们通过添加Extension和Task来让我们的插件变得更强大

Extension

我们在HelloPlugin同级目录下新建文件PersonExt.groovy

public class PersonExt {

    String name;

    int age;

    boolean boy;

    @Override
    public String toString() {
        return "I am $name, $age years old, " + (boy?"I am a boy":"I am a gril");
    }
}

并修改HelloPlugin的内容


class HelloPlugin implements Plugin<Project>
{

    @Override
    void apply(Project project) {
        project.extensions.add("personExt", PersonExt)

        project.task('printPerson') {
            group 'junli'

            doLast{
                PersonExt ext = project.personExt

                println ext
            }
        }
    }
}

我们对project添加了一个extension,这样子,我们可以在使用插件的地方,实现这个extension,我们能够在插件apply的时候拿到这个extension, 读取extension中的属性,

personExt{
    name  'Tom'
    age  10
    boy   false
}

我们再次运行我们的任务

gradlew mylibrary:printPerson

I am Tom, 10 years old, I am a gril

你可以看到这里的name和age 都没有使用=号进行复制,就是利用了grooy特性,它实际上是调用了setName方法,并且省略了方法调用的括号。

如果我们想读取一个列表,这个列表的长度不定呢?

我们需要使用NamedDomainObjectContainer,我们后面都简称NDOC 这是一个容纳object的容器,它的特点是它的内部使用SortedSet实现的,内部对象的name是unique的,而且是按name进行排序的。通常创建NDOC的方法就是调用

<T> [NamedDomainObjectContainer] <T> container(Class<T> type)

这里type有一个要求:必须有一个public的构造函数,接受string作为一个参数,必须有一个叫做name 的property。

接下来我们看如何获取一个person的列表

class HelloPlugin implements Plugin<Project>
{

    @Override
    void apply(Project project) {
        //创建一个容器
        NamedDomainObjectContainer<Person> persons = project.container(Person)
         //将容器添加为extension
        project.extensions.add('team', persons)

        def task = project.task('showTeam'){
            group 'junli'
            doLast {
                def team1 = project.extensions.getByName('team')


                println team1
            }
        }
    } 
}  

这样我们只需要修改extension为:

team{
    John{
        age = 10
        boy = false
    }

    Tom{
        age = 20
        boy = false
    }
}

最后通过println 我们可以看到我们这个容器中的内容:

[I am John, 10 years old, I am a gril, I am Tom, 20 years old, I am a gril]

我们了解了如何通过extension读取列表,接下来,我们可以混合列表和单个属性,就像android{...}一样

我们新建一个类Team

//Team.groovy
class Team {

    NamedDomainObjectContainer<Person>  persons;

    String name;

    int count;

    public Team(NamedDomainObjectContainer<Person> persons){
        this.persons = persons;
    }
    
    //persons函数,允许我们通过配置传入闭包,来给persons容器添加对象
    def persons(Closure closure){
        persons.configure(closure)
    }

    @Override
    String toString() {
        return "This is a team, name: $name, count: $count" + " persons: "+ persons
    }
}

这里的persons(closure)函数是必须的,只有实现了这个函数,Gradle在解析team的extension,遇到persons配置时,才能通过调用函数,调用 NamedDomainObjectContainer的configure方法,往里面添加对象。


class HelloPlugin implements Plugin<Project>
{

    @Override
    void apply(Project project) {

        NamedDomainObjectContainer<Person> persons = project.container(Person)

        Team team = new Team(persons)

        project.extensions.add('team', team)

        def task = project.task('showTeam'){
            group 'junli'
            doLast {
                def team1 = project.extensions.getByName('team')


                println team1
            }
        }

    }
}

team{
    name = 'GSW'
    count = 20
    persons{
        John{
            age = 10
            boy = false
        }

        Tom{
            age = 20
            boy = false
        }
    }
}

运行任务showTeam输出为:

This is a team, name: GSW, count: 20 persons: [I am John, 10 years old, I am a gril, I am Tom, 20 years old, I am a gril]

这样我们就通过一个简单的例子就熟悉了Gradle插件的编写规则,而且通过对groovy语法的了解,让我们对gradle的DSL不再陌生,好了这是第一篇我们要讲的内容,下一篇我会带来自定义Task,复杂的plugin,上传jcenter等内容,小伙伴记得关注哦。

码农日记
Web note ad 1