Thématiques principales

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

samedi 24 mars 2018

Maven : assembly, targz et jdeb

Aujourd’hui nous allons nous intéresser à la production du livrable issue de la production logicielle. Le sujet ne va pas etre forcement tres long a traiter tant maven permet de chose si l’on utilise les bons plugins.

La production du livrable impose avant tout la définition de celui ci. Souvent, il va être dépendant de la plateforme et du langage. Dans notre cas, nous allons tâcher de produire nos composants dans le contexte d’utilisation du langage Java et nous nous projeterons dans une plateforme linux, la debian et la plateforme windows.

Bien sûr on aurait pu passer par l’utilisation de profile maven dans lequel nous aurions spécifier selon l’OS différents plugins afin de répondre à cette problématique de production. Par exemple en définissant les profiles suivants:

<profiles>
 <profile>
  <activation>
   <os>
    <family>windows</family>
   </os>
  </activation>
  <build>...</build>
  ...
 </profile>
 <profile>
  <activation>
   <os>
    <family>linux</family>
   </os>
  </activation>
  <build>...</build>
  ...
 </profile>
</profiles>


Nous n'utilisons pas cette approche car d’une part non nécessaire puisque Java est multiplateforme et d’autre part elle complexifie lourdement la production (l’utilisation de profile n’etant une bonne chose que si on en peut vraiment pas faire autrement)

Au lieu de cela, nous allons plutôt tricher un peu et prendre le parti pris que nous souhaitons livrer simplement un targz pour windows et un deb sous linux (comme cela, on pourra produire le même jar et livrer d’un côté un script bash pour lancer notre appli et de l’autre un bat.) Pour répondre à ce problème nous allons utiliser le plugin maven-assembly-plugin.

Ce plugin est un plugin indispensable pour la plupart des builds maven car au delà de l’utilisation ce que nous allons en faire, il permet de faire surtout toute sorte de compositions, de construire tout type de paquet de livraison mais aussi de pouvoir construire des jar au contenu spécifique (nous y reviendrons mais pas dans cet article) .

Nous avons donc deux types de packaging à réaliser, un pour le targz et un autre pour le deb. On va configurer le plugin pour qu’il fasse les deux en même temps en définissant deux fichiers d’assembly:

<plugin>
 <groupid>org.apache.maven.plugins</groupid>
 <artifactid>maven-assembly-plugin</artifactid>
 <version>2.5.3</version>
      <configuration>
  <finalname>${project.artifactId}-${project.version}</finalname>
  <ignoredirformatextensions>true</ignoredirformatextensions>
  <outputdirectory>${project.build.directory}</outputdirectory>
       </configuration>

 <executions>
  <!-- Packaging debian -->
  <execution>
   <id>assembly-debian</id>
   <phase>prepare-package</phase>
   <goals>
    <goal>single</goal>
   </goals>
   <configuration>
    <appendassemblyid>false</appendassemblyid>
    <descriptors>
     <descriptor>src/main/assembly/assembly-debian.xml</descriptor>
    </descriptors>
   </configuration>
  </execution>
  <execution>
   <id>assembly-targz</id>
   <phase>package</phase>
   <goals>
    <goal>single</goal>
   </goals>
   <configuration>
    <appendassemblyid>false</appendassemblyid>
    <descriptors>
     <descriptor>src/main/assembly/assembly-targz.xml</descriptor>
    </descriptors>
   </configuration>
  </execution>
 </executions>
</plugin>


Quelques explications sur l’utilisation du plugin dans le pom. On précise le répertoire de sortie et le nom général de la composition. On spécifie ensuite par type de composition un élément de configuration en utilisant appendAssemblyId. Ceci permet d’ajouter le classifier “assembly” au nom de la composition, mais ici on va s’en passer. On met false. On précise également pendant quelle phase et quel goal les assembly devront être construit. Enfin on donne au plugin le fichier d’assembly qui précise le contenu de la composition.

Le fichier assembly est la dernière partie permettant de construire la composition. Il s’agit d’un fichier xml définissant plusieur choses: le format de sortie, les dépendances à joindre et ou les déposer et enfin les répertoires annexes que la composition doit contenir. Regardons celui du targz:

<assembly>
 <id>bundle</id>
 <formats>
  <format>tar.gz</format>
 </formats>
 <includebasedirectory>false</includebasedirectory>

 <dependencysets>
<!--  Inclusion interfaces classifier et librairies-->
  <dependencyset>
   <unpack>false</unpack>
   <scope>runtime</scope>
   <useprojectartifact>false</useprojectartifact>
   <useprojectattachments>true</useprojectattachments>
   <outputdirectory>/opt/monappli</outputdirectory>
   <includes>
    <include>*:${project.artifactId}:*:assembly:*</include>
   </includes>
  </dependencyset>
 </dependencysets>

  <filesets>
      <fileset>
          <directory>src/main/debian/etc/init.d</directory>
          <outputdirectory>/etc/init.d</outputdirectory>
          <usedefaultexcludes>true</usedefaultexcludes>
          <filtered>true</filtered>
          <includes>
              <include>monappli</include>
          </includes>
      </fileset>
  </filesets>
</assembly>


Inutile de commenter le format, il parle de lui-même. Les dependencySet se construisent pour incorporer à la composition les éléments des dépendances qu’il faudra ajouter et sous quel forme. Par exemple on peut spécifier si elles seront dézipper dans la composition (cela est pratique par exemple lorsque l’on veut fusionner des artifacts ensemble pour faire un jar), le répertoire ou ces éléments seront déposé et enfin un ensemble d'artifact déclarer sous la forme d’une regex.

Les fileSet précisent de la même manière que pour les artifacts l’ensemble des fichiers et répertoires à construire dans notre composition. Ces éléments sont généralement issus des ressources du projets et il est possible de réaliser du remplacement clef/valeur dedans (via le resources filtering). A l’issu de l’application de l’assembly, on aura alors tous ces éléments dans un tar.gz… cool!

Bon bien du coup on fait pareil pour le paquet debian non? et bien pas tout à fait. sur le principe, on va procéder de la même manière en utilisant un fichier d’assembly rigoureusement identique sauf sur le format que nous définirons a “dir”. Ce format ne fait rien a part préparer les éléments de la composition. Pourquoi prendre ce format? Tout simplement parce que le plugin assembly n’est pas capable de construire quelque chose d’aussi complexe qu’un paquet debian. On va donc s’appuyer d’un autre plugin pour faire cela: jdeb. Avant cela je vous renvoie à [3] pour compléter vos ressources avec les fichiers de contrôles qui vont bien et les intégrer à l’assembly.

