Jun 19

Pros y contras de maven

 Llevamos ya varios años usando maven y nos hemos acostumbrado a él. Recordamos ahora cómo teníamos antes los proyectos y nos asombramos de la mejora conseguida. Sin embargo, no todo es bueno con maven, tiene sus pegas.

Ventajas de maven

Hay principalmente tres grandes cosas que hemos conseguido con maven y sin las que ahora no seríamos capaces de trabajar.

La primera es que ahora todos nuestros proyectos están organizados igual. Esto, por supuesto, puede conseguirse con disciplina y sin necesidad de maven, pero en grupos numerosos de desarrolladores es más fácil de conseguir si la herramienta te obliga a seguir una estructura o al menos, si te obliga a hacer un esfuerzo importante si te quieres salir de esa estructura. Se acabó el  hacer scripts de compilado, el preguntar a otro cuando cambias de proyecto dónde están las cosas, el dejar ficheros o iconos por cualquier lado. Ahora cualquiera puede pasar de un proyecto a otro y sabe manejarse por la estructura sin ningún problema.

La segunda gran ventaja son los jar. Al dejar todos los jar de los proyectos en un repositorio centralizado (usamos nexus) y usar versiones SNAPSHOT de maven, todos tenemos siempre disponibles los últimos jars. Antes era necesario que cada uno compilase los jar que  necesitara o se los pidiera a alguien que los tuviera, o que alguien se acordara de meterlos en el sistema de control de versiones. Eso ya no es necesario, maven/nexus se encarga de que todos tengamos siempre los últimos jars.

Y la tercera gran ventaja es la herramienta de integración continua (Hudson), que todas las noches saca los fuentes del sistema de control de versiones, los compila y pone los jars en nexus. Al ser hudson el encargado de meter los jar en nexus, estos siempre están actualizados y siempre está disponible la última versión para todo el mundo. Y al ser hudson el que compila en un servidor separado, eliminamos por un lado los típicos errores de código que sólo compila en el PC del desarrollador Fulanito porque inadvertidamente ha puesto un path suyo local en algún sitio, o se ha olvidado de meter algo en el sistema de control de versiones. Este Hudson nos sirve además para obtener de él las versiones de instalación tanto para el entorno de producción como para pruebas. Ya no dependemos de que alguien tenga todo lo necesario para generar estas versiones y de que ese alguien esté.

Pegas con maven

Pero no todo son ventajas. La gran y enorme pega de maven es su dependencia de que haya internet o al menos red, para acceder al servidor de Hudson. Los problemas con la red suelen ser relativamente frecuentes: se va el servidor de nexus por el motivo que sea, se cae algún proxy o router, etc, etc. En esos casos, se nos queda un poco parado el tema de compilado. Sí, maven tiene una ejecución off-line, pero no funciona todo lo bien que debiera. Si le falta algo, se empeña en ir a buscarlo a través de la red.

Y esta dependencia de la red se nos hace especialmente grave cuando vamos a instalaciones del cliente en los que no hay internet ni, por supuesto, acceso a nuestro nexus. Si queremos llevarnos un entorno de desarrollo para depurar, corregir algún bug y compilar en las instalaciones del cliente, nos obliga a llevarnos toda una copia de repositorios, o montar el proyecto de forma totalmente independiente de maven. Hay comandos de maven que ayudan a hacer toda esta copia, como dependency:go-offline, pero hay que acordarse de hacerlo.

Es bastante molesto también el tema de plugins de maven. A veces y sin saber el motivo, maven decide que debe actualizar plugins a versiones más modernas. En la mayoría de los casos esto no afecta demasiado, pero a veces si coincide con algún tipo de problema de red o el estar off-line, nos hace imposible compilar.

En fin, desde luego las ventajas compensan con creces los inconvenientes y ni se nos pasa por la cabeza dejar de usar maven, pero a veces algunos problemas misteriosos pueden tenerte de pelea con ellos toda una mañana.

Mar 11

Subiendo “extraños” al repositorio de maven

 Cuando usamos maven entre varios desarrolladores en proyectos más o menos grandes, es normal que montemos un repositorio de jars, estilo nexus o archiva. Cuando ejecutamos el comando mvn deploy, maven sube nuestro jar recién compilado a este repositorio y lo hace accesible para los demás desarrolladores.

