Je pense que Crédic Champeau et les personnes argumentant en faveur du DSL Gradle dans les commentaires "just miss the point".
1) Le DSL ne permet pas d'hériter ou d'inclure un processus de build unique, public, documenté et standard, ce qui est indispensable en industrie. Certains répondraient qu'il est possible de le coder directement dans un plugin ce à quoi je réponds :
- Écrire du code qui va compiler du code, the what ?
- Mais admettons, je code le-dit plugin, si alors j'ai besoin de me sortir partiellement du processus standard que dois-je faire ? Forker le plugin ? Donc je rajoute du code à du code qui va compiler le code ? Encore une fois the what ?
2) Même en écrivant en DSL Gradle de manière déclarative, l'ordre des déclarations est important, ce qui de facto rend le code impératif puisqu'il nécessite de penser en termes d'ordonnancement de l'instantiation des élements qui constituent le DSL, ce qui n'est pas déclaratif.
3) Les DSL ne m'intéressent pas. Ta techno => tes problèmes. Je dois déjà faire du Kotlin, du Java, du TypeScript, du Spring, du Spring Boot, du Spring Cloud, du JPA, du Lombok, du Mapstruct, du Jackson, du Zuul, de l'Open API, de l'Ansible, du Docker, du Kubernetes, du SCSS et CSS, du Bootstrap, de l'Angular, du Karma, du JUnit Jupiter, du Mockito, du WireMock, du Rest Assured, du Protractor, du npm, du Maven, de l'Angular-cli, du SSL, du Linux (ssh + systemctl + GNU tools), du bash et du dash, du GraalVM, du Jasmine, de l'AOP (AspectJ), du Spring Data (JPQL), du Liquibase, du SQL, du PL/SQL, du REST, du SOAP, de l'Apache CXF, du HazelCast, du SonarQube, de l'OAuth2, et du clean code + clean architecture + Domain Driven Design + architecture hexagonale + Design Patterns et tout ça c'est uniquement sur un seul projet (il y en a une trentaine) et là on me dit qu'il faut que j'apprenne deux langages de plus (Groovy + DSL Gradle) juste pour compiler du code ? Comment leur dire à ces messieurs ? Nous ne vivons pas dans le même monde.
Bref, Maven Daemon pour speed up les builds + les pom de Maven écrits en Yaml et c'est bon.
Ça doit faire 4-5 ans que je me remets régulièrement à Gradle dans l'espoir de trouver une solution à un problème pourtant trivial : comment, avec Gradle, mutualiser un processus de build qui doit s'appliquer sur plusieurs projets qui n'ont rien à voir ?
Gradle confond la notion de projets et de modules, donc je dois clarifier ces deux définitions pour la suite :
- Un module, c'est ce qui va produire un jar au sein de votre projet Git et votre projet est composé de modules.
- Donc yn projet, c'est un répo Git et qui en général va produire une application et les modules la composent.
Dans toutes les docs de Gradle, un projet est un module, donc pas besoin d'héritage puisque le build.gradle à la racine va injecter sa conf dans les build.gradle des modules positionnés dans les sous-répertoires.
Sauf que ce genre de bidouilles, ça marche bien dans une startup ne concevant qu'un seul produit, mais imaginez que vous deviez proposer un socle de build gérant Java, Kotlin, Groovy et Scala et qui soit utilisé par 2000 personnes, sur plus de 100 applications et 200 libs, comment faites-vous avec Gradle ?
Permettez-moi de détailler, le processus de build standard inclut :
- La compilation en mode dev (sans optim) et en mode prod (avec optim).
- Les analyses de sécurité.
- Les analyses de code.
- La signature des jars.
- La production des jars des docs et des sources.
- Les analyses des dépendances.
- Etc.
Tout ce processus qui est agnostique du code lui-même DOIT être factorisé pour devenir un standard de l'entreprise et se pose en tant que quality-gate/build-breaker en cas de manquement.
Avec Maven facile, il suffit que le pom parent de votre projet hérite du pom sachant faire tout ça, mais comment faire avec Gradle ? Ce build.gradle "commun" ne peut pas être hérité car Gradle ne permet pas l'héritage et il ne peut pas lancer lui-même le build des 300 répos dont les produits ont des cycles de vie totalement indépendants.
C'est une vraie question car j'aimerai vraiment abandonner Maven (puisqu'il est beaucoup trop lent) au profit de Gradle, mais sans la capacité de mutualiser le processus de build entre deux projets indépendants, cela fait de Gradle un outil pour "amateurs", or sa percée sur le marché me pousse à croire qu'il doit bien exister une astuce mais que je n'ai encore jamais vue. Bref si quelqu'un a l'info...
Comment créer un build reproductible avec Gradle :
tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
Rappel : un build reproductible consiste en un processus qui soit capable de fournir le même binaire (à l’octet près) entre deux commandes de build jouées à deux dates différentes. Comme les archives JAR embarques un timestamp qui chronodate le jour et l'heure build, par défaut ça n'est pas possible.
Ohhh ! Pardon je veux dire
OHHH !!! (O_O)
Je ne migre pas les projets vers Gradle uniquement parce que le build n'est pas paramétrable et parce que je ne parviens pas à partager plusieurs configs de build entre deux projets (cf. principe de standardisation du processus de build/check dans une entreprise).
Et là @Philou m'apporte un début de solution sur un plateau. On devrait tous avoir un @Philou chez soi (^__^).
Réponse rapide : il n'y a pas de bonnes façon de faire cela.
Cela fait bientôt trois ans que je cherche une façon propre et pérenne d'utiliser Gradle au-delà d'un niveau "amateuriste" et ça n'est juste pas possible de le faire simplement. Pourquoi me direz-vous ? Parce que Gradle ne repose pas sur une configuration déclarative mais sur un DSL, c'est-à-dire du code ni plus ni moins.
En Gradle on écrit du code pour compiler, tester et packager du code. C'est une sorte de gros ANT où le XML à laisser sa place au Groovy avec un moteur certes hyper rapide mais Gradle demeure une immense régression en terme de gestion de projet par rapport à Maven et je compte argumenter.
En une phrase : un processus ce n'est pas un plugin !
Il est possible de coder un plugin pour Maven, mais ce plugin représente une tâche, une action spécifique, un traitement unique qui reposera sur une convention, restera paramétrable et surtout sera parfaitement réutilisable d'un projet à un autre projet.
Un exemple parfait : le maven-assembly-plugin permettant de fabriquer des tar.gz. Je peux donc avec Maven déclarer dans une configuration (donc pas du code) un processus qui fasse :
- Compilation (avec le kotlin-maven-plugin)
- Exécution des TU (avec le maven-surefire-plugin)
- Archivage des classes dans des JAR (avec le maven-jar-plugin)
- Analyse SAST des failles de sécurités des libs (avec le dependency-check-maven-plugin)
- Production du bundle qui embarquera jars + exec + conf (avec le maven-assembly-plugin)
L'ordre d'exécution c'est mon workflow, c'est mon processus de build. À aucun moment je n'ai eu à écrire du code pour réaliser tout ça. Mieux encore, ce processus standard peut être mis dans un projet à part en tant que workflow unique et standard de l'entreprise. Cela implique que tous les projets de l'entreprise en hériteront et cela garantira que chaque projet subira les mêmes contrôles. Mieux encore il peut être décomposer en plusieurs projets ou chacun est une couche spécialisée dans une préoccupation : la packaging, les tests, les rapports, les analyses de sécurité... Bref c'est facile et hyper-maintenable.
Il est possible de fabriquer plusieurs de ces configurations, une par types de projets (serveur, lib, batch, etc) et il est même possible d'avoir un héritage entre ces configuration afin qu'elles partagent un maximum de chose et ou chaque nouveau fils raffine le processus de build dans son domaine.
Avec Gradle, il faut que je code un plugin unique qui embarque toutes ces étapes... Comment expliquer à quel point c'est pourri...?... C'est vraiment pourri ! Voilà. Mais pourquoi est-ce donc pourri ?
Simple :
1) Parce que le code appelle le code. Plus j'en écris, plus je devrai en écrire alors qu'une configuration non.
2) Le code c'est incroyablement dur à relire, alors qu'une configuration non.
3) Le code doit être testé alors qu'une configuration non.
4) Le code a toujours le risque d'être écrit pour une plateforme particulière (linux, windows, etc) l'exemple typique étant le séparateur de répertoires '/' ou '\' qui est hardcodé, alors qu'une configuration non.
5) Le plugin Gradle embarquant le workflow implique que tout le processus est rédigé au même endroit, alors qu'une configuration non (elle peut-être éclatée).
6) Si j'ai deux processus distincts, je devrai écrire deux plugins distincts, alors qu'une configuration (je fais référence à celle de Maven) peut être étendue à chaque pom enfant (oui parce que si nos classes doivent être petite, nos pom doivent l'être aussi).
7) Une configuration peut être surchargée, alors que c'est impossible sur un plugin.
En résumé, je pense que Groovy est un langage qui s'appuie sur tout un tas de mauvaise idées (il possède quand même quelques bonnes, mais le typage faible qui est l'anti-pattern absolu sur la longue vie d'un projet est à mon sens le meilleur exemple). Je pens qu'un DSL est une encore plus mauvaise idée car il nécessite un apprentissage du DSL et la plupart du temps des fonctionnalités du vrai langage sous-jacent (et en toute franchise je n'ai pas le temps d'apprendre la techno de merde qui habite votre cœur)_.
Alors un DSL en Groovy ça me "juste tue"... Et Gradle mise sur ça !? C'est l'une des raisons pour lesquelles je préconise mille fois plus Gitlab-CI en lieu et place de Jenkins, car GitLab s'appuie sur une configuration déclarative et non sur du code à copier-coller en DSL-Groovy (et je vous recommande Go Task au passage pour ceux qui souhaitent se rendre indépendant d'un CI).
Je ne fais pas dans la bidouille, je dois gérer plusieurs centaines de builds par jour dans un département de 200 personnes qui fourni des outils rendant service à littéralement 60 millions de clients, je travaille à un niveau professionnel et non dans une "team-startup" qui code une appli Android en moins de 3 semaines et où bidouiller des scripts de build à l'image d'un étudiant en TP est un comportement tout à fait standard et acceptable.
Pour toutes ces raisons, Gradle ne peut pas être un outil que je recommande au niveau industriel. J'ai donc trois souhaits au choix :
- Soit que quelqu'un remplace le DSL de Gradle par une conf évolutive.
- Soit que Maven change son moteur pour rattraper les performances de Gradle au build.
- Soit que quelqu'un m'indique un troisième outil qui ferait mon bonheur en alliant conf et performances.
Comment récupérer une conf Gradle distante pour l'inclure dans son build Gradle (et donc reproduire le mécanisme d'héritage des plugins de Maven avec Gradle) :
apply from: 'http://server-url/nexus/service/local/artifact/maven/redirect?r=repository-name&g=group-name&a=build-common&e=gradle&v=LATEST'
Je suis en train de réfléchir sérieusement à une migration définitive vers Gradle a minima pour mes projets en Kotlin.
Rien que l'explication de l'article montrant la différence entre java et java-library dévoile l'écart de conceptualisation flagrant entre un Maven vieillissant et un Gradle jeune et adaptable.
Que ça me coûte de dire cela d'un si vieil ami !
L'entreprise Gradle est incroyable ! Elle fourni une extension Maven (payante) permettant à ce dernier de bénéficier du cache de Gradle.
Imaginez que vous soyez tellement performant par rapport à votre concurrent direct que vous vous permettiez de l'améliorer et d'en faire un produit d'appel !
Merci @Philou pour l'info.
Correspondances des goals Maven en Gradle :
Maven | Gradle |
---|---|
mvn clean | gradle clean |
mvn compile | gradle compile |
mvn test | gradle test |
mvn package | gradle assemble |
mvn verify | gradle check |
mvn install | task publishToMavenLocal |
mvn deploy | gradle publish |
Transformer automatiquement un pom.xml
en build.gradle
via la commande gradle init.
En quoi Gradle souffre du problème de l'arrêt des machines de Turing mais pas Maven.
Je ne suis pas une grande fana de Gradle (je n'aime pas avoir du code dans ma configuration de build, parce que cela transforme ma configuration tout bête et déclarative, en un script avec du code potentiellement très compliqué dedans).
Apache Buildr reprend les bonnes pratiques de Maven c'est-à-dire :
- Séparer la partie du build qui exécute quelque chose (les plugins)
- De la partie qui dit quoi exécuter et quand (le pom.xml)
L'autre bénéfice, c'est que cela vous pousse à simplifier votre processus de build plutôt que de couvrir votre dette technique avec un script Groovy/Gradle par-dessus : on ne résout pas un problème d'organisation avec du code, il faut être soit débile (cf. écrits de Deleuze) soit un gros nerd à moitié taré pour penser ça car ce faisant on ne remonte pas à la root cause du problème. #MonPointDeVue #SiTuTeSensViséTesKunKon.
Gradle promeut cette croyance de noob qui dit que fusionner la préoccupation de workflow de build avec la préoccupation d'exécution (c'est-à-dire avec du code, avec des if-then-else, while-for, throw-catch...) c'est jeun's, cool, rebelle et vachement-plus-rapide-nempêche. Or si vous m'avez suivi jusque-là, vous comprenez en quoi il est essentiel de séparer ces préoccupations !
C'est pour cela que Maven force les gens à développer des plugins, pour que ce travers de tout mélanger par fainéantise ne vous gagne pas. Une dette technique impardonnable en Java depuis 2010, c'est bien celle qui se situe au niveau du build, alors en 2016, c'est juste une honte.