Ceci étant fait, penchons nous sur le plugin jdeb. Celui ci se configure assez classiquement en précisant sa phase de build et son goal. A cela, on va preciser le nom du paquet debian, le repertoire des fichiers de controles, et enfin un ensemble de dataset precisant tous les repertoire, fichiers et autres resources qui va falloir ajouter au paquet. Attention, a ce nieau, il faut avoir une vision clair des droits qui seront appliqué lors de l’installation, puisque l’on va les preciser ici. Voyons cela:

<plugin>
 <artifactid>jdeb</artifactid>
 <groupid>org.vafer</groupid>
 <version>1.5</version>
 <executions>
  <execution>
   <phase>package</phase>
   <goals>
    <goal>jdeb</goal>
   </goals>
   <configuration>
    <deb>${project.build.directory}/${project.artifactId}-${build}_${versiontimestamp}_all.deb</deb>
    <verbose>true</verbose>
    <controldir>${project.build.directory}/${project.artifactId}-${project.version}/DEBIAN</controldir>
    <dataset>
     <data>
      <src>${project.build.directory}/${project.artifactId}-${project.version}/opt</src>
      <type>directory</type>
      <mapper>
       <type>perm</type>
       <prefix>/opt</prefix>
      </mapper>
     </data>
    </dataset>
   </configuration>
  </execution>
 </executions>
</plugin>


Voilà, nous venons de passer en revu comment construire les livrables de nos projets. Bien sur faire du java permet déjà de packager les sources mais pour être propre, l'idéal est de fournir un composant standard comme un targz ou mieux un deb contenant tous les éléments permettant de faire vivre l’application (fichier de conf, de scripts, etc…)

Références: 

[1] https://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html
[2] https://github.com/tcurdt/jdeb/blob/master/src/examples/maven/pom.xml
[3] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/construire-un-paquet-debian.html

lundi 26 février 2018

Dépôt Debian signé

Dans un article précédent nous avions traité de la construction de dépôts debian. Basiquement nous nous étions contenté de l’initialisation du listing des packages du dépôt en effectuant un scanpackage [1].

Aujourd’hui de façon à rendre un peu plus fiable et sécurisé les paquets et fournir plus de garanties aux utilisateurs du dépôts, nous allons regarder comment signer les éléments du dépôts, mais nous allons aussi industrialiser un peu la méthode de production du dépôt.

Pour cela nous allons tout d’abord récupérer reprepro [2] et gpg2 [3-4]. On aurait pu faire sans reprepro mais bon, quand il existe un outil qui sait faire autant ne pas s’en priver [5-6].

Générer la clef pour signer le dépôt

Tout d’abord il faut construire une clef pour notre futur dépôt et on vérifie la présence de notre clef dans le trousseau

gpg2 --gen-key
gpg2 --list-keys

Puis on export cette clef dans répertoire accessible au travers de votre serveur apache (un chemin proche de l’url du futur repo par exemple)

gpg2 --armor --export votreNom@mail.com >> /var/www/pathInApache/key/keyName.gpg.key

Ici par exemple notre clef sera accessible à l'adresse http://localhost/pathInApache/key/keyName.gpg.key. Pour l’ajouter il faudra ajouter la clef publique en mode admin (en root ou en ajoutant des sudo

wget -O - http://hostserver/pathInApache/key/keyName.gpg.key | apt-key add -

Comme ça lorsque l’on aura a faire le apt-get update, apt sera capable de vérifier la signature, grâce à la clef.

Construction du dépôt

Une fois la clef configurée, il ne nous reste plus qu'à construire le dépôt pour mettre à disposition les paquets Debian. Pour ce faire, nous allons dans un premier temps utiliser reprepro qui va automatiser une très grosse partie du travail en partant d’une config et d’un répertoire de livraison dans lequel nous déposerons nos paquets à la suite de leur production.

Il nous faut tout d’abord définir un répertoire accessible via http donc un répertoire tel que /var/www/apt par exemple. Au passage dans ce répertoire on peut y placer notre répertoire key, qui contient la clef publique du dépôt.

Dans ce répertoire apt nous allons y créer un répertoire livraison dédié aux paquets Debian. A côté de celui ci nous allons également créer un répertoire conf dans lequel on va déposer un fichier que l’on nommera distributions. Ce fichier contiendra toutes les informations nécessaire à reprepro pour initialiser le dépôt et l’alimenter.

Ainsi ce fichier doit contenir les champs suivants [10]:

Origin: Votre nom ou un url 
Label: monLabel 
Suite: stable 
Codename: le nom code de votre projet comme wheeze ou xenial par exemple “monCode” 
Version: 1.0 
Architectures: i386 amd64 armhf 
Components: all non-free contrib 
Description: Un peu de blabla 
SignWith: yes

Le paramètre SignWith est essentiellement le plus important pour ce qui nous préoccupe, c’est a dire de faire un dépôt signé. A la place du yes, il est possible de mettre la clef GPG mais avec le yes, reprepro ira directement la chercher dans le trousseau.

Ainsi on va avoir l’arborescence suivante:
  • /var/www/apt/conf/distributions
  • /var/www/apt/key/keyName.gpg.key
  • /var/www/apt/livraison/
On met un Debian dans le répertoire livraison et on applique la commande suivante:


