Die Automatisierung von Build-Aufgaben ist für jedes Softwareprojekt entscheidend, und Gradle bietet mit seinem Kotlin-DSL eine außergewöhnliche Flexibilität. Im Verlauf unseres Gesprächs haben wir erforscht, wie man Build-Logik zentralisieren und wiederverwenden kann, insbesondere für Testberichte, indem manallprojects, buildSrc, und personalisierte Aufgaben in Kotlin.

Zentralisiere die Konfigurationen mit allprojects und `subprojects

Die Blöcke`allprojects` et `subprojects`in Ihrem`build.gradle.kts`Die Wurzeln sind grundlegend für die Anwendung gemeinsamer Konfigurationen über dein Multi-Modul-Projekt.

*allprojects { …​ }`Wende die Konfiguration auf denRoot-Projekt und zu allen seinen Unterprojekten. Ideal zum Definierenden eines`group, eine`version`, oder einige`repositories`gemeinsame. *subprojects { …​ }`Wende die Konfiguration annur zu den Unterprojekten, ohne das Stammprojekt. Perfekt zum Anwenden von modulspezifischen Plugins (wie`java ou kotlin-jvm) oder gemeinsame Abhängigkeiten Ihrer Bibliotheken.

Hier ist ein illustratives Beispiel:

// 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`Das Schweizer Taschenmesser der Build-Logik

Wenn die Logik Ihrer Aufgaben komplex wird oder wiederverwendet werden muss,buildSrcist die bevorzugte Lösung. Es ist ein spezielles Gradle-Modul, das vor den Haupt-Build-Skripten kompiliert wird, wodurch seine Klassen im Klassenpfad des gesamten Builds verfügbar werden.

Warum verwenden`buildSrc`für die Aufgaben ?

  • Wiederverwendbarkeit: Eine definierte Aufgabe in`buildSrc`kann auf jedes Build-Projekt angewendet werden.

  • OrganisationZentralisiert den Build-Code, wodurch er sauberer und wartbarer wird.

  • Typsicherheit und Autovervollständigung: Der Kotlin-Code in`buildSrc`wird kompiliert, bietet die Fehlerprüfung und Autovervollständigung Ihrer IDE, verbessert das Entwicklungserlebnis.

\=== Flussdiagramm`buildSrc`(Code PlantUML)

Kopieren Sie den unten stehenden Code und fügen Sie ihn in ein Tool ein, das PlantUML unterstützt.

[source,plantuml]

@startuml skinparam handwritten true skinparam monochrome wahr

Rechteck "Gradle Build Process" { Komponente "buildSrc" als BS { file "MyCustomTask.kt" as T Datei "MyConventionPlugin.kt" als P } Komponente "Root Project" als RP Komponente "Subproject A" als SA Komponente "Subproject B" als SB }

T --\> P : "ist definiert in P -→ RP : "wird angewendet auf P --\> SA : "is applied to P --\> SB : "wird angewendet auf

RP --|\> SA : "enthält RP --|\> SB : "contains

RP -up→ BS : "abhängig von (für die Build-Logik) SA -up-\> BS : "abhängig von (für die Build-Logik) SB -up-\> BS : "abhängig von (für die Build-Logik)

Notiz rechts von T Benutzerdefinierte Aufgabenklassen (z. B.: OpenTestReportTask) Endnote

Notiz rechts von P Konventions-Plugins die die Aufgaben aufzeichnen Endnote

@enduml

\== Erstellung von abstrakten Berichtsaufgaben

Um die Testberichte zu verwalten, haben wir einen modularen Ansatz entwickelt, indem wir eine abstrakte Aufgabenclasse in`buildSrc`. Je nach Ihren Bedürfnissen kann diese Klasse von erben`DefaultTask` ou de Exec.

\=== Abstrakte Aufgabe`OpenTestReportTask`(erbend von`DefaultTask`)

Dieser Ansatz wird empfohlen, wenn Sie eine benutzerdefinierte Kotlin-Logik benötigen, die mit dem Dateisystem oder anderen Gradle-APIs interagiert und anschließend einen externen Befehl ausführt.

[source,kotlin]

package com.yourpackage

import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import java.io.File

abstract class 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}")
}
----

## }

\=== konkrete Implementierungen

Diese Klassen erben von der abstrakten Aufgabe und definieren den spezifischen Pfad des Berichts.

## [source,kotlin]

package com.yourpackage

abstract class ReportUnitTestsTask : OpenTestReportTask() init { description = "Öffnet den Einzeltestbericht in Firefox. reportPath = "build/reports/tests/test/index.html } }

package com.yourpackage

## abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "Öffnet den Funktionaltestbericht in Firefox." reportPath = "build/reports/tests/functionalTest/index.html" } }

\=== Aufzeichnung der Aufgaben

