Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Industrialisation d'un projet "legacy" (1/2)

Je travaille actuellement sur l'industrialisation d'un gros projet "legacy" : je mets en place le build par Maven, l'intégration continue avec Jenkins, et la qualimétrie avec notamment PMD et Checkstyle, configurés via Sonar.
Mais l'application ne respecte évidemment aucune des conventions de Maven - "multi-module" mais pas tout à fait, arborescence des sources différente, jars exotiques non identifiés...

Comme j'ai pas mal lutté pour faire marcher tout ça ensemble, je vous livre ici mon retour d'expérience et les quelques astuces que j'ai glanées ci et là.

Maven, Jenkins, et les jars sauvages


Jars sauvages
Des jars sauvages (image : northdevonfarmer)


Configuration de Maven

Premier problème, les très nombreux jars qui composent l'application (plus de 80) sont tous placés dans les répertoires "lib" ou "WEB-INF/lib" des différents sous-modules.

Certains sont bien identifiés (nom et version), d'autres pas du tout. Certains semblent être faits maison, sans qu'on puisse déterminer leur provenance ou retrouver leur code source. Il y a également des doublons, avec ou sans conflit de version. Enfin, certains ne sont visiblement utilisés que lors de la phase de build (servlet-api, jsp-api...).

Malgré l'effort d'identification, de dédoublonnage et de séparation par phase, le nombre de librairies problématiques était toujours trop important pour utiliser Maven selon la méthode conventionnelle.
Heureusement (?), Maven autorise la déclaration de dépendances sous la visibilité "système", qui permet de fournir des jars tels quels et d'outrepasser le mécanisme de résolution habituel. En contrepartie, il est obligatoire de fournir leur chemin complet "en dur".

Notez (c'est important pour la suite) que j'ai utilisé une propriété pour identifier le répertoire contenant les librairies.


<properties>
    <myProject.basedir>C:\Projets\MyProject</myProject.basedir>
    <myProject.lib>${myProject.basedir}/lib</myProject.lib>
</properties>
...
<dependency>
    <groupId>foo</groupId>
    <artifactId>foo</artifactId>
    <scope>system</scope>
    <version>1.0</version>
    <systemPath>${myProject.lib}/foo.jar</systemPath>
</dependency>

Déclarer ainsi les quelque 80 librairies à la main aurait été fastidieux ; j'ai donc utilisé un petit script shell (via Cygwin) pour générer cette partie du descripteur Maven.

for i in `ls *.jar`; do echo "
<dependency>
  <groupId>${i}</groupId>
  <artifactId>${i}</groupId>
  <scope>system</scope>
  <version>1.0</version>
  <systemPath>\${myProject.lib}/${i}</systemPath>
</dependency>" >> pom-dependencies.txt ; done

Il m'a ensuite suffi de copier/coller le contenu du fichier "pom-dependencies.txt" dans le pom.xml.

Configuration de Jenkins

Une fois que j'ai été en mesure de compiler le projet avec Maven (je vous passe les détails de la configuration des répertoires sources), j'ai installé et configuré un serveur Jenkins sur ma machine.

J'avais déjà travaillé avec Jenkins (Hudson à l'époque) à plusieurs reprises, mais c'est la première fois que j'installais moi-même le serveur. J'ai été agréablement surpris par la simplicité de l'opération, et la clarté de l'interface. Tout est expliqué, et il ne m'a pas fallu plus de quelques minutes pour déclarer mon projet. Chapeau !

Lancement du premier build, Jenkins récupère les sources, je croise les doigts en surveillant la console Maven... Build successful ! Like a boss.

Après quelques essais et réglages, je migre Jenkins vers une machine indépendante et là... c'est le drame. Plus rien le compile ! D'après les logs, Maven ne trouve plus les jars. Mais il les trouvait bien avant ?

facepalm.jpgEt là, j'ai une illumination : les chemins sont toujours "en dur" dans le pom !
Tant que Jenkins était sur ma machine, il n'utilisait pas les librairies récupérées sur Subversion, mais celles de mon workspace - qui n'existait évidemment pas sur la nouvelle machine.

La solution était simple heureusement : il a suffi de redéfinir la propriété "myProject.basedir" dans Jenkins :

jenkins-mvndeps.png

Suite à quoi, tout a bien fonctionné de nouveau, et l'équipe pouvait enfin bénéficier de l'intégration continue. Un petit pas pour Jenkins...

Dans notre prochain numéro...

Dans le prochain billet, je vous parlerai de l'aspect qualimétrique, et des bidouilles subtils réglages que j'ai effectués pour que tout fonctionne bien.
Stay tuned for more happy days !


Commentaires

1. Le dimanche 31 juillet 2011, 20:49 par sbrousse