APT_DIR=”/var/www/apt”reprepro --dbdir $APT_DIR/db --confdir $APT_DIR/conf -b $APT_DIR/deb include monCode $APT_DIR/livraison/*.deb

reprepro va procéder a l’analyse du répertoire livraison en récupérer la dernière version des Debian présents et les classer dans un répertoire deb/pool. Dans ce même répertoire, il va constituer un répertoire dists dans lequel il déposera l'équivalent du scanpackage avec sa version signée qui permettra au client d’identifier notre dépôt avec la clef publique qu’il aura obtenu. Le répertoire db est un répertoire de travail de reprepro, nous ne nous attarderons pas dessus.

Bien sur pour à chaque nouvelle production des paquets, il faudra rappeler ce script. Pour cela, il est possible d’utiliser le daemon mini-dinstall [11-13]. Cela sera peut être l’occasion d’un autre article.

Pour les plus avisés qui voudront aller vraiment plus loin, je vous invite lire l’article de Vincent qui reprend un cas d’utilisation complet [9]

Note (26/08/2018) : attention a utiliser les commandes gpg2 avec sudo de facon a ce que les clefs soient accessibles lors de l'appel de reprepro qui se fait assez logiquement aussi en sudo (puisque c'est pour initialiser le depot.... )

Note 2 (26/08/2018) Sinon pour convertir de gpg a gpg2 et reciproqement:

gpg2 --export-secret-keys | gpg --import -
gpg --export-secret-keys | gpg2 --import -

Références

[1] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/faire-un-depot-debian.html
[2] https://doc.ubuntu-fr.org/tutoriel/comment_creer_depot
[3] https://www.gnupg.org/
[4] https://doc.ubuntu-fr.org/gnupg#utilisation_et_configuration
[5] https://wiki.debian-fr.xyz/Faire_un_d%C3%A9pot_sign%C3%A9_ou_non
[6] http://blog.glehmann.net/2015/01/27/Creating-a-debian-repository/
[7] https://www.francoz.net/doc/gpg/x218.html
[8] http://www.serveur-linux.info/2012/01/depot-personnalise-paquets-debian/
[9] https://vincent.bernat.im/fr/blog/2014-depots-apt-locaux
[10] https://blog.packagecloud.io/eng/2017/03/23/create-debian-repository-reprepro/
[11] https://github.com/shartge/mini-dinstall
[12] https://manpages.debian.org/stretch/mini-dinstall/mini-dinstall.1.en.html
[13] https://debian-handbook.info/browse/fr-FR/stable/sect.setup-apt-package-repository.html

lundi 12 février 2018

Maven : Management

Un petit article pour comprendre quelques subtilités dans l’utilisation de maven et des éléments de build qui se finissent par Management.

En effet lorsque l’on construit ses poms, on a souvent besoin de définir certaines modalités particulieres des builds. Pour cela, on utilise des plugins. De la même façon lorsque l’on veut utiliser un composant, il est nécessaire de déclarer une dépendance à ce composant. Dans les deux cas la déclaration de ces éléments, une fois réalisé, est immuable.

Ainsi quoiqu’il arrive que la définition soit faites dans le pom parent ou dans le pom cible, lors du build, les éléments seront appelés tel quel moyennant les éventuelles surcharges mais il seront appelés.

En utilisant les éléments Management, c’est a dire dependencyManagement [1] ou pluginManagement [2], maven nous permet de définir des configurations potentiellement utilisables dans les poms enfants.

Par exemple dans un pom parent, en déclarant:


<dependencyManagement>
 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.10</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-all</artifactId>
   <version>1.9.5</version>
   <scope>test</scope>
  </dependency>
 </dependencies>
</dependencyManagement>


nous permet de déclarer dans notre pom cible:


<dependencies>
 <dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
 </dependency>
 <dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
 </dependency>
</dependencies>


sans avoir à déclarer une version particulière, celle ci étant celle donnée dans le pom parent et donc considérée comme préconisé.

Et rien n'empêche de déclarer une autre version, si l’on se sent suffisamment à l’aise pour assumer les éventuels conflits.

De la meme maniere, on pourra factoriser divers phases de builds que l’on appelera ou precisera selon le besoin dans les pom fils.

Par exemple un pom fils declarera de facon tres minimaliste:


<plugins>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <configuration>
    <archive>
     <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
    </archive>
   </configuration>
 </plugin>
 <plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
 </plugin>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
 </plugin>
 <plugin>
  <artifactId>jdeb</artifactId>
  <groupId>org.vafer</groupId>
 </plugin>
</plugins>


alors que la définition des configurations de ces plugins est juste un peu plus complexe:


<pluginManagement>
<plugins>
 <plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <version>2.3.7</version>
  <extensions>true</extensions>
  <configuration>
   <instructions>
    <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
    <Bundle-Name>${project.name}</Bundle-Name>
    <Bundle-Version>${project.version}</Bundle-Version>
    <Bundle-Vendor>TC</Bundle-Vendor>
   </instructions>
  </configuration>
  <executions>
   <execution>
    <id>bundle-manifest</id>
    <phase>process-classes</phase>
    <goals>
     <goal>manifest</goal>
    </goals>
   </execution>
  </executions>
 </plugin>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <version>2.3.1</version>
  <configuration>
   <archive>
    <manifest>
     <addClasspath>true</addClasspath>
     <classpathPrefix>./</classpathPrefix>
    </manifest>
    <manifestEntries>
     <Class-Path>./ ./${project.artifactId}-${project.version}/</Class-Path>
    </manifestEntries>
   </archive>
  </configuration>
  <executions>
   <execution>
    <id>assembly</id>
    <phase>prepare-package</phase>
    <goals>
     <goal>jar</goal>
    </goals>
    <configuration>
     <classifier>assembly</classifier>
     <includes>
      <include>**/*.class</include>
      <include>**/*.properties</include>
      <include>**/*-context.xml</include>
     </includes>
    </configuration>
   </execution>
  </executions>
 </plugin>
 <plugin>
  <artifactId>jdeb</artifactId>
  <groupId>org.vafer</groupId>
  <version>1.5</version>
  <executions>
   <execution>
    <phase>package</phase>
    <goals>
     <goal>jdeb</goal>
    </goals>
    <configuration>
     <deb>${project.build.directory}/${project.artifactId}-${build}_${versiontimestamp}_all.deb</deb>
     <verbose>true</verbose>
     <controlDir>${project.build.directory}/${project.artifactId}-${project.version}/DEBIAN</controlDir>
     <dataSet>
      <data>
       <src>${project.build.directory}/${project.artifactId}-${project.version}/opt</src>
       <type>directory</type>
       <mapper>
        <type>perm</type>
        <prefix>/opt</prefix>
       </mapper>
      </data>
     </dataSet>
    </configuration>
   </execution>
  </executions>
 </plugin>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
  <version>2.5.3</version>
  <configuration>
   <appendAssemblyId>true</appendAssemblyId>
   <attach>true</attach>
   <finalName>${project.artifactId}-${project.version}</finalName>
   <ignoreDirFormatExtensions>true</ignoreDirFormatExtensions>
   <outputDirectory>${project.build.directory}</outputDirectory>
   <archive>
    <manifest>
     <addClasspath>true</addClasspath>
     <classpathPrefix>./</classpathPrefix>
    </manifest>
   </archive>
  </configuration>
  <executions>
   <!-- Packaging debian -->
   <execution>
    <id>assembly-debian</id>
    <phase>prepare-package</phase>
    <goals>
     <goal>single</goal>
    </goals>
    <configuration>
     <appendAssemblyId>false</appendAssemblyId>
     <descriptors>
      <descriptor>src/main/assembly/assembly-debian.xml</descriptor>
     </descriptors>
    </configuration>
   </execution>
   <execution>
    <id>assembly-targz</id>
    <phase>package</phase>
    <goals>
     <goal>single</goal>
    </goals>
    <configuration>
     <appendAssemblyId>false</appendAssemblyId>
     <descriptors>
      <descriptor>src/main/assembly/assembly-targz.xml</descriptor>
     </descriptors>
    </configuration>
   </execution>
  </executions>
 </plugin>
