Jun 19

assembly, jar y snapshot incompatibles en maven

Me he vuelto a tropezar con un pequeño problema que esta vez, tampoco he conseguido resolver.

Con maven damos a los proyectos un número de versión, de forma que luego el jar generado lleva dicho número de versión. Así, por ejemplo, podemos tener un proyecto MiProyecto al que decimos a maven que es versión 1.0 y  maven generará un jar MiProyecto-1.0.jar. Mientras estamos desarrollando este proyecto, podemos decir a maven que es una versión "SNAPSHOT", de esta forma sabe que ese jar se va a generar muchas veces y que será posiblemente distinto cada vez. Al hacerlo así, maven genera el jar añadiendo la fecha y hora del compilado, tal que así: MiPoryecto-1.0-20080619-131234.jar.

Esto tiene una ventaja adicional. Si hacemos ahora otro proyecto que depende de la versión SNAPSHOT de MiProyecto, cada vez que compilemos este nuevo proyecto, maven buscará si hay un jar más moderno de MiProyecto y se lo bajará. De esta forma, sin nosotros molestarnos en actualizar el jar, nuestro nuevo proyecto siempre tendrá disponible la última versión snapshot de MiProyecto

A maven también podemos decirle que nos genere el fichero de manifiesto dentro de nuestro jar principal y podemos decirle que automáticamente ponga el Class-Path con todos los jar que necesite. Maven lo hace correctamente y pone en esta dependencia un MiProyecto-1.0-SNAPSHOT, así exactamente, con todas las letras y sin reemplazar SNAPSHOT por ninguna fecha.

Finalmente, hay un plugin de maven llamado assembly que permite meter automáticamente en un zip nuestro jar, nuestros ficheros de configuración y todos los jar de los que dependemos. De esta forma, eso sería un zip de distribución que, entregándoselo a otra persona, podría simplemente desempaquetarlo donde quisiera y ejecutar nuestro programa teniendo todo lo necesario.

Pues bien, este plugin assembly no está de acuerdo con lo del manifiesto del jar. El plugin assembly, al generar el zip, mete dentro la versión snapshot de mi proyecto con MiProyeto-1.0-20080619-131234.jar, con todas las fechas y horas habidas y por haber. Nuestro jar principal busca MiProyecto-1.0-SNAPSHOT.jar y claro, no lo encuentra.

Buscando y rebuscando, al final encuentro que es un bug de uno de los dos, bien de maven al generar el manifiesto, bien de assembly al guardar el jar de MiProyecto. Así que de momento me he auto-prohibido generar zips que dependan de snapshots.

Jun 12

Problemilla con las properties de Maven

En el post anterior comenté que se podían usar las properties de maven para fijar el número de versión que queremos usar en los subproyectos del proyecto principal. Pues bien, me he encontrado un pequeño problema que, afortunadamente, tiene solución.

Supongamos que tenemos un proyecto con varios subproyectos. En proyecto padre suele tener packaging=pom, mientras que los subproyectos de verdad tienen packaging=jar. Supongamos ahora que algunas dependencias son comunes a todos los subproyectos y decidimos poner dicha dependencia en el proyecto padre, tal que así

<packaging>pom</packaging>

<properties>
   <unNumeroVersion>1.0</unNumeroVersion>
</properties>

<dependency>
   <groupId>un.group.id</groupId>
   <artifactId>unArtifactId</artifactId>
   <version>${unNumeroVersion}</version>
</dependency>

Pues bien, todo funcionará correctamente.

Supongamos ahora un proyecto totalmente independiente de todo esto, pero que usa uno de los jar generados en ese proyecto anterior. Este nuevo proyecto irá al repositorio que tengamos de maven y cuando intente resolver las dependencias…. viene el problema. El .pom del proyecto padre sube al repositorio SIN resolver las propiedades, es decir, tendrá, literalmente, una dependencia de un.group.id:unArtifactId-${unNumeroVersion}.jar, sin reemplazar la propiedad por su valor. Nuestro nuevo proyecto tampoco intentará resolver esa propiedad, aunque la tenga definida, y buscará un jar en el repositorio que NO existe. El que existe es con número de versión 1.0 en nuestro ejemplo.

