Ottimizzare la gestione delle attività Gradle con Kotlin DSL e buildSrc
Publié le 11 July 2025
L’automazione delle attività di build è cruciale per qualsiasi progetto software, e Gradle, con il suo DSL Kotlin, offre una flessibilità eccezionale. Durante la nostra conversazione, abbiamo esplorato come centralizzare e riutilizzare la logica di build, in particolare per i report dei test, sfruttandotutti i progetti, buildSrc, e attività personalizzate in Kotlin.
Centralizzare le Configurazioni con allprojects e `subprojects
i blocchi`allprojects` et `subprojects`nel vostro`build.gradle.kts`La radice è fondamentale per applicare configurazioni comuni in tutto il tuo progetto multi-modulo.
*allprojects { … }: Applica la configurazione aprogetto radice e a tutti i suoi sottoprogetti. Ideale per definire un`group`, una`version`, o alcuni`repositories`comuni. *subprojects { … }: Applica la configurazionesolo ai sottoprogetti, escluso il progetto radice. Perfetto per applicare plugin specifici ai moduli (come`java` ou kotlin-jvm) o dipendenze comuni alle tue librerie.
Ecco un esempio illustrativo :
// 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: Il coltellino svizzero della logica di build
Quando la logica delle vostre attività diventa complessa o deve essere riutilizzata,buildSrcè la soluzione privilegiata. È un modulo Gradle speciale che viene compilato prima degli script di build principali, rendendo le sue classi disponibili sul classpath dell’intero build.
Perché utilizzare`buildSrc`per le attività ?
-
Riutilizzabilità: Un compito definito in`buildSrc`può essere applicata a qualsiasi progetto del build.
-
OrganizzazioneCentralizza il codice di build, rendendolo più pulito e mantenibile.
-
Sicurezza dei tipi e autocompletamento: Il codice Kotlin in`buildSrc`è compilato, offrendo la verifica degli errori e l’autocompletamento del tuo IDE, migliorando l’esperienza di sviluppo.
\=== Diagramma di flusso`buildSrc`(Codice PlantUML)
Per generare questo diagramma, copia il codice qui sotto e incollalo in uno strumento che supporta PlantUML.
[source,plantuml]
@startuml skinparam manoscritto true skinparam monochrome true
rettangolo "Gradle Build Process" { componente "buildSrc" come BS { file "MyCustomTask.kt" come T file "MyConventionPlugin.kt" as P } componente "Root Project" come RP componente "Subproject A" come SA componente "Subproject B" come SB }
T --\> P : "è definito in viene applicato a P --\> SA : "è applicato a P --\> SB : "viene applicato a
RP --|\> SA : "contiene RP --|\> SB : "contiene
RP -up-\> BS : "dipende da (per la logica di build) SA -up-\> BS : "dipende da (per la logica di build) SB -up→ BS : "dipende da (per la logica di compilazione)
nota a destra di T Classi di task personalizzate (es: OpenTestReportTask) nota finale
note right of P Plugin di convenzione che registrano le attività end note
@enduml
Creazione di compiti di report astratti
Per gestire i rapporti di test, abbiamo progettato un approccio modulare utilizzando una classe di compito astratto nella`buildSrc`. Secondo le vostre esigenze, questa classe può ereditare`DefaultTask` ou de Exec.
\=== Compito Astratto`OpenTestReportTask`(ereditando da`DefaultTask`)
Questo approccio è consigliato se è necessario avere una logica Kotlin personalizzata che interagisce con il file system o altre API Gradle, quindi esegue un comando esterno.
[source,kotlin]
pacchetto com.yourpackage
import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction importa 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}")
}
----
## }
\\=== Implementazioni concrete
Queste classi ereditano dal compito astratto e definiscono il percorso specifico del rapporto.
## [source,kotlin]
pacchetto com.yourpackage
abstract class ReportUnitTestsTask : OpenTestReportTask() inizializzazione { description = "Apre il rapporto dei test unitari in Firefox. reportPath = "build/reports/tests/test/index.html } }
package com.yourpackage
## abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "Apre il rapporto dei test funzionali in Firefox." reportPath = "build/reports/tests/functionalTest/index.html" } }
Registrazione delle attività
Nel`build.gradle.kts`del tuo progetto radice:
## [source,kotlin]
tasks.register<com.yourpackage.ReportUnitTestsTask>("reportTests") {} tasks.register<com.yourpackage.ReportFunctionalTestsTask>("reportFunctionalTests") {}
\=== Diagramma UML delle Attività del Rapporto (Code PlantUML)
Copia il codice seguente e incollalo in uno strumento che supporta PlantUML.
## [source,plantuml]
@startuml skinparam manoscritto vero skinparam monochrome true
classe astratta DefaultTask { }
classe astratta Exec { \+ commandLine(args: String...) \+ exec() }
abstract class OpenTestReportTask extends DefaultTask { \+ group: String = "verifica \+ descrizione: String \+ dependsOn("check") + abstract reportPath: String \+ openReport() : void }
classe astratta AbstractJbakeExecTask estende Exec { \+ gruppo: stringa = "verifica \+ descrizione: 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 { \+ percorsoRelativoRapporto: Stringa = "build/reports/tests/functionalTest/index.html }
OpenTestReportTask \<-- ReportUnitTestsTask OpenTestReportTask \<-- ReportFunctionalTestsTask
AbstractJbakeExecTask \<-- ReportJbakeTestsTask AbstractJbakeExecTask \<-- ReportJbakeFunctionalTestsTask
DefaultTask \<|-- OpenTestReportTask Exec \<|-- AbstractJbakeExecTask DefaultTask \<|-- Exec
## @enduml
\== Compito astratto`AbstractJbakeExecTask`(ereditando da`Exec`)
Se il tuo compito consiste principalmente nell'eseguire un comando esterno con argomenti variabili, ereditare direttamente da**Esegui**è più diretto.
## [source,kotlin]
package com.yourpackage
import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File
classe astratta 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 }
## }
\=== Implementazioni concrete per`Exec`
## [source,kotlin]
pacchetto com.yourpackage
classe astratta ReportJbakeTestsTask : AbstractJbakeExecTask() init { description = "Apre il rapporto di test unitario Jbake in Firefox. reportRelativePath = "build/reports/tests/test/index.html } }
package com.yourpackage
abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Apre il rapporto di test funzionale in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }
\== Visibilità delle Attività`buildSrc`
Le attività e le classi che definisci in**buildSrc**sono :
***Visibili e utilizzabili**per tutti i progetti del tuo build principale (radice e sottoprogetti). Perciò puoi utilizzare`com.yourpackage.ReportUnitTestsTask`in`allprojects { ... }`. ***Non eseguibili**direttamente come attività di buildSrc (ad esempio,`gradle :buildSrc:reportTests`non funzionerebbe se il compito non fosse registrato specificamente in`buildSrc/build.gradle.kts`).`buildSrc`è un modulo di compilazione, non un modulo applicativo eseguibile per queste attività del build principale.
Eseguire un rapporto per i test di`buildSrc`lui stesso
Si `buildSrc`ha i suoi test e genera dei rapporti, è possibile registrare un'attività di rapporto direttamente in`buildSrc/build.gradle.kts`</think>
## [source,kotlin]
plugins { `kotlin-jvm` }
repositori { mavenCentral() }
tasks.withType\<Test\> { useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir("reports/tests")) }
import com.yourpackage.ReportJbakeTestsTask // Importa la tua classe di attività
## tasks.register<ReportJbakeTestsTask>("reportBuildSrcTests") { // Il percorso è già definito nella classe, punterà verso i rapporti di buildSrc }
Potrai quindi eseguire :`./gradlew :buildSrc:test`seguito da`./gradlew :buildSrc:reportBuildSrcTests`.
Adottando queste pratiche, costruirete sistemi di build Gradle in Kotlin DSL che non sono solo potenti, ma anche incredibilmente modulari, mantenibili e facili da comprendere.
Articoli correlati
14 May 2026