Sin embargo, es posible subir a este tipo de repositorios ficheros que no sean .jar, podemos subir cualquier tipo de fichero. El comando mvn deploy:deploy-file con los parámetros adecuados, nos permite subir un fichero cualquiera.

¿Para qué queremos subir ficheros que no sean .jar?. Supón que como parte de nuestro proyecto java usamos JNI con una librería .dll cuyos fuentes en C/C++ también desarrollamos nosotros. No parece una buena forma de compartir la .dll recién generada metiéndola en nuestro sistema de control de versiones. Aunque podríamos hacerlo así, es más elegante usar el sistema de control de versiones para ficheros fuente o de texto que generemos a mano y no usarlo para los "artefactos" que construye nuestro proyecto, como ejecutables, librerías, etc. La solución entonces consiste en subir esa .dll recién generada al repositorio de jars, aunque no sea un jar. El comando puede parecerse a esto (no pongo todos los parámetros, que sería muy largo)

mvn deploy:deploy-file -DgroupId=… -DartifactId=libreria -Dversion=1.0 -Dpackaging=dll -Dfile=libreria.dll -Durl=….

Y esto subiría la .dll al repositorio de jars. La parte interesante de este asunto está en el -Dpackaging=dll. Esto hace que maven ponga al fichero la extensión .dll para subirlo y lo subirá con el nombre libreria-1.0.dll, independientemente del nombre que tenga la .dll antes de subirla.

Y ahora viene otra parte interesante. En nuestro proyecto java podemos poner, en el pom.xml, la dependencia en runtime de esa librería que acabamos de subir

<dependency>
   <groupId>…</groupId>
   <artifactId>libreria</artifactId>
   <version>1.0</version>
   <type>dll</type>
   <scope>runtime</scope>
</dependency>

Aquí, nuevamente, la gracia está en poner <type>dll</type>, ya que esto hace saber a maven y al repositorio de jars que en realidad estamos buscando un fichero .dll, que habíamos subido previamente con -Dpackaging=dll

Por supuesto, la ejecución del comando mvn deploy:deploy-file podemos añadirla al proceso de compilado de nuestros fuentes C/C++ y podemos poner version 1.0-SNAPSHOT, de forma que se suban versiones de desarrollo nuevas cada vez que se compile y que el resto de desarrolladores puedan disponer de ellas.

Mar 04

Rangos de dependencias con maven

 En maven todos estamos acostumbrados a poner las dependencias y en concreto, a poner la versión concreta que queremos de la dependencia. Por ejemplo, si nuestro proyecto depende de log4j, solemos poner algo como esto

<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.13</version>
</dependency>

es decir, ponemos la versión concreta que deseamos de log4j, en esta caso, la 1.2.13

Pues bien, es menos conocido, quizás porque la documentación de maven es algo escasa, pero podemos indicar a maven un rango de dependencias, de forma que maven traerá la más moderna disponible. Para ello se utiliza la notación matemática para rangos de valores, donde se abre paréntesis o corchete, se pone el número de versión inicial, coma, el número de versión final y se cierra paréntesis o corchete. Un corchete indica que la versión inicial o final es válida, mientras que un paréntesis indica que no es válida. Dejar en blanco uno de los números de versión indica que no hay límite por ese lado. Así, por ejemplo

[1.2, 1.5)     Cualquier versión entre la 1.2 y la 1.5, incluyendo la 1.2 (corchete al principio), pero excluyendo la 1.5 (paréntesis al final)

[1.3, )           Versión 1.3 o superior, incluyendo la 1.3

[1.0, 2.0)     Cualquier versión 1.x.x, pero inferior a la 2.0

En realidad maven admite hasta tres números en la versión más un "cualificador". El cualificador es un nombre cualquiera que se pone detrás del número de versión separado por un guión. Por ejemplo, imagina que en nuestro proyecto entregamos una versión al cliente FEDERICO y creamos una versión congelada para ese cliente. Su número de versión podría ser 1.2.3-FEDERICO. O más común, si es una "Release Candidate", le ponemos 1.2.3-rc, o si es una beta, pues 1.2.3-beta.

No lo he probado, pero imagino que esto permite poner cosas como

