Problème
Vous développez un plugin Maven qui doit valider des trucs sur vos projets. Dans certains cas le plugin doit planter le build, dans d'autres cas, le plugin ne fait pas planter le build.
Ce plugin sert au premier cas, car la présence d'erreur arrête le build, dans ce cas, une erreur souhaitée lors d'un test ne fait plus planter le build :
<plugin>
<groupId>com.soebes.itf.jupiter.extension</groupId>
<artifactId>itf-maven-plugin</artifactId>
<version>0.13.1</version>
</plugin>
Merci à @Philou pour le lien (ça faisait longtemps) !
Je n'ai pas encore migré nos projets mais le gain en temps de compilation semble formidable.
Voici le gain sur un projet cleané :
Et le gain sur un projet déjà compilé avec le build incrémental activé :
Je pense que je vais attendre le prochain patch de correctifs (car il y en aura sûrement) et je migrerai à ce moment-là.
Un plugin Maven qui ne rebuild qu'en cas de changement.
J'ai bidouillé mvnd
cette semaine et je l'ai testé sur un projet de 29 modules écrits en Kotlin (il s'agit du projet commons
de Lenny, Gejel et Kysofer pour ceux qui savent).
Résultats :
- Le build incrémental sans clean est passé de 4 min 20 à 1 min 53.
- Le build incrémental avec clean est passé de 4 min 43 à 2 min 05.
- Le build prod (optimisant le code + vérifs) sans clean est passé de 4 min 56 à 2 min 21.
- Le build prod (optimisant le code + vérifs) avec clean est passé de 4 min 58 à 2 min 22.
- L'analyse statique du code (SCA + SAST + LINTER) est passée de 5 min 11 à 2 min 17.
Conclusion simple, Maven Daemon ça marche !
Actuellement l'outil n'embarque pas encore de LSP (Language Server Page) incluant recompilation ciblée + exécution partielle des TU via graphe des dépendances, mais quand ce sera le cas, les temps de build devraient être divisés par 2 ou 3 ce qui correspondrait aux temps de build de Gradle sans avoir besoin d'écrire du code (via DSL Groovy ou Kotlin) pour compiler du code, pratique qui je l'affirme est une hérésie que les devops d'antant avaient presque réussi à tuer.
Ç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...
Je cite :
Le nouveau format de verrouillage des paquets déverrouillera la possibilité de faire des constructions reproductibles de manière déterministe et comprend tout ce dont npm aura besoin pour construire entièrement l'arbore des paquets. Avant que les fichiers yarn.lock de npm 7 ne soient ignorés, la CLI peut maintenant utiliser yarn.lock comme source de métadonnées de paquets et de conseils de résolution.
Imaginez que l'outil standard de construction des fronts en JS (l'outil de build si vous préférez) vient seulement de fournir la fonctionnalité de garantir un build reproductible...
Comment vous dire... Ça fait 7 ans, tous les fronts "pros" s'appuyaient dessus et nous sommes fin 2020...
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 (^__^).
Merci @Bronco. C'est marrant parce que j'en parlais avec @Lenny ce midi (je suis tombée sur Fontello par hasard ce matin). En fait, j'aimerai une opération qui soit 100 % utilisable dans une PIC (ie. Plateforme d'Intégration Continue pour ceux qui ne connaîtraient pas l'acronyme).
L'idée serait de pouvoir récupérer l'intégralité des fonts en local via une commande Yarn/NPM puis de produire le bundle avec juste ce qu'il faut, au moment du build, en ne déclarant que celles utilisées (dans une tâche Gulp par exemple).
Ce qui me gène dans Fontello, c'est qu'il faut cliquer, et donc qu'un humain doive le faire au moins une fois, puis à chaque mise à jour du set d'icônes ; augmenter ainsi la probabilité d'un oubli d'une icône précieuse au fil du temps...
La meilleure option reste de déclarer l'ensemble des icônes souhaitées dans une conf et de laisser le build produire l'archive qui va bien. Si je trouve une solution, je la posterai.
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 configurer et améliorer le build d'une application Aurelia via le plugin aurelia-webpack-plugin ?
Pour tout vous dire et sans trop forcer hier soir, en reparamétrant le build d'un des projets de @Lenny et @Kysofer, la taille du bundle JS s'est réduite de 12,8% et au vu de mes tests, nous devrions êtres en mesure de récupérer 15% à 20% de plus.
Cela fait de nos SPA que leurs pages d'accueil ne dépassent jamais les 685 Ko tout compris (ie. HTML + JS + CSS + IMG + FONTS) et moins de 180 Ko en utilisant une compression Gzip sur les fichiers statiques.
En ajoutant à cela un cycle de release non continu pour ne pas perdre les bénéfices de la mise en cache côté navigateurs, nous nous retrouvons avec une page de garde ne requérant plus que 3,6 Ko à télécharger lors de la seconde connexion et des suivantes.
Essayez d'en faire autant avec Angular et Bootstrap ! Je maintiens que le combo TypeScript + SCSS + Aurelia + Bulma + Karla Fonts + Font Awesome + TinyPNG est le plus efficace. J'ai vraiment hâte de passer à Aurelia 2 en 2020 !!! ٩(◕‿◕。)۶
La semaine reprend le lundi voyez-vous (en réalité elle ne s'arrête vraiment mais bon) et je ne suis pas toujours motivée comme ce matin par exemple. Et puis mon cher @Philou me sort des p'tits liens qui me font plaisir parce que voilà.
Ici, le fait que le maven-jar-plugin puisse enfin produire des JARs de manière reproductibles ! #Enjoy
Plus d'infos ici et merci @Philou.
Il est très difficile de créer des builds reproductibles en Java via Maven / Gradle & Co. Cette très courte présentation explique bien pourquoi.
Heureusement, il existe un plugin maven permettant de virer les timestamps et les méta-data pour rendre le build reproductible à l'octet près.
Encore une fois, merci à @Philou pour l'info.
MER - CI !!
Via Eric Bugnet
Trouver quels jars sont en conflits dans votre build Maven en une ligne : mvn dependency:tree -Dverbose
Afin de renforcer vos build Maven et garantir que vos 'pom.xml' ne tirent pas de dépendances inutiles vous pouvez configurer votre maven-dependency-plugin
avec l'exécution suivante :
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze-only</goal>
</goals>
<configuration>
<failOnWarning>true</failOnWarning>
<outputXML>true</outputXML>
</configuration>
</execution>
</executions>
</plugin>
Builder des modules en parallèle sous Maven, je me le note pour reminder (car je l'oublie tout le temps) :
mvn -T 4 clean install # Builds with 4 threads
mvn -T 1C clean install # 1 thread per cpu core
mvn -T 1.5C clean install # 1.5 thread per cpu core
Je me note la dépendance :
<dependency>
<groupId>com.oracle.substratevm</groupId>
<artifactId>svm</artifactId>
<version>1.0.0-rc8</version>
<scope>provided</scope>
</dependency>
Il faudra que je fasse un poc à ce sujet.
Edit 2 : bon aurelia-bundler ne peut fonctionner qu'avec JSPM qui dépend de SystemJS. Or la liste des dépendances de JSPM se trouvent dans un fichier s'appelant config.js... M'voilà. J'abandonne et je reste sur le build Aurelia/Webpack.
Ça me saoule car j'espérais vraiment mettre en place un build plus simple, voire de créer un plugin Aurelia pour Brunch mais c'est trop long, trop de choses à maîtriser, trop de trucs qui changes tout le temps, trop de hypsters s'imaginant fournir la nouvelle techno de demain et qui préfèrent ne pas apporter leur soutien à un projet existant (car ils recherchent la gloire et non l'émancipation du système tout marchand). Le problème avec JS, ce sont les gens qui codent en JS pour JS.
Edit : après avoir testé, le tuto ne fonctionne pas (comme quasiment tous les tutos avec Aurelia). J'ai l'impression que le aurelia-bundler ne fonctionne qu'avec JSPM. Mon problème avec ça ? C'est que j'ai déjà un gestionnaire de paquet intégré à mon outil de build et que ce gestionnaire s'appelle NPM ! D'autant que JSPM est un utilitaire qui n'a jamais décollé et ça n'est pas près d'arriver !
Plus j'utilise Aurelia et plus que je trouve ce framework est merdicimale juste à cause du build ! L'utilitaire au aka aurelia-cli passe son temps à réinventer la roue et à tout faire pour surcharger la conf standard d'outils connus, documentés et maîtrisés comme le sont Gulp, NPM. Pourquoi faut-il que les développeurs d'Aurelia utilisent toujours la dernière techno-hype merdique qui n'a pas encore eu le temps de percer, c'est chiant à la fin ! Si encore ils s'étaient appuyés sur Brunch pour réutiliser quelque chose d'existant et de simple plutôt que de tout refaire à leur sauce en mode hypster-à-la-noix.
Le plus terrible c'est que la communauté derrière le framework Aurelia revendique qu'il est l'un des frameworks les plus respectueux des standards... Mais dès qu'il s'agit du build, ils sont encore pire qu'Angular c'est vous dire à quel point le niveau de médiocrité est ineffable.
Bref, je continue mes investigations en espérant comprendre par moi-même comment fonctionne le aurelia-bundler et surtout à quoi sert ce foutu fichier config.js (je pressens qu'il a un lien avec JSPM, auquel cas je sens que je vais hurler car je fais tout mon possible pour avoir un build 100% Gulp + NPM et rien d'autre).
Comment utiliser l'API du aurelia-bundler directement dans Gulp et s'éviter Webpack ?
Je me permets d'ajouter un truc qui me frustre avec Aurelia : c'est un framework à la Spring ou à la JEE qui se prétend modulaire mais où chaque élément de la couche supérieure va tirer un élément de la couche inférieure.
Essayez de remplacer la DI de Spring, Aurelia ou Angular par une autre pour voir. C'est juste impossible, car le couplage entre les modules y est total ! #DesignDeNoob
Mais en vérité cette "erreur de design" arrange bien son fabriquant puisqu'il rend le développeur captif du framework et lui évite d'aller prendre une lib chez la concurrence. #PorteOuverteVersLesAutres
Ma décision pour 2019, trouver des micro-frameworks, indépendants, interchangeables et légers, pour le front, à l'image de ce que sont Sparkjava (web serveur REST), ActiveJDBC (ORM), Feather-java (DI) ou encore Jsoniter (conversion entity-json) pour le back sur JVM.