当开发一个 Android Library 时,如果还同时还要提供相应的 Gradle Plugin,对于 Gradle 新手来说,要在一个 Gradle 工程中同时集成 Android LibraryGradle PluginExample App 三个模块,并不是一件很容易的事,主要的问题在于 Android App 模块中无法引用同一个工程中的 Gradle Plugin 模块,这是因为 Gradle Plugin 需要先于所有的工程进行配置和编译,所以,很多工程师都会将 Gradle Plugin 作为一个独立的工程进行开发和发布,这对于频繁地开发和调试 Gradle Plugin 的工程师来说,是非常的痛苦,每次修改了 Gradle Plugin 都要先发布到 Maven Local,然后再跨工程进行调试,效率极低,那有什么优雅的解决方案呢?

buildSrc 模块

要让 Android App 能引用到同一个工程中的 Gradle Plugin 模块,那么,这个 Gradle Plugin 模块就不能是一个普通的模块,正好 Gradle 提供了 buildSrc 的机制,Gradle 默认会将工程根目录下的 buildSrc 目录作为 Composite Build 自动编译和测试,并将其产物添加到 buildscriptclasspath 中,这样就可以访问到 buildSrc 中定义的 Gradle Plugin 了。

虽然引用 Gradle Plugin 的问题解决了,但是,想要同时发布同一个工程中的 Gradle Plugin 模块和 Android Library 模块是一个问题,因为对于 buildSrc 来说,虽然是在同一个工程中,但其实在 Gradle 看来,它是一个完全隔离的工程,这就意味着:

  • buildSrc 模块与普通模块不能同时进行发布
  • 所有的配置和属性都不能在 buildSrc 和普通模块间共享,例如:版本号

那如何才能上述的问题呢?

影子模块

要想「鱼和熊掌兼得」,就必须将 buildSrc 模块变成一个普通模块,所以,我们可以再创建一个普通的 Java/Kotlin Library 模块,用来解决 Gradle Plugin 的发布问题,工程目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.
├── app
│   ├── build.gradle.kts
│   ├── proguard-rules.pro
│   └── src
├── sdk
│   ├── build.gradle.kts
│   ├── proguard-rules.pro
│   └── src
├── plugin (影子模块)
│   └── build.gradle.kts
├── gradle
│   └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── build.gradle.kts
├── buildSrc
│   ├── build.gradle.kts
│   └── src
└── settings.gradle.kts

但问题是,如何让这个普通模块共享 buildSrc 中的代码呢?

软链接

熟悉 Linux 的用户可能会想到一个比较便捷的方式 —— 软链接(Soft Link),有点类似于 Windows 中的「快捷方式」,通过 ln 命令来创建,我们可以在 plugin 模块下创建一个 src 的软链接到 buildSrc/src

1
2
$ cd plugin
$ ln -s ../buildSrc/src src

工程结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.
├── app
│   ├── build.gradle.kts
│   ├── proguard-rules.pro
│   └── src
├── sdk
│   ├── build.gradle.kts
│   ├── proguard-rules.pro
│   └── src
├── plugin (影子模块)
│   ├── src -> ../buildSrc/src (软链接)
│   └── build.gradle.kts
├── gradle
│   └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── build.gradle.kts
├── buildSrc
│   ├── build.gradle.kts
│   └── src
└── settings.gradle.kts

这样就很好的解决了普通 Gradle 模块与 buildSrc 模块共享代码的问题了。

Source Set

由于存在平台间的差异性,例如 Windows*nix 系统之间是否都能很好的兼容软链接,不是很确定(还未在 Windows 上测试过),有没有一种兼容性更好的方案呢?答案是肯定的 —— Source Set,我们可以为 plugin 模块配置额外的 SourceSet

1
2
3
4
5
6
7
sourceSets {
main {
java {
srcDirs("../buildSrc/src/main/java")
}
}
}

这样就完美的解决了所有问题,对于版本号的问题,我们可以在根目录下的 build.gradle.kts 中通过 allprojects 或者 subprojects 来统一配置:

1
2
3
4
allprojects {
group = "io.johnsonlee"
version = "1.0.0"
}

所以,plugin 模块用来解决 Gradle Plugin 的发布问题,而 buildSrc 用来解决同一个工程中的 Gradle Plugin 的引用问题,plugin 模块与 buildSrc 模块共享同一份代码,但 build.gradle.kts 除外,稍微有点细微的差别:

  • 由于 buildSrc 是一个完全独立的工程,如果使用 plugins DSL 来启用插件需要指定版本号
  • plugin 模块中使用 plugins DSL 来启用插件不用指定版本号(因为在根目录中的 build.gradel.kts 已经指定过)
  • buildSrc 模块不需要发布,所以,不需要启用 maven-publish 等跟发布相关的插件

buildSrc/build.gradle.kts

1
2
3
4
5
6
plugins {
`java-gradle-plugin`
`kotlin-dsl`
kotlin("jvm") version "1.5.30"
kotlin("kapt") version "1.5.30"
}

plugin/build.gradle.kts

1
2
3
4
5
6
7
plugins {
`java-gradle-plugin`
`maven-publish`
`signing`
kotlin("jvm")
kotlin("kapt")
}