Just Sherekan – Blog de Programación



Compartiendo conocimiento… intentando no reinventar la rueda…

Introducción a Ant

Esta es una introducción a la ampliamente utilizada herramienta para construir aplicaciones para entornos Java: Ant. En este artículo vamos a ver como se compone un build file, algunas buenas prácticas que es muy recomendable seguir a la hora de construir uno, y algunos ejemplos de build files para determinados tipos de aplicaciones.

Que es Ant

Es similar a GNU Make

Ant es una herramienta basada en Java que sirve para construir aplicaciones, permitiéndonos automatizar tareas mecánicas y repetitivas como las de compilación, empaquetado, deploy, etc. Es muy similar la conocida herramienta GNU Make pero con la notable diferencia de que Ant es portable y no depende del sistema operativo, por lo que es ideal para entornos Java.

Es portable

La característica principal de Ant, que lo diferencia de otras herramientas como GNU Make, es que es cross-plataform.

Cualquiera que haya programado bajo plataforma Linux, probablemente haya utilizado GNU Make. Y como ya sabrán, esta herramienta se basa en órdenes de shell, obviamente dependientes del sistema operativo. Esta es una gran limitación para el entorno Java, que es multiplataforma.

A diferencia de GNU Make, Ant nos provee de una gran rama de tareas core que podemos configurar mediante build files, que vendrían a ser lo equivalente a los make files de GNU Make, pero escritos en XML.

Está ampliamente difundido

Ant es un proyecto open source mantenido por Apache Software Foundation, y actualmente está ampliamente difundido. Prácticamente todos los IDEs para entornos Java ofrecen integración con Ant, incluso algunos lo utilizan internamente.

Instalación de Ant

Podemos obtener la última versión de Ant de la página del proyecto. Están disponibles las distribuciones binarias y las distribuciones de código fuente, ambas con sus respectivas firmas digitales y documentación de instalación. Por lo que no debería presentarles ningún inconveniente la instalación.

Uso de Ant

Terminología

Ant agrupa las tareas (o tasks) en targets, todo proyecto suele tener varios targets, generalmente uno para compilar, otro para empaquetar, etc. Por ejemplo, un target para compilar, llamémoslo compile, tendría varias tareas, una que verifica las dependencias, otra que crea los directorios donde se van a guardar los binarios, otra que invoca al compilador, etc.

Es decir, en Ant, a diferencia de otras herramientas, las órdenes de shell para copiar ficheros, compilar código, ejecutar aplicaciones, etc. están “encapsuladas” en tareas/tasks. Y estas tareas están agrupadas en targets.

Todos estos targets, con sus tasks, etc. se definen en un build file, llamado build.xml, que no es más que un documento XML mediante el cual le indicamos a Ant que hacer.

Ejemplo

Este es un ejemplo de build.xml:

[sourcecode language="xml"]
<project>

<target name="clean">
<delete dir="build"/>
</target>

<target name="compile">
<mkdir dir="build/classes"/>
<javac srcdir="src" destdir="build/classes"/>
</target>

<target name="jar">
<mkdir dir="build/jar"/>
<jar destfile="build/jar/HelloWorld.jar" basedir="build/classes">
<manifest>
<attribute name="Main-Class" value="oata.HelloWorld"/>
</manifest>
</jar>
</target>

<target name="run">
<java jar="build/jar/HelloWorld.jar" fork="true"/>
</target>

</project>
[/sourcecode]

Como podrán ver, es bastante intuitivo, hay 4 targets (clean, compile, jar y run). El target clean tiene una tarea delete para eliminar el directorio build, el target compile tiene dos tareas, una que crea el directorio build/classes y otra para invocar al compilador, el target jar tiene una tarea para crear el directorio build/jar y otra para crear el fichero JAR, y finalmente el target run tiene una tarea que ejecuta la aplicación.

Para que Ant procese el build file, simplemente lo invocamos así:

ant [target]

Siendo [target] un argumento opcional, para indicarle a Ant que target ejecutar. En caso de no especificar ninguno se ejecuta el target por default (que en un momento vamos a ver como configurar).

Build files

Ahora vamos a ver como se compone un build file en un poco más de detalle, pero no demasiado (para eso existe la documentación).

Projects

Cada build file debe tener al menos un tag project. Este tag tiene 3 atributos opcionales:

  • name: el nombre del proyecto.
  • default: el target por default, que se va a ejecutar en caso de que no se especifique ningún target.
  • basedir: el path que se va a usar como basedir para todos los paths a lo largo del build file. (Puede sobreescribirse más adelante).