[1.2, )-FEDERICO

de forma que maven traerá cualquier versión 1.2 o superior que tenga el cualificador FEDERICO, es decir, que sea del cliente FEDERICO.

Considero esto realmente interesante, porque en un proyecto grande en el que hay muchos jar desarrollados por la gente de nuestro equipo y en los que cada jar tiene sus responsables, cada responsable puede subir su número de versión cuando lo considere adecuado y los demás traerán automáticamente o no esa versión según lo que pongan en las dependencias. Si yo no quiero traerme los cambios que haga Juan en su jar, símplemente pongo que quiero la versión 1.4. Pero si me interesa traerme siempre su versión más moderna, entonces pongo [1.0, ).

Aunque supongo que es bastante conocido, aprovecho para poner aquí cual es el significado de esos tres números de versión. Si la versión es a.b.c, los números se incrementan normalmente siguiendo los siguientes criterios:

  • a se suele incrementar cuando el cambio es tan importante que hace la nueva versión incompatible con las anteriores. Si guardas datos con una versión 2.0 de un programa, posiblemente no puedas leer esos datos con la versión 1.0 del mismo programa (normalmente al revés si suele ser posible).
  • b se suele cambiar cuando no hay pérdida de compatibilidad, pero sí se han añadido nuevas funcionalidades. Por ejemplo, las versiones 1.3 y 1.4 pueden usar los mismos datos guardados indistintamente, pero la 1.4 permite imprimirlos mientras que la 1.3 no.
  • c se suele cambiar cuando se han arreglado errores respecto a la anterior. Por ejemplo, la 1.2.3 puede caerse cuando el usuario pulsa el botón A, luego minimiza la ventana y le da a la tecla de tabulador. La 1.2.4 ya no tiene ese error.

Por supuesto, esto es una convención más o menos aceptada, pero desde luego no es obligatoria.

 

Mar 03

javadoc con UML

Hurgando por ahí me he encontrado con UMLGraph, una herramienta que genera gráficos UML a partir de unos textos que los describen. Lo bueno es que, al menos para el diagrama de clases, el texto que describe el diagrama UML es exactamente igual que un fuente java. Dicho de otra forma, si ponemos en el fichero "class UnaClase extends UnPadre {}", obtendremos un diagrama con una cajita UnaClase que hereda (flechita con triángulo) de una cajita UnPadre. Puedes ver el ejemplo aquí. Esto lo hace ideal para generar los gráficos UML de clases a partir de código fuente ya hecho.

Otro punto interesante es que viene con la posibilidad de invocar javadoc usando UMLGraph, de forma que en nuestro javadoc se generaría diagramas de clases incrustados. UMLGraph viene con un doclet que se puede usar desde el comando javadoc de java y de esta forma, javadoc generará la documentación de la forma habitual, pero incluyendo un gráfico de UML. En la descripción de un package, pondrá todas las clase incluidas en ese package y las relaciones entre ellas. En la descripción de una clase pondrá un dibujo de dicha clase con las relaciones (herencias, dependencias, etc) con otras clases del paquete o de otros paquetes. En la figura puedes ver un ejemplo de javadoc generado con el doclet de UMLGraph.

javadoc con grafico UML

Un detalle a tener en cuenta es que para que UMLGraph pueda generar los ficheros gráficos es necesario tener instalado previamente GraphViz.

En el diagrama de clases podrían configurarse muchas cosas, poner notas asociadas a las clases, poner otro tipo de cajas que no sean de clases, métodos que deben o no mostrarse, etc. La pega de ello es que iría configurado en código a base de anotaciones, por lo que el código quedaría algo "guarreado" para luego ver el dibujo bonito.

También pueden hacerse diagramas de secuencia, pero desgraciadamente la sintaxis del fichero de texto que lo describe ya no es java, así que no deja de ser una forma alternativa de hacer el diagrama. Puede ser interesante, por ejemplo, si guardamos los diagramas de secuencia en un sistema de control de versiones (como subversion). Siempre ocupa menos y es más interesante para ver diferencias con versiones anteriores un fichero de texto que no un gráfico o un proyecto entero de alguna herramienta compleja de generación de gráficos UML (Together, Rational,…).

