Android Gradle Plugins系列-01-自定义Gradle插件入门指南

前言

本文内容已经有很多大佬写过了,不过这里为了知识体系的完整,就再写一遍,并加入Maven Publish插件的使用,不感兴趣跳过就好。官方文档:Developing Custom Gradle Plugins在这。

插件的开发语言

官方说了,不管你用啥语言,只要最终能编译成JVM 字节码就行。不过一般使用Java,Kotlin,Groovy的居多,而静态语言类型的Java和Kotlin相对于Groovy性能更好。

插件打包的三种方式

Build Script

直接build script中编写插件的源代码。这样做的好处是插件会自动编译并包含在build script的classpath中,不用我们执行任何操作。但是,插件在build script之外是不可见的,所以不能在定义它的build script之外复用这个插件。

buildSrc project

将插件的源代码放在 rootProjectDir/buildSrc/src/main/java 目录(或 rootProjectDir/buildSrc/src/main/groovy 或 rootProjectDir/buildSrc/src/main/kotlin,具体取决于使用哪种语言)。 Gradle 负责编译和测试插件,并使插件在build script的classpath上可用。该插件对项目里的所有build script都是可见的。 但是对其它项目就不可见了,因此其它项目不能复用该插件。

有关 buildSrc 项目的更多详细信息,请阅读组织 Gradle 项目

Standardalone project

为插件创建一个单独的项目(或单独的Module),最终编译并发布一个 JAR。通常来说,这个 JAR 可能包含一些插件,或者将几个相关的任务类捆绑到一个库中,或者两者的某种组合。

一般我们开发自定义插件,都是选择Standardalone project的方式居多,编译打包成JAR后,发布到Maven仓库(本地,远程,私有或公有都可以),以便提供给其它项目使用,这样的好处是方便复用,项目编译速度也会快一些。

编写一个简单的插件

创建 Gradle 插件,需要编写一个实现 Plugin 接口的类。 当插件应用于项目时,Gradle会自动创建插件类的实例并调用该实例的 Plugin.apply() 方法。 项目对象作为参数传递,插件可以使用它来配置项目。

在Build Script编写插件

下面的示例包含一个问候插件(GreetingPlugin类),它向项目添加了一个 hello 任务,用于输出一句问候的话。在CustomGradlePlugin Module的build.gradle脚本(groovy语言)中添加以下代码:

plugins {
    id 'maven-publish'
    id 'java-library'
    id 'kotlin'
}

//---------------------------------自定义插件----------------------------------
//添加一个名为GreetingPlugin的插件
class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hello') {
            doLast {
                println 'Hello from the GreetingPlugin'
            }
        }
    }
}

// Apply the plugin
apply plugin: GreetingPlugin
//---------------------------------自定义插件----------------------------------


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "com.android.tools.build:gradle:3.5.0"
    implementation gradleApi()
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30"
    implementation group: 'org.ow2.asm', name: 'asm', version: '7.2'
    implementation group: 'org.ow2.asm', name: 'asm-commons', version: '7.2'
}

publishing {
    publications {
        maven(MavenPublication) {
            groupId = 'com.nxg.plugin'
            artifactId = 'custom-plugin'
            version = '1.0.0'
            from components.java
        }
    }

    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir('repo')
        }
    }
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

点击Sync Projects With Gradle File,就会生成一个名为hello的task,可以在右上角的Gradle页面查看。

通过Gradle Task 执行插件

双击或右键执行,即可在控制台看到对应的打印。

至此,一个简单的插件就完成了。

通过命令行执行插件

你可能想使用命令行执行插件,在Terminal中执行:sh gradlew -q hello./gradlew -q hello

Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.

你可能会遇到报错如下:

nxg:/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample$ sh gradlew -q hello

FAILURE: Build failed with an exception.

* Where:
Build file '/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample/app/build.gradle' line: 2