</plugins>
</pluginManagement>


Mais cela revient a déclarer ce qu'il aurait de toute façon du être déclaré dans tous les pom fils (donc de multiple fois)

References:

[1] https://maven.apache.org/pom.html#Dependency_Management
[2] https://maven.apache.org/pom.html#Plugin_Management

dimanche 28 janvier 2018

Gradle

Lors de la production de systèmes logiciels, il ne fait aucun doute que la phase la plus importante est la compilation qui nécessite l’utilisation d'outils tels que gcc ou javac. Aujourd’hui pourtant limiter la production d’un logiciel a la compilation n’est plus suffisant. De nombreuses phases périphériques à la compilation sont devenu indispensables, comme l'exécution des tests unitaires ou des tests fonctionnels, la production automatique de documentations ou de code, le packaging multi plateforme, ou même la mise en production. Ceci s'appelle l'intégration continu au sein duquel on va trouver divers outils à différents niveau d’abstraction (Jenkins[1] , artifactory [2]) ou differents roles (Sonar [3], RobotFramework [4]). Nous reviendrons sur ces outils, mais nous en avions déjà vu un nommé Maven dans un précédent article [5] dont le but est justement de couvrir l’ensemble des phases du build.

Aujourd’hui nous allons nous intéresser à ce que l’on pourrait appeler un peu abusivement son concurrent: Gradle [7].

A mon sens Gradle en est plutot le successeur et nous verrons pourquoi. En effet, même si personnellement j’utilise encore beaucoup Maven, les raisons sont surtout historique et que faire la migration de l’un vers l’autre, ne peut être qu’un bien [9] mais cela repose sur une bonne disponibilité en temps.

Voyons ce que propose Gradle par dela Maven. Pour faire simple et rapide sur l'intérêt de Gradle est qu’il permet de dépasser le côté monolithe de Maven. En effet, Maven repose sur un socle stable et très sain d’un process de build. Du moins lorsque ce processus est classique et standard. Gradle, tout en conservant ce socle permet d'être plus souple et dynamique car contrairement a Maven qui repose sur xml pour décrire le contenu du build (donc a une vision configuration du process), Gradle lui repose sur groovy qui est lui un langage de programmation.

En fait si l’on reprend le titre de la publication de Leon Osterweil:  “software processes are software too” [8], nous sommes face à un véritable changement de paradigme dans la façon de gérer la production de logiciel ou celle-ci est également vu comme une partie intégrante du logiciel lui même.

Par exemple, Google a adopté Gradle pour la production de ses applicatifs Android et l’on peut assister que cette adoption a été largement suivi par une très bonne intégration de ce nouvel outils dans les IDE standard de la communauté. Ainsi developper une application android sous Gradle n’est vraiment pas un probleme [11] (nous y reviendrons dans un futur article)

Initialisation

Avant de nous pencher sur android, regardons déjà comment faire le build d’une application java standard avec Gradle.

Il faut commencer par télécharger Gradle [12] et d’ajouter son GRADLE_HOME/bin au PATH du système. Ceci permettra de lancer gradle en ligne de commande :

gradle -v

Votre installation est bonne? Ok nous allons alors pouvoir initialiser notre premier projet Gradle en lancant la commande init:

gradle init

A la console normalement vous devriez obtenir les log suivant

Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details
Support for running Gradle using Java 7 has been deprecated and is scheduled to be removed in Gradle 5.0. Please see https://docs.gradle.org/4.3/userguide/java_plugin.html#sec:java_cross_compilation for more details.
BUILD SUCCESSFUL in 7s
2 actionable tasks: 2 executed

Cette commande va tout d’abord lancer un demon pour accélérer les build suivant du projet et va preinitialiser pour votre système un certain nombre de fichiers, un répertoire wrapper et un répertoire .gradle (invisible sous unix)

ls -a
.  ..  build.gradle  gradle  .gradle  gradlew  gradlew.bat  settings.gradle

Alors commençons par la partie wrapper. Celle ci n’est pas indispensable mais Gradle préconise son utilisation afin de garantir l'homogénéité des builds du projet [13]. Les repertoires et fichiers concernés sont gradle, gradlew et gradlew.bat. En fait le wrapper permet contrairement à Maven,  le rechargement lors du build de la version proposée de gradle qui doit être utilisée pour construire le projet. Ainsi, pour tous les développeurs utilisant le wrapper, le build sera forcément identique (ou du moins reposera sur un environnement identique). Les fichiers gradlew permettent de lancer le wrapper et dans le repertoire nous trouverons un jar (réalisant le maintien de la version) et un fichier de propriétés pour le wrapper (contenant des données d’environnement) [14].

Les fichiers et répertoires participant a proprement parler au build sont build.gradle, settings.gradle et .gradle. Le repertoire .gradle est un repertoire interne a gradle propre au projet permettant à gradle de tagger les fichiers nouvellements modifiés et devant être incorporé au build incremental (c’est lui qui va rendre le processus de build plus efficace et plus rapide également). Normalement il ne devrait pas y avoir de besoin d’aller mettre son nez dedans; par contre, nous arrivons enfin sur les deux fichiers qui nous interesse le plus settings.gradle et build.gradle.

Le fichier settings.gradle est optionnel mais, il est recommandé de systématiquement l’utiliser. En effet celui-ci n’est pas utile pour un projet isolé mais sera indispensable dans le cas des multi-projets (equivalent aux modules maven). De plus il permet de définir un nom aux projets qui par défaut s’il n’existe pas est pris sur la base du nom du repertoire contenant le projet (ce qui n’est pas tiptop…)

settings.gradle

De façon simple voici ce qu’il peut contenir:

/*
// To declare projects as part of a multi-project build use the 'include' method
include 'shared'
include 'api'
include 'services:webservice'
*/
rootProject.name = 'monProjetGradle'

On aura compris que rootProject.name est le nom de notre projet…. A noter la primitive include afin de déclarer des sous projets à construire.

build.gradle