Im`build.gradle.kts`von Ihrem Stammprojekt :

## [source,kotlin]

tasks.register\<com.yourpackage.ReportUnitTestsTask\>("reportTests") {} tasks.register\<com.yourpackage.ReportFunctionalTestsTask\>("reportFunctionalTests") {}

\=== UML-Diagramm der Berichtsaufgaben (Code PlantUML)

Kopieren Sie den unten stehenden Code und fügen Sie ihn in ein Tool ein, das PlantUML unterstützt.

## [source,plantuml]

@startuml skinparam handgeschrieben true skinparam monochrome true

abstrakte Klasse DefaultTask { }

abstrakte Klasse Exec { \+ commandLine(args: String...) \+ exec() }

abstrakte Klasse OpenTestReportTask extends DefaultTask { \+ group: String = "verification \+ description: String \+ dependsOn("check") + abstrakt reportPath: String \+ openReport() : void }

abstrakte Klasse AbstractJbakeExecTask extends Exec { \+ group: String = "Überprüfung \+ description: String \+ dependsOn("check") \+ abstract reportRelativePath: String \+ exec() : void }

class ReportUnitTestsTask extends OpenTestReportTask { \+ reportPath: String = "build/reports/tests/test/index.html }

class ReportFunctionalTestsTask extends OpenTestReportTask { \+ reportPath: String = "build/reports/tests/functionalTest/index.html }

class ReportJbakeTestsTask extends AbstractJbakeExecTask { \+ reportRelativePath: String = "build/reports/tests/test/index.html }

class ReportJbakeFunctionalTestsTask extends AbstractJbakeExecTask { \+ reportRelativePath: String = "build/reports/tests/functionalTest/index.html }

OpenTestReportTask \<-- ReportUnitTestsTask OpenTestReportTask \<-- ReportFunctionalTestsTask

AbstractJbakeExecTask <-- ReportJbakeTestsTask AbstractJbakeExecTask \<-- ReportJbakeFunctionalTestsTask

DefaultTask \<|-- OpenTestReportTask Exec \<|-- AbstractJbakeExecTask Standardausgabe \<|-- Ausführen

## @enduml

\== Abstrakte Aufgabe`AbstractJbakeExecTask`(erben von`Exec`)

Wenn Ihre Aufgabe hauptsächlich darin besteht, einen externen Befehl mit variablen Argumenten auszuführen, erben Sie direkt von**Ausführen**ist direkter.

[source,kotlin]

package com.ihrpaket

import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File

abstrakte Klasse 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 }

## }

\=== Konkrete Implementierungen für`Exec`

## [source,kotlin]

Paket com.yourpackage

abstrakte Klasse ReportJbakeTestsTask : AbstractJbakeExecTask() { init { description = "Öffnet den Jbake-Unit-test Bericht in Firefox. reportRelativePath = "build/reports/tests/test/index.html } }

package com.yourpackage

## abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Öffnet den Funktionaltestbericht in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }

\== Sichtbarkeit der Aufgaben`buildSrc`

Die Aufgaben und Klassen, die Sie in**buildSrc**sind :

***Sichtbar und nutzbar**von allen Projekten deines Hauptbuilds (Wurzel- und Unterprojekte). Aus diesem Grund kannst du verwenden`com.yourpackage.ReportUnitTestsTask`in`allprojects { ... }`. ***Nicht ausführbar**direkt als buildSrc-Aufgaben (zum Beispiel,`gradle :buildSrc:reportTests`würde nicht funktionieren wenn die Aufgabe nicht speziell gespeichert wird in`buildSrc/build.gradle.kts`).`buildSrc`ist ein Kompiliermodul, kein ausführbares Anwendungsmodul für diese Aufgaben des Haupt-Builds.

\=== Einen Bericht für die Tests von`buildSrc`ihm selbst

Si `buildSrc`er hat seine eigenen Tests und generiert Berichte, Sie können eine Berichtsaufgabe direkt in`buildSrc/build.gradle.kts`:

## [source,kotlin]

plugins { `kotlin-jvm` }

Repositorys { mavenCentral() }

tasks.withType\<Test\> { useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir("reports/tests")) }

import com.yourpackage.ReportJbakeTestsTask // Importiere deine Taskklasse

) { // Der Pfad ist bereits in der Klasse definiert, er zeigt auf die BuildSrc-Berichte }

Sie können anschließend ausführen :`./gradlew :buildSrc:test`gefolgt von`./gradlew :buildSrc:reportBuildSrcTests`.

Wenn Sie diese Praktiken übernehmen, werden Sie Gradle‑Build‑Systeme in Kotlin DSL erstellen, die nicht nur leistungsstark sind, sondern auch außergewöhnlich modular, wartbar und leicht verständlich.

Verwandte Artikel