△ Gradle 与 AGP 构建 API: 进一步完善您的插件
在本文中,您将会学习 Gradle 的 Task、Provider、Property 以及使用 Task 进行输入与输出。同时您也将进一步完善您的插件,并学习如何使用新的 Artifact API 访问各种构建产物。
Artifact API
https://developer.android.google.cn/studio/build/extend-agp#variant-api-artifacts-tasks
Property
假设我想要创建一个插件,该插件可以使用 Git 版本自动更新应用清单文件中指定的版本号。为了达到这一目标,我需要为构建添加两个 Task。第一个 Task 会获取 Git 版本,而第二个 Task 将会使用该 Git 版本来更新清单文件。
让我们从创建名为 GitVersionTask 的新任务开始。GitVersionTask 需要继承 DefaultTask,同时实现带有注解的 taskAction 函数。下面是查询 Git 树顶端信息的代码。
abstract class GitVersionTask: DefaultTask() {
fun taskAction(){
// 这里是获取树版本顶端的代码
val process = ProcessBuilder(
"git",
"rev-parse --short HEAD"
).start()
val error = process.errorStream.readBytes().toString()
if (error.isNotBlank()) {
System.err.println("Git error : $error")
}
var gitVersion = process.inputStream.readBytes().toString()
//...
}
}
abstract val gitVersionOutputFile: RegularFileProperty
RegularFileProperty
https://docs.gradle.org/current/javadoc/org/gradle/api/file/RegularFileProperty.html
Property
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Property.html
OutputFile
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/OutputFile.html
abstract class GitVersionTask: DefaultTask() {
abstract val gitVersionOutputFile: RegularFileProperty
fun taskAction() {
gitVersionOutputFile.get().asFile.writeText("1234")
}
}
现在,Task 已经准备就绪,让我们在插件代码中对其进行注册。首先,我会创建一个名为 ExamplePlugin 的新插件类,并在其中实现 Plugin。如果您不熟悉在 buildSrc 文件夹中创建插件的流程,可以回顾本系列的前两篇文章:《Gradle 与 AGP 构建 API: 配置您的构建文件》、《Gradle 与 AGP 构建 API: 如何编写插件》。
Plugin
https://docs.gradle.org/current/javadoc/org/gradle/api/Plugin.html
override fun apply(project: Project) {
project.tasks.register(
"gitVersionProvider",
GitVersionTask::class.java
) {
it.gitVersionOutputFile.set(
File(
project.buildDir,
"intermediates/gitVersionProvider/output"
)
)
it.outputs.upToDateWhen { false }
}
}
在 Task 执行完毕后,我就可以检查位于 build/intermediates 文件夹下的 output 文件了。我只要验证 Task 是否存储了我所硬编码的值即可。
接下来让我们转向第二个 Task,该 Task 会更新清单文件中的版本信息。我将它命名为 ManifestTransformTask,并使用两个 RegularFileProperty 对象作为它的输入值。
abstract class ManifestTransformerTask: DefaultTask() {
abstract val gitInfoFile: RegularFileProperty
abstract val mergedManifest: RegularFileProperty
}
fun taskAction() {
val gitVersion = gitInfoFile.get().asFile.readText()
var manifest = mergedManifest.asFile.get().readText()
manifest = manifest.replace(
"android:versionCode=\"1\"",
"android:versionCode=\"${gitVersion}\""
)
}
abstract val updatedManifest: RegularFileProperty
注意 : 我本可以使用 VariantOutput 直接设置 versionCode,而无需重写清单文件。但是为了向您展示如何使用构建产物转换,我会通过本示例的方式得到相同的效果。 https://developer.android.google.cn/reference/tools/gradle-api/7.1/com/android/build/api/variant/VariantOutput?hl=en#versionCode:org.gradle.api.provider.Property
val androidComponents = project.extensions.getByType(
AndroidComponentsExtension::class.java
)
androidComponents.onVariants { variant ->
//...
}
AndroidComponentsExtension
https://developer.android.google.cn/reference/tools/gradle-api/7.0/com/android/build/api/extension/AndroidComponentsExtension
Provider
您可以使用 Provider 连接 Property 到其他需要执行耗时操作 (例如读取文件或网络等外部输入) 的 Task。
Provider
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html
我会从注册 ManifestTransformerTask 开始。此 Task 依赖 gitVersionOutput 文件,而该文件是前一个 Task 的输出。我将通过使用 Provider 来访问这一 Property。
val manifestUpdater: TaskProvider = project.tasks.register(
variant.name + "ManifestUpdater",
ManifestTransformerTask::class.java
) {
it.gitInfoFile.set(
//...
)
}
get()
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#get--
map()
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#map-org.gradle.api.Transformer-
flatMap()
https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html#flatMap-org.gradle.api.Transformer-
当我查看 register() 的返回类型时,发现它返回了给定类型的 TaskProvider。我将其赋值给了一个新的 val。
val gitVersionProvider = project.tasks.register(
"gitVersionProvider",
GitVersionTask::class.java
) {
it.gitVersionOutputFile.set(
File(
project.buildDir,
"intermediates/gitVersionProvider/output"
)
)
it.outputs.upToDateWhen { false }
}
register()
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskContainer.html#register-java.lang.String-
TaskProvider
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskProvider.html
现在我们回过头来设置 ManifestTransformerTask 的输入。在我尝试将来自 Provider 的值映射为输入 Property 时,产生了一个错误。map() 的 lambda 参数接收某种类型 (如 T) 的值,该函数会产生另一个类型 (如 S) 的值。
然而,在本例中,set 函数需要 Provider 类型。我可以使用 flatMap() 函数,该函数也接收一个 T 类型的值,但会产生一个 S 类型的 Provider,而不是直接产生 S 类型的值。
it.gitInfoFile.set(
gitVersionProvider.flatMap(
GitVersionTask::gitVersionOutputFile
)
)
转换
variant.artifacts.use(manifestUpdater)
.wiredWithFiles(
ManifestTransformerTask::mergedManifest,
ManifestTransformerTask::updatedManifest
).toTransform(SingleArtifact.MERGED_MANIFEST)
toTransform()
https://developer.android.google.cn/reference/tools/gradle-api/7.1/com/android/build/api/artifact/InAndOutFileOperationRequest#toTransform(com.android.build.api.artifact.InAndOutFileOperationRequest.toTransform.ArtifactTypeT)
BuiltArtifactsLoader
DirectoryProperty
https://docs.gradle.org/current/javadoc/org/gradle/api/file/DirectoryProperty.html
FileProperty
https://docs.gradle.org/current/javadoc/org/gradle/api/file/RegularFileProperty.html
SingleArticfact.APK
https://developer.android.google.cn/reference/tools/gradle-api/7.0/com/android/build/api/artifact/SingleArtifact.APK
我还需要一个类型为 BuiltArtifactsLoader 的 Property 作为 Task 的第二个输入,我会用它从元数据文件中加载 BuiltArtifacts 对象。元数据文件描述了 APK 目录下的文件信息。若您的项目包含原生组件、多种语言等要素,那么每次构建都可以产生数个 APK。BuiltArtifactsLoader 抽象了识别每个 APK 及其属性 (如 ABI 和语言) 的过程。
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
BuiltArtifactsLoader
https://developer.android.google.cn/reference/tools/gradle-api/7.0/com/android/build/api/variant/BuiltArtifactsLoader
BuiltArtifacts
https://developer.android.google.cn/reference/tools/gradle-api/7.0/com/android/build/api/variant/BuiltArtifacts
val builtArtifacts = builtArtifactsLoader.get().load(
apkFolder.get()
)?: throw RuntimeException("Cannot load APKs")
if (builtArtifacts.elements.size != 1)
throw RuntimeException("Expected one APK !")
val apk = File(builtArtifacts.elements.single().outputFile).toPath()
println("Insert code to verify manifest file in ${apk}")
println("SUCCESS")
project.tasks.register(
variant.name + "Verifier",
VerifyManifestTask::class.java
) {
it.apkFolder.set(variant.artifacts.get(SingleArtifact.APK))
it.builtArtifactsLoader.set(
variant.artifacts.getBuiltArtifactsLoader()
)
}
总结
插件
https://github.com/android/gradle-recipes/blob/agp-7.1/BuildSrc/manifestUpdaterTest/buildSrc/src/main/kotlin/ExamplePlugin.kt
首先
https://github.com/android/gradle-recipes/blob/agp-7.1/BuildSrc/manifestUpdaterTest/buildSrc/src/main/kotlin/GitVersion.kt
随后
https://github.com/android/gradle-recipes/blob/agp-7.1/BuildSrc/manifestUpdaterTest/buildSrc/src/main/kotlin/ManifestTransformerTask.kt
最后
https://github.com/android/gradle-recipes/blob/agp-7.1/BuildSrc/manifestUpdaterTest/buildSrc/src/main/kotlin/VerifyManifestTask.kt
官方文档
https://developer.android.google.cn/studio/build/extend-agp
gradle-recipes
https://github.com/android/gradle-recipes
推荐阅读
点击屏末 | 阅读原文 | 即刻了解扩展 Android Gradle 插件更多内容