* What went wrong:
An exception occurred applying plugin request [id: 'com.android.application']
> Failed to apply plugin 'com.android.internal.application'.
   > Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
     You can try some of the following options:
       - changing the IDE settings.
       - changing the JAVA_HOME environment variable.
       - changing `org.gradle.java.home` in `gradle.properties`.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 12s
nxg:/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample$ 

解决办法是进入设置界面修改Gradle JDK的版本为JDK 11。

但是笔者测试发现这样还不能解决问题,需要在项目根目录的gradle.properties文件中指定JAVA_HOME的路径。

//JDK路径可以在Setting--->Build,Exceution,Deployment--->Build Tools--->Gradle JDK中获取
org.gradle.java.home=/work/android/android-studio-4.0/android-studio/jre

在buildSrc目录编写插件

新建buildSrc目录

在项目的根目录新建一个名为buildSrc的目录,注意名称一定要对。

新建build.gradle

由于新建的buildSrc什么都没有,显然是不能正常工作,此时需要手动建一个build.gralde文件,并填入以下代码:

plugins {
    //使用maven-publish插件
    id 'maven-publish'
    //使用java库,用于开发插件
    id 'java-library'
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation gradleApi()
}

publishing {
    publications {
        maven(MavenPublication) {
            groupId = 'com.nxg.plugin'
            artifactId = 'custom-plugin'
            version = '1.0.0'
            from components.java
        }
    }

    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir('repo')
        }
    }
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
//指定源码/资源路径
sourceSets {
    main {
        java {
            srcDir 'src/main/java'
            //srcDir 'src/main/kotlin'
        }
        groovy {
            srcDir 'src/main/groovy'
        }
        resources {
            srcDir 'src/main/resources'
        }
    }
}

Sync Project With Gradle Files,buildSrc目录下就会生成.gralde和build目录。

新建源码目录

根据此路径rootProjectDir/buildSrc/src/main/java 目录(或 rootProjectDir/buildSrc/src/main/groovy 或 rootProjectDir/buildSrc/src/main/kotlin,具体是哪个路径要看你用的开发语言)新建对应的目录,在对应目录中新建自己的包。

右键buildSrc目录,New--->Directory,新建目录,由于build.gradle中已经配置了sourceSets,因此这里直接就提示出来了,双击对应的路径既可完成新建。

这里为了演示,Java,Kotlin,Groovy的目录都创建了,实际插件还是用的Java。

新建插件类

在对应开发语言的目录,创建插件的包名,再新建一个GreetingPlugin类实现Plugin<Project>接口,代码如下:

package com.nxg.plugins;

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

public class JavaGreetingPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        System.out.println("JavaGreetingPlugin(buildSrc) ---> apply");
        project.task("hello").doLast(task -> System.out.println("Hello from the com.nxg.plugins.JavaGreetingPlugin(buildSrc)"));
    }
}

新建properties文件

同样右键buildSrc目录,New--->Directory,新建目录resources,然后按照以下路径:

resources/META-INF/gradle-plugins/com.nxg.plugins.greeting.properties

建立对应的目录和properties文件。

xxx.properties文件,这里的xxx对应的插件id,com.nxg.plugins.greeting.properties的id就是com.nxg.plugins.greeting,通常用包名+插件名命名,或者用其他唯一标示都是可以的。id通常是在bulid.gradle中使用plugins依赖插件时用于指定插件。

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
}

com.nxg.plugins.properties的内容为:

implementation-class=com.nxg.plugins.JavaGreetingPlugin

implementation-class=xxx,xxx填写插件的包名加类名。

官方原文说明如下:

Behind the scenes

So how does Gradle find the Plugin implementation? The answer is - you need to provide a properties file in the JAR’s META-INF/gradle-plugins directory that matches the id of your plugin, which is handled by Java Gradle Plugin Development Plugin.

Example: Wiring for a custom plugin

src/main/resources/META-INF/gradle-plugins/org.example.greeting.properties