Intéressons nous au fichier de build lui-même: build.gradle. Ce fichier a pour vocation de décrire le contenu du build lui-même en s’appuyant sur la syntaxe Groovy. On imagine alors ce que l’on va y decrire sera considéré avant tout comme du comportement plutôt que de la configuration (a l’inverse de Maven) et c’est effectivement le cas à quelques exception près.

Prenons l’exemple d’un projet Java, il va falloir commencer par indiquer à Gradle que le build s’effectuera en Java. Pour cela, on va lui specifier l’utilisation d’un plugin [15]:

apply plugin: 'java'

Ensuite, à l'instar de maven on pourra donner une description et une version à l’artifact que nous allons créer. A la suite de quoi on va lui donner la version java pour la compilation:

description ="Projet de test pour gradle"
version = '0.1.0-SNAPSHOT'
sourceCompatibility = 1.8

Pour identifier les sources, il est possible de spécifier leur emplacement, ainsi que l’emplacement des tests.

sourceSets {
    main {
         java {
              srcDir 'src/main/java'
              }
         }
    test {
         java {
              srcDir 'src/test/java'
              }
         }
}

Encore une fois comme pour maven, il faut définir quelles vont être les dépendances nécessaires à la compilation et aux tests: Plus simplement que pour maven il suffit de déclarer le bloc dependencies comme suit:

dependencies {
    compile 'org.tc.osgi.bundle.utils:tc-osgi-bundle-utils-interfaces:0.1.0-SNAPSHOT'
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-all:1.9.5'
}

A noter que le nommage des artifacts peut suivre les règles d’ecriture prescrit par Maven et permettre d’utiliser des repository Maven.
C’est d’ailleur ce que l’on va ajouter à notre build, la possibilité d’aller rechercher des artifacts dans un artifactory maven. Ainsi, si vous utilisez un repository maven perso, il faudra le déclarer avec la primitive repositories comme suit:

repositories {
    //jcenter()
    maven {
        url "http://localhost:8081/artifactory/cots"
        credentials {
            username = "toto"
            password = "tata"
        }
   }
}

Voila, et enfin pour les plus aguerri, il est possible de modifier manuellement le Manifest du jar produit. Pour exemple, pour construire des Bundles OSGI (nous regarderons cela dans un autre article sur ce qu’est OSGI) il faudra non seulement rajouter le plugin osgi mais aussi triturer un peu le manifest:

apply plugin: 'osgi'
jar {
    manifest {
    attributes 'Bundle-SymbolicName' : project.name
attributes 'Bundle-Name' : project.name + '-' + version
attributes 'Bundle-Version' : version
attributes 'Bundle-Vendor' : 'TC'
        attributes 'Bundle-Activator': 'org.tc.osgi.bundle.utils.module.activator.UtilsActivator'
        attributes 'Bundle-Description': 'Un bundle pour contenir des services utilitaires'
    }
}

ou plus classiquement pour un jar java standard:

jar {
    manifest {
        attributes 'Main-Class': 'monProjet.Main'
    }
}

Voila, ceci termine la partie statique du build. Maintenant découvrons sa partie dynamique, les taches.

Les taches

Les tâches sont du code groovy permettant d’orchestrer le déroulement du build. Alors bien sur, il ne faudra pas écrire tout le code nécessaire à son exécution. En effet si il existe deux types de tâches, les premières et les plus employées sont celles par défauts déclarer dans les plugins [15] [16].

Ensuite bien sur, il est possible d’en créer soit même avec à terme la définition d’un plugin.. A noter que outre la simplicité évidente du langage, dans le monde maven, il ne serait pas possible d’ajouter du comportement au build sans faire obligatoirement un plugin-maven puisque l’on ne peut ajouter directement du comportement dans le pom (a moins d’utiliser un plugin de type script).

Par exemple voici une petite tache propre a notre build:

// définition d'une tache perso nommé hello
task matache {
    doLast {
        println 'Building ' + project.name
    }
}

Voila il suffira d’appeler la commande

gradle matache

Pour afficher dans la console de build, “Building monProjetGradle”

Voila on a fait le tour de Gradle sans forcement etre rentrer dans le détail mais probablement suffisamment pour pouvoir rapidement mettre en oeuvre un début de build. A mon sens gradle, au vue de sa flexibilité, ne peut que finir par remplacer Maven car il en détient tous les avantages et en complète les défauts (attention par contre a ce que flexibilité ne ressemble pas a chaos). Finalement, son seul tort est peut etre encore son manque de popularité et de visibilité auprès des grands groupes du fait de son “jeune” âge même si la communauté est très active et fournit du contenu riche et très varié.  Pour plus d’informations, n'hésitez pas à consulter [17] [18] qui m’ont été d’une grande aide pour découvrir gradle et écrire cet article.

References:

[1] https://jenkins.io/
[2] https://jfrog.com/artifactory/
[3] https://www.sonarqube.org/
[4] http://robotframework.org/
[5] https://maven.apache.org/
[6] http://un-est-tout-et-tout-est-un.blogspot.com/2017/12/maven-introduction.html
[7] https://gradle.org/releases/
[8] Leon Osterweil:  “software processes are software too”, Proceeding ICSE '87 Proceedings of the 9th international conference on Software Engineering, Pages 2-13
[9] https://dzone.com/articles/gradle-vs-maven?edition=306207&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=dd%202017-07-02
[10] https://devops.com/puzzle-gradle-maven/
[11] http://www.tutos-android.com/maitriser-gradle-partie-1
[12] http://www.gradle.org/downloads
[13] https://docs.gradle.org/current/userguide/gradle_wrapper.html
[14] https://docs.gradle.org/2.4/userguide/build_environment.html#sec:gradle_configuration_properties
[15] https://docs.gradle.org/2.4/userguide/standard_plugins.html
[16] https://plugins.gradle.org/
[17] https://www.petrikainulainen.net/getting-started-with-gradle/
[18] http://www.vogella.com/tutorials/Gradle/article.html

vendredi 29 décembre 2017

Construire un paquet Debian


Dans l’article précédent, nous avions vu comment préparer un dépôt debian viable de façon a faire des installation et des déploiements sur une plateforme debian [3].

Nous allons nous intéresser dans ce présent article sur comment alimenter notre dépôt et donc comment nous allons procéder pour construire un paquet debian.

Structure du paquet debian

Un paquet debian [4] n’est initialement qu’un tar.gz dont l'extension est .deb, construit à l’aide de la commande dpkg-deb (il existe d’autre approche comme jdeb [5]) qui sera déployé à la racine /. Cela signifie qu’il sera nécessaire lors de la conception du paquet de reproduire l’arborescence cible si l’on souhaite déployer des fichiers dans des répertoires précis du système.