Y otra cosa que a mí siempre me viene bien, es que UMLGraph está subido al repositorio ibiblio de maven y tiene plugin para el mismo. De esta forma, configurando el fichero pom.xml de nuestro proyecto maven (en concreto, configurando el plugin de javadoc para que use el doclet de UMLGraph), podemos generar el javadoc con gráficos UML directamente desde maven. La configuración sería algo parecido a esto

<reporting>
   <plugins>
      <plugin>
         <artifactId>maven-javadoc-plugin</artifactId>
         <configuration>
            <source>1.5</source>
            <aggregate>true</aggregate>
            <doclet>gr.spinellis.umlgraph.doclet.UmlGraphDoc</doclet>
            <docletArtifact>
               <groupId>gr.spinellis</groupId>
               <artifactId>UmlGraph</artifactId>
               <version>4.6</version>
             </docletArtifact>
             <additionalparam>
                    -inferrel -inferdep -quiet -hide java.*
                    -collpackages java.util.* -qualify
                    -postfixpackage -nodefontsize 9
                   -nodefontpackagesize 7
             </additionalparam>
          </configuration>
       </plugin>
    </plugins>
</reporting>

Con esto, un simple mvn javadoc:javadoc nos generaría la documentación javadoc de nuestro proyecto maven, gráficos UML incluidos.

 

Feb 24

OutOfMemoryError con test de maven

 

El otro día me salto un OutOfMemoryError al ejecutarse un test automático desde maven. Teóricamente, para evitar problemas de memoria con maven basta con poner la variable de entorno MAVEN_OPTS con los parámetros que le queremos pasar a la máquina virtual de java, en concreto, los de aumento de memoria

set MAVEN_OPTS=-Xmx512m

De hecho, tengo esa variable puesta por defecto en el entorno y estaba correctamente inicializada. Pero el OutOfMemoryException persiste. Así que a buscar en google.

Al final encuentro que maven arranca una máquina virtual java separada para ejecutar los test y que el parámetro MAVEN_OPTS sólo afecta a la máquina virtual en la que corre maven y no a la máquina virtual en la que se ejecutan los test. El plugin de maven que se encarga de ejecutar los test automáticos se llama maven-surefire-plugin y tiene su propia configuración. La variable argLine permite indicar, entre otras cosas, la cantidad de memoria que queremos que se asigne a la máquina virtual java en la que se ejecutan los test. Para ello, debemos ejecutar así

mvn -DargLine=-Xmx512m test

o bien, configurarlo en el mismo pom.xml del proyecto

<project>
<build>
<plugins>
   …
   <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>               
      <configuration>
         <argLine>-Xmx512m</argLine>
      </configuration>
   </plugin>
 

Nov 25

Archiva vs Nexus

 

En su día nos instalamos un repositorio propio para nuestros jar, de forma que estuvieran accesibles para todos los desarrolladores. Para ello usamos archiva, y ha funcionado más o menos bien con sus cosillas. Hace además las veces de proxy con los repositorios de maven que hay por internet. De esta forma, cada desarrollador únicamente debe configurar maven para que busque los jars en el repositorio de archiva y es este el que se encarga de acceder a internet y buscarlos si es necesario.

No hace mucho descubrí que había otra herramienta similar llamada nexus. Como archiva nos hacía cosas raras de vez en cuando (no traía las cosas de los repositorios de internet, no sé muy bien si por culpa de archiva o de nuestra conexión a internet, que va con proxy autentificado. También dejaba ficheros tmp vacíos en el repositorio de vez en cuando). Así que hoy me he decidido a instalar nexus y probar.

La instalación sencilla, un zip que te bajas, desempaquetas y tienes los scripts necesarios de windows, linux, solaris… para instalar nexus como servicio y arrancarlo y pararlo. Eso sí, hay dos versiones, la gratis con menos posibilidades de autentificación/seguridad, y la de pago que tiene de todo. La gratis en principio tiene lo necesario: gestión de usuarios y permisos propia, funciones de proxy y repositorios propios.

La interface web mucho mejor que la de archiva. Bastante más bonita y agradable. Rápidamente me puse en ella a configurar nuestros repositorios, tanto los propios, como los repositorios que son proxy de los estándar de internet (que ya vienen configurados los de maven central, apache y codehaus).

