使用 Kotlin DSL 和 buildSrc 优化 Gradle 任务管理
Publié le 11 July 2025
构建任务的自动化对任何软件项目都至关重要,而 Gradle 凭借其 Kotlin DSL 提供了卓越的灵活性。在我们的对话过程中,我们探讨了如何集中和复用构建逻辑,尤其是针对测试报告,通过利用所有项目, buildSrc和自定义 Kotlin 任务。
使用 allprojects 和 subprojects 集中管理配置
这些块`allprojects` et `subprojects`在你的`build.gradle.kts`根是应用跨多模块项目的通用配置的基础。
*allprojects { … }: 应用配置到根项目及其所有子项目. 适合定义一个`group`,一个`version`, 或一些`repositories`共同的。 *subprojects { … }: 应用配置仅限于子项目, 排除根项目。非常适合将特定于模块的插件应用于模块(如`java` ou kotlin-jvm) 或您库中的公共依赖
以下是一个示例:
// build.gradle.kts (projet racine)
plugins {
base // Appliqué au projet racine
}
allprojects {
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
}
tasks.withType<org.gradle.api.tasks.testing.Test> {
useJUnitPlatform() // Configuration commune des tests pour tous les projets
}
}
subprojects {
apply(plugin = "java")
apply(plugin = "org.jetbrains.kotlin.jvm")
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}
}
\== `buildSrc`构建逻辑的瑞士军刀
当您的任务逻辑变得复杂或需要重复使用时buildSrc是首选方案。这是一个特殊的 Gradle 模块,在主要构建脚本之前编译,使其类在整个构建的 classpath 上可用。
\=== 为什么使用`buildSrc`对于任务?
-
可重用性一个定义在`buildSrc`可以应用于构建的任何项目。
-
组织: 集中构建代码,使其更清晰且易于维护。
-
类型安全和自动补全: Kotlin 代码在`buildSrc`已编译,提供 IDE 的错误检查和自动补全,提升开发体验。
\=== 流程图`buildSrc`(PlantUML代码)
要生成此图表,请复制以下代码并将其粘贴到支持 PlantUML 的工具中。
[source,plantuml]
@startuml skinparam 手写的 true skinparam 单色 true
矩形 "Gradle 构建过程" { 组件 "buildSrc" 作为 BS { file "MyCustomTask.kt" as T 文件 "MyConventionPlugin.kt" 作为 P } 组件 "Root Project" 作为 RP 组件 "Subproject A" 作为 SA 组件 "Subproject B" 作为 SB }
T --\> P : "定义于 P --\> RP : \"应用于\ P --\> SA : "应用于 P -→ SB : "被应用于
RP --|\> SA : "contains RP --|\> SB : "contains
RP -up-\> BS : "depends on (for build logic) SA -up-\> BS : "依赖(用于构建逻辑) SB -up-\> BS : "依赖于(用于构建逻辑)
T 右侧的注释 自定义任务类 (例如: OpenTestReportTask) end note
注释在P的右侧 约定插件 记录任务的 尾注
@enduml
抽象报告任务创建
为了管理测试报告,我们设计了一种模块化的方法,通过使用抽象任务类在`buildSrc`. 根据您的需求,此类可以继承`DefaultTask` ou de Exec。
\=== 抽象任务`OpenTestReportTask`(继承自`DefaultTask`)
这种方法适用于您需要自定义的 Kotlin 逻辑与文件系统或其他 Gradle API 交互,然后启动外部命令的情况。
[source,kotlin]
包 com.yourpackage
导入 org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction 导入 java.io.File
抽象类 OpenTestReportTask : DefaultTask() {
----
----
init {
group = "verification"
description = "Opens a test report in Firefox."
dependsOn("check") // Assure que les rapports sont générés
}
@get:Input
abstract var reportPath: String
@TaskAction
fun openReport() {
val separator = File.separator
val reportFile = project.layout.projectDirectory.asFile.toPath()
.resolve(reportPath.replace("/", separator))
.toAbsolutePath()
.toFile()
if (!reportFile.exists()) {
logger.warn("Report file does not exist: $reportFile. Ensure 'check' ran.")
return
}
project.exec {
commandLine("firefox", "--new-tab", reportFile.absolutePath)
}
logger.lifecycle("Opened test report: ${reportFile.absolutePath}")
}
----
## }
\=== 具体实现
这些类继承自抽象任务,并定义了报告的特定路径。
## [source,kotlin]
包 com.yourpackage
抽象类 ReportUnitTestsTask : OpenTestReportTask() init { 在 Firefox 中打开单元测试报告。 reportPath = "build/reports/tests/test/index.html } }
package com.yourpackage
abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "在Firefox中打开功能测试报告。" reportPath = "build/reports/tests/functionalTest/index.html" } }
\=== 任务记录
在`build.gradle.kts`您的根项目 :
## [source,kotlin]
tasks.register\<com.yourpackage.ReportUnitTestsTask\>("reportTests") {} tasks.register\<com.yourpackage.ReportFunctionalTestsTask\>("reportFunctionalTests") {}
报告任务UML图 (PlantUML代码)
复制下面的代码,然后粘贴到支持 PlantUML 的工具。
## [source,plantuml]
@startuml skinparam handwritten true skinparam 单色 true
�抽象类 DefaultTask { }
abstract class Exec { \+ commandLine(args: String...) \+ exec() }
abstract class OpenTestReportTask extends DefaultTask { \+ group: String = "verification + 描述: 字符串 \+ dependsOn("check") + 抽象 报告路径: 字符串 + openReport() : void }
抽象类 AbstractJbakeExecTask 继承自 Exec { \+ group: String = "验证 \+ 描述: 字符串 \+ dependsOn("check") \+ abstract reportRelativePath: String \+ exec() : 无返回值 }
类 ReportUnitTestsTask 继承自 OpenTestReportTask { \+ reportPath: String = "build/reports/tests/test/index.html }
class ReportFunctionalTestsTask extends OpenTestReportTask { \+ reportPath: String = "build/reports/tests/functionalTest/index.html }
类 ReportJbakeTestsTask 继承 AbstractJbakeExecTask { \+ reportRelativePath: String = "build/reports/tests/test/index.html }
类 ReportJbakeFunctionalTestsTask 继承 AbstractJbakeExecTask { \+ reportRelativePath: 字符串 = "build/reports/tests/functionalTest/index.html }
OpenTestReportTask \<-- ReportUnitTestsTask OpenTestReportTask \<-- ReportFunctionalTestsTask
AbstractJbakeExecTask <-- ReportJbakeTestsTask AbstractJbakeExecTask \<-- ReportJbakeFunctionalTestsTask
默认任务 \<|-- 打开测试报告任务 Exec \<|-- AbstractJbakeExecTask 默认任务 <|-- 执行
## @enduml
\== 抽象任务`AbstractJbakeExecTask`(继承`Exec`)
如果您的任务主要是执行带有变量参数的外部命令,请直接继承**�执行**更直接。
## [source,kotlin]
包 com.yourpackage
import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File
抽象类 AbstractJbakeExecTask : Exec() {
----
init { group = "verification" description = "Opens a Jbake project report in Firefox." dependsOn("check") }
@get:Input abstract var reportRelativePath: String
override fun exec() { val separator = File.separator val reportFile = project.layout.projectDirectory.asFile.toPath() .resolve(reportRelativePath.replace("/", separator)) .toAbsolutePath() .toFile()
if (!reportFile.exists()) { logger.warn("Report file does not exist: $reportFile. Ensure 'check' ran.") return }
commandLine("firefox", "--new-tab", reportFile.absolutePath) logger.lifecycle("Attempting to open report: ${reportFile.absolutePath}") super.exec() // Appelle la méthode exec() de la super-classe Exec }
## }
\=== 具体实现用于`Exec`
## [source,kotlin]
package com.yourpackage
抽象类 ReportJbakeTestsTask : AbstractJbakeExecTask() 初始化 { 在 Firefox 中打开 Jbake 单元测试报告。 reportRelativePath = "build/reports/tests/test/index.html } }
package com.yourpackage
## abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "在 Firefox 中打开功能测试报告。" reportRelativePath = "build/reports/tests/functionalTest/index.html" } }
\== 任务可见性`buildSrc`
您定义的任务和类在**buildSrc**是:
***可见且可用**由您的主构建中的所有项目(根项目和子项目)。因此,您可以使用`com.yourpackage.ReportUnitTestsTask`在`allprojects { ... }`. ***不可执行的**直接作为 buildSrc 任务(例如,`gradle :buildSrc:reportTests`如果任务没有被具体地记录在`buildSrc/build.gradle.kts`)。`buildSrc`这是一个编译模块,而不是用于这些主构建任务的可执行应用模块。
\=== 为测试运行报告`buildSrc`他自己
Si `buildSrc`它有自己的测试并生成报告,您可以直接在其中记录报告任务`buildSrc/build.gradle.kts`:
## [source,kotlin]
插件 { `kotlin-jvm` }
仓库 { mavenCentral() }
tasks.withType<Test>{ useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir("reports/tests")) }
import com.yourpackage.ReportJbakeTestsTask // 导入您的任务类
tasks.register<ReportJbakeTestsTask>("reportBuildSrcTests") { // 路径已经在类中定义,它将指向 buildSrc 的报告。 }
然后您可以执行 :`./gradlew :buildSrc:test`随后`./gradlew :buildSrc:reportBuildSrcTests`.
通过采用这些实践,您将构建出不仅功能强大,而且模块化程度极高、易于维护且易于理解的 Gradle Kotlin DSL 构建系统。