Affichage des articles dont le libellé est maven. Afficher tous les articles
Affichage des articles dont le libellé est maven. Afficher tous les articles


Maven, tour d'horizon du plugin Assembly

Parce que la création d'un JAR peut ne pas suffire lors de la livraison d'une application, le plugin maven-assembly permet de réaliser des paquets plus complexes.

Prenons l'exemple de la création d'une archive ZIP structurée ainsi :

- racine/
   - conf/
      - *.properties
   - lib/
      - *.jar
   - scripts/
      - application.jar
      - run.sh

Tout commence par l'ajout du plugin essentiel au POM du projet :

<plugin>
 <artifactId>maven-assembly-plugin</artifactId>
 <version>2.3</version>
 <configuration>
   <descriptors>
  <descriptor>assemble/assembly.xml</descriptor>
   </descriptors>
 </configuration>
  </plugin>

Comme on le voit, cet extrait fait appel à assembly.xml qui est la description des opérations à effectuer. Voici un exemple de sa structure :

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
  <id>mon-zip-final</id>
  <formats>
    <format>zip</format>
  </formats>
  <fileSets>
      <!-- Récupération du JAR livrable -->
  <fileSet>
   <directory>target</directory>
   <outputDirectory>scripts</outputDirectory>
   <includes>
    <include>*.jar</include>
   </includes>
  </fileSet>
  <!-- Récupération des scripts shell de lancement -->
  <fileSet>
   <directory>deployment</directory>
   <outputDirectory>scripts</outputDirectory>
   <includes>
    <include>*.sh</include>
   </includes>
  </fileSet>
  <fileSet>
   <directory>src/main/resources/env/${env}</directory>
   <outputDirectory>conf</outputDirectory>
   <includes>
    <include>*.properties</include>
   </includes>
  </fileSet>
  </fileSets>
</assembly>

La première balise formats permet de définir la liste des formats attendus pour la distribution finale.

Chaque balise fileSet permet de créer des règles d'inclusion et/ou d'exclusion de fichiers à partir d'un répertoire :

  • On récupère le jar créé à partir du POM dans le répertoire "target" de maven
  • On regroupe les scripts shell de lancement issus du répertoire "deployment" du projet
  • On cherche les fichiers de propriétés à déplacer dans le répertoire "conf" en fonction de l'environnement cible, défini par la ligne de commande du build, par exemple : -Denv=production

Ces environnements sont configurés dans le POM par l'intermédiaire des balises "profile" :

<profiles>
 <!-- Profil de l'environnement de developpement -->
 <profile>
  <id>env.developpement</id>
  <activation>
   <property>
    <name>env</name>
    <value>developpement</value>
   </property>
   <activeByDefault>true</activeByDefault>
  </activation>
  <properties>
   <env>developpement</env>
  </properties>
  <build>
   ...

Reste maintenant à s'occuper du classpath de l'application et du jar exécutable.

Pour créer le répertoire "lib" contenant toutes les dépendances du projet, il faut ajouter à notre assembly les balises :

<!-- Pour mettre les dépendances du projet dans le répertoire lib -->
<dependencySets>
 <dependencySet>
  <outputDirectory>/lib</outputDirectory>
 </dependencySet>
</dependencySets>

La constitution du MANIFEST est dédiée au plugin JAR de maven, configuré ainsi :

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-jar-plugin</artifactId>
 <version>2.2</version>
 <configuration>
  <archive>
   <manifestEntries>
    <Class-Path>../conf/</Class-Path>
   </manifestEntries>
   <manifest>
    <addClasspath>true</addClasspath>
    <mainClass>com.developpef.CamelStartup</mainClass>
    <classpathPrefix>../lib/</classpathPrefix>
   </manifest>
  </archive>
 </configuration>
</plugin>

Où :

  • manifestEntries permet d'indiquer des éléments personnalisés à ajouter au classpath, ici notre répertoire contenant les fichiers de propriétés
  • mainClass est la classe principale du jar à exécuter
  • classpathPrefix spécifie le préfixe à ajouter à chaque entrée du classpath, le but étant dans notre exemple de pointer vers le répertoire parent "lib"

A ce niveau, maven est d'ores-et-déjà capable de produire une distribution proche de ce que nous voulions, structurée ainsi :

distribution-mon-zip-final.zip :
   - <artifact-id>-<version>/
      - conf/
         - *.properties
      - lib/
         - *.jar
      - scripts/
         - application-<version>.jar
         - run.sh

Il reste cependant quelques détails à régler.

Par défaut, le nom de l'archive finale est suffixé par l'identifiant de l'assembly. Pour éviter cela, il est possible d'ajouter la balise suivante dans le POM au niveau de la configuration du plugin "assembly" :

<appendAssemblyId>false</appendAssemblyId>

