Paul Merlin (@eskat0s) - Gradle Inc.
speaker {
name = "Paul Merlin"
company = "Gradle Inc."
locations = setOf("Montpellier, France", "Cevennes <3")
oss = "Apache Polygene PMC, former chair"
successes = listOf(
"BASIC 'Hello, World!' in 1986",
"C 'Hello, World!' in 1989",
"Java 'Hello, World!' in 1996"
"Kotlin 'Hello, World!' in 2015",
"tools", "daemons", "apps", "frameworks", "libs"
),
failures = generateSequence(code) { bugs },
twitter = "@eskat0s",
github = "eskatos"
}Qui construit pour la JVM ?
Qui construit pour les VM JavaScript ?
Qui construit du natif ?
Autre chose ? Quoi ?
Qui se sert uniquement de Gradle au boulot ?
Qui se sert uniquement de Maven au boulot ?
Qui se sert des deux au boulot ?
Qui se sert d’autres outils de build ? lesquels ?
Qui a déjà modifié/écrit un build Gradle ?
Gradle, c’est quoi ?
Comment on s’en sert ?
Basique!
En pratique
Organiser la logique de build
Des builds performants
Maintenir et faire évoluer des builds
Gradle est un outil de construction et d’automatisation.
Gradle Build Tool
Tourne sur une JVM
Implémenté en Java
Apache License 2.0
Écosystème JVM
Java, Kotlin, Groovy, Scala, Clojure …
Écosystème natif
C, C++, Swift, …
Android
Et bien d’autres
JavaScript, Python, Go, Rust, Asciidoctor, Docker …
>7.0M téléchargements par mois
35+ ingénieurs Gradle
300K constructions par semaine @LinkedIn
La compagnie derrière Gradle.
"Build Happiness"
Emploie des ingénieurs à plein temps
Produit aussi Gradle Enterprise
(Gradle consulting, support, service etc.)
(Training: online, public and in-house)
Produit commercial - Productivité des développeurs
Build Scans
enregistrement persistent et partageable
de ce qui s’est passé pendant un build
Build Cache
réutilisation des outputs de build
Installation sur site, cache distribué, historique des constructions, dashboards, export API etc…

Déjà utilisé par plein de projets open-source
gradle --scan
Le Build Cache local est intégré à Gradle en Open Source
Image docker d’un noeud de service Build Cache, non distribué
Une équipe de développement totalement distribuée
Un projet intéressant utilisé par des millions
Des positions dans l’équipe Build Tool et Gradle Enterprise
Si ce qui suit est un problème intéressant à résoudre à vos yeux,
ou parlez moi à la pause :-)
en ligne de commande
en intégration continue
depuis un IDE
via une API
gradle buildGradle est avant tout un outil en ligne de commande
USAGE: gradle [option…] [task…]
Un client qui démarre et réutilise un démon
performance: JVM JIT & caches mémoire
Un wrapper enregistré dans le dépôt de sources
Version de l’outil de construction fixée
Pas besoin d’installation, seulement d’une JVM
git clone foo && cd foo && ./gradlew build
Les services d’intégration continue executent Gradle, tout simplement. Certains d’entre eux fournissent des fonctionnalités supplémentaires en consommant les résulats de construction:
sortie console,
résultats d’execution de tests (xUnit, coverage etc..),
URL du Build Scan,
etc …
Certains IDEs supportent Gradle nativement
On importe un build Gradle directement dans l’IDE
IntelliJ IDEA, CLion, Eclipse, Netbeans
mais aussi les Language Servers (LSP)
L’IDE interroge Gradle pour obtenir le modèle du build
les sets de sources, leurs dépendances etc..
les tâches disponibles
la configuration de l’IDE
Pour d’autres IDE qui ne fonctionnent qu’à partir de fichiers de configuration, Gradle peut générer ceux-ci.
IntelliJ IDEA et Eclipse (déprécié en faveur de l’import)
Visual Studio
XCode
Dans tous les cas, l’objectif est de configurer l’IDE
depuis le build
depuis le dépôt de sources
pour tout le monde pareil
sans configuration manuelle
Il est également possible de piloter Gradle via une api, la Tooling API.
C’est ce que les IDE permettant l’import de builds Gradle utilisent.
Scripts de build en Kotlin et Groovy
En fait, Gradle c’est une API Java
Plus un DSL en Kotlin ou Groovy
Configurer et executer des tâches
Résoudre des dépendances
Éviter le répéter travail

