Les plugins Gradle précompilés : dites adieu aux scripts répétitifs

Au cours de ma carrière en tant que développeur Android, j’ai travaillé sur de grosses applications dont le code était séparé dans des dizaines de modules Gradle.

Une structure de projet de ce type a des avantages et inconvénients (laissez moi un commentaire à la fin de cet article si vous souhaitez que j’écrive un article sur ce sujet 🙂) et j’ai remarqué que le contenu des différents scripts build.gradle était quasiment toujours le même.

Le pire était qu’une modification dans l’un de ces fichiers devait être répercutée partout et l’ajout d’un nouveau module se résumait à copier / coller le script. Un vrai cauchemar!

C’est l’expérience que j’ai vécue pendant des années. J’ai perdu un temps fou à copier-coller du code et à corriger les mêmes erreurs à répétition 😥. 

Heureusement, il existe aujourd’hui une solution simple et élégante d’éviter cela et c’est ce dont je vais vous parler dans cet article 👏

Vous allez découvrir comment les plugins Gradle précompilés vont augmenter votre productivité et améliorer l’organisation de vos projets 💪

Comment fonctionnent les plugins Gradle précompilés ?

métaphore entre un plugin Gradle précompilé et un appartement clef en main

Un plugin Gradle précompilé c’est comme un appartement à la location clé en main. Vous n’avez pas besoin de vous souciez des meubles à acheter, de la cuisine à installer, de la peinture rose à re-peindre en blanche… Tout est déjà fait. Vous avez juste besoin de récupérer les clefs et déballer vos affaires.

Dans le cadre de votre application, le plugin Gradle précompilé vous fournit la configuration de base pour chacun de vos scripts Gradle. Vous n’avez plus besoin de l’écrire à chaque fois pour chaque module, juste à l’utiliser tel quel.

Techniquement, les plugins Gradle précompilés sont des plugins écrits en Kotlin, puis compilés en un format binaire. La différence  avec des scripts Gradle classics écrits en Kotlin DSL est que ces derniers sont interprétés lors de l’exécution.

Les avantages d’un plugin précompilé sont nombreux et permettent : 

  1. Temps de build plus rapides : Les plugins précompilés sont plus rapides que les script Gradle, car ils sont déjà compilés en bytecode.
  2. Meilleure organisation du code : En séparant la logique de build dans des plugins précompilés, vous conservez vos scripts gradle propres et concis. Cette séparation améliore la lisibilité et la maintenabilité.
  3. Réutilisabilité du code : Les plugins précompilés peuvent être réutilisés dans différents modules. Cela évite donc la duplication de code.

Comment créer un plugin Gradle précompilés

Avant de créer son propre plugin précompilé, il faut connaître le dossier buildSrc. Ce répertoire, qui n’est pas créé par défaut au sein de votre projet, permet de stocker la logique de build et les plugins. 

Il permet donc d’organiser et gérer votre code lié au build séparément du code de votre application. 
Une fois ce dossier créé dans votre projet, nous allons créer notre premier plugin précompilé. L’objectif de celui-ci sera de factoriser le code qu’on retrouve dans les différents scripts build.gradle des modules gradle

Problème : la duplication des scripts build.gradle

Pour mieux vous expliquer ce concept, prenons l’exemple d’une application Android dont le code est organisé en trois modules comme ci-dessous :

architecture d'un plugin Gradle précompilé

Chaque bloc est un module Gradle avec son propre script build.gradle. Le module App est l’application Android et les modules Authentication et Database sont des fonctionnalités permettant respectivement de s’authentifier et de stocker des données dans l’application.

Maintenant, intéressons nous au script build.gradle du module Authentication :

//build.gradle.kts du module Authentication sans plugin précompilé

plugins {
    id("com.android.library")
}