Ainsi par exemple si l’on souhaite construire un paquet permettant de déployer un binaire dans le système, on créera un répertoire du nom du paquet dans lequel on trouvera le chemin /usr/bin/monBinaire:
/home/moi/monPaquet/usr/bin/monBinaire
Ainsi quand l’on construira le paquet, celui ci déposera monBinaire dans /usr/bin.

Cette démarche est valable pour tout fichier déposé statiquement dans le système. A noter que lors de cette étape, dpkg, le composant procédant à l’installation, fait le listing des fichiers déposés et défini le paquet (et ses éventuelles versions futur) comme responsable du cycle de vie du fichier dans le système. Ceci a pour intérêt de détecter lors de l’installation d'éventuels conflit entre paquet qui seront à traiter par des primitives particulières quand cela est possible [2] (ce ne sera pas le cas dans cet article). Pour que dpkg puisse clairement identifier le paquet, celui ci doit obligatoirement contenir un fichier de control contenant toutes les informations sur l’identité, la version, l’utilisation du paquet. Vous verrons plus loin dans l’article ou le déposer et comment le construire

Par contre si nous avons ici un moyen simple et efficace pour déployer des fichiers, nous n’avons rien pour dynamiquement configurer l’installation en fonction du système courant.
En effet, souvent, lors de l’installation, il est nécessaire de prendre en considération au mieux des besoins et de l’environnement en initialisant des fichiers de conf selon les autres applications présentes, le contexte réseau éventuel ou même des préférences utilisateurs.

La première approche est alors d’apporter statiquement des configurations par défaut et de les modifier après l’installation. Cette approche fonctionne si et seulement si l'application ne nécessite pas une synchronisation particulière avec d’autres applications du système qui nécessite un redémarrage (par exemple la prise en compte d’un plugin par une application amené par deux paquets différents qu’il faut nécessairement redémarrer)

Les fichiers du responsable


Pour répondre à ce problème spécifique, en plus du fichier de control (obligatoire) et il est possible de définir des fichiers du responsable (postinst, preinst, postrm et prerm). Ces fichiers sont à placer dans un répertoire DEBIAN de notre paquet. Notre paquet devient alors:
(/home/moi) ./monPaquet/usr/bin/monBinaire(/home/moi) ./monPaquet/DEBIAN/control (obligatoire)(/home/moi) ./monPaquet/DEBIAN/postinst (optionnel)(/home/moi) ./monPaquet/DEBIAN/preinst (optionnel)(/home/moi) ./monPaquet/DEBIAN/postrm (optionnel)(/home/moi) ./monPaquet/DEBIAN/prerm (optionnel)
Ces fichiers sont alors exécuter automatiquement par l’outil dpkg [6] pendant l’installation ou la suppression.

Par exemple la phase d’installation exécute les phases suivantes:



De la même façon la suppression d’un paquet exécute les fichiers du responsables comme suit:



Enfin leur combinaison permet d’obtenir le comportement que l’on aura lors d’une mise à jour:



A noter que les scripts peuvent être des simples fichiers qui s'exécutent indifféremment du contexte (pas de la phase) mais qu’il est possible de leur attribuer des spécificités selon que l’on fait justement par exemple une installation ou une mise a jour. Pour cela il est conseillé de s’appuyer sur un squelette de script par défaut ou skeleton [7] (que je ne détaillerai pas ici).

Le fichier de control

Voila maintenant que l’on a construit la structure du deb, il reste finaliser le debian en définissant dans le détail le fichier de control.

Le fichier de control est comme dit sommairement au début de cet article, la pièce d’identitée du paquet debian a construire.

Il se constitue des champs suivant [8] (directement extrait de la documentation):
Package: hello
Version: 2.9-2+deb8u1
Architecture: amd64
Maintainer: Santiago Vila <sanvila@debian.org>
Depends: libc6 (>= 2.14)
Conflicts: hello-traditional
Breaks: hello-debhelper (<< 2.9)
Replaces: hello-debhelper (<< 2.9), hello-traditional
Section: devel
Priority: optional
Homepage: http://www.gnu.org/software/hello/
Description: example package based on GNU hello
      The GNU hello program produces a familiar, friendly greeting.  It
      allows non-programmers to use a classic computer science tool which
      would otherwise be unavailable to them.
      .
      Seriously, though: this is an example of how to do a Debian package.
      It is the Debian version of the GNU Project's `hello world' program
      (which is itself an example for the GNU Project).
  • Le champ « Package » contient le nom du paquet. C'est le nom par lequel les outils de gestion de paquets peuvent le manipuler. Il est habituellement similaire mais pas nécessairement le même que la première chaîne composant le nom de l'archive Debian.
  • Le champ de « Version » donne le numéro de version du composant.
  • Le champ « Architecture » indique pour quel processeur ce binaire particulier a été compilé.
  • Le champ « Depends » donne une liste des paquets qui doivent être installés afin d'installer ce paquet avec succès.
  • La ligne « Section » indique la section où le paquet Debian est conservé sur les sites FTP de Debian.
  • Le champ « Maintainer » contient l'adresse électronique de la personne actuellement responsable de la maintenance du paquet.
  • Le champ « Description » fournit un bref résumé des fonctionnalités du paquet.

Bien sur tous ces champs suivant une nomenclature et de standards pour permettre de normaliser l’utilisation des paquets. Entre autre par exemple il est important de respecter certaines règles d'écriture du numéro de version afin de permettre des mises à jours conforme a l’attendu (voir les schémas du chapitre des fichiers du responsable sur les phases du cycle de vie d’installation, désinstallation et de mise à jour des paquets).

Chaque champs peut avoir son importance même s’ils ne sont pas obligatoire. Il convient donc de bien les étudier afin de prévenir ou de gérer les conflits entre paquets.

Construction du paquet debian

Pour finir cet article, il fait maintenant finaliser le packaging en construisant le .deb. Pour cela il suffit d’appeler la commande dpkg-deb:
dpkg-deb --build $REPERTOIRE_DEBIAN $REPO_DEBIAN/
avec REPERTOIRE_DEBIAN contenant le chemin du debian a packager et REPO_DEBIAN le répertoire de destination ou mettre le deb final (on privilégiera directement le dépôt debian)

Voila le paquet est construit et déposé dans un dépôt, il ne reste qu'à l’installer après une mise a jour du dépôt et un apt-get coté client.