implementation-class=org.example.GreetingPlugin

Notice that the properties filename matches the plugin id and is placed in the resources folder, and that the implementation-class property identifies the Plugin implementation class.

遇到的问题

Gradle sync failed: Entry META-INF/gradle-plugins/com.nxg.plugins.properties is a duplicate but no duplicate handling strategy has been set. Please refer to https://docs.gradle.org/7.0.2/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:duplicatesStrategy for details. (650 ms)

看打印,先入为主的认为是properties文件重复导致了,找了一圈github,官方论坛,stackoverflow,尝试各种配置duplicatesStrategy,均无效。最后使用另外的项目来新建buildSrc,发现竟然成功了,对比后发现,是少了buildSrc里的build.gradle文件,再深入对比后发现是build.gradle里的maven-publish插件的问题,将插注释掉重新sync就好了(或者删除整个build.gradle也行,测试发现是可以的,不影响),buildSrc也用不到maven-publish。

使用插件

在app的build.gradle,使用以下方式依赖写好的自定义插件,其中id对应的值com.nxg.plugins.greeting,即是com.nxg.plugins.greeting.properties的文件名。

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
}

// Apply the plugin,这两种方式都是支持的
//apply plugin: 'com.nxg.plugins.greeting'

插件的使用更详细的内容参考官方文档Using Gradle Plugins

运行插件

添加好自定义插件的依赖后,点击Sync Projects With Gradle File,就会生成一个名为hello的task,可以在右上角的Gradle页面查看,同时Build控制台中也打印出apply方法的日志。

双击执行hello task,即可看到相关日志打印。

独立的自定义插件项目

Build Script和buildSrc的方式写插件虽然方便编译和调试,但却不能共享给多个项目使用。对于多项目开发的使用场景,将自定义的插件移动到一个独立的项目中,以便可以发布它并分享出去是一个更好的选择。

自定义插件项目的编译产物(Plugin Marker Artifact)通常是一个包含插件类的 JAR,而打包和发布插件的最简单且推荐的方法是使用Java Gradle Plugin Development Plugin。这个插件将自动使用Java Plugin,并且自动将gradleApi()依赖项添加到 api 配置中,且最终生成的 JAR 文件中会包含所需的插件描述符(com.nxg.plugins.greeting.properties),并配置发布时要使用的Plugin Marker Artifact

新建Android Library Module

ModuleName和Package 根据需要修改,这里笔者选择Kotlin语言,Bytecode Level选择默认8。

删掉不必要的包和文件,最终的目录如下:

使用java-gradle-plugin插件

删除build.gradle里的内容,添加java-gradle-plugin插件,并配置插件id和实现类。

plugins {
    id 'java-gradle-plugin'
}

gradlePlugin {
    plugins {
        standaloneGradlePlugins {
            id = "greeting-standalone"
            implementationClass = 'com.nxg.plugins.GreetingStandaloneGradlePlugins'
        }
    }
}

更多介绍,点击阅读java-gradle-plugin的使用。

新建插件类

根据以上插件配置,新建对应的插件类:

package com.nxg.plugins;

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

public class GreetingStandaloneGradlePlugins implements Plugin<Project> {

    private static final String TAG = "GreetingStandaloneGradlePlugins";

    @Override
    public void apply(Project project) {
        System.out.println("GreetingStandaloneGradlePlugins(standalone) ---> apply");
        project.task("helloStandalone").doLast(task -> System.out.println("Hello from the com.nxg.plugins.GreetingStandaloneGradlePlugins(standalone)"));
    }
}

一个简单的插件就完成了。

配置MavenPublish插件

使用maven-publish插件并配置如下:

plugins {
    id 'java-gradle-plugin'
    id 'maven-publish'
}

