sábado, 2 de julio de 2011

Generando números de versión automáticamente con Maven

En esta entrada de hoy mostraré como implementar un sistema para que los usuarios de nuestras librerías puedan averiguar fácilmente cual es la versión que están utilizando y que se mantenga automáticamente al generar nuestros .jar con Maven.

Introducción
Uno de los “problemas/features” cuando uno usa Maven/Ivy/Grape o sistemas similares para gestionar sus dependencias es que independientemente de la versión, las librerías acaban teniendo un nombre común. Es decir, que la librería org.hibernate:hibernate:3.2.6.ga acaba llamandose hibernate.jar, igual que si fuera la 3.3.0.SP1 o cualquier otra versión. Esta característica ayuda a la hora de reemplazar una versión con otra, pero dificulta averiguar de un vistazo que versiones de librería estamos utilizando.

En caso de que nosotros hagamos una librería y se use a través de uno de estos sistemas, nuestros usuarios tendrán el mismo problema para averiguar en el sistema final qué versión está desplegada. Como somos unos “grandes” programadores, queremos facilitar a nuestros usuarios el averiguar está información, pero como somos unos programadores vagos eficientes no queremos tener que hacerlo a mano cada vez que generamos un .jar, y menos si usamos algo como Maven para gestionar nuestro proyecto. Así pues, ¿como lo podemos hacer?

Implementación
Paso 1:
Lo primero que tenemos que hacer es almacenar el número de versión de forma automática en algún sitio. Un buen sitio para hacerlo es en el fichero Manifest de nuestro .jar, y para hacerlo de forma automática podemos utilizar el plugin de Maven org.codehaus.mojo.buildnumber-maven-plugin. La documentación que tiene no es demasiado extensa, pero investigando un poco podemos averiguar cómo usarlo. En mi caso, dado que utilizo Mercurial como sistema de versiones y el numero de versión que utiliza no es muy significativo (no es un número correlativo) así que lo he sustituido por una marca de tiempo que me da una indicación más fiable. Si usas algo como Subversion, entonces quizá la configuración estándar del plugin ya te sirva Dado que el plugin no se encuentra en el repositorio central de Maven, tenemos que añadir un repositorio de plugins para encontrarlo, de la siguiente forma:
    
        codehaus-snapshot
        Cohehaus snapshot repository
        https://nexus.codehaus.org/content/groups/snapshots-group
        
          true
        
    

Una vez hecho esto, configuramos el plugin para que nos defina unas variables con la información que queremos, así:
    
      org.codehaus.mojo
      buildnumber-maven-plugin
      1.0-beta-5-SNAPSHOT
      
        
          validate        
          
            create
          
        
      
      
        true
        true
        {0,date,dd/MM/yyyy HH:mm:ss}
        
          timestamp
        
      
    


Con esto le decimos que nos añada el timestamp a la variable buildNumber, que es donde el plugin pone su información.
Por último, configuramos el plugin org.apache.maven.plugins.maven-jar-plugin para que use esa información para añadir una nueva entrada en el Manifest de nuestro jar. De la siguiente forma:


 org.apache.maven.plugins
 maven-jar-plugin
 2.3.1      
 
  
   false
   
    ${version}-${buildNumber}
    my.package.MainApp
   
  
 



Una vez hecho esto, si ejecutamos mvn package, por ejemplo, para generar nuestro fichero ,jar y miramos el fichero MANIFEST.MF dentro del directorio META-INF, deberíamos ver algo tal que así:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: usuario
Build-Jdk: 1.6.0_21
Main-Class:  my.package.MainApp
MyLibrary-version: 0.1-SNAPSHOT-25/06/2011 13:33:01

Paso 2:
Bien, ya tenemos almacenada la versión en el .jar. ¿Ahora que hacemos para facilitar que el usuario pueda ver esa información sin tener que abrir el .jar y mirar el manifest? Muy sencillo: la clase my.package.MainApp que es la principal de nuestra aplicación tiene que mostrar esa versión. En mi caso, la librería es una utilidad que no se lanza en linea de comandos, se usa añadiéndola al classpath, así que mi clase MainApp simplemente muestra la versión de la librería.

¿Y como hacemos para leer el manifest del mismo fichero del cual nos estamos ejecutando? Pues averiguando donde se encuentra el .jar en tiempo de ejecución y accediendo a él para leer el fichero manifest. Podemos hacerlo así (código simplificado sin gestión de errores):

String jarFileURL = 
  MainApp.class.getProtectionDomain().getCodeSource().getLocation().toString();
int pos = jarFileURL.indexOf("!");
if(pos!=-1)
{
  jarFileURL = jarFileURL.substring(0,pos);
}
if(!jarFileURL.startsWith("jar:"))
{
  jarFileURL = "jar:" + jarFileURL;
}
URL manifestUrl = new URL(jarFileURL + "!/META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(manifestUrl.openStream());      
return manifest.getMainAttributes().getValue(“MyLibrary-version”);

Y así podemos hacer que al ejecutar java -jar milibreria.jar nos devuelva la versión de la librería, o si lo ponemos en un método público, podemos usar este número para mostrarlo en las aplicaciones que usen la librería y así puedan saber que versión están utilizando, comprobar si existen nuevas versiones etc.

Espero que os sirva, al menos a mi me ha servido, y que vuestros usuarios estén más contentos :).

Happy coding! EJ