Gradle自定义插件总结
Gradle 自定义插件总结
Gradle plugin build.gradle[.kts]
配置
src 下对应的 java 目录没有被 AS 识别出来
问题:AGP8.1.3,AS(2024.3 最新版本)
build.gradle.kts
文件上方提示: standalone script under build root isn’t highlighted as standalone
 |
 |
src/main/java
在 AS 中也没有变颜色
 |
问题分析:如果是手动创建文件夹,然后创建 build.gradle.kts,AS 识别不了,可能是 AS 的 bug?
解决:通过 File→New→New Module
来创建 module,这样就可以解决了
publish 的 jar 包没有包括 classes
- 用对应语言写插件,就放到对应 sourceSets 文件夹下去(与 jar 包中是否有 classes 没关系)
- java 编写的就放`main/java
- groovy 编写的就放
main/groovy
- kotlin 编写的就是
main/kotlin
- 解决
- 在
build.gradle(.kts)
中需要添加 groovy 插件:apply plugin: 'groovy'
,即使放在 java 目录也是可以的 - 如果在 java 目录写 kotlin 代码,需要引入
id("org.jetbrains.kotlin.jvm")
否则发布的 jar 包没有 kotlin 类的,放在 kotlin 目录也是可以的
- 在
使用 java-gradle-plugin
插件
参考:[[04.Gradle三方插件#java-gradle-plugin]]
extension 相关
extension 定义相关
extension 类需要可继承的
- 如果是 kotlin class,需要 open 修饰
- 一般用 abstract class
extension 定义需要 create 后再使用
否则会报错:
Could not find method dingPgyerConfig() for arguments [build_6l85nymtuc9s9srndt732is8y$_run_closure1@29e705c9] on project ':Google' of type org.gradle.api.Project.
自定义的 extension 定义获取不到值,在 afterEvaluate 后获取
create 之后立即使用,这个时候 bean 里面是默认值,也就是说 build.gradle 中的信息还没有加载进去,需要在整个 gradle 配置完成之后 bean 中才会填充上相应的值。
需要在 project.afterEvaluate{}
中获取
1
2
3
4
5
6
7
8
final DemoExtension extension = project.getExtensions().create("demoConfig", DemoExtension.class);
System.out.println(TAG + "extension: " + extension); // 这里获取不到值
project.afterEvaluate(new Action<Project>() {
@Override
public void execute(Project project) {
System.out.println(TAG + "extension afterEvaluate: " + extension); // 这里可以获取
}
});
获取已创建的 extention
findByName
1
DingTalkExtension dingTalkExtension = target.getExtensions().findByName("dingPgyerConfig")
findByType
1
2
3
4
5
6
7
8
9
DingTalkExtension config = project.getExtensions().findByType(DingTalkExtension.class)
subprojects {
afterEvaluate {
extensions.findByType<KotlinProjectExtension>()?.apply {
jvmToolchain(11)
}
}
}
extention
嵌套
extension 嵌套,其实就是嵌套个内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
open class MyPluginExtension(project: Project) {
var title: String? = null
var chapter: Int? = 0
var subExtension: SubExtension? = null
init {
subExtension = project.extensions.create("sub", SubExtension::class.java)
}
fun string(): String {
return "title: $title, chapter: $chapter, author: ${subExtension?.author}"
}
}
open class SubExtension {
var author: String? = null
}
使用:
1
2
3
4
5
6
7
myPluginExtension {
title = "Hello from myPluginExtension"
chapter = 2
sub {
author = "Hacket"
}
}
获取 App 和 Lib 的 extention
1
2
3
4
5
6
7
8
9
10
11
// 获取app的Extention
static AppExtension getConfig(Project project) {
AppExtension config = project.getExtensions().findByType(AppExtension.class)
return config
}
// 获取lib的Extention
static LibraryExtension getConfig(Project project) {
LibraryExtension config = project.getExtensions().findByType(LibraryExtension.class)
return config
}
闭包类型是否应用插件
1
2
3
4
5
6
7
class DingTalkExtension {
public Closure<Boolean> groovyEnableByVariant
boolean isEnable(String variantName) {
if (groovyEnableByVariant == null) return true
return groovyEnableByVariant.call(variantName)
}
}
使用:
1
2
3
4
5
6
7
apply plugin: 'dingPgyerPlugin'
dingPgyerConfig {
pgyerApiKey = "[google]70885395bcdfd5ebdb72a5856c95596c"
groovyEnableByVariant = { variantName ->
variantName.toLowerCase().contains("release") // release才开启
}
}
判断是否有某个插件
判断是否应用了 com.android.application
插件,代码来自 walle
1
2
3
4
5
6
7
8
9
10
11
12
class GradlePlugin implements org.gradle.api.Plugin<Project> {
public static final String sPluginExtensionName = "walle";
@Override
void apply(Project project) {
// 旧的写法
// if (!project.plugins.hasPlugin("com.android.application")) {
// throw new ProjectConfigurationException("Plugin requires the 'com.android.application' plugin to be configured.", null)
// }
if (!Project.getPluginManager().hasPlugin("com.android.application")) {
throw new ProjectConfigurationException("Plugin requires the 'com.android.application' plugin to be configured.", null)
}
}
Task 相关
Configuration Task 和 task 的输入输出
- 方式 1:
register(xx, configuration)
1
2
3
4
5
6
7
8
9
10
11
12
13
val pgyerTaskName = Constants.TASK_UPLOAD_PGYER + variantName
val uploadPgyerTaskProvider = target.tasks.register(
Constants.TASK_UPLOAD_PGYER + variantName,
PgyUploadApkTask::class.java
) {
// Configuration会执行
// 输入
// PgyUploadApkTask依赖于variant.outputs中的第一个
it.apkFileProperty.set(variant.outputs.first().outputFile)
// 输出
it.outputFilProperty.set(target.layout.buildDirectory.file("output.txt"))
}
- 方式 2:
configure { }
1
2
3
4
5
6
7
8
val dingTalkTaskProvider = target.tasks.register(
dingTalkTaskName,
SendBuildResultToDingTalkTask::class.java
)
// 配置
dingTalkTaskProvider.configure {
it.apkFileProperty.set(variant.outputs.first().outputFile)
}
插件中的 http 请求时老是 timeout
通过 OkHttp 请求,老是 timeout
- 原因:网络问题
- 解决:把全局代理关掉;抓包软件关掉
Gradle 插件中的 task 要用子线程
子线程配合 CountDownLatch
- 可以开子线程,在 Task 中可以用 CountDownLatch 等待子线程执行完毕,不然你的 task 可能就执行完毕了
Retrofit+RxJava 做网络请求
如做一个上传蒲公英的插件,上传蒲公英需要 3 个接口才能完成上传:
- key 上传文件存储标识唯一 key 通过该接口,开发者可以获取预上传 url 和相关的签名参数 POST https://www.pgyer.com/apiv2/app/getCOSToken
- 上传文件到第上一步获取的 URL POST 在这一步中上传 App 成功后,App 会自动进入服务器后台队列继续后续的发布流程。所以,在这一步中 App 上传完成后,并不代表 App 已经完成发布。一般来说,一般 1 分钟以内就能完成发布。要检查是否发布完成,请调用下一步中的 API。
- 检测应用是否发布完成,并获取发布应用的信息 GET https://www.pgyer.com/apiv2/app/buildInfo
如果在创建 Retrofit 时的 CallAdapterFactory 是 create():
1
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
会出现 task 执行完毕了,上传 APK 并没有完成
原因是 RxJava3CallAdapterFactory.create()
是异步,导致 task 执行完毕了
解决:用 RxJava3CallAdapterFactory.createSynchronous()
同步的即可。
封装第三方插件
封装 maven-publish 三方插件
见 maven-publish.md
根据 Variant(productFlavor+buildType) 引用 Gradle 插件(按需加载插件)
背景:只想在某一些 buildType 或者 flavor 中应用这个插件。
分析:这个问题的根本原因在于 Variant(buildType + flavor) 不是一个 Gradle 概念,而是源于 Android Gralde Plugin(AGP)的多渠道配置。所以从 Gradle 平台的角度很难向上提供便捷的支持:一个工程内的插件引入是全局的、平台性的,不以某一个插件的意志(AGP)而改变下层平台的机制。
案例:
mashi 的 preview 包,配置了混淆,配置了 Firebase 插件,估计 Firebase 插件是根据是否设置了混淆来自动执行 uploadCrashlyticsMappingFileDevPreview,但 preview 是测试包,并不需要上传 mapping 到 Firebase,测试包我们用的 bugly。所以就出现了打 preview 包的时候也执行了该 task,而打包这台机器又 404 了,所以就上传超时了,gg 了。
1
2
Execution failed for task ':app:uploadCrashlyticsMappingFileDevPreview'.
11:55:07 > org.apache.http.conn.HttpHostConnectException: Connect to firebasecrashlyticssymbols.googleapis.com:443 [firebasecrashlyticssymbols.googleapis.com/142.251.43.10] failed: Operation timed out (Connection timed out)
根据 Variant 去 apply 插件 – 无效
1
2
3
4
5
6
7
8
9
10
11
flavorDimensions += "server"
productFlavors {
// ...
create("production") {
dimension = "server"
applicationIdSuffix = ".production"
versionNameSuffix = "-production"
versionCode = 2
apply("xxx") // 无效
}
}
create("…"){}
的闭包上下文是this:ApplicationProductFlavor
- apply(…) 则为挂载于 PluginAware 的一个扩展方法(以 build.gradle.kts),作用于整个、Project,这样一来你的插件还是会被全局引入。
同样的经典误区 1:
1
2
3
4
5
6
7
8
9
10
11
create("production") {
dimension = "server"
applicationIdSuffix = ".production"
versionNameSuffix = "-production"
versionCode = 2
packagingOptions {
jniLibs {
excludes.add("...")
}
}
}
- packageingOptions 是 CommonExtension 接口的一个方法,并不属于 ApplicationProductFlavor,也即它是为整个 Android Gradle Plugin 进行设置,而不是单一 flavor,如果你在每个 flavor 中都进行不同的设置,最后一次的设置会覆盖前面所有的
同样的经典误区 2:自定义插件根据 variant 去 apply 第三方插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ThirdManagerPlugin : Plugin<Project> {
companion object {
private const val EXTENSION_NAME = "thirdConfig"
private const val ANDROID_EXTENSION_NAME = "android"
}
override fun apply(target: Project) {
val container = target.extensions
container.create(EXTENSION_NAME, ThridManagerExtension::class.java)
target.afterEvaluate {
val extension = container.findByName(EXTENSION_NAME)
val androidExtension = container.findByName(ANDROID_EXTENSION_NAME) as? AppExtension
?: return@afterEvaluate
val variants = androidExtension.applicationVariants
variants.all { variant ->
val variantName = variant.baseName.capitalize()
when {
variantName.toLowerCase().contains("debug") -> {
}
variantName.toLowerCase().contains("release") -> {
val map = mapOf("plugin" to "me-hacket-helloworld")
target.apply(map) // 无效,config后,release的variant也调用了apply,也就全局apply了,插件就生效了
}
}
}
}
}
}
根据命令去 apply 插件 – 部分有效
1
2
3
4
5
6
7
8
9
10
11
if (gradle.startParameter.taskRequests.toString().contains("production")) {
apply(...)
}
// 在root build.gradle下设置ext,其他module可以直接使用
ext {
// 命名为develop而不是debug, 规避ext可能存在重命名的风险
develop = gradle.startParameter.taskNames.any { it.toLowerCase().contains('debug') }
test = gradle.startParameter.taskNames.any { it.toLowerCase().contains('debug') || it.toLowerCase().contains('preview') }
println("develop=$develop , test=${test}")
}
输入:
1
./gradlew clean assembleDebug
输出:
1
2
taskNames=[clean, assembleDebug]
taskRequests=[DefaultTaskExecutionRequest{args=[clean, assembleDebug],projectPath='null'}]
- 如果是
./gradlew clean assembleD
那就不行了
禁用对应 Variant 的插件 Task – 有效 (总是引用插件,但禁用掉不需要的 Task)
whenTaskAdded{…}
是 Gradle 平台的 API,它才不管你上层注册的 Task 分不分 Variant,它只管把所有注册后且确定会添加进运行图(是个有向无环图 DAG)的 Task 在这里提供一个回调的时机给开发者。与此同时,几乎所有的 Android 生态协同插件都会基于 Variant 的名字去给自己 Task 命名(如果它需要是一个 VariantAware Task 的话),例如 “UploadArchiveWithLogForProductionRelease”。这个不成文的规则给了我们字符串匹配的机会,也即你看到的下面代码:
1
2
3
4
5
6
7
8
9
10
plugins { id("com.example.ua") }
// or apply("com.example.ua")
// ...
tasks.whenTaskAdded {
if (name.contains("production", true)
&& name.contains("release", true)
&& name.contains("UploadArchive", true)) {
enabled = false
}
}
mashi 案例:开发包 测试包开启 bugly,release 包开启 Firebase Crashlytics
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
tasks.whenTaskAdded { task ->
def taskName = task.name
if (taskName.contains('SymtabFile')) {
// release包不开启bugly,开发||测试包开启
if (task.name.contains('uploadDevPreviewSymtabFile')) {
task.enabled = true
println("tasks.whenTaskAdded task(${task.name}) enabled=${task.enabled}")
} else {
task.enabled = false
println("tasks.whenTaskAdded task(${task.name}) enabled=${task.enabled}")
}
}
if (taskName.contains('CrashlyticsMappingFile')) {
// release包开启Firebase,开发||测试包不开启
if (task.name.contains('uploadCrashlyticsMappingFileProductRelease')
|| task.name.contains('injectCrashlyticsMappingFileIdProductRelease')
) {
task.enabled = true
println("tasks.whenTaskAdded task(${task.name}) enabled=${task.enabled}")
} else {
task.enabled = false
println("tasks.whenTaskAdded task(${task.name}) enabled=${task.enabled}")
}
}
}
在 Task 注册时拦截 – 可以,高效
配置 Extension:
1
2
3
4
5
6
7
8
9
10
11
12
13
class DingTalkExtension {
public String pgyerApiKey
public Closure<Boolean> groovyEnableByVariant
// For Gradle Groovy DSL
void enableByVariant(Closure<Boolean> selector) {
groovyEnableByVariant = selector.dehydrate()
}
boolean isEnable(String variantName) {
if (groovyEnableByVariant == null) return true
return groovyEnableByVariant.call(variantName)
}
// ...
}
插件不生成对应的 task:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class SendApkToDingTalkPlugin implements Plugin<Project> {
@Override
void apply(Project target) {
target.afterEvaluate {
DingTalkExtension dingTalkExtension2 = target.getExtensions().findByName("dingPgyerConfig")
ExtensionContainer container = target.getExtensions()
AppExtension androidExtension = container.findByName(Constants.ANDROID_EXTENSION_NAME)
DomainObjectSet<ApplicationVariant> variants = androidExtension.getApplicationVariants()
variants.all { ApplicationVariant variant ->
String variantName = variant.baseName.capitalize()
boolean isEnable = dingTalkExtension2.isEnable(variantName)
if (!isEnable) {
return
}
// 创建蒲公英上传task
def pgyerTask = target.tasks.create("${Constants.TASK_UPLOAD_PGYER}$variantName", PgyerUploadTask.class)
pgyerTask.init(variant, target)
// 创建发送钉钉消息task
def dingTalkTask = target.tasks.create("${Constants.TASK_SEND_DINGTALK}$variantName", SendMsgToDingTalkTask.class)
dingTalkTask.init(variant, target)
def provider = variant.getAssembleProvider()
provider.get().dependsOn(target.getTasks().findByName("clean"))
pgyerTask.dependsOn(provider.get())
dingTalkTask.dependsOn(pgyerTask)
}
}
}
}
使用:
1
2
3
4
5
6
7
8
9
apply plugin: 'ding-pgyer'
dingPgyerConfig {
pgyerApiKey = "[google]70885395bcdfd5ebdb72a5856c95596c"
apiToken = "[google]632474cbdf7cce9a68cd8ece8d8aecc27eead23d888874aca73f435bd0014c83"
atMobiles = ["[google]13510590884","[google]13168757017"]
groovyEnableByVariant = { String variant ->
variant.name.toLowerCase().contains("release") // release才开启
}
}
Ref
- https://github.com/easilycoder/EasyDependency
- Android 使用自己封装的 maven-publish 插件 (maven 和 maven-publish),发布到自己或者公司的私有仓库流程
https://blog.csdn.net/intbird/article/details/105969242
遇到的问题
如何查看 Gradle 源码?
查看 build.gradle[.kts]
源码
groovy 集成
在 build.gradle
按 Ctrl + 鼠标左键
查看 dependencies
,出现的 Project.class
看不到源码
 |
解决:
将 distributionUrl
中的 xxx-bin.zip
改成包含源码的 xxx.all.zip
,再点击进去就能看到源码了
1
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
kts
集成
点击 Index 就可以查看 Gradle 源码了
如何查看插件源码?
groovy
- 编译后,在
External Libraries
查看
1
2
compile gradleApi()
compile 'com.android.tools.build:gradle:8.1.3'
- 手动 Ctrl+ 左键点击 api 进去还是查看的是 class 文件,定位到该 class,然后手动打开该文件就是源码了
kts
方式
在 External Libraries
下可以看到有很多 Script,里面就是源码
 |
适配 plugins 方式
未发布到 gradle plugin portal 仓库的三方插件适配
plugins 的方式引入:因为插件并没有发布到 gradle plugin portal 仓库中,所以需要自己修改插件的解析规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pluginManagement {
resolutionStrategy {
// 定义id和插件库映射关系
def modules = [
'android-aspectjx' : 'io.github.wurensen:gradle-android-plugin-aspectjx:2.1.0-SNAPSHOT',
]
eachPlugin {
println "id=" + requested.id.id
def module = modules.get(requested.id.id)
if (module != null) {
useModule(module)
}
}
}
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
maven-publish 适配 plugins
只使用了 maven-publish
插件,未使用 java-gradle-plugin
插件
先看看插件 module 的配置:
build.gradle.kts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
plugins {
id("java")
id("org.jetbrains.kotlin.jvm") //支持kotlin编写插件,不加上这个发布的jar包没有kotlin的类
id("maven-publish") //maven发布插件
}
// GAV
val myGroup = "me.hacket.plugin"
val myArtifactsId = "myArtifactsId"
val myVersion = "1.0.0"
val pluginId = "me.hacket.myPluginId"
publishing {
publications {
create<MavenPublication>("myMaven") {
// 这个name随意,到时是出现在publishing group作为task的名字一部分
// MavenPublication是一个类型,这里是创建一个MavenPublication类型的对象
groupId = myGroup
// artifactId = myArtifactsId
version = myVersion
from(components["java"])
}
} repositories {
maven {
url = uri("$rootDir/custom_plugin_repo")
}
}}
dependencies {
compileOnly(gradleApi()) //gradleApi()是一个函数,返回一个对象,不加上会找不到gradle相关Api
compileOnly("com.android.tools.build:gradle:8.1.2")
}
- 发布后
 |
再看看使用插件的项目配置
- 根目录的
build.gradle.kts
1
2
3
4
5
6
7
8
9
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("org.jetbrains.kotlin.jvm") version "1.8.10" apply false
id("com.android.library") version "8.2.0" apply false
// 先声明插件, 后面才能使用,否则resolution eachPlugin Strategy找不到version
id("me.hacket.myPluginId") version "1.0.0" apply false
- app
build.gradle.kts
1
2
3
4
5
6
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("me.hacket.myPluginId")
}
构建后报错
问题分析:
这是由于 plugins 方式引入插件,通过 id 和 version 没找到可实现的依赖,找不到就报错了
解决 1(只修改插件使用方): app build.gradle.kts
不使用全局的 plugins 方式引入,改成 classpath
方式,其他地方不用变
1
2
3
4
5
6
7
8
9
10
11
12
buildscript {
dependencies{
classpath("me.hacket.plugin:myGradlePlugin:1.0.1")
}
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.2.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("org.jetbrains.kotlin.jvm") version "1.8.10" apply false
id("com.android.library") version "8.2.0" apply false
}
解决 2:plugins 方式
去除 app build.gradle.kts
中的配置,还是使用 plugins 方式,但是需要手动将 plugins 方式通过 id 和 version 手动查找插件的依赖,在 setting.gradle.kts
配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven {
url = uri("$rootDir/custom_plugin_repo")
}
}
resolutionStrategy {
eachPlugin {
if (requested.id.name == "myPluginId") {
val module = "me.hacket.plugin:myGradlePlugin:${requested.version}"
useModule(module)
}
}
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
include(":app")
include(":myGradlePlugin")
//...
小结:
- 只通过
maven-publish
插件发布,未和java-gradle-plugin
插件配合,需要引入java
或java-library
插件 - 需要手动添加依赖
compileOnly(gradleApi())
,否则找不动 Gradle 相关 API maven-publish
插件不会生成 Plugin Marker Artifacts,要使用 plugins 方式需要手动映射;手动映射通过 pluginManagement resolutionStrategy 来添加
maven-publish
配合 java-gradle-plugin
插件适配 plugins
插件 module build.gradle.kts
配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
plugins {
id("java-gradle-plugin") //会自动引入java-library、gradleApi()
id("org.jetbrains.kotlin.jvm") //支持kotlin编写插件,不加上这个发布的jar包没有kotlin的类
id("maven-publish") //maven发布插件
}
// GAV
val myGroup = "me.hacket.plugin"
val myArtifactsId = "myArtifactsId"
val myVersion = "1.0.1"
val pluginId = "me.hacket.myPluginId"
gradlePlugin {
plugins {
create("myJavaGradlePluginName") { // 这个name随意
group = myGroup
version = myVersion
id = pluginId
implementationClass = "me.hacket.mygradleplugin.GreetingPlugin"
}
}}
publishing {
publications {
create<MavenPublication>("maven") {
// 这个name随意,到时是出现在publishing group作为task的名字一部分
// MavenPublication是一个类型,这里是创建一个MavenPublication类型的对象
groupId = myGroup
// artifactId = myArtifactsId
version = myVersion
from(components["java"])
}
} repositories {
maven {
url = uri("$rootDir/custom_plugin_repo")
}
}}
dependencies {
compileOnly("com.android.tools.build:gradle:8.1.2")
}
生成的 publish 相关 task
 |
publish 生成的 jar 包:
 |
me.hacket.myPluginId.gradle.plugin-1.0.1.pom
文件的 dependency 就是插件实际的 GAV,符合 plugin.id:plugin.id.gradle.plugin:plugin.version
格式的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.hacket.myPluginId</groupId>
<artifactId>me.hacket.myPluginId.gradle.plugin</artifactId>
<version>1.0.1</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>me.hacket.plugin</groupId>
<artifactId>myGradlePlugin</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
</project>