//定义Maven仓库信息
def MAVEN_GROUP_ID = "com.nxg.plugins"
def MAVEN_ARTIFACT_ID = "greeting-standalone"
def MAVEN_VERSION = "1.0.0"
def MAVEN_NAME = "repo"
def MAVEN_RELEASE_URL = "mavenLocal"
def MAVEN_USERNAME = ""
def MAVEN_PASSWORD = ""


gradlePlugin {
    plugins {
        standaloneGradlePlugins {
            id = MAVEN_ARTIFACT_ID
            implementationClass = 'com.nxg.plugins.GreetingStandaloneGradlePlugins'
        }
    }
}

publishing {
    publications {
        maven(MavenPublication) {
            groupId = MAVEN_GROUP_ID
            artifactId = MAVEN_ARTIFACT_ID
            version = MAVEN_VERSION
            from components.java
        }
    }

    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir(MAVEN_NAME)
        }
    }
}

maven-publish插件入门和踩坑记录见:

Android Gradle Plugins系列-02-Maven Publish 插件入门指南

发布插件到maveLocal

配置好后,点击Sync Projects With Gradle File,即可在右上角的gradl task list中看到对应module的publish tasks。

双击执行publistoMavenLocal,即可发布插件的Artifact(构建产物)到本地Maven仓库,一般是在.m2(mavenLocal)目录中。

发布插件到指定目录

我想发布到指定目录行不行?当然可以。其实上面已经配置好路径了,url可以替换为自定义路径,笔者这里配置的是build目录的repo文件夹。

publishing {

    repositories {
        maven {
            // change to point to your repo, e.g. http://my.org/repo
            url = layout.buildDirectory.dir(MAVEN_NAME)
        }
    }
}

双击执行publish task,执行完毕后会在url配置的路径中生成插件的构建产物。

使用插件

插件的使用大同小异,不过要注意的是,发布的到mavenLocal的插件不能通过Gradle DSL(plugins{})或者apply直接使用,原因是插件的Artifact(构建产物)不在classpath中。

通过apply的方式使用插件需要在buildscript中指定本地maven仓库路径,并指定classpath,并且buildscript代码块要在plugins代码块之前声明,具体如下:

buildscript {
    repositories {
        mavenLocal()
    }
    dependencies {
        classpath 'com.nxg.plugins:greeting-standalone:1.0.0'
    }
}

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
    //id 'com.nxg.plugins.greeting-standalone' version '1.0.0'
}

// Apply the plugin
apply plugin: 'greeting-standalone'

那我想通过plugins 指定id行不行?当然那行,将插件发布到JFrog Bintray即可。

那我不想发布,就想使用mavenLocal的插件呢?理论上应该可以,但是笔者按照官方文档测试,怎样修改的不行。

具体做法是在项目根目录中的settings.gradle中添加pluginManagement如下:

dependencyResolutionManagement {
    //repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        pluginManagement {
            repositories {
                mavenLocal()
                gradlePluginPortal()
            }
        }
        google()
        mavenCentral()
        mavenLocal()
    }
}

rootProject.name = "AndroidGradlePluginsSample"
include ':app'
include ':GradlePluginInBuildScript'
include ':StandaloneGradlePluginProject'

并且带上版本号:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
    id 'com.nxg.plugins' version '1.0.0'
}

还是报错如下:

Build file '/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample/app/build.gradle' line: 16

Plugin [id: 'com.nxg.plugins.greeting-standalone', version: '1.0.0'] was not found in any of the following sources:

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.plugins.UnknownPluginException: Plugin [id: 'com.nxg.plugins.greeting-standalone', version: '1.0.0'] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (could not resolve plugin artifact 'com.nxg.plugins.greeting-standalone:com.nxg.plugins.greeting-standalone.gradle.plugin:1.0.0')
  Searched in the following repositories:
    MavenLocal(file:/home/lb/.m2/repository/)

真的难搞哦。看下面的日志,很明显压根对不上啊。