Core Plugins (java, jacoco, maven-publish …)
Community Plugins (kotlin, android, golang, docker, asciidoctor …)

Des plugins Gradle contribuent
des tâches configurables et réutilisables
des extensions Gradle configurables

Des plugins Gradle contribuent un modèle à configurer
dans les scripts de build
en utilisant un DSL

plugins {
`java-library`
}
dependencies {
api("com.acme:foo:1.0")
implementation("com.zoo:monkey:1.1")
}
tasks.withType<JavaCompile> {
// ...
}plugins {
`cpp-application`
}
application {
baseName = "my-app"
}
toolChains {
// ...
}Unité de travail: une tâche
@Input* ⇒ Task ⇒ @Output*
UP_TO_DATE - Build Incrémental
Les inputs n’ont pas changé, les outputs sont présents et inchangés
FROM_CACHE - Build Cache
Les inputs n’ont pas changé, les outputs ont été rapatrié depuis le cache
Hello, Gradle World!
Un petit build
Un gros build
Plein de builds
Avec Gradle installé
gradle init
Depuis une interface Web
gradle initUn seul projet
Les plugins contribuent un modèle qui se configure via le DSL
Configuration vs. Execution

Démarrage
Initialisation
Settings
Configuration des Projets
Execution
Multi-projets
3 dans notre exemple
10 à 100, raisonnable et fréquent
500 et plus, moins fréquent mais ça existe
Hiérachie de projets
Configuration
Configurer les sous-projets
Séparer un gros build en plusieurs petits
Différentes équipes, cycle de livraison différent etc…
Mono-repo vs. multi-repo
Mono-build vs. multi-builds
Settings
Composite Builds - Included Builds
Fini les -SNAPSHOTS !
Utiles aussi pour travailler sur des librairies externes
Augmente/Limite le scope disponible dans l’IDE
Source Dependencies
Dépendre d’un dépôt git distant
Librairie ou fix non publié
Sources non modifiables
Pas de logique de build commune
Pas de réutilisation
Comment organiser sa logique de build ?
Beaucoup de logique de build dans les scripts
Maintenabilité :-(
Réutilisation :-(
Extraire des conventions
Maintenabilité :-)
Réutilisation :-)
En extrayant du code pour qu’il soit réutilisable
Refactoring typique
Extraire vers des plugins Gradle réutilisables
et configurables si besoin
Démarrage
Initialisation (& plugins)
Settings (& plugins)
Configuration des Projets (& plugins)
Execution
buildSrcChaque build a, par convention, un build inclu pour sa logique de build
./buildSrc
C’est un build comme un autre, on peut y coder en Kotlin, Groovy, Java etc..
En utilisant l’API Gradle ou son DSL
Ce que produit ce build est disponible pour Settings et tous les Projets
buildSrcbuildSrcTous les scripts de projets deviennent declaratifs
On peut tester la logique de build
Même mechanismes que le développement de plugins Gradle
Ou de n’importe quel code sur la JVM, même outillage
Partager de la logique de build avec plusieurs builds
Publier un plugin Gradle
Utiliser les composite builds
Combiner les deux si besoin de publier
Composite builds avec un peu de cérémonie
Très similaire à buildSrc
N-Repositories
Logique de build testée
Toujours les mêmes mechanismes et outillage
Rien de nouveau sous le soleil
Mais, j’ai déjà assez à faire avec le code de production!
C’est sans compter les tests unitaires, d’intégration, fonctionnels, bariolés …
OUI … MAIS
Qui doit s’occuper du build ?
Qui se sert du build ?
Tout le monde
@dev, @ide, @ci, @qa, @ops etc…
Le produit du build c’est ce qui va en prod
⇒ @users
Qui doit s’occuper du build ?
Projet solo
C’est bibi !
Projet en équipe(s)
Tout le monde, éventuellement un build-master/team
Grandes organisations
Idéalement un build-master ou une build-team, pour l’uniformité et la réutilisation
YMMV - Mais il faut s’en occuper, ça n’a pas à être pénible
Et puis …
Je peux automatiser n’importe quoi
Générer des trucs et des machins
Les envoyer dans les nuages
Interagir avec des APIs
Déployer sur les ordinateurs d’un autre (aka. le Cloug™)
C’est un bon moyen de réduire la fracture entre les Devs des Ops (hein?)
Et puis …
Je peux automatiser n’importe quoi
Générer des trucs et des machins
Les envoyer dans les nuages
Interagir avec des APIs
Déployer sur les ordinateurs d’un autre (aka. le Cloug™)
Le build c’est aussi un bon moyen de rapprocher les Devs des Ops (hein?)
Dans tous les cas …
Ne pas sur-concevoir
KISS - C’est pas de la rocket-science non plus
Ou plutôt: Keep It As Simple As Possible
Le build c’est un peu comme les tests
S’en occuper, c’est rendre service à son futur soi/utilisateur
Si le build est bien fait et testé
Je peux le faire évoluer facilement
Je ne le casse pas sans m’en rendre compte
Si le build est maintenu performant, je ne perds pas de temps

