Aprendiendo a ser una hormiga

24
Junio
2006

Cuando se programa una de las cosas más odiosas es tener que teclear largas ordenes para que se ejecute el compilador, el enlazador o lo que sea. Además estas líneas se tienen que teclear una y otra vez, lo que tiende a provocar errores y quebraderos de cabeza. Por suerte existen herramientas para automatizar las llamadas al compilador (y a veces mucho más que simplemente llamar al compilador).

La mayoría de los IDE disponen de un botoncito para compilar (algunos, como el Eclipse, ni siquiera disponen de ese botón y realizan automáticamente la compilación)), pero su problema es que necesitas disponer del IDE para utilizar ese botón. Un problema cuando queremos utilizar, por ejemplo, un sistema de integración continua.

Cuando hay que programar en Java mi sistema de automatización por excelencia es Ant, que permite decribir las tareas a realizar de una forma declarativa. Cuando toca programar en lenguajes .Net existe NAnt. Cuando programo en otros lenguajes (que no sean de scripting) me echo las manos a la cabeza (aunque Ant permite definir nuevas tareas e incluso ejecutar ordenes, personalmente utilizarlo para proyectos no escritos en Java presenta más problemas que beneficios).

La razón para escribir esta entrada viene de una serie de entradas sobre servicios web con Java que está publicando Diego en su blog (1, 2, 3, 4 y 5, por ahora) donde el pobrecillo teclea unas ordenes enormes (tanto que se le salen de la plantilla del blog :D) y como me dió pena pues me he decidido a escribir yo esta entrada.

Pero no podemos empezar por donde está él, así que por ahora todos nos contentaremos con la plantilla muy completa para proyectos simples (y de hecho no tan simples, pero bueno).

Podeís descargar directamente la plantilla (recordar renombrarla a build.xml). Las explicaciones después del salto.

Actualización (2006-06-25): He corregido el archivo, que contenía unos pequeños errores. Por suerte esto ha estado caído todo el día.

Puede parecer extraño que una plantilla pueda servir para varios proyectos diferentes, pero con un poco de organización para cada proyecto quizá sea necesario cambiar unas cuatro o cinco líneas de la plantilla. De las, aproximadamente, 120 que tiene, no está mal.

Para utilizar la plantilla necesitaremos una estructura de directorios como la siguiente:

Organización de los archivos

Tenedremos 3 directorios: src contiene el código de nuestra aplicación (los archivos .java) organizado mediante directorios según el paquete de nuestro archivos (en el ejemplo he utilizado el paquete org.example.miproyecto), en test guardaremos las pruebas unitarias de JUnit (estabais programando pruebas ¿verdad?) y en lib copiaremos (o enlazaremos simbolicamente, si tenemos “suerte”) las bibliotecas externas que utilice nuestra aplicación (en vez de llenar el classpath del sistema de bibliotecas que solo utilizan un par de proyectos).

(Nota: Personalmente intento mantener el classpath lo más vacio posible. Idealmente serían las bibliotecas de clases de Java, Ant, JUnit y log4j).

La primera parte de la plantilla simplemente define un par de cosas del proyecto, como su nombre y su descripción. Esos son dos elementos que hay que cambiar en cada proyecto.

<?xml version="1.0"?>
<project name="miproyecto" default="compile" basedir=".">
    <description>Plantilla de Ant para proyectos sencillos</description>

Hay que cambiar la cadena “miproyecto” que define el nombre del proyecto y también la descripción.

A continuación vamos a definir lo que Ant denomina “properties”, que se pueden comparar con el concepto de constante en los lenguajes de programación ya que una vez definidas no se pueden modificar. Todas estas propiedades son de nuestro proyecto, no de Ant, es decir, los nombres los hemos elegido nosotros, y podemos crear muchas más utilizando los mismos conceptos.

<property name="application.main" value="org.example.miproyecto.Inicio" />
<property name="application.args" value="arg1 arg2 arg3" />

