本文主要内容:
- 依赖管理:gradle 插件 7.0 以上版本使用 version catalog 进行依赖管理
- 通用编译扩展属性管理:在插件工程配置各子工程通用的扩展属性、插件、依赖
- 插件工程:插件工程和主工程混合编译,以及 gradle 插件 7.0 以上版本如何发布到 jitpack。
现在的项目基本都是多模块结构,每次新增模块都复制一遍 minSdk
、targetSdk
之类的通用扩展属性会比较麻烦,应该用通用的插件来管理。
插件使用案例:plugin/sample
目前已发布到 gradle 插件仓库:config-plugin。
方式一:
// 项目根目录 build.gradle.kts
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath("io.github.jadyli:config-plugin:0.1.14")
}
}
方式二(推荐):
// 项目根目录 settings.gradle.kts
pluginManagement {
repositories {
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
when (requested.id.id) {
"io.github.jadyli.config-plugin" -> {
useModule("io.github.jadyli:config-plugin:0.1.14")
}
}
}
}
}
方式一:在项目根目录 gradle.properties 文件中配置
minSdk=21
targetSdk=31
compileSdk=31
javaMajor=11
javaVersion=11
composeCompiler=1.1.0-beta04
方式二(推荐):在项目根目录 build.gradle.kts 中配置
// 这里是使用 version catalog 的写法,也可以直接替换成对应的版本号,参考方式一
ext {
set("minSdk", libs.versions.minSdk.get())
set("targetSdk", libs.versions.targetSdk.get())
set("compileSdk", libs.versions.compileSdk.get())
set("javaMajor", libs.versions.java.major.get())
set("javaVersion", libs.versions.java.asProvider().get())
set("composeCompiler", libs.versions.compose.get())
}
这些都是必填项(compose 也是的,插件会默认添加对应版本的 compose 依赖和配置,如果实在不需要可以评论留言,后续版本可以加判断去掉)。
// 方式一(推荐),配合 version catalog 可以写成 alias(libs.plugins.config.plugin)
plugins {
id("io.github.jadyli.config-plugin")
}
// 方式二
apply(from = "io.github.jadyli.config-plugin")
添加完成后,一个 library 的配置文件将会非常简洁,只需要添加一些模块需要的插件和依赖就好了(下面的例子用了 toml)。
@file:Suppress("UnstableApiUsage")
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.hilt.android)
alias(libs.plugins.config.plugin)
}
dependencies {
// official library
implementation(libs.bundles.compose.core)
implementation(libs.hilt.runtime)
kapt(libs.bundles.hilt.compiler)
// other module
api(projects.framework.utils)
}
gradle 共享依赖官方文档:https://docs.gradle.org/current/userguide/platforms.html
toml 官网:https://toml.io/en/
以前,依赖的管理一直没有一种统一的方式,有的是通过设置进 project properties 中(比如建里一个 dependencies.gradle 文件管理),有的是建立一个插件工程,把所有的依赖都放插件里,这样有个好处就是这个插件可以多个项目共用,取的时候也是变量的形式来使用,version catalog 跟这种插件的形式比较像,只是 version catalog 是通用 toml 格式的文件来定义依赖的。
[versions]
# build config
compileSdk = "31"
# official library
kotlin = "1.6.0"
compose = "1.1.0-beta04"
androidx-appcompat = "1.4.0"
[libraries]
# official library
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-activity-compose = "androidx.activity:activity-compose:1.4.0"
compose-material = { module = "androidx.compose.material3:material3", version = "1.0.0-alpha02" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } }
[plugins]
android-application = { id = "com.android.application", version.ref = "android-plugin" }
[bundles]
compose-core = ["androidx-activity-compose", "compose-uiTooling", "compose-material"]
总结一下:
- gradle 支持四种节点:
[versions]
用于定义版本号,版本号必须用字符串。[libraries]
用于定义依赖,支持的格式参考上面的例子[plugins]
用于定义插件[bundles]
用于定义依赖组
- 版本号支持指定单个版本和版本范围。具体规则可以参考 VersionConstraint 类的注释,也可以参考 Declaring Rich Versions,这里简单说明下:
- 版本范围用区间表示。
(
、)
表示开区间,[
、]
表示闭区间。[
放在区间右边相当于)
,比如[1, 2[
相当于[1, 2)
,]
放在区间左边相当于(
,比如]1, 2]
相当于(1, 2]
。 - 有四种版本匹配模式可以指定:
strictly
、require
、prefer
、reject
。 require
(不指定模式时,require
是默认模式)的版本或版本范围对应的是 VersionConstraint 中的getRequiredVersion()
,required version 表示最低支持的版本,可以更高,但不能比它低。strictly
表示严格要求的版本,字面意思,prefer
、reject
也是字面意思。
- 版本范围用区间表示。
定义的这些依赖可以本地用,也可以发布后使用。
toml 文件位置没有规则,可自定义放置,一般放根目录 gradle
目录下,也可以放自己的配置文件目录,只要脚本文件中能引用到。现在可以定义 version catalog 容器了,参考:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("${rootDir.path}/.config/dependencies-common.toml"))
}
}
}
sync 完之后会根据 toml 文件生成对应的 java 代码,你在 build.gradle
中已经能使用 libs
变量了。举个例子:
[versions]
kotlin = "1.6.0"
kotlin-coroutines = "1.6.0-RC2"
[libraries]
kotlin-stdlib-jdk7 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" }
[bundles]
compose-core = ["androidx-activity-compose", "compose-uiTooling", "compose-material"]
[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
对应的代码调用为:
val kotlinVersion = libs.versions.kotlin.asProvider().get()
val kotlinCoroutinesVersion = libs.versions.kotlin.coroutines.get()
implementation(libs.kotlin.stdlib.jdk7)
implementation(libs.bundles.compose.core)
// 当前版本的 as 对 version catalog 支持不好,需要加下 @Suppress。
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.kotlin.android)
}
发布代码:
@file:Suppress("UnstableApiUsage")
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(commonLibs.plugins.kotlin.dsl)
alias(commonLibs.plugins.gradle.publish)
}
group = "io.github.jadyli"
version = "0.1.14"
gradlePlugin {
plugins.register("config") {
id = "io.github.jadyli.config-plugin"
implementationClass = "com.jady.lib.config.ConfigPlugin"
displayName = "Common config plugin for Android"
description = "A plugin help you to config android extension"
}
}
pluginBundle {
website = "https://github.com/Jadyli/composing"
vcsUrl = "https://github.com/Jadyli/composing.git"
tags = arrayListOf("android", "config")
}
publishing {
repositories {
maven {
setUrl("../../local-repo")
name = "localRepo"
}
}
}
dependencies {
implementation(commonLibs.plugin.source.android)
implementation(commonLibs.plugin.source.kotlin)
}
发布完后,更改上面提到的 dependencyResolutionManagement 即可。其他项目也可以通过这种方式共用依赖。
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from("com.mycompany:catalog:1.0")
}
}
}
这里是的扩展属性是指这样的(BaseAppModuleExtension
):
android {
compileSdk 30
defaultConfig {
applicationId "com.jady.sample"
minSdk 21
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
多模块的时候,buildTypes
、compileOptions
这样的属性,每个模块都配置一遍倒是不算太麻烦的,麻烦的是每次修改的时候,每个模块都要改,这对于维护来说是非常不友好的。在插件里处理掉这些属性是比较好的一种做法。
我们先来创建一个插件项目。在创建之前,先讲下 Composing builds,虽然几年前就有了,但国内好像用的公司不多。那我这里来简单说下,Composing build 我个人更喜欢叫混合编译,也有翻译成复合构建或者组合构建的,我不管,我这里就叫混合编译🧐。
简单来说,混合编译的主要作用就是既可以让小模块作为独立工程编译运行,又可以作为大工程的一员一起编译运行。目前国内做编译加速的思路基本都是模块 aar 化,一个几十个模块的工程,针对没有改动的模块使用 aar 编译, 改动了的模块使用源码编译。实际上,如果模块比较独立,还可以更快一点,就是混合编译,单个模块做为独立工程开发调试,开发完之后啥也不用改,用大工程出包。
可以看我们插件的 master 分支。
注意插件是一个独立的工程,可以单独编译运行,可以切到插件源码 feature/plugin 分支查看。
所以这里的插件工程就是采用混合编译的形式,在主项目的 settings.gradle 文件中 includeBuild("plugins")
,然后其他跟第一节讲的使用方式一样就可以了,是不是很神奇?即使我们在 pluginManagement
里设置了对应插件 id 的依赖地址,工程编译还是会使用我们的插件源码!当我们开发完毕,只需要发布新版,注释掉 includeBuild("plugins")
就好了。
当然 includeBuild
也可以用于普通工程,和插件工程稍微有点不同,比如有如下结构的多个工程:
------------------------------------------------------------
Root project 'ProjectA'
------------------------------------------------------------
Root project 'ProjectA'
+--- Project ':app'
Included builds
\--- Included build ':ProjectB'
Project ':ProjectB'
+--- Project ':Android:moduleA'
+--- Project ':ProjectB:moduleB'
在 ProjectA
里 includeBuild
了 ProjectB
,:ProjectA:app
想要使用 :ProjectB:moduleB
,你可能会这样写:
// :ProjectA:app build.gradle
dependencies {
implementation project(':ProjectB:moduleB')
}
你会发现,找不到这个模块,因为这个 project()
方法是 app
模块的,这个模块以及 rootProject
(projectA
)都不持有 moduleB
,moduleB
是属于 ProjectB
的,所以这样写不行。
正确的做法是,用依赖库的形式,就是 moduleB
发布到 maven
时需要填写 group
、name
、version
,在 app
模块的 build.gradle
文件也是这样写,比如:
implementation 'com.jady.demo:moduleB:1.0'
然后在 ProjectA
的 settings.gradle
中加入混合编译的代码:
includeBuild("../ProjectB"){
dependencySubstitution {
substitute(module("com.jady.demo:moduleB:1.0")).using(project(":moduleB"))
}
}
含义很明显,使用源码工程依赖替换这个 maven 的依赖,所以这个 moduleB
可以不用实际发布到 maven
,反正会被替换。
下面来配置插件的 build.gradle
。
plugins {
alias(commonLibs.plugins.kotlin.dsl)
alias(commonLibs.plugins.gradle.publish)
}
group = "io.github.jadyli"
version = "0.1.14"
gradlePlugin {
plugins.register("config") {
id = "io.github.jadyli.config-plugin"
implementationClass = "com.jady.lib.config.ConfigPlugin"
displayName = "Common config plugin for Android"
description = "A plugin help you to config android extension"
}
}
pluginBundle {
website = "https://github.com/Jadyli/composing"
vcsUrl = "https://github.com/Jadyli/composing.git"
tags = arrayListOf("android", "config")
}
引入插件,设置 group 和 version,定义插件 id 和入口,就完成了。
目前已经不发布到 jitpack 了,直接发布到 gradle 中心了,所以下面的过程可以忽略,实在需要发布到 jitpack 的也可以参考。
看下怎么发布到 jitpack。切到 分支,跑插件工程,jitpack 默认使用 java 1.8 编译,我们这里用的是 java 11,所以需要配置一下 jitpack,根目录添加 feature/plugin,内容如下:~~jitpack.yml~~
jdk:
- openjdk11
然后提交,给提交加个 tag,就可以在 jitpack 跑了(jitpack 地址),点击 Get it 会执行编译。
看日志文件最后的输出:
Build artifacts:
com.github.Jadyli.composing:config-plugin:0.1.2
com.github.Jadyli.composing:com.jady.lib.config-plugin.gradle.plugin:0.1.2
你会发现有两个,我们直接用第一个就好啦,具体参考第一节。
下面来写插件:
@Suppress("UnstableApiUsage")
class ConfigPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.run {
plugins.all {
when (this) {
is LibraryPlugin -> configureLibraryPlugin()
is AppPlugin -> configureAppPlugin()
}
}
}
}
private fun Project.configureLibraryPlugin() {
configCommonPlugin()
configCommonDependencies()
extensions.getByType<LibraryExtension>().configCommonExtension(this@configureLibraryPlugin)
}
private fun Project.configureAppPlugin() {
configCommonPlugin()
configCommonDependencies()
extensions.getByType<BaseAppModuleExtension>().run {
configCommonExtension(this@configureAppPlugin)
defaultConfig {
splits {
abi {
isEnable = true
reset()
include("x86", "armeabi-v7a", "arm64-v8a")
isUniversalApk = false
}
}
}
...
}
}
private fun BaseExtension.configCommonExtension(project: Project) {
setCompileSdkVersion(project.property("compileSdk").toString().toInt())
...
}
private fun Project.configCommonPlugin() {
plugins.apply("org.jetbrains.kotlin.android")
...
}
private fun Project.configCommonDependencies() {
...
}
}
插件很简单,可以配置属性、插件、依赖,看代码都懂,没啥好说的,大家有需求可以评论,上面的 split 应该是有的项目用不了的,也可以根据自己的需求 fork 一份出来随意改。