Targets

Dentro de cada tag project, puede haber cero o más tags target. Los targets como ya dijimos agrupan tareas, y se definen mediante tags target. Este tag tiene un atributo requerido:

  • name: el nombre del target, debe ser único ya que sirve para identificarlo y referirse a él más tarde.

Y también tiene otros atributos opcionales que suelen ser útiles:

  • description: una breve descripción de lo que hace el target. (Es buena práctica colocarla).
  • depends: los targets de los cuales depende el target, Ant se encarga de resolver las dependencias ejecutando esos targets antes de ejecutar el target. (Se indican los nombres separados por coma).
  • if: se ejecuta sólo si está seteada una determinada propiedad (en un rato vamos a ver lo que son las propiedades, pero mientras tanto ya se lo pueden ir imaginando).
  • unless: lo mismo que el anterior pero al revés (sólo si la propiedad no está seteada).

Tasks

Dentro de los targets se encuentran las tareas o tasks, estas como ya dijimos, a diferencia de otras herramientas como GNU Make, donde se crean mediante shell scripting, en Ant en cambio existe una gran cantidad de core tasks, que generalmente nos serán más que suficientes para lo que necesitamos, y como Ant está 100% basado en Java están garantizadas para hacer exactamente lo mismo sin importar el sistema operativo donde se ejecuten. Igualmente, en caso de que estas tareas core no sean suficientes, Ant nos permite crear nuestras propias tareas usando shell scripting, de hecho existen muchas tareas third party dando vueltas. Claro que estas no cuentan con la ventaja de la portabilidad.

Las tareas se definen así:

[sourcecode language="xml"]
<nombre_tarea atributo1="valor1" atributo2="valor2" … />
[/sourcecode]

En la documentación oficial de Ant, podemos encontrar esta lista de core tasks, que como podrán ver es bastante completa y detallada. En este artículo sólo vamos a ver algunas, sólo las más comunes.

  • javac: sirve para compilar utilizando el compilador javac. Tiene varios atributos, la mayoría mapeados con los argumentos que acepta javac:
    • srcdir: el path donde está el código fuente a compilar.
    • destdir: el path donde se van a guardar los binarios.
    • classpath: el classpath.

    [sourcecode language="xml"]
    <javac srcdir="src"
    destdir="bin"
    classpath="lib/mycommons.jar"
    />
    [/sourcecode]

  • mkdir, delete, copy: sirven para crear, eliminar y copiar directorios y ficheros. No necesitan demasiada explicación, veamos algunos ejemplos:
    [sourcecode language="xml"]
    <delete dir="build/bin" />
    <mkdir dir="build/bin" />
    <copy file="myfile.xml" todir="../some/other/dir" />
    [/sourcecode]
  • jar: sirve para crear ficheros JAR, tiene dos atributos:
    • jarfile: el nombre del fichero JAR a crear.
    • basedir: el path donde están los ficheros a comprimir en el JAR.

    [sourcecode language="xml"]
    <jar jarfile="build/dist/myapp.jar"
    basedir="build/bin" />
    [/sourcecode]

Bueno, hay muchas más tareas que vienen como core en Ant, esas sólo son las más utilizadas. Pero hay muchas más para trabajar con el sistema de ficheros (mover ficheros, cambiar permisos, etc.), obtener revisiones de repositorios SVN, CVS… deployar aplicaciones en contenedores J2EE, correr tests unitarios con JUnit, etc.

Properties

Es una buena práctica definir ciertas propiedades, que contengan paths y otros valores que se puedan reutilizar a lo largo del build file. Estas propiedades se definen mediante el tag property, así:

[sourcecode language="xml"]
<property name="dir.bin" value="dist/bin" />
[/sourcecode]

También podemos definir las propiedades en un fichero externo y llamarlo desde Ant así:

[sourcecode language="xml"]
<property file="ant.properties"/>
[/sourcecode]

Este fichero de propiedades debe tener el siguiente formato:

[sourcecode language="xml"]
propiedad=valor
[/sourcecode]

Se puede acceder a estas propiedades desde cualquier lugar del build file mediante:

[sourcecode language="xml"]
${nombre_propiedad}
[/sourcecode]

También es útil poder acceder a variables de entorno del sistema, esto se hace así:

[sourcecode language="xml"]
${env.nombre_variable}
[/sourcecode]

Podemos asignarle a una propiedad el valor de una variable de entorno, así:

[sourcecode language="xml"]
<property name="JAVA_HOME" value="${env.JAVA_HOME}" />
[/sourcecode]

Buenas prácticas

Hay ciertas prácticas recomendables para construir build files que deberíamos siempre tener en cuenta:

  • Un sólo build file: algunas personas prefieren partir el build file en varios pequeños build files, esto si está bien hecho puede ser útil, pero hay veces que termina agregando complejidad innecesaria. De cualquier manera, siempre es recomendable tener un build file en el root directory que delegue el trabajo a los demás build files.
  • Colocar build file en el directorio raíz: aunque el build file puede colocarse en cualquier lugar, es recomendable colocarlo en el directorio raíz del proyecto. Es una convención bastante común, ya que los paths relativos son más fáciles de entender, y además así no hace falta cambiar el working directory para invocar a ant, pudiendo hacer esto:
    ant -find compile
    

    El argumento find le dice a Ant que busque el build file en los directorios padre.

  • Proveer ayuda útil: hay tres cosas que deberían utilizarse para proveer buena ayuda:
    • Especificar atributos description para los targets.
    • Proveer un target help para los desarrolladores.
    • Escribir XML comments en el build file.
  • Proveer target clean: es recomendable crear un target clean que se encargue de eliminar todos los ficheros y directorios generados. (Volviendo todo a su estado original, tal como estaba en el repositorio antes de invocar ningún target).

    NOTA: no es recomendable autoinvocar este target, sino que es preferible que el desarrollador lo invoque cuando lo crea conveniente.

  • Manejar dependencias con Ant: una aplicación Java lo considerablemente compleja, podría consistir por ejemplo en una interfaz web, una capa EJB, una GUI y algo de utility code. A medida que el sistema crece, hay que compilar más ficheros cada vez que se cambia algo. Imagínense que no tiene mucho sentido recompilar los componentes EJB y los servlets cada vez que se cambia algo de la GUI.

    Para evitar esto, se debe hacer un manejo consistente de las dependencias y Ant simplifica bastante esta tarea. Por un lado, mediante un buen uso del atributo depends de los targets, se puede lograr que los desarrolladores no tengan que ejecutar los targets en un orden específico. Por otro lado, se debe diseñar el build file para que se compile la aplicación por partes, separando bien por ejemplo el utility code de los componentes de la GUI.

  • Definir paths como propiedades: para favorecer la reutilización de paths, es una buena práctica definir propiedades que guarden todos estos paths al principio del build file.
  • Mantener el build file self contained: es preferible no referirse a paths y librerías externas si se quiere mantener la portabilidad. Y en caso de ser necesario hacerlo, se deberían utilizar propiedades para que estos paths puedan configurarse fácilmente.
  • Usar propiedades para la configuración: todo lo que necesite ser configurado dentro del build file, debería ser definido con propiedades. Generalmente se colocan al principio del fichero, o bien en un fichero aparte que se incluye mediante:
    [sourcecode language="xml"]
    <property file="myproject.properties"/>
    [/sourcecode]
  • Mantener build file bajo control de versiones: es importante que el build file sea versionado al igual que el resto de los ficheros, ya que esto permite que si se vuelve a una revisión anterior, se pueda construir la aplicación sin problemas de dependencias, etc.

Integración de IDEs con Ant

Hoy en día casi todos los IDEs para entornos Java se integran con Ant, ofreciéndonos editores, generadores automáticos de build files con targets plantilla, entre otras cosas bastante útiles como debuggers para Ant, etc.

Una de las ventajas de esta integración, es que en un equipo de desarrollo, cada desarrollador puede utilizar su IDE preferido, ya que Ant actúa como denominador común.

Ejemplos con Ant

Veamos un ejemplo de build file para un proyecto web con Tomcat:

[sourcecode language="xml"]
<project name="My Project" default="all" basedir=".">

<property name="app.name" value="myapp"/>
<property name="app.path" value="/${app.name}"/>
<property name="app.version" value="0.1-dev"/>
<property name="build.home" value="${basedir}/build"/>
<property name="catalina.home" value="${env.CATALINA_HOME}"/>
<property name="dist.home" value="${basedir}/dist"/>
<property name="docs.home" value="${basedir}/docs"/>
<property name="manager.url" value="http://localhost:8080/manager"/>
<property name="src.home" value="${basedir}/src"/>
<property name="web.home" value="${basedir}/web"/>