De la même façon, le répertoire racine de la distribution est suffixé par la version de l'artifact. Pour l'enlever, il faut ajouter cette balise dans l'assembly :

<baseDirectory>${artifactId}</baseDirectory>

Enfin, puisque le jar de l'application est exécuté par le script shell, pour ne pas avoir à réécrire ce dernier à chaque changement du version du jar, il est possible d'indiquer à maven le nom du jar à produire. Pour cela, ajoutons cette balise à la configuration du plugin "JAR" dans le POM :

<finalName>${artifactId}</finalName>

Nous y voilà! Il ne reste plus qu'à décompresser l'archive produite et lancer le script shell pour démarrer notre application :

distribution.zip :
   - <artifact-id>/
      - conf/
         - *.properties
      - lib/
         - *.jar
      - scripts/
         - application.jar
         - run.sh

Pour perfectionner notre système, il est possible de configurer l'appel à l'assemblage de manière implicite lors d'une phase du build maven. Par exemple, si nous voulons que notre distribution soit produite chaque fois que le jar est déployé, il suffit d'intégrer l'assemblage au moment de la phase "package" du build, comme ceci :

<plugin>
 <artifactId>maven-assembly-plugin</artifactId>
 <version>2.2</version>
 <executions>
  <execution>
   <phase>package</phase>
   <goals>
    <goal>single</goal>
   </goals>
   <configuration>
    <descriptors>
     <descriptor>assemble/assembly.xml</descriptor>
    </descriptors>
    <appendAssemblyId>false</appendAssemblyId>
   </configuration>
  </execution>
 </executions>
</plugin>

Note :

Certaines versions de maven et/ou du plugin "assembly" peuvent provoquer l'erreur suivante lors du build :

[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed to create assembly: Error adding file '(...)' to archive: /(...)/target/classes isn't a file.

Ceci est du à la présence de la balise dependencySets dans l'assembly ainsi qu'à un conflit lors de l'exécution des différentes phases du build (dans un projet multimodules par exemple).

La solution de contournement la plus rapide consiste à copier manuellement les dépendances du projet dans un répertoire temporaire et les ajouter ensuite dans la distribution.

Il faut donc configurer dans le POM le plugin "maven-dependency-plugin" pour appeler la copie :

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-dependency-plugin</artifactId>
 <executions>
  <execution>
   <id>copy-dependencies</id>
   <phase>package</phase>
   <goals>
    <goal>copy-dependencies</goal>
   </goals>
   <configuration>
    <outputDirectory>dependencies</outputDirectory>
    <overWriteReleases>false</overWriteReleases>
    <overWriteSnapshots>false</overWriteSnapshots>
    <overWriteIfNewer>true</overWriteIfNewer>
   </configuration>
  </execution>
 </executions>
</plugin>

Puis récupérer par l'assembly tous les jar produits dans notre zip final (ce qui remplace l'appel à dependencySets) :

<fileSet>
 <directory>dependencies</directory>
 <outputDirectory>lib</outputDirectory>
 <includes>
  <include>*.jar</include>
 </includes>
</fileSet>

Enfin, puisqu'il s'agit d'un répertoire temporaire, ne pas oublier de l'inclure dans la phase de "clean" du projet. Dans le POM :

<plugin>
 <artifactId>maven-clean-plugin</artifactId>
 <configuration>
  <filesets>
   <fileset>
    <directory>dependencies</directory>
   </fileset>
  </filesets>
 </configuration>
</plugin>

Bon déploiement!


Fichier(s) joint(s) :



Obtenir un graphique de dépendances Maven

Il arrive assez souvent d'avoir besoin de savoir quelle version de librairie est liée à quelle dépendance, afin par exemple de résoudre des conflits de versions... Pour ceci, il est nécessaire d'établir un arbre des dépendances du ou des projets en question. Voici trois méthodes qui devraient subvenir à tous les besoins :

La première et la plus simple tout d'abord. Maven fournit la possibilité d'afficher dans console tous les liens entres les librairies, par projets. Pour ce faire, rendez-vous à cette adresse pour configurer le POM du projet principal. Il ne reste ensuite plus qu'à lancer la ligne de commande : mvn dependency:tree pour afficher quelque chose du genre :

Ceci peut être utile pour avoir accès rapidement à certaines informations, mais il faut avouer que ce n'est pas très lisible... Qui plus est, ce bloc de dépendances est généré uniquement par projet, difficile donc de faire des croisements dans le cas des projets multi-modules (reactors).

Il est alors possible d'obtenir une version plus graphique et élégante des dépendances grâce au plugin Eclipse pour Maven m2Eclipse fournit par la société Sonatype (éditrice de Maven). La dernière version est devenue très stable et permet de combler les précédentes lacunes d'Eclipse vis-à-vis de Netbeans concernant l'intégration de Maven. Entre autres choses, il est aisé de générer des graphes du style :