Y vamos a las cosas que me han gustado y que me han decidido a intentar el cambio en serio:

  1. Es más estricto que archiva con los SNAPSHOTS y las releases. Archiva permite subir y bajar jars snapshots y no snapshots de repositorios snapshots y no snapshots indistintamente. Somos los desarrolladores los que tenemos que tener cuidado de dónde subimos los jar. Nexus es más estricto, si intentamos subir un jar no snapshot a un repositorio que hemos marcado como snapshot, protesta. Y al revés también.
  2. Permite programar tareas de mantenimiento periódicas y entre ellas, la que veo más útil en nuestro caso: permite que se limpien automáticamente las versiones snapshots más antiguas o indicar cuántos snapshots quieres como máximo por cada jar. En un entorno de desarrollo como el nuestro en el que Hudson genera y sube muchos snapshots gigantes todas las noches, una limpieza periódica se hace imprescindible.
  3. Cuando configuras un repositorio como proxy de uno externo, tienes más visibilidad de si tiene o no conexión con el repositorio externo, si se ha bajado algo de él y qué se ha bajado.

Para ser justos, estoy comparando una versión antigua de archiva, que instalé hace mucho, con la última de nexus. Es posible que las versiones más modernas de archiva hayan mejorado o permitan hacer estas cosas que digo que hace nexus.

May 08

Sobre dependencias

 

Un par de cosillas/problemillas que me he encontrado sobre las dependencias de unos jar con otros.

Maven por un lado

Supón que tenemos un proyecto A con maven que tiene a su vez dos subproyectos B y C. Si le decimos a maven a través de los ficheros pom.xml que B necesita de C para compilar y que C necesita de B para compilar, maven, obviamente, protesta. La excusa es que no sabe qué debe compilar primero, ya que el uno depende del otro. Hasta aquí todo parece correcto.

Supongamos ahora la misma estructura de proyectos de antes, pero esta vez decimos que B necesita a C para compilar y que C necesita de B sólo en tiempo de ejecución (runtime). Pues bien, maven protesta igualmente. Sin embargo, esta vez, la excusa ya no vale. Si B necesita de C para compilar y C sólo necesita a B en runtime, entonces está claro que se puede compilar primero C y luego B. Esto ya es algo que puede en un momento dado molestar, aunque quizás no sea muy correcto un proyecto con esta dependencia.

Pero ahora viene lo peor. Si en vez de un proyecto A con subproyectos B y C tenemos directamente dos proyectos maven independientes, B y C, maven teóricamente sería capaz de detectar igualmente las dependencias. En el repositorio de jars que usemos estarán los jar de B y C y sus ficheros pom.xml con las dependencias. Sin embargo, al ser independientes, NO protesta. En principio no podemos llegar a esta situación directamente, ya que si B necesita de C para compilar y C necesita de B para compilar, no podemos compilar ninguno de los dos, ya que en ningún caso estará el jar del otro en el repositorio (ya que no hemos podido compilarlo). Pero sí podemos hacerlo poco a poco, en unos proyectos reales que evolucionan con el tiempo. Podemos empezar creando los proyectos B y C como indpendientes e ir compilándolos. Según avanzan los proyectos, en un momento dado podemos necesitar la dependencia de B con C y la añadimos. Todo bien. Pero según avanzamos más y por descuido, podemos añadir la dependencia de C con B…. y maven no protesta, puesto que encuentra los jar anteriores en el repositorio y no revisa las dependencias entre proyectos independientes.

Usar muchas librerías, por otro lado

Este otro problema es más filosófico que de una herramienta concreta, aunque me he tropezado con él en la realidad. Supón que haces un proyecto java y decides usar librerías de terceros, de esas libres que hay por ahí, por ejemplo, jasperreport, jfreechart, ibatis, hibernate, etc. Digamos, por ejemplo, que decides usar la A y la B. Pues bien, es bastante fácil que ambas necesiten de alguna librería más de terceros, por ejemplo, log4j. Por generalizar, tanto A como B necesitan que nos descarguemos e incluyamos en nuestro proyecto a C. Sin problemas de momento.