Tout le monde veut un build qui va vite™
Éviter de répéter le travail
Faire le travail plus vite
Unité de travail: une tâche
@Input* ⇒ Task ⇒ @Output*
UP_TO_DATE - Build Incrémental
Les inputs n’ont pas changé, les outputs sont présents et inchangés
FROM_CACHE - Build Cache
Les inputs n’ont pas changé, les outputs ont été rapatrié depuis le cache
Si rien n’a changé, aucune tâche ne devrait être executée
Conçu pour le développement en local
Comparaison des inputs
Comparaison des outputs
Évite d’executer une tâche
abstract class CustomTask : DefaultTask() {
@get:InputDirectory
abstract val sourceDirectory: DirectoryProperty
@get:OutputFile
abstract val outputFile: FileProperty
// ...
}Limitations
Fonctionne localement uniquement
Optimisé pour des changements incrémentaux
Pièges
Inputs volatiles (identifiants uniques, timestamps, ordre non stable)
tasks.jar {
manifest {
attribute("Build-ID", UUID.randomUUID().toString())
}
}
Les identifiants uniques ou timestamps sont à proscrire pour des builds reproductibles et performants!
est-un stockage semi-permanent
activé avec --build-cache
repose sur le build incrémental
stocke les outputs des tâches
l'addresse c’est les inputs de la tâche
le contenu c’est les outputs de la tâche
avec quelques astuces/complications
@CacheableTask
abstract class CustomTask : DefaultTask() {
// ...
}
Est utile pour
travailler sur des branches
git bisect
clean accidentel :-)
Est utile
en intégration continue pour réutiliser les outputs
entre les changesets, entre les agents CI, entre les jobs CI
En developpement
pas besoin de reconstruire les changements des autres
Meilleure stratégie
seule la CI pousse les outputs dans le cache partagé
Inputs volatiles (identifiants uniques, timestamps, ordre non stable)
Chemins absolus - "Relocatability"
Différence de plateformes - Line separators
abstract class CustomTask : DefaultTask() {
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val sourceDirectory: DirectoryProperty
// ...
}La clé c’est les inputs et outputs
Déclarer les bonnes meta-données
Les tests de tâches aident beaucoup à s’assurer que ça fonctionne
Utilisez les dernières version de JVM et de Gradle
Donnez assez de mémoire à Gradle
Le tuning de JVM et ses flags obscurs font souvent plus de mal qu’autre chose
Prenez plutôt le temps de faire des améliorations structurelles
Activez --parallel (org.gradle.parallel=true)
Activez --build-cache (org.gradle.caching=true)
Identifiez vos cas d’utilisation
Automatiser vos mesures
Identifiez le goulet d’étranglement principal
Fixez le goulet
Vérifiez le fix en mesurant de nouveau
Répéter
configurationTime {
tasks = ["help"]
}
cleanBuild {
tasks = ["build"]
cleanup-tasks = ["clean"]
}
cachedCleanBuild {
tasks = ["build"]
cleanup-tasks = ["clean"]
gradle-args = ["--build-cache"]
}Commencez par observer un build
gradle --scan ou gradle --profile

