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?

Dec 08

Entretenido con maven site

Como el diablo, cuando no tiene nada que hacer, espanta moscas con el rabo y, aprovechando que soy un poco "nerd" y este puente, me estoy dedicando a generar un sitio web con maven, para mi pequeño proyecto de librería de gráficos en java.

Por supuesto, esta librería no es nada del otro mundo, simplemente un entretenimiento casero para las aburridas largas tardes de invierno. De todas formas, me he entretenido en ponerla disponible para que se pueda usar en proyectos maven, y en generar un sitio maven. Es sólo el comienzo y como hobby, pero ahí está.

A ver en qué queda todo esto…

PD: Estoy totalmente de acuerdo con las conclusiones sobre maven site de ese enlace.

Oct 19

Maven properties o parameter expressions

El otro día, por jugar un poco, me puse a hacer un plugin de maven, para ver cómo va el tema.

Pues es mas fácil de lo que parece. Por supuesto, con el mismo maven creamos un proyecto para desarrollar nuestro plugin

mvn archetype:create
  -DgroupId=com.chuidiang.ejemplos.plugin_maven
  -DartifactId=plugin_maven
  -DarchetypeArtifactId=maven-archetype-mojo

o sea, igual que cualquier otro proyecto, pero indicando con -DarchetypeArifactId que queremos hacer un plugin con esa palabreja tan  rara de mojo. Al ejecutar este comando, nos sale un proyecto maven con una clase con el plugin en plan ejemplo ya creada. Sólo tenemos que tocar a nuestro gusto.

Con un simple mvn install, se instala nuestro plugin y ya lo tenemos disponible.

Dentro del código java del plugin, hay atributos declarados así:

/**
* El parametro saludo.
*
* @parameter expression="Hola"
*/
private String saludo;

cuando se ejecute nuestro plugin, maven se encargará de rellenar nuestro atributo "saludo" con la "expression" que hayamos puesto en el @parameter. En nuestro caso, cuando se ejecute nuestra clase plugin, saludo tendrá el valor "Hola":

Es posible en ese @parameter expression poner cosas como esta

@parameter expression="${project.version}"

con lo que nuestro atributo se rellenaría con el número de versión de nuestro proyecto. Estupendo y maravilloso pero, como no, nos surgen problemas. Una vez que ves el ejemplo te dedicas a buscar una lista de posibles expresiones que puedes poner. Sí, sí, la versión del proyecto, pero ¿qué más cosas tengo disponibles?. Pues bien, ahí está el problema. No existe una documentación con esa lista.

Investigando, investigando, he descubierto tres cosas a partir de las cuales sacar estas posibles expresiones.

  • La primera es que si en mi plugin pongo expression="${project}" y el tipo de atributo Object y el código se dedica a investigar qué es lo que obtengo, resulta que tengo una clase MavenProject. Tirando del hilo, descubro que los get de esta clase son el segundo nivel de propiedad. Es decir, existe project.version porque hay un MavenProject.getVersion(). Y existe ${project.build.directory} porque se puede hacer MavenProject.getBuild().getDirectory(). De esta forma, la API de MavenProject nos puede ayudar a sacar posibles expresiones.
  • En este enlace hay una lista de propiedades que la gente ha recopilado.
  • En el directorio lib de donde tenemos instalado maven, tenemos un maven-core-xxx.jar. Ahí dentro, en el directorio META-INF/maven/plugin-expressions tenemos tres ficheros xml en el que se indican los posibles nombres de propiedades y el tipo de dato que pueden contener.

Yo por mi parte y por supuesto, he hecho mi propio resumen de los parámetros maven en la Chuwiki. Si estais interesados en el tema, estais invitados a colaborar ampliando o completando esa lista.

Oct 18

Más problemas con Maven

Entre ayer y hoy me he dedicado a arreglar un poco los pom.xml de algunos proyectos. En concreto, de un proyecto que tiene como 10 subproyectos debajo. La idea, aprovechando la herencia de proyectos en maven, es quitar lo más posible las cosas que están repetidas en los pom.xml de los subproyectos y ponerlo en el proyecto principal.