Pero, ¿qué pasa si A necesita C-1.0 y B necesita C-2.0 y las versiones 1.0 y 2.0 son incompatibles?. Pues básicamente que la hemos cagado. Para que todo vaya bien, necesitamos las dos versiones de la librería y tendremos problemas a la hora de configurar el classpath, porque muchas clases estarán repetidas en ambas versiones y se encontrará la primera que pongamos en el classpath, que puede ser la que no le gusta a la librería A y que de paso le sienta mal a la B. Y si ponemos sólo una, A no funciona y si ponemos sólo la otra, es B el que protesta.

Desgraciadamente, estas situaciones van siendo cada vez más comunes. Hay librerías java como las apache-commons, log4j, etc que son muy, pero que muy utilizadas por muchísimas librerías de más alto nivel, como hibernate, jfreechart, etc. Todas estas librerías de alto nivel no avanzan a la vez ni se actualizan con la misma frecuencia, por lo que puede ser normal que una necesite log4j-1.2.13 y otra log4j-1.2.15. Con log4j en concreto no hay mucho problema, pero sí hay otras más conflictivas.

A cuento con lo de elegancia o sencillez, este parece un motivo para ir más a la sencillez que a la elegancia. Las liberías de alto nivel que pretendan ser compatibles con otras librerías de alto nivel que no tienen nada que ver ellas, deberían casi casi reinventar la rueda cada vez en vez de usar librerías de más bajo nivel. O quizás estas librerías de muy bajo nivel y mayoritariamente usadas deberían venir prácticamente incluidas en el JDK de java.

No se, veo difícil solución y preveo que a la larga la cosa ira empeorando. De momento es difícil tropezarse con estas situaciones, pero a mí ya me ha ocurrido alguna vez.

Apr 25

¿Elegancia o sencillez?

 

En el trabajo llevamos varios días peleándonos con la instalación de una versión de nuestro software en uno de nuestros sistemas. El sistema consta de unas diez estaciones de trabajo solaris y unos veinte PCs con Windows. En todos ellos corren aplicaciones nuestras, en su mayoría java. Estas aplicaciones tienen algunas partes comunes, pero son distintas en cada una de las estaciones y de los PCs (cada uno está especializado en diversas funciones, algunas comunes, otras no y comparten mucha información entre ellos). En las estaciones hay bases de datos Oracle, con muchas tablas comunes, pero otras distintas en cada estación. Y en todo esto reside el problema de la instalación.

La gente está dividida en dos posibles tipos de instalación.

Junto con algunos, yo soy partidario de implementar las distintas funcionalidades del sistema en jar distintos e instalar en cada estacion/PC sólo aquellos jar que son necesarios, de forma que ninguna estación/PC lleve más jar o ficheros de configuración que no va a usar. Esta es la solución que considero elegante, pero es más compleja. Requiere generar instaladores/zips disintos para cada estación/PC, así como ser mucho más cuidadoso en esta generación de instaladores/zips, muchos jar, muchos grupos de ficheros de configuración, partes comunes y partes específicas.

Otros piensan que es mejor hacer un único mega-jar, o unos pocos jar grandes, un único mega-grupo de ficheros de configuración e instalar todo en todos lados. De esta forma, un único instalador o un único zip vale para todas las estaciones/PCs. Luego es el propio software el que mirando el nombre de la estación/PC en el que corre, sabe qué fichero concreto de configuración leer, de qué clase principal hacer el new y actuar como lo que le toca. Esta instalación es, desde mi punto de vista, más chapuza, pero es innegable que es infinitamente más sencilla.

Y después de la pelea de estos días atrás para la instalación según mi punto de vista (disintos zips/instaladores que instalan en cada estación/PC sólo lo necesario), creo que estoy empezando a cambiar de opinión. Los instaladores/zips, desde luego, se hacen con procesos automáticos, pero alguien tiene que decirle a ese proceso qué debe meter. Según evoluciona el software y va llevando más funcionalidades y ficheros, hay que tocar la configuración de la herramienta que genera los instaladores/zips (izpack, maven assembly,…) y hay que hacerlo con cuidado. Este proceso es manual y está sujeto a errores humanos, por lo que a nuestros instaladores siempre les acaba faltando alguna cosa y necesitan su proceso de "depuración".