Merci pour l'article, ça fait plaisir de voir que c'est possible de rafraichir les vieux projets.
2 remarques utiles cependant :
Si tes librairies sont dans le SCM, tu peux t'éviter la déclaration de ${myproject.basedir} en utilisant la propriété maven ${project.basedir}. Ca donne un chemin absolu vers l'endroit où est ton pom. Avec ça plus de modification de configuration suivant les environnements.

Sinon, si tu as un repo manager (Nexus ou autre) tu peux facilement installer des jars en définissant ses groupId/artifactId/version toi même via l'interface. Ca te permet de partager les librairies "old-school" qui seraient définies dans plusieurs projets/modules.

2. Le dimanche 31 juillet 2011, 21:32 par pullotitus

Pour les librairies non identifiées n'est'il pas préférable de creer un sous module qui les installe dans la repository local et ensuite de les utiliser comme des dépendances maven normales ?

3. Le dimanche 31 juillet 2011, 22:20 par Olivier Croisier

@sbrousse : Merci pour l'astuce, je teste ça demain ! J'espère que c'est compatible avec l'obligation de donner des chemins "en dur" imposée par le scope "system".

@sbrousse + @pullotitus : On n'a pas de repository manager, et la connectivité vers les repositories sur le net est... limitée. On doit se débrouiller localement avec les moyens du bord.

4. Le lundi 1 août 2011, 07:27 par Aurélien

Pour les jars sauvage, le repository manager est la solution. En plus comme c'est un serveur central internes tu dois pouvoir lui négocier une connectivité vers le net. Voir argumenter question sécurité que c'est un point central de contrôle des dépendances utilisés.

5. Le lundi 1 août 2011, 07:58 par Grégory Boissinot

Je pense que Maven t'était imposé dans ton cas.
Car au regard de l'existant, peut-être la solution la plus simple aurait été de ne pas utiliser Maven, mais de continuer par exemple avec des scripts Ant puis de rajouter Ivy pour la partie gestion de dépendances et la construction de librairies normalisées.

Dans ton cas, l'ensemble des jars sont en gestion de conf sous un répertoire lib. Si tu n'as pas de gestionnaire de repository (repository manager comme Archiva, Artifactory, Nexus) t'offrant des espaces de stockage, une solution intermédiaire aurait peut-être été de les mettre sur un filesystem partagé au niveau de ton entreprise.

Ta solution actuelle de déporter l'utilisation de la variable 'myproject.basedir' dans Jenkins n'est pas complètement satisfaisante car tu donnes dans ta configuration Jenkins un chemin absolue et donc potentiellement dépendant du noeud (slave Jenkins) sur lequel va s'exécuter le job.
Par rapport à la valeur que tu donnes qui est le workspace (bac à sable du job), tu peux utiliser la variable $WORKSPACE mise à disposition par Jenkins.

Mai sinon, comme il est dit dans les commentaires, tu peux utiliser ${projet.basedir} ou simplement ${basedir} dans ton pom pour éviter de devoir préciser des informations de localisation dans Jenkins.

Et pour finir, pour répondre à @pullotitus:
L'installation des libraires dans le repository local est une mauvaise idée car le repository local est un repository de cache, donc sous-entendu pouvant potentiellement être supprimé.
De plus, le repository est (et doit) être unique à chaque utilisateur sinon cela n'est pas viable en terme d'intégration continue.
La seule voix de l'utilisation dans le repo local sera le déploiement dans le repo local à chaque build ce qui n'est pas vraiment viable en terme de cout.

6. Le lundi 1 août 2011, 08:04 par pullotitus

L'installation des librairies se fait en local pendant le build ; pas besoin dans ce cas d'une repository distante . Voici un exemple qui vient d'une migration que je suis en train de faire :

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<executions>
<execution>
<id>install-sonic-client</id>
<phase>install</phase>
<goals>
<goal>install-file</goal>
</goals>
<configuration>
<file>${external.libdir}/sonic_Client.jar</file>
<groupId>com.sonic</groupId>
<artifactId>sonic-client</artifactId>
<version>7.6.1</version>
<packaging>jar</packaging>
<generatePom>true</generatePom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
---
Dans le reste du projet il ne te reste plus qu'a déclarer tes dépendances normalement.

<dependency>
<groupId>com.sonic</groupId>
<artifactId>sonic-client</artifactId>
<version>7.6.1</version>
</dependency>

7. Le lundi 1 août 2011, 08:21 par X-Blaster

La personne qui s'occupe de l'intégration continue dans notre structure refuse et déconseille de passer un projet sous maven sous prétexte qu'il n'y a selon lui aucun interet pour les vieux projets.

Pourquoi as-tu passé ce projet legacy sous maven ? Qu'est-ce que cela a apporté au projet ?

