AsciiDoc : Découvrir et maîtriser la syntaxe pour une documentation efficace

11 July 2025

L'automatisation des tâches de build est cruciale pour tout projet logiciel, et Gradle, avec son DSL Kotlin, offre une flexibilité exceptionnelle. Au cours de notre conversation, nous avons exploré comment centraliser et réutiliser la logique de build, notamment pour les rapports de tests, en tirant parti de **allprojects**, **buildSrc**, et des tâches personnalisées en Kotlin.

1. Centraliser les Configurations avec allprojects et subprojects

Les blocs allprojects et subprojects dans votre build.gradle.kts racine sont fondamentaux pour appliquer des configurations communes à travers votre projet multi-modules.

  • allprojects { …​ }: Applique la configuration au projet racine et à tous ses sous-projets. Idéal pour définir un group, une version, ou des repositories communs.

  • subprojects { …​ }: Applique la configuration uniquement aux sous-projets, excluant le projet racine. Parfait pour appliquer des plugins spécifiques aux modules (comme java ou kotlin-jvm) ou des dépendances communes à vos bibliothèques.

Voici un exemple illustratif :

// 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: Le Couteau Suisse de la Logique de Build

Lorsque la logique de vos tâches devient complexe ou doit être réutilisée, buildSrc est la solution privilégiée. C’est un module Gradle spécial qui est compilé avant les scripts de build principaux, rendant ses classes disponibles sur le classpath de l’ensemble de votre build.

\=== Pourquoi utiliser buildSrc pour les Tâches ?

  • Réutilisabilité: Une tâche définie dans buildSrc peut être appliquée à n’importe quel projet du build.

  • Organisation: Centralise le code de build, le rendant plus propre et maintenable.

  • Type Safety et Autocomplétion: Le code Kotlin dans buildSrc est compilé, offrant la vérification des erreurs et l’autocomplétion de votre IDE, améliorant l’expérience de développement.

\=== Diagramme de Flux buildSrc (Code PlantUML)

Pour générer ce diagramme, copiez le code ci-dessous et collez-le dans un outil supportant PlantUML.

2. [source,plantuml]

@startuml skinparam handwritten true skinparam monochrome true

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

T --\> P : "is defined in" P --\> RP : "is applied to" P --\> SA : "is applied to" P --\> SB : "is applied to"

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

RP -up-\> BS : "depends on (for build logic)" SA -up-\> BS : "depends on (for build logic)" SB -up-\> BS : "depends on (for build logic)"

note right of T Classes de tâches personnalisées (ex: OpenTestReportTask) end note

note right of P Plugins de convention qui enregistrent les tâches end note

3. @enduml

\== Création de Tâches de Rapport Abstraites

Pour gérer les rapports de tests, nous avons conçu une approche modulaire en utilisant une classe de tâche abstraite dans `buildSrc`. Selon vos besoins, cette classe peut hériter de `DefaultTask` ou de `Exec`.

\=== Tâche Abstraite `OpenTestReportTask` (héritant de `DefaultTask`)

Cette approche est recommandée si vous avez besoin d'une logique Kotlin personnalisée qui interagit avec le système de fichiers ou d'autres APIs Gradle, puis lance une commande externe.

## [source,kotlin]

// buildSrc/src/main/kotlin/com/yourpackage/OpenTestReportTask.kt
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}")
}
```

## }

\=== Implémentations Concrètes

Ces classes héritent de la tâche abstraite et définissent le chemin spécifique du rapport.

## [source,kotlin]

// buildSrc/src/main/kotlin/com/yourpackage/ReportUnitTestsTask.kt
package com.yourpackage

abstract class ReportUnitTestsTask : OpenTestReportTask() {
init {
description = "Opens the unit test report in Firefox."
reportPath = "build/reports/tests/test/index.html"
}
}

// buildSrc/src/main/kotlin/com/yourpackage/ReportFunctionalTestsTask.kt
package com.yourpackage

## abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "Opens the functional test report in Firefox." reportPath = "build/reports/tests/functionalTest/index.html" } }

\=== Enregistrement des Tâches

Dans le `build.gradle.kts` de votre projet racine :

## [source,kotlin]

// build.gradle.kts (au niveau de la racine du projet)

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

\=== Diagramme UML des Tâches de Rapport (Code PlantUML)

Copiez le code ci-dessous et collez-le dans un outil supportant PlantUML.

## [source,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"
\+ description: String
\+ dependsOn("check")
\+ abstract reportPath: String
\+ openReport() : void
}

abstract class 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 \<|-- Exec

## @enduml

\== Tâche Abstraite AbstractJbakeExecTask (héritant de Exec)

Si votre tâche se résume principalement à exécuter une commande externe avec des arguments variables, hériter directement de Exec est plus direct.

4. [source,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
}

5. }

\=== Implémentations Concrètes pour Exec

6. [source,kotlin]

package com.yourpackage

abstract class ReportJbakeTestsTask : AbstractJbakeExecTask() { init { description = "Opens the Jbake unit test report in Firefox." reportRelativePath = "build/reports/tests/test/index.html" } }

package com.yourpackage

7. abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Opens the functional test report in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }

\== Visibilité des Tâches `buildSrc`

Les tâches et classes que vous définissez dans **buildSrc** sont :

  * **Visibles et utilisables** par tous les projets de votre build principal (racine et sous-projets). C'est pourquoi vous pouvez utiliser `com.yourpackage.ReportUnitTestsTask` dans `allprojects { ... }`.
  * **Non exécutables** directement en tant que tâches de buildSrc (par exemple, `gradle :buildSrc:reportTests` ne fonctionnerait pas si la tâche n'est pas enregistrée spécifiquement dans `buildSrc/build.gradle.kts`). `buildSrc` est un module de compilation, pas un module d'application exécutable pour ces tâches du build principal.

\=== Exécuter un rapport pour les tests de `buildSrc` lui-même

Si `buildSrc` a ses propres tests et génère des rapports, vous pouvez enregistrer une tâche de rapport directement dans `buildSrc/build.gradle.kts` :

## [source,kotlin]

// buildSrc/build.gradle.kts

plugins {
`kotlin-jvm`
}

repositories {
mavenCentral()
}

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

import com.yourpackage.ReportJbakeTestsTask // Importez votre classe de tâche

## tasks.register\<ReportJbakeTestsTask\>("reportBuildSrcTests") { // Le chemin est déjà défini dans la classe, il pointera vers les rapports de buildSrc }

Vous pourrez ensuite exécuter : `./gradlew :buildSrc:test` suivi de `./gradlew :buildSrc:reportBuildSrcTests`.

En adoptant ces pratiques, vous construirez des systèmes de build Gradle en Kotlin DSL qui sont non seulement puissants, mais aussi incroyablement modulaires, maintenables et faciles à comprendre.