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