<path id="compile.classpath">
<pathelement location="${catalina.home}/common/classes"/>
<fileset dir="${catalina.home}/common/endorsed">
<include name="*.jar"/>
</fileset>
<fileset dir="${catalina.home}/common/lib">
<include name="*.jar"/>
</fileset>
<pathelement location="${catalina.home}/shared/classes"/>
<fileset dir="${catalina.home}/shared/lib">
<include name="*.jar"/>
</fileset>
<fileset dir="${catalina.home}/shared/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="all"
depends="clean,compile"
description="Clean build and dist directories, then compile"/>

<target name="clean"
description="Delete old build and dist directories">
<delete dir="${build.home}"/>
<delete dir="${dist.home}"/>
</target>

<target name="compile"
depends="prepare"
description="Compile Java sources">
<mkdir dir="${build.home}/WEB-INF/classes"/>
<javac srcdir="${src.home}"
destdir="${build.home}/WEB-INF/classes"
debug="${compile.debug}"
deprecation="${compile.deprecation}"
optimize="${compile.optimize}">
<classpath refid="compile.classpath"/>
</javac>
<copy todir="${build.home}/WEB-INF/classes">
<fileset dir="${src.home}" excludes="**/*.java"/>
</copy>
</target>

<target name="dist"
depends="compile,javadoc"
description="Create binary distribution">
<mkdir dir="${dist.home}/docs"/>
<copy todir="${dist.home}/docs">
<fileset dir="${docs.home}"/>
</copy>
<jar jarfile="${dist.home}/${app.name}-${app.version}.war"
basedir="${build.home}"/>
</target>

<target name="install"
depends="compile"
description="Install application to servlet container">

<install url="${manager.url}"
username="${manager.username}"
password="${manager.password}"
path="${app.path}"
war="file://${build.home}"/>
</target>

<target name="javadoc" depends="compile"
description="Create Javadoc API documentation">
<mkdir dir="${dist.home}/docs/api"/>
<javadoc sourcepath="${src.home}"
destdir="${dist.home}/docs/api"
packagenames="*">
<classpath refid="compile.classpath"/>
</javadoc>
</target>

<target name="list"
description="List installed applications on servlet container">
<list url="${manager.url}"
username="${manager.username}"
password="${manager.password}"/>
</target>

<target name="prepare">
<mkdir dir="${build.home}"/>
<mkdir dir="${build.home}/WEB-INF"/>
<mkdir dir="${build.home}/WEB-INF/classes"/>
<copy todir="${build.home}">
<fileset dir="${web.home}"/>
</copy>
<mkdir dir="${build.home}/WEB-INF/lib"/>
</target>

<target name="reload"
depends="compile"
description="Reload application on servlet container">
<reload url="${manager.url}"
username="${manager.username}"
password="${manager.password}"
path="${app.path}"/>
</target>

<target name="remove"
description="Remove application on servlet container">
<remove url="${manager.url}"
username="${manager.username}"
password="${manager.password}"
path="${app.path}"/>
</target>

</project>
[/sourcecode]

Otro ejemplo. Este es un build file para un proyecto EJB con JBoss:

[sourcecode language="xml"]
<project name="ejb3-project" default="jar" basedir=".">

<property environment="env" />
<property name="src.dir" value="${basedir}/src/main" />
<property name="src.resources" value="${basedir}/src/main/resources" />
<property file="${user.home}/.ant.properties"/>
<property name="jboss.home" value="${env.JBOSS_HOME}" />
<property name="jboss.server" value="default"/>
<property name="build.dir" value="${basedir}/target" />
<property name="build.classes.dir" value="${build.dir}/classes" />
<property name="jboss.base" value="${jboss.home}/server/${jboss.server}"/>
<property name="deploy.dir" value="${jboss.base}/deploy"/>

<path id="classpath">
<fileset dir="${jboss.base}/lib">
<include name="*.jar" />
</fileset>
<fileset dir="${deploy.dir}/ejb3.deployer">
<include name="*.jar" />
</fileset>
<fileset dir="${deploy.dir}/jboss-aop-jdk50.deployer">
<include name="*.jar" />
</fileset>
<fileset dir="${jboss.home}/lib">
<include name="*.jar" />
</fileset>
<pathelement location="${build.classes.dir}" />
</path>

<property name="build.classpath" refid="classpath" />

<target name="prepare">
<mkdir dir="${build.dir}" />
<mkdir dir="${build.classes.dir}" />
</target>

<target name="compile" depends="prepare">
<javac srcdir="${src.dir}" destdir="${build.classes.dir}" debug="on" deprecation="on" optimize="off" includes="**">
<classpath refid="classpath" />
</javac>
</target>

