La automatización de las tareas de build es crucial para cualquier proyecto de software, y Gradle, con su DSL Kotlin, ofrece una flexibilidad excepcional. Durante nuestra conversación, hemos explorado cómo centralizar y reutilizar la lógica de build, especialmente para los informes de pruebas, aprovechandoallprojects, buildSrc, y tareas personalizadas en Kotlin.

Centralizar las Configuraciones con allprojects y `subprojects

Los bloques`allprojects` et `subprojects`en su`build.gradle.kts`raíz son fundamentales para aplicar configuraciones comunes a lo largo de su proyecto de múltiples módulos.

*allprojects { …​ }: Aplica la configuración aproyecto raíz y a todos sus subproyectos. Ideal para definir un`group`, una`version`, o unos`repositories`comunes. *subprojects { …​ }: Aplicar la configuraciónúnicamente a los subproyectos, excluyendo el proyecto raíz. Perfecto para aplicar plugins específicos a los módulos (como`java` ou kotlin-jvm) o las dependencias comunes de tus bibliotecas.

Este es un ejemplo ilustrativo :

// 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`El Cuchillo Suizo de la Lógica de Build

Cuando la lógica de tus tareas se vuelve compleja o debe ser reutilizada,buildSrces la solución preferida. Es un módulo Gradle especial que se compila antes de los scripts de compilación principales, haciendo que sus clases estén disponibles en el classpath de todo tu build.

\=== ¿Por qué utilizar?`buildSrc`para las Tareas?

  • ReutilizabilidadUna tarea definida en`buildSrc`Puede ser aplicada a cualquier proyecto del build.

  • Organización: Centraliza el código de construcción, haciéndolo más limpio y mantenible.

  • Type Safety y autocompletado: El código Kotlin en`buildSrc`está compilado, ofreciendo la verificación de errores y el autocompletado de tu IDE, mejorando la experiencia de desarrollo.

\=== Diagrama de flujo`buildSrc`(Código PlantUML)

Para generar este diagrama, copie el código a continuación y péguelo en una herramienta que admita PlantUML.

[source,plantuml]

@startuml skinparam handwritten true skinparam monocromo verdadero

rectángulo "Gradle Build Process" { componente "buildSrc" como BS { file "MyCustomTask.kt" as T archivo "MyConventionPlugin.kt" como P } componente "Root Project" como RP componente "Subproject A" como SA componente "Subproject B" como SB }

T --\> P : "está definido en P --\> RP : "se aplica a P --\> SA : "se aplica a P --\> SB : "se aplica a

RP --|\> SA : "contiene RP --|\> SB : "contiene

RP -up-\> BS : "depende de (para la lógica de compilación) SA -up-\> BS : "depende de (para la lógica de compilación) SB -up-\> BS : "depende de (para la lógica de compilación)

nota a la derecha de T Clases de tareas personalizadas ejemplo: `OpenTestReportTask nota final

nota a la derecha de P Plugins de convención que registran las tareas nota final

@enduml

\== Creación de Tareas de Reporte Abstractas

Para gestionar los informes de pruebas, hemos diseñado un enfoque modular utilizando una clase de tarea abstracta en`buildSrc`. Según sus necesidades, esta clase puede heredar de`DefaultTask` ou de Exec.

\=== Tarea abstracta`OpenTestReportTask`(heredando de`DefaultTask`)

Este enfoque se recomienda si necesita una lógica personalizada de Kotlin que interactúe con el sistema de archivos o con otras API de Gradle y luego ejecute un comando externo.

[source,kotlin]

paquete 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}")
}
----

## }

\=== Implementaciones Concretas

Estas clases heredan de la tarea abstracta y definen la ruta específica del informe.

## [source,kotlin]

package com.yourpackage

clase abstracta ReportUnitTestsTask : OpenTestReportTask() { init { description = "Abre el informe de pruebas unitarias en Firefox. reportPath = "build/reports/tests/test/index.html } }

paquete com.yourpackage

abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "Abre el informe de pruebas funcionales en Firefox." reportPath = "build/reports/tests/functionalTest/index.html" } }

\=== Registro de las Tareas

En el`build.gradle.kts`de tu proyecto raíz :

## [source,kotlin]

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

\=== Diagrama UML de las Tareas de Informe (Código PlantUML)

Copie el código a continuación y péguelo en una herramienta que soporte PlantUML.

[source,plantuml]

@startuml skinparam handwritten true skinparam monochrome true

clase abstracta DefaultTask { }

clase abstracta Exec { \+ commandLine(args: String...) \+ exec() }

abstract class OpenTestReportTask extends DefaultTask { \+ grupo: String = "verificación \+ descripción: String \+ dependsOn("check") \+ abstract reportPath: Cadena \+ openReport() : void }

clase abstracta AbstractJbakeExecTask extends Exec { \+ group: String = "verification \+ 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 DefaultTask \<|-- Ejecutar

## @enduml

\== Tarea Abstracta`AbstractJbakeExecTask`(heredando de`Exec`)

Si tu tarea se resume principalmente a ejecutar un comando externo con argumentos variables, heredar directamente de**Ejecutar**es más directo.

## [source,kotlin]

paquete 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 }

## }

Implementaciones Concretas para`Exec`

[source,kotlin]
----

----

Articles connexes