Gradle-taakbeheer optimaliseren met Kotlin DSL en buildSrc
Gepubliceerd op 11 July 2025
- Centraliseer de configuraties met
allprojectsen `subprojects - [source,plantuml]
- @enduml
- [source,kotlin]
- }
- [bron,kotlin]
- abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Opent het functionele testrapport in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }
- [bron,kotlin]
- tasks.register\<ReportJbakeTestsTask\>("reportBuildSrcTests") { // Het pad is al gedefinieerd in de klasse, het zal verwijzen naar de rapporten van buildSrc }
De automatisering van buildtaken is cruciaal voor elk softwareproject, en Gradle, met zijn Kotlin DSL, biedt een uitzonderlijke flexibiliteit. Tijdens ons gesprek hebben we onderzocht hoe we de buildlogica kunnen centraliseren en hergebruiken, met name voor testrapporten, door gebruik te maken vanallprojects, buildSrc, en aangepaste taken in Kotlin.
Centraliseer de configuraties met allprojects en `subprojects
De blokken`allprojects` et `subprojects`in uw`build.gradle.kts`de wortels zijn essentieel om gemeenschappelijke configuraties toe te passen in uw multi-module project
*allprojects { … }: Pas de configuratie toe op dehoofdproject en aan alle onderprojecten thereof. Ideaal om een te definiëren`group`, een`version`, of enkele`repositories`gemeenschappelijk. *subprojects { … }: Pas de configuratie toeuitsluitend voor de subprojecten, zonder het hoofproject. Perfect voor het toepassen van modulespecifieke plugins (zoals`java` ou kotlin-jvm) of gemeenschappelijke afhankelijkheden voor uw bibliotheken.
Hier is een illustratief voorbeeld:
// 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: Het Zwitserse zakmes van de Build-logica
Wanneer de logica van uw taken complex wordt of moet worden hergebruikt,buildSrcest de voorkeursoplossing. Het is een speciaal Gradle-module dat wordt gecompileerd vóór de belangrijkste build-scripts, waardoor zijn klassen beschikbaar zijn op het classpath van uw hele build.
\=== Waarom gebruiken`buildSrc`voor de taken ?
-
Herbruikbaarheid: Een gedefinieerde taak in`buildSrc`kan worden toegepast op elk project van de build.
-
Organisatie: Centraliseert de buildcode, waardoor deze schoner en onderhoudsvriendelijker wordt.
-
Type Safety en Autocompleet: De Kotlin-code in`buildSrc`wordt gecompileerd, biedt foutcontrole en automatische aanvulling van uw IDE, waardoor de ontwikkelervaring wordt verbeterd.
\=== Stroomdiagram`buildSrc`(Code PlantUML)
Om dit diagram te genereren, kopieer de code hieronder en plak deze in een tool die PlantUML ondersteunt.
[source,plantuml]
@startuml skinparam handgeschreven true skinparam monochrome true
rechthoek "Gradle Build Process" { component "buildSrc" als BS { file "MyCustomTask.kt" as T file "MyConventionPlugin.kt" as P } component "Root Project" als RP onderdeel "Subproject A" als SA component "Subproject B" als SB }
T -→ P : "is gedefinieerd in P --\> RP : "wordt toegepast op P --\> SA : "wordt toegepast op P --\> SB : "wordt toegepast op
RP --|\> SA : "bevat RP --|\> SB : "bevat
RP -up-\> BS : "is afhankelijk van (voor build-logica) SA -up→ BS : "is afhankelijk van (voor build-logica) SB -up→ BS : "is afhankelijk van (voor de buildlogica)
note right of T Aangepaste taakklassen (ex: OpenTestReportTask) eindnoot
noot rechts van P Conventie-plugins die de taken registreren eindnoot
@enduml
\== Aanmaken van abstracte rapporttaken
Om de testrapporten te beheren, hebben we een modulaire aanpak ontworpen met behulp van een abstracte taakklasse in`buildSrc`. Volgens uw behoeften, deze klasse kan erven van`DefaultTask` ou de Exec.
\=== Abstracte taak`OpenTestReportTask`(erfend van`DefaultTask`)
Deze aanpak wordt aanbevolen als je een aangepaste Kotlin-logica nodig hebt die interactie heeft met het bestandssysteem of andere Gradle-API’s, en daarna een externe opdracht uitvoert.
[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}")
}
----
## }
\=== Concreet Implementaties
Deze klassen erven van de abstracte taak en definiëren het specifieke pad van het rapport.
## [source,kotlin]
pakket com.yourpackage
abstracte klasse ReportUnitTestsTask : OpenTestReportTask() { init { description = "Opent het eenheidstestrapport in Firefox. reportPath = "build/reports/tests/test/index.html } }
package com.yourpackage
## abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "Opent het functionele testrapport in Firefox." reportPath = "build/reports/tests/functionalTest/index.html" } }
\=== Registratie van de taken
In de`build.gradle.kts`van uw hoofdproject :
## [source,kotlin]
tasks.register\<com.yourpackage.ReportUnitTestsTask\>("reportTests") {} tasks.register\<com.yourpackage.ReportFunctionalTestsTask\>("reportFunctionalTests") {}
\=== UML-diagram van de rapporttaken (PlantUML-code)
Kopieer de onderstaande code en plak deze in een tool die PlantUML ondersteunt.
## [source,plantuml]
@startuml skinparam handgeschreven waar skinparam monochrome true
abstracte klasse DefaultTask { }
abstracte klasse Exec { \+ commandLine(args: String...) \+ exec() }
abstracte klasse OpenTestReportTask extends DefaultTask { \+ group: String = "verification \+ beschrijving: String \+ dependsOn("check") \+ abstract reportPath: String \+ openReport() : void }
abstracte klasse AbstractJbakeExecTask extends Exec { \+ group: String = "verificatie \+ beschrijving: 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 DefaultTask \<|-- Exec
## @enduml
\== Abstracte taak`AbstractJbakeExecTask`(ervend van`Exec`)
Als je taak vooral bestaat uit het uitvoeren van een externe opdracht met variabele argumenten, erf dan rechtstreeks van**Exec**is directer.
## [source,kotlin]
pakket com.yourpackage
import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File
abstracte 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 }
## }
\=== concreete implementaties voor`Exec`
## [source,kotlin]
package com.yourpackage
abstract class ReportJbakeTestsTask : AbstractJbakeExecTask() initialiseer { Het Jbake-unittestrapport openen in Firefox reportRelativePath = "build/reports/tests/test/index.html } }
pakket com.yourpackage
## abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Opens the functional test report in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }
\== Zichtbaarheid van taken`buildSrc`
De taken en klassen die u definieert in**buildSrc**zijn :
***Zichtbaar en bruikbaar**door alle projecten van je hoofd-build (root en sub-projecten). Daarom kun je gebruiken`com.yourpackage.ReportUnitTestsTask`in`allprojects { ... }`. ***Niet uitvoerbare**direct als taken van buildSrc (bijvoorbeeld,`gradle :buildSrc:reportTests`zou niet werken als de taak niet specifiek wordt geregistreerd in`buildSrc/build.gradle.kts`).`buildSrc`is een compilatiemodule, geen uitvoerbaar applicatiemodule voor deze taken van de hoofdbuild.
\=== een rapport uitvoeren voor de tests van`buildSrc`zichzelf
Si `buildSrc`heeft zijn eigen tests en genereert rapporten, u kunt een rapporttaak direct registreren in`buildSrc/build.gradle.kts` :
## [source,kotlin]
plugins { `kotlin-jvm` }
dépôts { mavenCentral() }
tasks.withType\<Test\> { useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir("reports/tests")) }
import com.yourpackage.ReportJbakeTestsTask // Importeer uw taakklasse
tasks.register\<ReportJbakeTestsTask\>("reportBuildSrcTests") { // Het pad is al gedefinieerd in de klasse, het zal naar de buildSrc-rapporten wijzen }
U kunt vervolgens uitvoeren :`./gradlew :buildSrc:test`gevolgd door`./gradlew :buildSrc:reportBuildSrcTests`.
Door deze praktijken te volgen, bouw je Gradle-buildsystemen in Kotlin DSL die niet alleen krachtig zijn, maar ook buitengewoon modulair, onderhoudbaar en gemakkelijk te begrijpen.
----
## }
\=== Concrete Implementaties
Deze klassen erven van de abstracte taak en definiëren het specifieke pad van het rapport.
## [bron,kotlin]
package com.yourpackage
abstract class ReportUnitTestsTask : OpenTestReportTask() { init { description = "Opent het unit test-rapport in Firefox. reportPath = "build/reports/tests/test/index.html } }
package com.yourpackage
## abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "Opent het functionele testrapport in Firefox." reportPath = "build/reports/tests/functionalTest/index.html" } }
\=== Registratie van Taken
In het`build.gradle.kts`van uw rootproject:
## [bron,kotlin]
## tasks.register\<com.yourpackage.ReportUnitTestsTask\>("reportTests") {} tasks.register\<com.yourpackage.ReportFunctionalTestsTask\>("reportFunctionalTests") {}
\=== UML-diagram van Rapporttaken (PlantUML-code)
Kopieer de onderstaande code en plak deze in een tool die PlantUML ondersteunt.
## [bron,plantuml]
@startuml skinparam handwritten true skinparam monochrome true
abstract class DefaultTask { }
abstract class Exec { \+ commandLine(args: String...) \+ exec() }
abstract class OpenTestReportTask extends DefaultTask { \+ group: String = "verification \+ beschrijving: String \+ dependsOn("check") \+ abstract reportPath: String \+ openReport() : void }
abstract class AbstractJbakeExecTask extends Exec { \+ group: String = "verification \+ beschrijving: 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 StandaardTaak \<|-- Exec
## @enduml
\== Abstracte Taak`AbstractJbakeExecTask`(ervend van`Exec`)
Als uw taak hoofdzakelijk bestaat uit het uitvoeren van een extern commando met variabele argumenten, erf dan rechtstreeks van**Uitvoeren**is directer.
## [bron,kotlin]
package com.yourpackage
import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File
abstract class 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
}
}
\=== Concrete Implementaties voor`Exec`
[bron,kotlin]
package com.yourpackage
abstract class ReportJbakeTestsTask : AbstractJbakeExecTask() { init { description = "Opent het Jbake unit test rapport in Firefox. reportRelativePath = "build/reports/tests/test/index.html } }
package com.yourpackage
abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Opent het functionele testrapport in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }
\== Zichtbaarheid van taken`buildSrc`
De taken en klassen die u definieert inbuildSrczijn:
*Zichtbaar en bruikbaardoor alle projecten van je hoofdbuild (root en subprojecten). Daarom kun je`com.yourpackage.ReportUnitTestsTask`in`allprojects { … }`Please provide the text you would like me to translate. *Niet-uitvoerbaardirects als buildSrc-taken (bijvoorbeeld,gradle :buildSrc:reportTests`zou niet werken als de taak niet specifiek is opgeslagen in`buildSrc/build.gradle.kts).`buildSrc`is een compilatiemodule, geen uitvoerbare applicatiemodule voor deze taken van de hoofdbuild.
\=== Een rapport uitvoeren voor de tests van`buildSrc`zichzelf
Si `buildSrc`aan zijn eigen tests en genereert rapporten, kunt u een rapporttaak rechtstreeks opslaan in`buildSrc/build.gradle.kts`provide the text you want translated.
[bron,kotlin]
plugins { kotlin-jvm }
repositories { mavenCentral() }
tasks.withType\<Test\> { useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir("reports/tests")) }
import com.yourpackage.ReportJbakeTestsTask // Importeer uw taakklasse
tasks.register\<ReportJbakeTestsTask\>("reportBuildSrcTests") { // Het pad is al gedefinieerd in de klasse, het zal verwijzen naar de rapporten van buildSrc }
U kunt vervolgens het volgende uitvoeren:`./gradlew :buildSrc:test`gevolgd door`./gradlew :buildSrc:reportBuildSrcTests`.
Door deze praktijken toe te passen, bouwt u Gradle build-systemen in Kotlin DSL die niet alleen krachtig zijn, maar ook ongelooflijk modulair, onderhoudbaar en gemakkelijk te begrijpen.