La solución, aunque en algún momento dado puede ser incomoda, es sencilla. Basta con no poner dependencias que usen propiedades en un proyecto cuyo packaging sea pom. Sólo podemos usar este truco de propiedades con dependencias en los subproyectos con packaging jar.

Jun 10

Properties en Maven

En los ficheros pom.xml de maven podemos poner properties de esta manera

<project>
   …
   <properties>
      <nombrePropiedad1>valor1</nombrePropiedad1>
      <nombrePropiedad2>valor2</nombrePropiedad2>
      …
   </properties>
   …

Por supuesto, si tenemos un proyecto grande compuesto de varios subproyectos maven, los proyectos hijos heredan las propiedades del padre. Podemos obtener el valor de estas propiedades poniendo ${nombrePropiedad1} donde sea necesario.

¿Para qué sirve todo esto?. Pues gracias a Random thoughts by Ceki Gülcü he encontrado una posibilidad estupenda, que además me soluciona un problema que me traía de cabeza desde hace tiempo.

El tema es que tengo proyectos maven grandes, con varios subproyectos dentro y algunos de ellos incluso con otros subproyectos. En ellos, hay dependencias repetidas, digamos, por ejemplo, que los subproyectos A, B y C tiran de unaLibreria-1.2.jar. Pues bien, esa dependencia con ese número de versión está en los pom.xml de A, B y C

<dependency>
   <groupId>un.group.id</groupId>
   <artifactId>unaLibreria</artifactId>
   <version>1.2</version>
</dependency>

Muchas veces, esa librería es una librería nuestra interna y su número de versión se actualiza con cierta frecuencia. Cuando cambiamos este número de versión, hasta ahora no nos quedaba más remedio que ir rebuscando esta dependencia por todos los pom.xml de los subproyectos para cambiarla.

Pues bien, con esto de las propiedades, hay una solución mucho más sencilla. En el pom.xml de proyecto padre/raíz, ponemos una propiedad tal que así

<properties>
   <unaLibreriaVersion>1.2</unaLibreriaVersion>
   …
</properties>

y en la dependencia de los subproyectos, ponemos

<dependency>
   <groupId>un.group.id</groupId>
   <artifactId>unaLibreria</artifactId>
   <version>${unaLibreriaVersion}</version>
</dependency>

De esta forma, cuando cambia la versión de la librería, sólo tenemos que cambiar el valor de la propiedad en el pom.xml del proyecto raíz/padre.

Una pequeña maravilla de la técnica.

May 29

¿Es tan bueno maven?

Habitualmente usamos maven en el trabajo y estoy encantado. Hay muchas cosas que resuelve él solito y cuando lo tienes ya todo configurado es realmente una maravilla.

Sin embargo, hay un pequeño problema, que aunque conozco de hace tiempo, todavía no lo había sufrido o, aunque lo hubiera sufrido, no me había afectado. Sin embargo, hoy ha "cantado".

El problema son las dependencias. Imagina por ejemplo que tu proyecto decides depender de log4j-1.2.15.jar y de otra-libreria-1.3.jar. Aunque tú no lo ves, esta librería puede a su vez decir que depende de log4j-1.2.13.jar. Cuando compilas, haces tu despliegue, etc, etc … tienes en el classpath y en todos lados dos versiones de distintas de log4j. Si no son muy compatibles, puedes tener problemas al compilar o en ejecución.

Existe forma de decir que no quieres que se traiga las dependencias anidadas, pero eso ya requiere que las vayas identificando para decir "esta sí", "esta no".

El problema que se nos ha presentado hoy ha sido el siguiente: En un módulo que estamos desarrollando nuevo hemos metido un plugin de maven pmd que revisa las métricas del código (complejidad ciclomática, métodos largos, etc, etc), de forma que el compilado falla si las métricas no se cumplen. Pues bien, la versión en CVS a unos les compilaba y a otros les daba fallo de métricas.