Beaucoup plus intéressant! Mais là encore une fois, une limitation importante est l'impossibilité de créer un graphique inter-modules...

Note : L'update site indiqué sur le site officiel de m2eclipse pour son installation ne fonctionne que pour des version de l'IDE supérieures ou égales à Ganymede (3.5), comme l'indique le bug ouvert ici.

Pour finalement arriver à nos fins, il faut utiliser le plugin Maven "Maven Graph Plugin". Il est utilisable sous deux modes, dont "reactor", qui permettra de lancer la création de l'arbre depuis un POM parent contenant plusieurs modules :

Voici donc un panel d'outils qui vous permettra de créer des graphiques très pratiques et rapidement exploitables de vos dépendances.


Fichier(s) joint(s) :



Maven, Hibernate et Persistence provider...

Ce post a pour but d'expliquer les différentes raisons pour lesquelles, lors du lancement d'une application packagée sous forme de jar par Maven, est parfois levée l'erreur :

javax.persistence.PersistenceException: No Persistence provider for EntityManager named ...

Pour changer des mes habitudes, l'environnement de développement utilisé ici est Netbeans, mais les instructions que je vais donner sont valables également pour Eclipse.

Il y a 3 choses à vérifier pour corriger ce problème, de la plus simple à la plus compliquée :


  1. Vérifier la présence du fichier de configuration hibernate persistence.xml dans le projet, au bon endroit! Sous Netbeans, il doit se trouver à ce niveau :
    Pour le créer, il est possible d'utiliser l'assistant 'Nouveau fichier'>'Catégorie Persistence'>'Type Persitence Unit'

  2. Le contenu du fichier persistence.xml doit être de la forme :
    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
      <persistence-unit name="PU_NAME" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>my.class.EntityName</class>
        <class>...</class>
        <properties>
          <property name="hibernate.connection.username" value="username"/>
          <property name="hibernate.connection.driver_class" value="driver_class"/>
          <property name="hibernate.connection.password" value="password"/>
          <property name="hibernate.connection.url" value="jdbc:..."/>
          <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
          <property name="hibernate.dialect" value="org.hibernate.dialect.TheDialect"/>
          <property name="hibernate.show_sql" value="false"/>
        </properties>
      </persistence-unit>
    </persistence>
    

  3. Si vous arrivez ici, c'est que les choses se compliquent pour vous! ;) En effet, malgré l'assurance du fait que le fichier de configuration soit placé au bon endroit, il se peut que l'erreur continue de se produire... C'est ce qui se passait dans mon cas : lorsque je lançait mon application depuis l'IDE, tout fonctionnait à merveille. Mais lorsque je voulais l'exécuter en ligne de commande depuis le jar produit par Maven, plus rien à faire... Et même si l'erreur renvoyée est toujours la même, le problème se situe en réalité du côté des dépendances que Maven n'a pas résolues. En effet, puisque nous avons déclaré dans le fichier de configuration hibernate, le provider org.hibernate.ejb.HibernatePersistence, il faut inclure de manière explicite la dépendance suivante dans le pom.xml du projet, comme indiqué ici Doc Hibernate :
    <dependency>
      <artifactId>hibernate-entitymanager</artifactId>
      <groupId>org.hibernate.jpa</groupId>
      <type>jar</type>
      <version>3</version>
    </dependency>
    
    Mais va maintenant se poser le problème de toutes les autres dépendances liées à celle-ci... Mais alors, comment cela fonctionne-t-il dans l'IDE et pas en ligne de commande? La réponse vient de la faculté assez vicieuse pratique de Netbeans à se coupler à Maven : il se peut que vous ayez défini, dans l'IDE, une librairie pointant directement sur votre repository local Maven, comme ceci par exemple :
    Ce qui a pour conséquence de permettre à l'IDE de facilement trouver les librairies dont vous avez besoin pour votre projet, mais de ne finalement pas les lui lier directement... D'où leur absence lors du build Maven... Pour régler ceci, il faut déclarer dans le pom TOUTES les dépendances nécessaires au projet! Afin que Maven puisse les récupérer tout seul comme un grand. Pour ce faire, dans Netbeans, vous pouvez sélectionner les librairies du projet et utiliser l'action "Declare as direct dependency" :
    Et le tour est joué! Le build Maven va maintenant pouvoir clairement récupérer tout le nécessaire au projet et permettre son exécution de manière indépendante en ligne de commande.

La morale de cette histoire est de toujours vérifier, lors de développements d'applications vouées à être utilisées aussi bien depuis un IDE que de manière autonomes (via un jar construit par Maven), que l'IDE n'a pas été un peu trop "zélé" dans son rôle de "facilitateur" dans la mise en place du projet...


Fichier(s) joint(s) :