También intenté arreglar el tema para que generara un sitio web adecuado, con un montón de informes: cobertura, jxr, métricas pmd, javadoc, etc. Como el CruiseControl está en el mismo sitio que el servidor web, la idea es que el mismo CruiseControl generara el sitio web con mvn site. Puesto que no necesito hacer un mvn site:deploy en condiciones para "subir" el sitio web al servidor web, me planteé la opción de hacer un mvn site:stage, que simplemente genera el sitio en el directorio local que se le indique. Dicho directorio, por supuesto, sería el público del servidor Apache.

mvn site:stage -DstagingDirectory=c:/sitio_web/documentacion_proyecto

Pues nuevamente problemas. Parece que con site:stage algunos plugins no funcionan bien en proyectos con subproyectos. En concreto el jxr, cobertura, etc. Dicen ahí que está corregido para jxr, pero a mi me sigue fallando. Supongo que tendré que jugar con las versiones para asegurarme que cojo la última….

Oct 16

Repositorio interno con maven

Hace tiempo que usamos maven. Tenemos todos los proyectos en CVS, con estructura de directorios estilo maven. También tenemos CruiseControl para el compilado nocturno de todos ellos.

En su día, sobre una máquina Sun, configuré un repositorio maven con nuestros jar, de forma que al compilar un proyecto se bajara de ese repositorio los jar necesarios. También, con mvn deploy, podíamos subir a ese repositorio los jar de nuestros proyectos. Haciendo caso a la documentación de maven, el repositorio está accesible por medio de ftp.

Sin embargo, teníamos un problema que comenté hace tiempo. Desde windows y a través de ftp, se nos mezclaban los nombres de los ficheros con las fechas, por lo que muchas veces maven era incapaz de encontrar los jar en el repositorio ftp.

En su día intenté hacer el repositorio con http. Aprovechando que tengo montado un servidor Apache en la máquina donde corre CruiseControl, se me ocurrió que podía poner el repositorio local de jars de esa máquina en la parte pública del servidor Apache. De esta forma, la gente tendría que configurar maven de forma que un repositorio más fuera con http el del servidor Apache con CruiseControl.

Pues no iba, no había manera. Cuando alguien compilaba e intentaba acceder a este repositorio web, maven se quedaba bloqueado uno o dos minutos intentando bajar los jar y finalmente daba un error. Lo dejé por imposible dando por supuesto que era uno de esos bugs que tiene maven que a veces salen.

Sin embargo, hoy me puse a ello y volví a intentarlo. Volví a hacer todo el montaje… y el mismo resultado. Trabajamos contra un proxy, así que me dio por pensar que quizás maven estaba buscando mi servidor en internet y por eso no lo encontraba. Sin embargo, por más que miraba en la configuración de maven, aparentemente estaba bien. Indicaba en <nonProxyHosts> que no usara proxy con mi servidor.

Pues bien, el problema era haberme fiado de la documentación. En la referencia de proxies del settings.xml de Maven pone que en <nonProxyHosts> se ponen los servidores con los que no se quiere usar proxy y que en el ejemplo se usa como separador una barra vertical |, pero se admite también comas. Pues efectivamente, como tan bien indica la documentación, las comas NO VALEN. Si pones comas, no se hace caso y esos servidores se buscan a través del proxy de internet. Puse las | y todo solucionado.

Al final, una pequeña maravilla. El servidor de CruiseControl es el que compila, genera todos los jar y además accede a internet para traerse otros jar como log4j, junit y demás. Los programadores configuramos maven para acceder a este servidor nuestro y no necesitamos tener acceso a internet. Todos los jar que necesitamos, tanto propios como ajenos, estan ahí y se actualizan, con CruiseControl, todas las noches. Esto nos viene estupendo para los PC de los proyectos, a los que por motivos de seguridad no se les da acceso a internet. Hasta ahora, en ellos, no podíamos usar maven. Ahora ya podemos. Es más, si alguien necesita el equivalente a un mvn deploy, le basta con pulsar el botón "build" del CruiseControl en el proyecto correspondiente.