Plugin [id: 'com.nxg.plugins.greeting-standalone', version: '1.0.0'] was not found in any of the following sources:
跟这个
Plugin Repositories (could not resolve plugin artifact 'com.nxg.plugins.greeting-standalone:com.nxg.plugins.greeting-standalone.gradle.plugin:1.0.0')
 再看看classpath
  classpath 'com.nxg.plugins:greeting-standalone:1.0.0'

此问题留着以后有时间再看看吧。

插件的使用更详细的内容还是建议参考官方文档Using Gradle Plugins,多看看官方文档,顺便学学英语。

2022年02月10日新增

针对使用插件id apply 发布到mavenLocal的自定义插件失败的问题。目前已经找到问题所在,还是官方文档看得不仔细啊,摘抄关键文档如下:

Note for plugins published without java-gradle-plugin

If your plugin was published without using the Java Gradle Plugin Development Plugin, the publication will be lacking Plugin Marker Artifact, which is needed for plugins DSL to locate the plugin. In this case, the recommended way to resolve the plugin in another project is to add a resolutionStrategy section to the pluginManagement {} block of the project’s settings file as shown below.

Example 7. Resolution strategy for plugins without Plugin Marker Artifact

GroovyKotlin

settings.gradle

resolutionStrategy {         
    eachPlugin {             
        if (requested.id.namespace == 'org.example') {                 
            useModule("org.example:custom-plugin:${requested.version}")             
        }         
    }     
}

原文链接:Developing Custom Gradle Plugins

就不翻译原文了,只说解决方案。如果使用 plugins DSL来定位插件,即以下面这种形式引用插件。

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.nxg.plugins.greeting'
    //如果本地没发布过该插件,需要先注释掉这里,插件发布后再打开注释
    id 'com.nxg.plugins.greeting-standalone' version '1.0.0'
}

但是编译出现Plugin [id: 'xxx.xxx.xxx', version: '1.0.0'] was not found in any of the following sources:的问题:

Build file '/home/work/AndroidStudioProjects/AndroidDevelopmentPractices/AndroidGradlePluginsSample/app/build.gradle' line: 16

Plugin [id: 'com.nxg.plugins.greeting-standalone', version: '1.0.0'] was not found in any of the following sources:

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.plugins.UnknownPluginException: Plugin [id: 'com.nxg.plugins.greeting-standalone', version: '1.0.0'] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (could not resolve plugin artifact 'com.nxg.plugins.greeting-standalone:com.nxg.plugins.greeting-standalone.gradle.plugin:1.0.0')
  Searched in the following repositories:
    MavenLocal(file:/home/lb/.m2/repository/)

那就需要在项目的根目录的setting.gradle(setting.gradle.kts)中根据插件的namespace指定完整的插件信息,包含id和version。

完整的setting.gradle示例代码如下:

pluginManagement {
    resolutionStrategy {
        eachPlugin {
            println("resolutionStrategy eachPlugin ${requested.id.namespace}")
            if (requested.id.namespace == 'com.nxg.plugins') {
                useModule("com.nxg.plugins:greeting-standalone:${requested.version}")
            }
        }
    }

    repositories {
        mavenLocal()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    //repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        mavenLocal()
    }
}

rootProject.name = "AndroidGradlePluginsSample"
include ':app'
include ':GradlePluginInBuildScript'
include ':StandaloneGradlePluginProject'

以上代码已经提交到代码仓库:https://github.com/xiangang/AndroidDevelopmentPractices/tree/master/AndroidGradlePluginsSample

注意,别忘了先把插件通过maven-pulish插件的publish task 发布到mavenLocal。

写在最后,首先非常感谢您耐心阅读完整篇文章,坚持写原创且基于实战的文章不是件容易的事,如果本文刚好对您有点帮助,欢迎您给文章点赞评论,您的鼓励是笔者坚持不懈的动力。若文章有不对之处也欢迎指正,再次感谢。

学如逆水行舟,不进则退;心似平原走马,易放难追。

参考资料

Developing Custom Gradle Plugins

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

农贤钢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值