Estas dos propiedades determinan la clase principal de nuestro proyecto (la que tiene el método main) y los argumentos que utilizamos para invocarlo. Servirán más tarde cuando queramos ejecutar nuestro proyecto sencillamente.

<property name="build.debug" value="on" />
<!– <property name="build.debug" value="off" /> –>

Esta propiedad determina si se compilará generando símbolos de depuración o no. He escrito las dos propiedades (una de ella comentada), pero es mucho más sencillo cambiar el valor de on a off y viceversa.

<property name="build.dir" location="build" />
<property name="build.prod.dir" location="${build.dir}/prod" />
<property name="build.test.dir" location="${build.dir}/test" />
<property name="src.dir" location="src" />
<property name="test.dir" location="test" />
<property name="test.reports.dir" location="${test.dir}/reports" />
<property name="test.results.dir" location="${test.dir}/results" />
<property name="lib.dir" location="lib" />
<property name="dist.dir" location="dist" />

Todas estas propiedades definen directorios y hay que fijarse que Ant dispone del atributo location en vez de value (ideal para cadenas y valores sencillos) cuando tratamos con rutas del sistema de ficheros. Dos cosas a destacar son que para hacer referencia a las propiedades ya definidas se utiliza la construcción ${...} y que los puntos dentro de las variables no tienen significado para Ant, son simples “separadores”.

Por ahora no explicaré para que sirven todos los directorios (algunos ya están descritos) pero como recomendación si tenemos dos directorios que cuelgan de uno común (por ejemplo build/prod y build/test) es recomendable definir una propiedad para el padre, de esa forma será muy sencillo mover ambas ramas a otra localización si es necesario.

<tstamp/>
<property name="project.name" value="${ant.project.name}" />
<property name="project.version" value="${DSTAMP}" />
<property name="jarfile.name"
    value="${project.name}-${project.version}.jar" />
<property name="jarfile.path" location="${dist.dir}/${jarfile.name}" />

Aquí, a parte de más propiedades, tenemos el uso de la tarea tstamp que sirve para definir unas cuantas propiedades dependiendo del momento actual. La que a nosotros nos interesa es DSTAMP que contendrá la fecha actual en formato “YYYYMMDD”. También vemos otra propiedad que no habíamos definido nosotros ant.project.name, que Ant define automáticamente del texto del atributo name de la etiqueta project.

Además de propiedades, Ant disponen de estructuras de path que permite definir conjuntos de rutas a directorios o archivos que pueden ser referenciadas más tarde mediante un nombre simbólico.

<path id="project.classpath">
    <pathelement location="${build.prod.dir}" />
    <pathelement location="${build.test.dir}" />
    <fileset dir="${lib.dir}">
        <include name="*.jar" />
    </fileset>
</path>

Aquí estamos definiendo el classpath del proyecto, por lo que incluimos los dos directorios donde se almacenarán las clases de nuestra aplicación (build.prod.dir para el código de la aplicación y build.test.dir para el código de pruebas) y además todos los archivos Jar que se encuentre en el directorio de bibliotecas (lib.dir, definido más arriba).

Una vez definidas las propiedades y las rutas que queramos utilizar vienen los target. Un target es lo que “ejecutamos” al invocar Ant. Si habeís sido observadores habréis visto que cuando definimos la etiqueta del proyecto había un atributo default con el valor “compile”, que será uno de nuestros targets, de hecho será el target que se ejecutará cuando no se especifique otro alternativo. Para especificar otro target la orden de invocación de Ant debe tener como argumento el nombre o nombres de los targets alternativos.

<target name="init">
    <mkdir dir="${build.dir}" />
    <mkdir dir="${build.prod.dir}" />
    <mkdir dir="${dist.dir}" />
</target>

Este target crea los directorios que se utilizarán cuando se compile y se empaquete la distribución. En build.prod.dir se almacenarán los archivos .class compilados y en dist.dir se almacenará el archivo .jar creado a partir de nuestro código compilado.