<target name="jar" depends="compile">
<jar jarfile="${build.dir}/${ant.project.name}.jar">
<fileset dir="${build.classes.dir}">
<include name="**/*.class" />
</fileset>
<fileset dir="${src.resources}/">
<include name="META-INF/persistence.xml" />
</fileset>
</jar>
</target>

<target name="deploy" depends="jar">
<copy file="${build.dir}/${ant.project.name}.jar" todir="${deploy.dir}" />
</target>

<target name="run.client" depends="deploy">
<java classname="com.uberdose.Client" fork="yes" dir=".">
<classpath refid="classpath" />
</java>
</target>

<target name="clean.db">
<delete dir="${jboss.base}/data/hypersonic" />
</target>

<target name="clean">
<delete dir="${build.dir}" />
<delete file="${deploy.dir}/${ant.project.name}.jar" />
</target>

</project>
[/sourcecode]

8 comentarios

8 comentarios

  1. Pedro SPAIN Junio 22nd, 2009 2:18 am

    Bastante completo.

    ¿Existe alguna herramienta visual para construir estos ficheros?

    Por otra parte ,has probado Grand y Vizan ?
    Me comentaron que sirven para crear un grafo de dependencias del build de ant.

    http://www.ggtools.net/grand/

  2. alberto MEXICO Junio 22nd, 2009 3:59 am

    Hace algunos años estaba buscando un tuto sobre Ant y jamas lo encontré. Muy buen artículo felicidades.

  3. Tomiito ARGENTINA Junio 23rd, 2009 11:05 pm

    Perdon por el off topic.. se q deberia ir en tu sector sobre vos pero queria q lo leas :P

    1) Te felicito por tus logros.. acabo de ver q lo que creia imposible se hizo realidad… te juro que
    pensaba que no iba a conocer nunca una programadora que realmente sienta lo que hace…..

    2) No puedo creer que encima conozcas aperturas de ajedrez.. y hayas leido el eternauta.. ¿q por cierto tiene segunda parte?
    – otra.. cuando fui a bariloche hice snowboard.. te lo recomiendo .. es el mejor deporte que practique en la vida.. y eso que me gusta muchisimo el futbol..

    3) Tmb soy programador java, tengo 19 años, trabajo de eso en devoto, Bs As, y diseño una aplicacion de workflow para mi empresa ( Apache Tomcat, con JSF, Spring e Hibernate – MySQL, en un Red Hat) … bueno .. perdon otra vez por q ya parece mi presentacion mas q tu blog.. xd

    queria comentarte hace unas semanas rendi el examen SCJP 5, y mis resultados no fueron como los tuyos (saque un 48%) triste pero bue… creo que se lo debo a mi excesiva confiaza.. y a mis ganas por divertirme con tecnologias como el EJB, y JSF Richfaces, que no me permitieron estudiar… pero me parece que voy a seguir tus recomendaciones .. y comprar el libro.. y ponerme a estudiar… :P para rendirlo nuevamente.. ya q tengo la suerte que mi empresa lo paga :)

    Una pregunta.. aprendiendo tanto de Java, y todo lo que esta marivollosa tecnologia ofrece.. trabajas con php por q se te dio .. o por q realmente te gusta.. ?

    (sin ofender a los programadores de php.,, reconozco que es una tecnologia muy util y MUY utilizada, pero las hermosuras de q brinda la arquitectura propuesta por java y su utilizacion de patrones es otra cosa q puede gustarte o no)

    Saludos..

    PD: me voy a leer este tuto de ant.. q te juro q por culpa del netbeans no se nada :P

  4. Tomiito ARGENTINA Junio 23rd, 2009 11:05 pm

    me fui al carajo con el post xd

  5. Carlos SPAIN Junio 24th, 2009 7:08 am

    Buen post, como siempre. Saludos.

  6. Juanjo SPAIN Agosto 14th, 2009 5:44 am

    Hola

    Me encanta tu pagina es muy buena.

    Gracias!

  7. Edilmar MEXICO Agosto 28th, 2009 1:25 am

    omfg no podia creer lo de tu edad y sexo sorry pero soy algo machista y se que esta mal lo acepto, pero me sorprendiste DEMASIADO!, llegue a tu web investigando como usar el ant y me sirvio de maravilla, directo a mis favoritos.

  8. yiyux CHILE Noviembre 17th, 2009 6:42 pm

    Muy bueno el tutorial. Felicitaciones desde Chile.

Dejá una respuesta

Mexico