构建任务的自动化对任何软件项目都至关重要,而 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 &#60;-- 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 构建系统。

相关文章