Références

[1] http://blog.valouille.fr/2013/10/creer-un-paquet-debian-vide/
[2] https://www.debian.org/doc/manuals/debian-handbook/sect.package-meta-information.fr.html
[3] https://www.debian.org/doc/manuals/debian-faq/index.fr.html#contents
[4] https://www.debian.org/doc/manuals/debian-faq/ch-pkg_basics.fr.html
[5] http://tcurdt.github.io/jdeb/
[6] https://www.debian.org/doc/debian-policy/#maintainer-script-flowcharts
[7] https://lists.debian.org/debian-mentors/1998/04/msg00004.html
[8] https://www.debian.org/doc/manuals/debian-faq/ch-pkg_basics.fr.html#s-controlfile

dimanche 10 décembre 2017

Maven : Introduction

Qu'est ce que Maven ? Je vais vous en faire un résumé rapide mais pour ceux qui veulent une bible et de details, consulter ce livre.

Généralités

Maven est un gestionnaire de build ou constructeur de projet. Il est open source et soutenu par la fondation Apache. Maven a pour but de simplifier, rationaliser et automatiser la construction d'un projet sur l’ensemble de ses phases et activités de façon a garantir la qualité de no build.

Initialement orienté pour les projet Java, Maven est maintenant capable de construire des projets de tout type de nature, c++, Python, etc....

Ainsi, et c'est sa fonction première, Maven va permettre de compiler le code de notre application, ceci en fournissant des moyens de gestion simple pour les dépendances du projet. Cependant, s’arrêter a cela, serait hyper réducteur car Maven a plus la vocation de couvrir tout le périmètre de build.

En fait avec Maven, il est possible de:
  • préparer la compilation (en générant du code par exemple) 
  • effectuer la compilation
  • exécuter des tests unitaire, 
  • faire de l'analyse de code,
  • faire du packaging (construction de jar, war, tar.gz ou même des deb) 
  • exécuter des procédures d'installation sur des plateformes spécifiques de tests, exécuter des tests fonctionnels (avec roboframework par exemple)
  • préparer un livrable
  • générer differentes doc (technique ou utilisateur)
  • etc... car il est possible d'adjoindre des plugins supplémentaires pour compléter son fonctionnement.
Tout ceci concerne les activités directement lié au build de notre projet mais avec maven, il sera aussi possible d'aller plus loin en permettant la constitution de processus plus générique autour de la gestion du projet comme :
  • la construction automatique de template de projet
  • la gestion de multi-projet, de leurs dépendances et de leur orchestration dans un même process
  • la gestion d'un cache local de vos dépendances
  • la gestion des builds passés sous la forme de SNAPSHOT et de RELEASE
  • du deployement de documentation web en ligne

Configuration