Bueno, en realidad en los PC de los proyectos sí podíamos, y de hecho estabamos, usando maven. Habiamos instalado un proxy maven, pero no acaba de funcionar todo lo bien que nos hubiera gustado. En el acceso a determinados jar internos de maven -de algún plugin- el proxy maven fallaba y al final lo teníamos que copiar a mano.

Oct 12

Plugin cobertura para Maven

Cobertura es un plugin para Maven que nos permite obtener el porcentaje de líneas de código por las que han pasado los test unitarios. Después de generar la documentación con Maven -mvn site-, obtenemos un informe html en el que clase por clase y paquete por paquete nos da el porcentaje de cobertura de los test, es decir, el porcentaje de líneas de código por los que ha pasado el test. Si además con Maven generamos la documentación de los fuentes con el plugin jxr, podemos incluso ver nuestros fuentes java con las líneas por las que ha pasado el test marcadas en verde y en rojo las que no.

Lo tengo instalado y llevo usándolo desde bastante tiempo. No con intención de llegar al 100% de cobertura en los test, sino como un indicador de qué tal vamos con los test. Si hacemos muchos o pocos, etc. Tengo unos scripts que se lanzan automáticamente todas las noches, generando esta documentación y publicándola en web, junto con métricas PMD, javadocs, etc. De esta forma, todos los programadores tenemos disponible esta documentación actualizada a diario.

El plugin Cobertura estuvo funcionando muy bien hasta hace casi un año. Desde ese momento, sin saber por qué, empezó a dar 100% de cobetura en todas las clases y todos los paquetes, indpendientemente de que hubiera o no tests. Reconozco que soy un poco vago, pero después de casi un año me puse a investigarlo. Parece que es un bug de la versión 2.1 de cobertura, que lleva todo ese tiempo sin funcionar y que todavía no han resuelto. La solución ha sido sencilla, basta con poner en el pom.xml que queremos la versión 2.0 de Cobertura. Algo tan simple como añadir la línea

<version>2.0</version>

Con esto ya funcionó y obtuve nuevamente los resultados correctos. Por supuesto, me he dado de alta en la página esa de los bugs, he votado para que lo resuelvan y me he apuntado para que me avisen. No sé qué interés tengo en la versión 2.1 si la 2.0 me funciona, pero supongo que es un poco de cargo de conciencia por llevar un año sin preocuparme del tema.

Además, animado con el éxito de auto-resolver el bug -cambiando el número de versión-, me puse a investigar otro problema que tenía también con Cobertura:

Si en Maven tengo un proyecto A del que cuelgan dos subproyectos B y C, lo normal es que el "packaging" del proyecto A sea "pom" y los B y C, en mi caso, sea "jar". Es decir, el proyecto A no tiene fuentes y sólo sirve como "contenedor" o "padre" de los proyectos B y C. Una ventaja de organizar esto así, es que si ponemos algo en el pom.xml de A, automáticamente lo heredan los proyectos B y C. En el caso concreto de Cobertura, basta con poner en el pom.xml de A que queremos los informes de Cobertura para que se generen tanto los de B como los de C.

¡¡Pues primera en la frente!!. El hacerlo así y ejecutar mvn site, tenemos un error de Cobertura. Mi solución hasta ahora era copiar la configuración de Cobertura en los pom.xml de B y C y ejecutar mvn site individualmente en cada uno de ellos.

Revisando la página de bugs, veo que ese bug también está y además lo han resuelto un poco "de aquella manera". La solución es borrar, donde no hay fuentes, el directorio "java" de src/main/java. Si existe el directorio src/main/java, Cobertura da por supuesto que hay fuentes y si no las hay, da un error y no hace nada. Pues nada, nuestro amigo el comando "rm -rf", ese que borra tus directorios más importantes en un descuido, es el que me solucionó el problema.