<target name="compile" depends="init"
    description="Compila el código fuente de la aplicación">
    <javac srcdir="${src.dir}"
        destdir="${build.prod.dir}"
        debug="${build.debug}"
        target="1.4"
        source="1.4">
        <classpath refid="project.classpath" />
    </javac>
</target>

Este target es dependiente del target anterior, y así lo define en su etiqueta mediante el atributo depends. Además este es el target definido por defecto en el proyecto. El target compila todos los archivos .java en el directorio src.dir (y en sus subdirectorios) (atributo srcdir y dejará los resultados en build.prod.dir (atributo destdir). Además mediante la propiedad build.debug se determinará si los símbolos de depuración serán generados o no. Los dos últimos atributos (target y source) determinan que versión del lenguaje debe el compilador utilizar cuando generar el bytecode y cuando interpreta el lenguaje (siempre es interesante definir las dos propiedades y no confiar en los valores por defecto de los compiladores).

(Nota: ¿Recordaís cuando ese profesor tan orgulloso os dice que Java 1.5 está “roto”? Bueno, pues se equivoca. “Roto”, lo que se dice “roto”, no está, pero está muy cambiado y sin cuidado puede “romper” muchos proyectos. Esas dos propiedades hacen que el compilador de Java 1.5 se comporte como si fuera el de 1.4, dejando de estar “roto”; el compilador de Java 1.4 se comportará igual; y el de Java 1.3 e inferiores posiblemente no funcionen).

<target name="dist" depends="compile"
    description="Crear un archivo Jar para distribuir la aplicación">
    <jar destfile="${jarfile.path}"
        basedir="${build.prod.dir}" />
</target>

Cuando hayamos decidido que nuestro proyecto puede ser distribuido podremos utilizar el target dist para generar el el directorio dist.dir un archivo .jar con el código compilado que se encuentra en build.prod.dir (atributo basedir).

<target name="execute" depends="compile"
    description="Ejecuta nuestra aplicación">
    <java classname="${application.main}">
        <classpath refid="project.classpath" />
        <arg line="${application.args}" />
    </java>
</target>

Para terminar vemos el target execute que utilizando el nombre de la clase de application.main ejecutará nuestro programa pasandole los parámetros de application.args (la tarea esta vez es java y no javac como cuando compilabamos).

<target name="clean"
    description="Elimina los directorios creados">
    <delete dir="${build.dir}" />
    <delete dir="${dist.dir}" />
</target>

El último target de este bloque se encarga de hacer limpieza borrando los directorios que creamos en el target init.

En el caso de las pruebas de unidad el procedimiento es parecido: init-tests crea los directorios necesarios (y borra los antiguos) y compile-tests compila las pruebas. Pero, a diferencia del código de la aplicación, las pruebas no tienen un programa ejecutable, por lo que recurriremos a la genial integración entre Ant y JUnit.

<target name="test" depends="compile-tests"
    description="Realiza las pruebas de jUnit a nuestra aplicación">
    <junit haltonfailure="true">
        <classpath refid="project.classpath" />
        <formatter type="brief" usefile="false" />
        <formatter type="xml" />
        <batchtest todir="${test.results.dir}">
            <fileset dir="${build.test.dir}"
                includes="**/*Test.class" />
        </batchtest>
    </junit>

    <!– Generamos un informe –>
    <junitreport todir="${test.results.dir}">
        <fileset dir="${test.results.dir}">
            <include name="TEST-*.xml" />
        </fileset>
        <report format="noframes" todir="${test.reports.dir}" />
    </junitreport>
</target>

La tarea junit se encarga de realizar las pruebas, mientras que la tarea junitreport generará un informe en tests.report.dir con los resultados de las pruebas. Para la tarea junit pedimos mediante el atributo haltonfailure que cuando se produzca un fallo se aborte la ejecución de Ant, lo que es útil durante el desarrollo para darnos cuenta de los fallos que hemos cometido. Para realizar las pruebas utilizamos todas las clases dentro de build.test.dir que sigan el patrón **/*.Test.class que es el patrón que deben de tener las clases con pruebas de unidad de JUnit. Finalmente cuando se ejecutan las pruebas Ant les proporciona los resultados a los formatters, de los que nosotros hemos definido dos: el brief aparece por pantalla, mientras que el xml es guardado en un fichero en el directorio test.results.dir. Estos son los ficheros que junitreport utiliza más adelante para generar el informe.

No hay más. Desde un pequeño proyecto de una clase a un proyecto mediano, esta plantilla pretende cubrir a la mayoría de ellos. De cualquier forma tengo que avisar de una cosilla: a pesar de que no utilicéis bibliotecas externas o no realicéis pruebas de unidad (mal, mal, mal) los directorios deben existir, ya que si no Ant protestará.

Más adelante veremos como crear targets para las tareas necesarias para utilizar servicios web y para instalarlos en los contenedores de servlets como Tomcat.


7 comentarios a “Aprendiendo a ser una hormiga”

  1. Gravatar sEnC dice:

    O_o

    Pero esto no iba de hormigas?

  2. Gravatar Daniel dice:

    Ya, bueno, creo que al final he desvariado un poco y no me he ceñido al “tema”, pero seguro que pillas un buen documental de hormigas en National Geographic o Discovery Channel (más verdades que yo seguro que dicen).

  3. Gravatar deigote dice:

    Gracias por haberte dado pena. Supongo que sabes que uso un script salchichero escrito en tcsh ¿no? ya sabes que yo soy más de tirar por la vía rápida-cutre para estas cosas que la vía perfeccionista…

  4. Gravatar deigote dice:

    Anda mi madre! ahora tengo un avatar y todo. Guado de bilbado!

  5. Gravatar Daniel dice:

    Conocía tu script, pero ¿sirve para todos los proyectos? Bueno, quizá sí, si lo has hecho muy bien, pero la teoría es que Ant es mejor que un script (sobre todo cuando no vuelve a recompilar lo que no se ha actualizado).

    Lo de los avatares es a través de Gravatar, que hasta hace poco utilizaba también menéame. Creo que Blogger también lo utiliza para sus cuentas. Está bien poder reconocer a la gente de un vistazo.

  6. Gravatar deigote dice:

    Conocía tu script, pero ¿sirve para todos los proyectos?

    Creo que la palabra “salchichero” es lo bastante descriptiva como para saber que ni de coña ;-) pero para este me vale y tardé un minuto en hacerlo. A esto me refiero con la vía cutre y rápida (y no es que me guste esa vía… pero la otra me sigue dando pereza para los “añadidos”). De todas formas lo que has escrito no me vale de mucho para este caso, sin embargo…

    Más adelante veremos como crear targets para las tareas necesarias para utilizar servicios web y para instalarlos en los contenedores de servlets como Tomcat.

    Aquí, aquí esta la chicha, lo bueno, lo que te voy a copiar miserablemente en otro post lo que voy a enlazar en una futura entrada que tenía ganas de hacer pero no tiempo.

    Por cierto lo del gravatar ya lo sabía, pero hace cienes de años que me hice la cuenta y es la primera vez que me funciona. Y otro por cierto, muy bueno y útil la preview al vuelo.

  7. Gravatar Creando ficheros tipo JAR de Java en Unix (sólo clases) at Deigote’s Blog dice:

    [...] Si, ya sé que algunas herramientas como ant hacen este tipo de tareas más fácilmente. [...]

Deja un comentario

Puedes enterarte de las respuestas a tus comentarios de esta entrada mediante myComments.

XHTML: Puedes utilizar las siguientes etiquetas: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tu servidor sin límites: 20GB de espacio, 1TB de transferencia, 1 dominio gratuito. Por 1.5€ al mes utilizando el código "RUIDOBLANCO" en DreamHost. Más información.