Maven s'articule autour d'un runner que l'on telecharge ici. Je ne vais pas rentrer dans le details de l'installation de Mave, mais celle-ci se résume globalement a spécifier convenablement JAVA_HOME qui exécutera Maven. (Attention, ce dernier n'est pas forcement celui employé pour la compilation). On pourra specifier ce JAVA_HOME directement lors de l'appele de Maven en faisant un alias par exemple sous unix/cygwin.

Il est ensuite nécessaire de configurer ce que l'on appelle les Settings. Ce setting est constitué de deux fichiers, le fichier système présent dans le répertoire conf de Maven et un fichier utilisateur présent dans le répertoire .m2 de l'utilisateur courant.

Comme on peut se le douter, le setting système permet de configurer Maven pour toutes ses utilisations alors que celui de l'utilisateur sera spécifique au compte utilisateur lançant le processus Maven. (la commande mvn, que nous verrons plus loin)

L'ordre de chargement de ces settings se réalisera toujours dans le sens Systeme puis Utilisateur, le dernier chargé faisant fois de sa configuration en cas de conflits. A noter qu'il est possible de spécifier un setting lors du build afin de personnaliser celui-ci avec l'option -s

Le settings est le fichier de configuration dans lequel seront définis les variables d’environnement locales (comme les differentes versions Java installées sur la machine),  les repositories de dépendances (artifactory ou nexus, SNAPSHOT ou RELEASE), les serveur web, des serveurs de base de données, des server de vérification de code (Sonar par exemple) , des secrets éventuels pour s'y connecter, etc.... en fait toute configuration et ressource spécifique a l'environnement de build, utilisable non directement par les projets.

Par convention (unix) on build dans un compte spécifique (maven par exemple) et on initialise ces données dans le settings utilisateur, le settings système étant réservé aux configurations partagées.

Par exemple un settings spécifiant les variables pour la compilation en java 6, 7 ou 8, les variables pourront alors être utiliser dans les pom.xml des projets (nous verrons par la suite ce que sont les pom):


<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository>/home/m2Repo</localRepository>
  <profiles>
    <profile>
      <id>compiler</id>
        <properties>
          <JAVA_1_6_HOME>jdk1.6.0_33_win_x64</JAVA_1_6_HOME>
          <JAVA_1_7_HOME>jdk1.7.0_80_win_x64</JAVA_1_7_HOME>
          <JAVA_1_8_HOME>jdk1.8.0_66_win_x64</JAVA_1_8_HOME>
        </properties>
    </profile>
  </profiles>  
  <activeProfiles>
    <activeProfile>compiler</activeProfile>
  </activeProfiles>
</settings>


Ici nous avons spécifique un répertoire différents du répertoire par défaut du repository maven locale qui permet a Maven de stoker toutes les dépendances dont il aura besoin lors des builds (nous verrons également ce point par la suite)

Dans ce cas ci, nous n'utilisons pas de repository distant permettant a Maven de resoudre les dépendances manquantes de son repository locale. Nous sommes donc dans une configuration hors-ligne. Donc si lors du build Maven, il manque une dépendance dans le repository locale, Maven finira en echec.

Pour permettre a Maven de résoudre des dépendances manquantes et de les mettre dans son repository locale, on ajoutera le serveur central Maven :


<profile>
  <id>securecentral</id>
  <!--Override the repository (and pluginRepository) "central" from the
     Maven Super POM -->
  <repositories>
    <repository>
      <id>central</id>
      <url>https://repo1.maven.org/maven2</url>
      <releases>
        <enabled>true</enabled>
      </releases>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>central</id>
      <url>https://repo1.maven.org/maven2</url>
      <releases>
        <enabled>true</enabled>
      </releases>
    </pluginRepository>
  </pluginRepositories>
</profile>


Processus Maven

Maintenant que Maven est configuré (sommairement) intéressons nous aux processus de build en lui-même.

Nous l'avons vu précédemment, Maven répond a un grand nombre de besoin, son processus va s'appuyer sur de nombreux plugin décrits sur cette page qui vont interagir afin de procéder a l’exécution de tout ou partie du cycle de vie.

Le cycle de vie Maven est ce que l'on pourra appelé complet. Il se compose d'un cycle par défaut contenant les processus de compilation, test, packaging et déploiement précédé optionnellement d'un cycle de nettoyage des projets et suivi optionnellement d'un cycle de génération de site documentant les projets.




Ainsi via l'appel de la commande mvn (répertoire bin de Maven) et en se plaçant dans notre projet, il sera alors possible de faire appel a un processus déroulant les phases décrites par le schéma ci dessus. A noter que les deux cycles de vie optionnels clean et site ne seront déroulés que si appeler explicitement via la commande Maven alors que le cycle de vie Default sera de toutes façon exécuter jusqu’à l’étape (ou phase dans le jargon Maven) explicitement appelée

Ainsi, la commande


$ mvn compile 


exécute les phases validate a compile du cycle default, tandis que


$ mvn clean test


exécute toutes les phases du cycle clean et les phases validate a test du cycle default.

Ainsi, la commande


$ mvn clean deploy site-deploy


exécute toutes les phases, mais ce ne sera pas forcement une bonne idée....

Mais on a parler de plugins ? qu'est ce que c'est?

Et bien tout le processus présenté ci dessus est la modélisation des activités classiques auxquelles les builds vont répondre. Cependant, on peut facilement imaginer que d'un projet a l'autre les builds ne vont pas s'appuyer sur les même concepts selon les phases. Ainsi tout bêtement la compilation d'un projet Java ne sera pas la même chose qu'une compilation d'un projet C++. De même, certains projets vont s'appuyer sur des configurations differentes de packaging, de compilations, de test. Et même sur des projets relativement semblables, des choix technologiques peuvent amener des outils différents.

Face a cette richesse de besoin et de possibilité, Maven a adopté le principe du : voici un plugin implémentant le comportement par défaut pour le cas le plus général et si le plugin ne correspond pas, est bien il existe des moyens de créer les siens (nous reviendrons sur ce point dans un autre article).

Ainsi pour que pour chaque phase soit proposée  les outils et les moyens de configuration adaptés, Maven va s'appuyer sur ces fameux plugins pour donner une implémentation et une opérationnabilité liés aux spécificités de chaque projet. Le plugin selon le cas sera configuré difficilement ou simplement substitué par un autre, voir même parfois ajouter et exécuté parallèlement.

Pour une liste exhaustive de l'ensemble des plugins "standard" utilisable je vous invite a consulté cette page. Cela donnera une idée des possibilités par défaut, et les plugins utilisables selon les phases des cycles de vie Maven.

Il manque maintenant un dernier élément a tout cela. Comment donner a Maven la carte des plugins a utiliser pour son cycle de vie et leur configuration? Le POM!

Project Object Model

Le POM ou fichier pom.xml est ce qui permet à Maven de connaitre le comment builder le projet courant.

Il va en exister de différents types, si l'on veut suivre de bonnes pratiques Maven, on va s'appuyer sur le pattern parent/module/projet (décrit aussi dans cet article):

  • Des pom projet supportant la production d'un composant logiciel spécifique de type jar, deb, ear, etc... Il s'agit de ce que l'on peut considérer comme étant la composante Maven qui nous vient en premier a l'esprit. Ce pom va donc définir les variables locales au projet, les processus spécifiques nécessaire à la compilation du composant, les dépendances et ou encore de quel pom parent le projet va hériter.
  • Des pom parent qui ont pour but de factoriser des comportements standard de build dont vont heriter des pom projets . Dans ces pom on trouvera alors l'ensemble des spécificités propre à des familles de projets partageant des processus ou des propriétés de compilation commune.
  • Des pom module permettant d'orchestrer la construction d'un ensemble de projets techniquement interdépendant en les agrégeant.

Attention un POM se définit en XML, de ce fait, ce dernier est conforme a ce que l'on appelle un schéma (XSD) pour en structurer les informations a la manière d'une grammaire. Ainsi, tous les types de pom se base sur le même XSD amenant souvent a des utilisations fusionnées des précédents concepts présentés. Alors bien sur, Maven aurait pu simplement définir des XSD différent pour répondre aux différents types. Le choix a été fait de ne pas brider leur utilisation et de permettre malgré tout quelques digressions. Quelques digressions ne signifie pas que cela doit être une généralité car suivre le pattern parent/module/projet permet largement de couvrir 90% des cas d'utilisation. Et comme dans tous les cas d'utilisation des patterns, si vous faites autrement, c'est que vous avez une vraie bonne raison technique (et pas une raison du type : on livre demain...)

Pour avoir une illustration plus précise de cette décomposition, je vous invite a consulter cette question sur stackoverflow. Elle traite bien de la question et nous ramène comme souvent au problème de la division des préoccupations et des besoins comme en POO et ce selon la complexité du problème. En effet, dans un cas simple, on préférera avoir un POM projet classique.... ensuite lorsque les projets commenceront a se multiplier un peu on décidera de construire un POM parent pour factoriser les propriétés et donc on y déposera également une définition des modules à construire (la liste des POM projets). Finalement, lorsque l'on aura des projets dont les POM parent n'auront plus de points communs et qu'il faudra les diviser, alors on placera des POM modules afin d'orchestrer le ou les builds possibles pour les lots de projets a gérer...

Ainsi rien que par cet exemple, certes, on se rend compte que progressivement on en arrivera de toute façon a tendre vers l'application du pattern parent/module/projet... cependant, afin d’éviter des modifications douloureuses dans les dépendances des builds, il est vraiment préférable de définir de suite un structure de POM parent, factorisant des processus standard de build, et des modules définissant séparément les lots de builds.

Je ne vous ferais pas l'affront de vous donner un exemple, avec les differentes références que je vous ai donné ci dessus, vous avez largement les moyens de soit débuter, soit enrichir l'architecture des builds de vos projets.

Voila, un grand tour d'horizon de Maven, ce que l'on en fait et ce qui est important de faire des le départ au niveau structure des projets. Ceci n’était cependant qu'une introduction donc dans les prochains articles sur Maven sous essayeront de voir les points suivant:

  • un POM et une architecture parent/module/projet en vrai
  • comment faire son propre plugin
  • c'est quoi les archétypes