En fin, no me gustan las chapuzas y tengo que pensar seriamente la forma de mejorar el proceso de generar los zips/instaladores, pero desde luego, es difícil resistirse a la facilidad de instalación de "todo va en todos sitios, aunque no se use". Es mucho más fácil instalar un solo mega-jar en todos lados que instalar varios jar distintos en cada estación/PC.

Apr 15

¿Qué debemos meter en el sistema de control de versiones?

 

Cuando trabajamos en proyectos que usan sistema de control de versiones, pero todavía no tenemos muy afianzado qué cosas debemos meter en dicho sistema, pueden aparecer determinados ficheros en los que nos entra la duda de si debemos meterlos o no. Por ejemplo, ¿metemos o no los ficheros de proyecto de eclipse o netbeans? ¿metemos o no las librerías externas a nuestro proyecto, como log4j, drivers de bd, etc?.  La decisión, por supuesto, depende mucho de los gustos personales de la gente que lleva el proyecto, pero también depende en parte de las herramientas que usemos que no tienen mucho que ver con el sistema de control de versiones. Voy a comentar aquí un posible criterio más o menos lógico.

En el sistema de control de versiones deben ir todos aquellos ficheros que nosotros debemos hacer o proporcionar manualmente al proyecto. No deben ir todos aquellos ficheros que se generan de forma automática.

En el primer grupo, los que nosotros generamos, están claramente los fuentes de nuestro código (los .java por ejemplo), los scripts de compilado o de arranque de la aplicación, los ficheros de configuración (properties de java), los iconos de nuestra aplicación, etc, etc. Quedarían excluidos, por ejemplo, los fuentes que genere automáticamente alguna herramienta o script a partir de otros ficheros que sí hacemos manualmente.

Es muy importante meter en el sistema de control de versiones absolutamente todo lo necesario para construir desde cero y ejecutar nuestra aplicación tal cual era en un momento dado. De nada sirve tener versiones de los fuentes si no tenemos también almacenadas las versiones correspondientes de los scripts de arranque, de los ficheros de configuración (properties) o de creación de las tablas de base de datos.

En el segundo grupo, los generados automáticamente y que no van en el sistema de control de versiones, están los ejecutables y librerías que genera nuestro proyecto. Ni siquiera tendríamos que meterlos para conservar una versión especialmente estable o buena, ya que los sistema de control de versiones suelen soportar cosas como etiquetas o ramas y bastaría con marcar los fichero que sí van con una etiqueta para ser capaces de generar ese ejecutable estupendo.

Bien, esos son los ficheros más o menos claros, pero, ¿qué hacemos con las librerías externas?. Esta decisión ya puede depender de gustos y de las herramientas que usemos. Si usamos, por ejemplo, maven, todos los desarrolladores tendrán acceso a esos jar a través de internet de forma automática y sin preocuparse en absoluto de ello, por lo que con este tipo de herramientas no es necesario en absoluto meter en el sistema de control de versiones cosas como log4j.jar, junit.jar o ojdbc14.jar. Si no usamos una herramienta de este tipo, para comodidad para los desarrolladores, debemos tener todos estas librerías accesibles de forma fácil en algún sitio y una buena opción es meterlas en el sistema de control de versiones. Normalmente metemos cada una de estas librerías una sola vez y no van a tener modificaciones. Un desarrollador nuevo tendrá todas las necesarias símplemente con hacer un checkout y los scripts de compilado sabrán dónde buscar estas librerías.

¿Y qué hacemos con los ficheros de proyecto del IDE de trabajo, como los .project, .classpath o .settings de eclipse?. Nuevamente, la decisión puede depender de gustos y de las herramientas a usar. Nuevamente, si usamos maven, el comando mvn eclipse:eclipse nos genera estos ficheros automáticamente, por lo que es cómodo generarlos para un desarrollador recién incorporado al grupo y no sería necesario meterlos en el sistema de control de versiones. Si no usamos este tipo de herramientas, podemos meter estos ficheros en el sistema de control de versiones, pero tenemos que ser muy conscientes de que pueden ser problemáticos:

  • A veces estos ficheros llevan paths absolutos, por lo que todos los desarrolladores deben sacar el proyecto en el mismo directorio absoluto en su ordenador (por ejemplo, C:\PROYECTO) y posiblemente, tener las librerías externas en el mismo path absoluto (sea dentro de control de versiones o no).
  • Si un desarrollador quiere modificar algo en su IDE que afecte a estos ficheros (por ejemplo, añadir una dependencia nueva para probar o símplemente, cambiar el nombre del proyecto porque no le gusta el oficial), debe ser muy cuidadoso para no hacer un commit de esos ficheros descuidadamente.

