前言
本文内容已经有很多大佬写过了,不过这里为了知识体系的完整,就再写一遍,并加入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,官方论坛,*,尝试各种配置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,多看看官方文档,顺便学学英语。
参考资料
Developing Custom Gradle Plugins