android {
    namespace = "com.precompiledplugin.example.authentication"
    compileSdk = 34
    defaultConfig {
        minSdk = 30
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
}

dependencies {
    implementation(libs.androidCoreKtx)
    implementation(libs.bundles.kotlinBundle)
    // Dépendances d'une bibliothèque permettant de faire de l'authentification 
    // ....
}

Nous pouvons voir dans ce script Gradle que :

  • Le bloc plugins {………} : définit ce module en tant que librairie Android
  • Le bloc android {………} : définit les paramètres de compilation de l’application Android
  • Le bloc dependencies {………} : définit les dépendances dont a besoin le code du module pour être compilé 

Comparons maintenant avec le script build.gradle du module Database :

//build.gradle.kts du module Database sans plugin précompilé

plugins {
    id("com.android.library")
}

android {
    namespace = "com.precompiledplugin.example.database"
    compileSdk = 34
    defaultConfig {
        minSdk = 30
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
}

dependencies {
    implementation(libs.androidCoreKtx)
    implementation(libs.bundles.kotlinBundle)
    // Dépendances d'une bibliothèque permettant de faire de la base de donnée 
    // ....
}

Nous pouvons voir que les deux scripts sont identiques à 99%. La seule différence se situe au niveau des dépendances

Solution : plugin Gradle précompilé et la simplification des scripts build.gradle

Vous l’avez compris, avoir du code dupliqué dans nos script gradle n’est pas optimal (surtout lorsque vous avez beaucoup de module gradle) car une modification affectant tous les modules devra être répercuter plusieurs fois d’affilés .

Pour éviter cette duplication, nous allons créer un plugin Gradle précompilé qui sera ensuite utilisé dans les modules Authentication et Database. Les noms des dossiers et fichiers ci-dessous sont donnés à titre d’exemple mais pouvez les nommer comme bon vous semble 😉

Pour cela il faut :

  • Créer l’arborescence buildSrc/src/main/kotlin/precompiled_plugin/
  • Créer un script gradle dans le dossier précédent : android-library-base.gradle.kts et le remplir avec le code suivant :
//plugin précompilé android-library-base.gradle.kts

import COMPILE_SDK
import JAVA_VERSION
import JVM_TARGET
import MIN_SDK
import androidCoreKtx
import kotlinBundle

plugins {
    id("com.android.library")
}

android {
    compileSdk = COMPILE_SDK
    defaultConfig {
        minSdk = MIN_SDK
    }
    compileOptions {
        sourceCompatibility = JAVA_VERSION
        targetCompatibility = JAVA_VERSION
    }
    kotlinOptions {
        jvmTarget = JVM_TARGET
    }
}

dependencies {
    implementation(androidCoreKtx)
    implementation(kotlinBundle)
}

Vous avez surement remarqué que des nouvelles constantes ont été ajoutées (cf. import au début du fichier). Nous y reviendrons plus bas dans cet article 😉

Maintenant nous pouvons utiliser ce plugin précompilé dans nos modules Database et Authentication. Leur script build gradle deviennent respectivement :

//build.gradle.kts du module Authentication avec plugin précompilé

plugins {
    id("precompiled_plugin.android-library-base")
}

android {
   namespace = "com.precompiledplugin.example.authentication"
}

dependencies {
    // Dépendances d'une bibliothèque permettant de faire de l'authentification 
    // ....
}
//build.gradle.kts du module Database avec plugin précompilé

plugins {
    id("precompiled_plugin.android-library-base")
}

android {
   namespace = "com.precompiledplugin.example.database"
}

dependencies {
     // Dépendances d'une bibliothèque permettant de faire de la base de donnée
    // ....
}

Vous pouvez voir sur les exemples le code ci-dessus qu’il n’y a plus de duplications de code et les scripts gradle sont maintenant bien plus faciles à lire 👏

Astuce : accéder au version catalog depuis un module précompilé

Depuis la version 7.4 de Gradle, un nouveau système permet d’ajouter et de gérer des dépendances beaucoup plus facilement : c’est le catalogue de versions (si ça vous intéresse, laissez moi un commentaire en bas de cet article et j’écrirai un article dessus).

Malheureusement ce catalogue n’est pas accessible depuis les plugins précompilés et pour résoudre ce problème, j’ai créé des fonctions d’extensions permettant quand même d’y accéder (ce sont les fameux import au début du script Gradle du fichier android-library-base.gradle.kts ) :

  • Aller dans l’arborescence buildSrc/src/main/kotlin/extensions/
  • Créer les fichiers suivants :
//Const.kts

internal const val catalogue_name = "libs"
//BundleExt.kts

import org.gradle.api.Project
import org.gradle.api.artifacts.ExternalModuleDependencyBundle
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.getByType

internal fun Project.bundle(key: String): Provider<ExternalModuleDependencyBundle> = extensions
    .getByType<VersionCatalogsExtension>()
    .named(catalogue_name)
    .findBundle(key)
    .get()

internal val Project.kotlinBundle get() = bundle("kotlinBundle")
internal val Project.kotlinTest get() = bundle("kotlinTest")
internal val Project.androidTest get() = bundle("androidTest")
internal val Project.androidXCompose get() = bundle("androidXCompose")
internal val Project.koinAndroidX get() = bundle("koinAndroidX")
internal val Project.koinAndroidBase get() = bundle("koinAndroidBase")
//LibrayExt.kt

import org.gradle.api.Project
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.getByType

internal fun Project.library(key: String): Provider<MinimalExternalModuleDependency> = extensions
    .getByType<VersionCatalogsExtension>()
    .named(catalogue_name)
    .findLibrary(key)
    .get()

internal val Project.androidCoreKtx get() = library("androidCoreKtx")
//VersionExt.kt

import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.getByType

fun Project.version(key: String) = extensions
    .getByType<VersionCatalogsExtension>()
    .named(catalogue_name)
    .findVersion(key)
    .get()
    .requiredVersion

fun Project.versionInt(key: String) = version(key).toInt()

val JAVA_VERSION get() = JavaVersion.VERSION_11
val JVM_TARGET get() = "11"

val Project.MIN_SDK get() = versionInt("minSdk")
val Project.COMPILE_SDK get() = versionInt("compileSdk")

🎯Conclusion

Vous avez maintenant compris que les plugins précompilés de Gradle sont un outil puissant permettant d’éviter la duplication des scripts surtout dans un projet avec beaucoup de modules.

Lorsqu’ils sont combinés avec le catalogue de version, on a une configuration du projet qui devient ultra optimisée.

Leur utilisation est assez simple et on peut aller encore plus loin en les partageant entre différentes équipes et avoir une base de configuration commune entre différents projets (mais ce point mérite un nouvel article 🙂)

Si vous avez des questions, des remarques etc… n’hésitez pas à les poser dans les commentaires. Et si vous souhaitez que j’écrive un article sur un sujet particulier, dites-le moi ! 😊

Lien officiel de la documentation Gradle sur les plugin précompilés

Si vous avez aimé l'article, vous êtes libre de le partager! 🙂

Comments

No comments yet. Why don’t you start the discussion?

Laisser un commentaire