Si decidimos no meterlos, debemos ser conscientes de que los desarrolladores nuevos deben montar manualmente el proyecto. Y si decidimos hacer cambios "oficiales" (añadir una dependencia, por ejemplo), todos deben realizar ese cambio manualmente.

 

Mar 29

Más sobre pom_version

 

Hace tiempo comenté que me había hecho un plugin de maven, pom_version, para ayudarme a cambiar los números de versión en los pom.xml de los proyectos maven. El problema era que en proyectos grandes, con muchos subproyectos, muchos pom.xml y muchas dependencias, era un pequeño infierno cambiar la versión de uno de los jar, ya que requería ir buscando y editando todos los pom.xml uno a  uno. El plugin hacía esto de forma más o menos automática en un proyecto maven.

El otro día me tocó hacer una rama de CVS para un hito con el cliente. Por un lado, eché casi un día entero haciendo sólo la rama de CVS. Los proyectos son grandes y los cvs tag recorren todos los ficheros uno a uno, por lo que sólo hacer los "cvs tag" en todos los proyectos implicados me llevó todo el día.

Al día siguiente me dediqué al cambio de números de versione en los pom.xml. Al crear una rama para un hito de cliente, todos los jar deben pasar a un número de versión nuevo, el del entregable y la rama en cuestión. Pensé que con el plugin el tema iba a ser rápido, pero no lo fue. Eché toda la mañana y no terminé.

El primer problema es que el plugin sólo maneja un proyecto maven con todos sus subproyectos. Pero nuestros proyectos tiran de librerías propias que a su vez tiran de librerías propias y muchas de ellas son grandes, con subproyectos y más subproyectos. La ejecución del plugin es inmediata en un proyecto maven, pero tengo que ir pasando manualmente por todos los proyectos, empezando por los más básicos hasta el de más alto nivel, revisando que no se quedan dependencias sin cambiar, que todo compila como debe, etc, etc.

El segundo problema, menor, es que el plugin no me soporta las propiedades en los números de versión. Si un pom.xml se define una propiedad "numeroVersion=1.2.3" y luego en las dependencias unos usas ${numeroVersion} y otros 1.2.3, el plugin sólo cambia los que usan variable o los que usan número, según se le diga.

Así que me he replanteado el tema, no puedo echar dos días cada vez que quiera hacer una rama general de todo el proyecto.

  1. Sólo para entretenerme, porque quizás deje de usarlo, en el plugin le he hecho un arreglo para que soporte bien las propiedades. Ahora está en versión pom_version 1.1.0. He aprovechado además para quitarle la dependencia que tenía con Xerces y hacer algo más de test junit (he intentado aplicar TDD para estas mejoras).
  2. La primera medida seria va a ser pasar de CVS a Subversion. En Subversion la creación de un tag o rama tarda entre dos o tres segundos, independientemente del tamaño del proyecto. A veces me gusta darle un comando al ordenador y luego estar un rato rascándome la barriga (por no decir otra cosa) mientras el ordenador trabaja, pero estar rascándome un día entero, acaba escociendo.
  3. La segunda medida seria y es sólo una idea, es hacerme una pequeña aplicación a la que se le configure diciéndole dónde están todos los proyectos maven que usa un determinado proyecto de cliente, decirle qué número de versión quiero para cada proyecto y que él se encarge de hacer todos los cambios en todos los proyectos. Por supuesto, esa pequeña configuración por proyecto, debe guardarse en algún sitio para no tener que indicar todos los directorios cada vez. No creo que una herramienta así me lleve mucho hacerla, ya que es simplemente buscar ficheros xml de nombre pom y hacer cambios en ellos. En el punto 1 comenté que quizás dejaría de usar el plugin y lo dejare si hago esta herramienta.

La duda que tengo respecto a la herramienta, es si hacerla yo mismo en mis ratos libres (y así disfrutar de un rato de codificación) o pasársela a uno de los FP que van a venir a hacer tres meses de prácticas…