8. Le lundi 1 août 2011, 09:53 par Olivier Croisier

Comme dit Grégory, je suis passé à Maven parce que c'était demandé, suite à un rapport d'audit d'architecture notamment. C'est pour cette raison également que Sonar a été mis en place (avant mon arrivée).

@X-Blaster : ce que ça apporté au projet, c'est tout le bénéfice de l'intégration continue. Transparence, feedback, qualimétrie... Au moins on n'avance plus dans le brouillard. Ce sera l'objet du prochain article. Par contre effectivement, il faut utiliser Maven comme un simple outil, et pas comme l'épine dorsale du projet.

9. Le lundi 1 août 2011, 10:24 par Guillaume LOURS

Bonjour Olivier,

Ayant effectué la mavenisation de projet plusieurs fois, voici une petite astuce qui te permettra de (re)trouver les véritables références d'un jar (pour ceux qui sont identifiés sur des repos maven).

- Mettre en place un script qui parcours tes répertoires lib.
- Récupérer l'emprunte Sha1 de chaque jar 
- Appeler le webservice REST de Nexus Repo de Sonatype ([https://repository.sonatype.org/]) permettant de faire une recherche à partir de l'emprunte. (Si ta connectivité à ce repo te le permet bien évidemment)
- Générer les déclarations des dépendances dans un fichier de sortie
- Copier/Coller le résultats dans ton pom.xml

Tu n'auras ainsi plus qu'à t'occuper des jars non connus dans le repo

Nicolas Deloof a fait un billet à ce sujet http://blog.loof.fr/2011/01/maveniz...

A bientôt

Guillaume LOURS

10. Le mercredi 3 août 2011, 11:28 par X-Blaster

@Olivier Croisier : La plupart des projets utilisant ANT peuvent être passés sous Jenkins avec Sonar.

L'avantage de Maven est de mettre en place (selon moi) une certaine standardisation. Maven est donc très utile pour des nouveaux projets.

Je vois assez difficilement l'avantage de passer un projet Legacy sous maven.

Je vous laisse jeter un oeil sur cette page très interessante de StackOverflow http://stackoverflow.com/questions/...

11. Le mercredi 3 août 2011, 11:42 par Olivier Croisier

Oui, je préfère souvent Ant à Maven. Mais Sonar est historiquement très lié à Maven, celui-ci m'était donc quasiment imposé par l'infrastructure existante.

12. Le mercredi 3 août 2011, 11:56 par tosirap

Salut,

Je me permets de faire deux remarques...

Il est possible d'utiliser Jenkins et Sonar pour des projets ANT sans avoir à les migrer vers Maven... et heureusement d'ailleurs! Donc dire que le bénéfice d'une migration vers Maven, c'est de pouvoir faire de l'intégration continue, c'est un raisonnement très léger... et surtout un leurre: c'est, comme tu le dis toi même, le résultat d'un audit qui a poussé cette migration.

Seconde remarque: Maven est mon outil de build favori, mais je préfère un projet ANT bien structuré plutôt qu'un projet Maven qui utilise '<systemPath>' pour court-circuiter le fonctionnement normal de Maven!

En tout cas bon courage pour la suite.

A+

13. Le mercredi 3 août 2011, 12:47 par X-Blaster

Il y a malheureusement trop de mauvaises pratiques dans ton article. Un repository Archiva local serait par exemple largement recommandée.

Tu utilise des hacks dans maven (en utilisant des commandes deprecated comme systemPath) qui se repercute ensuite par des hacks aussi dans Jenkins.

La maintenance de ce projet sera beaucoup plus dur sur un maven hacké de cette manière que l'intégration de la tache Ant d'origine dans Jenkins.

14. Le mercredi 3 août 2011, 13:16 par Olivier Croisier

@X-Blaster : Encore une, fois cet article n'a pas la prétention d'expliquer les bonnes pratiques, c'est juste un compte-rendu du processus -laborieux- d'outillage mis en place sur un projet legacy.
Le contexte du projet est assez particulier, et je ne suis pas totalement maître ni des solutions à mettre en place, ni de l'environnement ou de l'infrastructure.

Je ne prétends absolument pas être expert en Maven/Jenkins/Sonar, et j'ai volontiers pris en compte certaines des remarques ci-dessus pour améliorer le système.

Alors oui, je sais bien qu'il faudrait X, que ce serait bien de Y, et qu'il faudrait mettre Z en place (Nexus, Ant, Sonar, etc.), mais ce n'est pas posible dans ce cas particulier, alors je me débrouille avec les moyens du bord. Ce qui compte pour les équipes, c'est le résultat : intégration continue, transparence et feedback immédiat. Le progrès par rapport à la situation précédente est immense !

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.