Así que nos pusimos en dos ordenadores, uno en el que sí compilaba y oto en el que daba fallo. Revisamos que el proyecto está correctamente sacado de CVS sin cambios en ninguno de los dos lados, que toda la jerarquía de pom.xml hasta el padre es igual y nada, seguimos teniendo el problema.

Cogemos en ambos ordenadores y en el repositorio local de jars de maven borramos el del plugin de pmd. Compilamos … y sorpresa. Cada uno se baja una versión distinta de pmd. Una casca y la otra no. En ningún sitio ponemos la versión de pmd que queremos, así que maven se baja la que le apetece según algún criterio misterioso.

Así que ahí estamos, pensando si obligar a maven a bajar una determinada versión de pmd o si pasar del plugin este y dejar a la complejidad ciclomática evolucionar a su libre albedrío.

May 12

log4j 1.2.15

 

Normalmente en mis proyectos maven añado el log4j, siempre la versión 1.2.12. No por ningún motivo en particular. Símplemente era la versión disponible la primera vez que lo añadí a un proyecto maven y luego ha prevalecido el copy-paste de esa dependencia de un proyecto a otro.

El otro día cree un proyecto en casa y no tenía ningún sitio de donde copiar esta dependencia, así que me fuí a internet y busqué cómo ponerla. Puse la 1.2.15, que es la última que encontré disponible para maven.

Me pongo a compilar con maven, a generar el proyecto para eclipse y … ¡¡ Sorpresa !!. Da fallo. Busco el motivo y resulta que entre los jar que no puede bajarse, está el javamail, el activation.jar y algún otro más de los de SUN. Efectivamente, SUN no permite distribuir sus jar, por lo que oficialmente no se puede hacer. En los repositorios maven que hay por el mundo, no están estos jar de SUN, así que maven no se los puede bajar. Hay que bajárselos a mano y ponerlos en tu repositorio local de maven.

Y digo yo… ¿necesito javamail para log4j? Y aunque sea así, ¿voy a usarlo?. Pues más bien no. No tengo ningún interes en recibir por correo el log de mi aplicación y tampoco tengo ningún enemigo al que odie lo bastante como para mandárselo. Mejor dicho, sí lo tengo, pero lo que no tengo son ganas de quedarme de patitas en la calle.

Creo que esta vez se han pasado un poco. Entiendo que un momento dado, ante un error crítico, alguna aplicación crítica quiera enviar un correo a alguien. Pero, ¿es con un log.error(…) la mejor forma de hacerlo?. Desde luego puede ser cómoda en vez de usar javamail directamente, pero ¿tienen que cargar todas las aplicaciones que quieran un uso normal de log4j cargar con javamail?. A mi, desde luego, no me gusta.

Y aquí es donde llegamos a un punto donde siempre he tenido mis dudas. Por un lado la lógica y la elegancia me dicen que debería hacerse algo como log4j-core.jar con lo básico de log4j, es decir, sacar los log por pantalla o por fichero y poco más. Luego, deberían hacerse otros jar de amplicación, como log4j-mail.jar, log4j-bd.jar, etc, de forma que cada uno cargue sólo con lo que necesita. Sin embargo, la pereza me pediría que hubiera un único log4j-con-todo.jar y despreocuparme de andar buscando los que debo. Si me sobra el 90% del jar, da igual, no pasa nada.

Y esas dudas son las que siempre me corroen en el trabajo. Ante un proyecto gigante… ¿hacemos muchos jar pequeñitos por temas, de forma que en otros proyectos podamos llevarnos aquellos mini-jar que necesitamos? o por el contrario ¿hacemos un mega jar con todo y si un proyecto no necesita parte de él no pasa nada?

En la primera opción, al empezar un proyecto, debemos empezar a elegir jars que necesitamos y a coger también los jar de los que dependen esos jar. Afortunadamente maven nos ayuda en el proceso. Sin embargo, la segunda opción es menos elegante pero infinitamente más cómoda. Cuando empiezo el proyecto, me copio el mega-jar-que-lo-tiene-todo y a trabajar, sin más complicaciones.

¿Tú qué eliges?. ¿Elegancia o pereza?