Startup/buildSrc/Settings > 1s
Temps de configuration > 10ms/projet
Changer une ligne de code ~= clean build
Un build censé être un NO-OP qui fait quelque chose
Long temps de GC (Garbage Collection)
Quoi ?
Application les plugins
Evaluation les scripts de build
Execution les callbacks (e.g. afterEvaluate {})
Quand ?
gradle help ou gradle tasks
Synchronisation d’un IDE
à chaque invocation, c’est un coût fixe!
Principales causes de lenteur
Résolution de dépendances à la configuration
I/O à la configuration
Plugins inefficaces
Logique répetée

tasks.register<Jar>("uberJar") {
from(sourceSets["main"].output)
from(configurations["runtime"].map { it.isDirectory ? it : zipTree(it) })
classifier = "uber-jar"
}
configurations["runtime"].map provoque la résolution de dépendances
tasks.register<Jar>("uberJar") {
from(sourceSets["main"].output)
from({ configurations["runtime"].map { it.isDirectory ? it : zipTree(it) } })
classifier = "uber-jar"
}
Préférez les évaluations paresseuses
tasks.register("projectStats") {
val statsFile = file("$buildDir/stats.txt")
statsFile.parentFile.mkdirs()
statsFile.writeText("Source files: ${sourceSets["main"].java.size()}")
}
Attention en écrivant des tâches !

Ce script à l’air couteux
tasks.register("projectStats") {
val statsFile = file("$buildDir/stats.txt")
inputs.files(sourceSets["main"].java)
outputs.file(statsFile)
doLast {
statsFile.parentFile.mkdirs()
statsFile.writeText("Source files: ${sourceSets["main"].java.size()}")
}
}
Ne pas oublier doLast {}
abstract class ProjectStats : DefaultTask() {
@get:InputFiles
abstract val sources: ConfigurableFileCollection
@get:OutputFile
abstract val statsFile: FileProperty
@TaskAction
fun stats() = statsFile.get().asFile.apply {
parentFile.mkdirs()
writeText("Source files: ${sources.size()}")
}
}
tasks.register<ProjectStats>("projectStats") {
sources.from(sourceSets["main"].java)
statsFile.set(file("$buildDir/stats.txt))
}
Sur tous les projets ?
Est-il possible de réutiliser le travail ?
Exemple: lire la version depuis git rev-parse HEAD
subprojects {
apply(plugin = "set-version-from-git")
}plugins {
id("set-version-from-git")
}
subprojects {
version = rootProject.version
}Optimisez les algorithmes

gradle-profiler --profile async-profiler
Execution des tâches
Build Incrémental
Build Cache
Éxecution sérielle

Éxecution parallèle

Modularisation
⇒ Compilation avoidance on non-abi change
⇒ Parallélisation
Code découplé
⇒ Compilation incrémentale plus efficace
Processeurs d’annotations
⇒ Vérifiez bien qu’ils soient incrémentaux
Dynamic versions & locking
Repositories
As few as possible
Filtering repositories content
no mavenLocal()
Pour qu’elle ne regresse pas
L’intégration continue peut alerter, voir fournir des tabeaux de bords
Gradle Enterprise fourni des solutions dédiées à la gestion de la performance du build

C’est du code comme un autre
Il faut mettre à jour les dépendances
Il faut le tester
Il faut le refactorer petit à petit pour qu’il soit meilleur
le build ce n’est pas sale
le build ce n’est pas sale
le build ce n’est pas sale
le build c’est de l’automatisation
et l’automatisation c’est la vie (de développeur)
tout comme pour le code de production, il faut
connaître ses outils
tester
maintenir
Gradle c’est bien™
https://github.com/eskatos/jug-montpellier-gradle-best-practices
Comment se sert-on de Gradle ?