Manual básico de ANT

De Wikilibros, la colección de libros de texto de contenido libre.

Apache Ant es una herramienta usada en programación (véase Fundamentos de programación) para la realización de tareas mecánicas y repetitivas, normalmente durante la fase de compilación y construcción (build). Es similar a Make pero desarrollado en lenguaje Java y requiere la plataforma Java.

Esta herramienta, hecha en el lenguaje de programación Java, tiene la ventaja de no depender de las órdenes del shell de cada sistema operativo, sino que se basa en archivos de configuración XML y clases Java para la realización de las distintas tareas, siendo idónea como solución multi-plataforma.

Ejecución de Apache Ant[editar]

¿Qué se necesita para ejecutar ANT?[editar]

Para utilizar ANT basta con disponer de una distribución binaria de ANT y tener instalado la versión 1.4 o superior del JDK. La distribución binaria consiste en la siguiente estructura de directorios:

ant
  +--- bin  // contains launcher scripts
  |
  +--- lib  // contains Ant jars plus necessary dependencies
  |
  +--- docs // contains documentation
  |      +--- ant2    // a brief description of ant2 requirements
  |      |
  |      +--- images  // various logos for html documentation
  |      |
  |      +--- manual  // Ant documentation (a must read ;-)
  |
  +--- etc // contains xsl goodies to:
           //   - create an enhanced report from xml output of various tasks.
           //   - migrate your build files and get rid of 'deprecated' warning
           //   - ... and more ;-)

De dichos directorios, sólo se necesitan bin y lib para ejecutar ANT.

Algunas consideraciones a tener en cuenta[editar]

  • Al ser una herramienta basada en XML, los archivos Ant deben ser escritos en XML.
  • La mayoría de las antiguas herramientas — las que se usan todos los días, como <javac>, <exec> y <java> — tienen malas configuraciones por defecto, valores para opciones que no son coherentes con las tareas más recientes.
  • Cuando se expanden las propiedades en una cadena o un elemento de texto, las propiedades no definidas no son planteadas como error, sino que se dejan como una referencia sin expandir (ej.: ${unassigned.property}).
  • No es un lenguaje para un flujo de trabajo general, y no debería ser usado como tal.

Ejecución de ANT[editar]

Para ejecutar ANT basta con escribir ant en la línea de comandos. Si se ha especificado la ejecución de un objetivo por defecto en la cabecera del proyecto del fichero build.xml no es necesario el uso de ningún parámetro ya que, como acabamos de ver en el ejemplo anterior, se ejecutara este por defecto. Para utilizar un buildfile diferente de build.xml hay que añadir el parámetro -buildfile nombre_fichero, donde nombre_fichero es el buildfile que se quiere utilizar:

ant -buildfile buildfile_alt.xml

Si se desea ejecutar uno o varios objetivos concretos, se debe añadir el nombre de este o estos objetivos en la línea de comandos vv:

ant clean

En este caso se ignorará el objetivo indicado en la cabecera del proyecto.

Configuración[editar]

Una vez obtenida la anterior distribución de ANT, se deben seguir los siguientes pasos:

- Añadir el directorio "bin" al path. - Asignar a la variable de entorno "ANT_HOME" la ruta donde se ha instalado ANT. - Opcionalmente, asignar a la variable de entorno "JAVA_HOME" la ruta donde se ha instalado el JDK.

Dependiendo del sistema operativo en el que nos encontremos tendremos los siguientes ejemplos de configuración:

Windows y OS/2
(Suponiendo que se ha instalado ANT en c:\ant\)

set ANT_HOME=c:\ant
set JAVA_HOME=c:\jdk1.2.2
set PATH=%PATH%;%ANT_HOME%\bin


Unix (bash)
(Suponiendo que se ha instalado ANT en /usr/local/ant)

export ANT_HOME=/usr/local/ant
export JAVA_HOME=/usr/local/jdk-1.2.2
export PATH=${PATH}:${ANT_HOME}/bin

Unix (csh)
setenv ANT_HOME /usr/local/ant
setenv JAVA_HOME /usr/local/jdk-1.2.2
set path=( $path $ANT_HOME/bin )

Estructura de un fichero build.xml[editar]

Los ficheros buildfiles de ANT están escritos en XML. Cada buildfile contiene un proyecto (project) y al menos un objetivo (target). Cada objetivo puede contener varias tareas (task) que son fragmentos de código a ejecutar. Un proyecto, además, puede constar de diversas propiedades. Cada propiedad consta de nombre y valor y son usadas para asignar valores a los atributos de los task.

La estructura de un fichero buildfile podría ser la siguiente:

buildfile
  +--- project // proyecto del buildfile
        +
        +--- property_1
        +--- property_2
        +--- target_1    // objetivo_1
        |    |
        |    +--- task_1  // tarea 1 de target_1  
        |
        +--- target_2  // objetivo_2
        |    |
        |    +--- task_1  // tarea 1 de target_2
        |    +--- task_2  // tarea 2 de target_2
        |
        +--- target_3  // objetivo_3
             |
             +--- task_1  // tarea 1 de target_3
        

Ejemplo de buildfile[editar]

A continuación se muestra un ejemplo de un archivo build.xml:

<project name="MyProject" default="dist" basedir=".">
   <description>
       simple example build file
   </description>
 <property name="src" location="src"/>
 <property name="build" location="build"/>
 <property name="dist"  location="dist"/>
 <target name="init">
   <tstamp/>
   <mkdir dir="${build}"/>
 </target>
 <target name="compile" depends="init" description="compile the source " >
   <javac srcdir="${src}" destdir="${build}"/>
 </target>
 <target name="dist" depends="compile" description="generate the distribution" >
   <mkdir dir="${dist}/lib"/>
   <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
 </target>
 <target name="clean" description="clean up" >
   <delete dir="${build}"/>
   <delete dir="${dist}"/>
 </target>
</project>


La primera línea se corresponde con el proyecto que compone el buildfile, en ella pueden aparecer los siguientes atributos:

- name    = "MyProject": identifica el nombre del proyecto
- default = "dist": identifica el objetivo que se ejecuta por defecto, en caso de no especificarle uno en concreto.
- basedir = ".": directorio base sobre el que vamos a trabajar. En este ejemplo se trabajaría sobre el directorio actual.

A continuación aparecen un listado de propiedades, su declaración se compone del literal property y los atributos name para especificar el nombre y location para asignarles un valor.

<property name="src" location="src"/>


Lo siguiente que aparece en el ejemplo son los objetivos. El primero de ellos es init:

<target name="init">
   <tstamp/>
   <mkdir dir="${build}"/>
 </target>

Como se mencionó anteriormente, un objetivo esta compuesto por una o varias tareas, en este caso tenemos las tareas tstamp y mkdir, las cuales muestran la fecha y crean el directorio descrito por la propiedad build respectivamente.

El siguiente objetivo que aparece es compile

<target name="compile" depends="init" description="compile the source " >
   <javac srcdir="${src}" destdir="${build}"/>
</target>

En la declaración del objetivo podemos ver los siguiente atributos:

- name        = "compile": identifica el nombre del objetivo
- depends     = "init": identifica el objetivo que se debe ejecutar antes de lanzarse el actual. 
En este caso se debe ejecutar previamente el objetivo init (mostrar la fecha del sistema y crear un directorio) antes de ejecutar este objetivo.
- description = "compile the source": breve descripción de lo que hace el objetivo.

El cuerpo del objetivo está formado por la tarea javac, que toma los ficheros fuente ubicados en la ruta indicada por el atributo srcdir y los compila en destdir.

El siguiente objetivo que compone el buildfile es dist.

<target name="dist" depends="compile" description="generate the distribution" >
   <mkdir dir="${dist}/lib"/>
   <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
</target>

En este caso, dist depende del objetivo compile para ejecutarse, luego, antes de lanzar sus tareas se debe haber ejecutado compile. En este objetivo aparece la tarea mkdir descrita anteriormente y jar, esta tarea construye un jar con el nombre MyProject concatenado con la fecha del sistema (DSTAMP) a partir de los compilados en el directorio indicado por la propiedad build.


Por ultimo tenemos el objetivo clean

<target name="clean" description="clean up" >
   <delete dir="${build}"/>
   <delete dir="${dist}"/>
</target>

Compuesto por 2 tareas delete. Simplemente borra los directorios temporales necesarios para la creación del jar.

Si repasamos la cabecera del proyecto, vemos que por defecto llama al objetivo dist, el cual depende de compile y este a su vez de init, por lo que el orden lógico de ejecución de este build.xml es:

 Ejecutar objetivo init
 - mostrar fecha de sistema
 - crear directorio build
Ejecutar objetivo compile - compilar fuentes de src y guardarlos en build
Ejecutar objetivo dist - crear directorio lib - crear jar a partir de los compilados

El objetivo clean no depende del resto y tampoco se especifica en la cabecera del proyecto, por lo que se debe referenciar de forma explicita en la línea de comandos para que se ejecute.

Otro ejemplo de build.xmlpuede ser el siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<project name="projetWar" basedir="." default="checkstyle">

	<property name="base.dir" value="../" />
	<property name="baseTool.dir" value="../../outils/" />

	<property name="src.dir" value="${base.dir}src" />
	<property name="build.dir" value="${base.dir}webapp/WEB-INF/classes" />
	<property name="doc" location="${base.dir}doc" />
	<property name="lib" location="${base.dir}lib" />

	<property name="reports" value="${baseTool.dir}/rapports" />
	<property name="classpath" value="${build.dir}" />

	<taskdef resource="checkstyletask.properties" classpath="${baseTool.dir}checkstyle-4.0-beta6/checkstyle-all-4.0-beta6.jar" />

	<target name="reports" depends="checkstyle, classcycle, findbugs, jdepend">
		<echo>Génération des rapports terminé.</echo>
	</target>

	<target name="jdepend">
		<delete dir="${reports}/jdepend/" />
		<mkdir dir="${reports}/jdepend/" />
		<java classname="jdepend.xmlui.JDepend" fork="yes">
			<classpath>
				<fileset dir="${baseTool.dir}/jdepend/" includes="**/*.jar" />
			</classpath>
			<arg value="-file" />
			<arg value="${reports}/jdepend/jdepend-report.xml" />
			<arg value="${build.dir}" />
		</java>

		<style basedir="${reports}/jdepend/" destdir="${reports}/jdepend/" includes="jdepend-report.xml" style="${baseTool.dir}/jdepend/jdepend.xsl" />
	</target>

	<taskdef classpath="${baseTool.dir}findbugs-0.9.1/lib/findbugs-ant.jar" name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask" />

	<property name="findbugs.home" value="${baseTool.dir}findbugs-0.9.1/" />

	<target name="findbugs">
		<delete dir="${reports}/findbugs/" />
		<mkdir dir="${reports}/findbugs/" />
		<findbugs home="${findbugs.home}" output="html" outputFile="${reports}/findbugs/bcel-fb.html">
			<classpath>
			</classpath>
			<sourcePath path="${src.dir}" />
			<class location="${build.dir}" />
		</findbugs>

	</target>

	<taskdef classpath="${baseTool.dir}/Classycle/classycle.jar" name="classycleReport" classname="classycle.ant.ReportTask" />

	<target name="classcycle">
		<delete dir="${reports}/classycle/" />
		<mkdir dir="${reports}/classycle/" />
		<classycleReport reportFile="${reports}/classycle/classycle.xml">
			<fileset dir="${build.dir}">
				<include name="**/*.class" />				
			</fileset>
		</classycleReport>

		<style in="${reports}/classycle/classycle.xml" out="${reports}/classycle/classycle.html" style="${baseTool.dir}/Classycle/reportXMLtoHTML.xsl" />
		<copydir dest="${reports}/classycle/images" src="${baseTool.dir}/Classycle/images">
		</copydir>
	</target>

	<target name="init">
		<!-- création d'un répertoire pour la compilation -->
		<mkdir dir="${build.dir}" />
	</target>

	<target name="checkstyle" description="Generates a report of code convention violations.">
		<delete dir="${reports}/checkstyle/" />
		<mkdir dir="${reports}/checkstyle/" />
		<checkstyle config="${baseTool.dir}/checkstyle-4.0-beta6/sun_checks.xml" failureProperty="checkstyle.failure" failOnViolation="false">
			<classpath>
				<!-- use the value of the ${classpath} property in the classpath -->
				<pathelement path="${classpath}" />
				<!-- include all jar files  -->
				<fileset dir="${lib}">
					<include name="**/*.jar" />
				</fileset>
				<fileset dir="\var\www\jakarta-tomcat-4.0.6-LE-jdk14\common\lib">
					<include name="**/*.jar" />
				</fileset>
			</classpath>
			<formatter type="xml" tofile="${reports}/checkstyle/checkstyle_report.xml" />
			<fileset dir="${src.dir}">
				<include name="**/*.java" />
			</fileset>
		</checkstyle>
		<style in="${reports}/checkstyle/checkstyle_report.xml" out="${reports}/checkstyle/index.html" style="${baseTool.dir}/checkstyle-4.0-beta6/contrib/checkstyle-noframes-sorted.xsl" />
	</target>

	<target name="compil" depends="cleanTomcat">
		<echo>Compilation des sources java</echo>
		<!-- compilation des sources Java -->
		<javac srcdir="${src.dir}" destdir="${build.dir}">
			<classpath>
				<!-- use the value of the ${classpath} property in the classpath -->
				<pathelement path="${classpath}" />
				<!-- include all jar files  -->
				<fileset dir="${lib}">
					<include name="**/*.jar" />
				</fileset>
				<fileset dir="\var\www\jakarta-tomcat-4.0.6-LE-jdk14\common\lib">
					<include name="**/*.jar" />
				</fileset>
			</classpath>
		</javac>
	</target>

	<target name="compilJUnit">
		<echo>Compilation des sources Junit</echo>
		<!-- compilation des sources Java -->
		<javac srcdir="tests" destdir="tests">
			<classpath path="c:/eclipse3.1-WTP/plugins/org.junit_3.8.1/">
				<!-- use the value of the ${classpath} property in the classpath -->
				<pathelement path="${classpath}" />
				<!-- include all jar files  -->
				<fileset dir="${lib}">
					<include name="**/*.jar" />
				</fileset>
				<fileset dir="\var\www\jakarta-tomcat-4.0.6-LE-jdk14\common\lib">
					<include name="**/*.jar" />
				</fileset>
			</classpath>
		</javac>
	</target>

	<target name="doc" depends="compil" description="Generation de la documentation">
		<delete dir="${doc}/" />
		<mkdir dir="${doc}/" />
		<javadoc destdir="${doc}/" author="true" version="true" use="true" package="true">
			<fileset dir="${src.dir}">
				<include name="**/*.java" />
				<exclude name="**/*Test*" />
			</fileset>
			<classpath>
				<pathelement path="${java.class.path}" />
			</classpath>
		</javadoc>
	</target>

	<target name="clean">
		<delete dir="${build.dir}" />
	</target>

	<target name="all" depends="init ,compil, doc" description="Generation complete">
		<echo message="Generation complete." />
	</target>

	<taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" classpath="${baseTool.dir}/pmd-bin-3.3/pmd-3.3/lib/pmd-3.3.jar" />

	<target name="pmd">
		<delete dir="${reports}/pmd/" />
		<mkdir dir="${reports}/pmd/" />
		<taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" classpath="${baseTool.dir}/pmd-bin-3.3/pmd-3.3/lib/pmd-3.3.jar" />
		<pmd rulesetfiles="rulesets/optimizations.xml">
			<formatter type="xml" toFile="${reports}/pmd/report_pmd.xml"/>
			<fileset dir="${src.dir}">
					<include name="**/*.java" />				
			</fileset>
		</pmd>
		<xslt in="${reports}/pmd/report_pmd.xml" style="${baseTool.dir}/pmd-bin-3.3/pmd-3.3/etc/xslt/pmd-report-per-class.xslt" out="${reports}/pmd/report_pmd.html" />
	</target>

</project>

Ejemplo para una aplicación Hola Mundo[editar]

Fichero de ejemplo build.xml[editar]

Debajo se muestra un archivo de ejemplo (build.xml) para una aplicación "Hola mundo" en Java. El archivo define cuatro objetivos - clean, clobber, compile y jar, cada uno de los cuales tiene una descripción asociada. El objetivo jar lista el objetivo compile como dependencia. Esto le dice a ANT que, antes de empezar el objetivo jar, debe completar el objetivo compile.

Dentro de cada objetivo están las acciones que debe tomar ANT para construirlo (el objetivo); éstas se realizan usando tareas (tasks) incluidas (built-in). Por ejemplo, para construir el objetivo compile, ANT debe primero crear un directorio llamado classes (ANT sólo lo hará si éste no existe previamente) y luego llamar al compilador de Java. Por lo tanto, las tareas utilizadas son mkdir y javac. Éstos realizan una tarea similar a las utilidades de línea de comandos del mismo nombre.

Otra tarea usada en este ejemplo se llama jar:

 <jar destfile="hello.jar">

Esta tarea de Apache Ant se llama igual que la utilidad de línea de comandos común de Java, JAR, pero es en realidad una llamada al soporte de archivos jar/zip de Ant. Este detalle no es relevante para la mayoría de los usuarios finales, que obtienen el JAR que querían, con los archivos que pidieron.

Muchas de las tareas Ant delegan su trabajo a los programas externos, ya sean nativos o Java. Utilizan <exec> propia Ant y tareas <java> configurar las líneas de comandos, y manejar todos los detalles de la cartografía (mapeo) de la información en el fichero de construcción a los argumentos del programa y la interpretación del valor de retorno. Los usuarios pueden ver a las tareas hacer esto (por ejemplo, <cvs>, <signjar>, <chmod>, <rpm>), al tratar de ejecutar la tarea en un sistema sin el programa subyacente en la ruta del sistema de archivos, o sin un conjunto de desarrollo Java (JDK) completo instalado.

<?xml version="1.0"?>
<project name="Hello" default="compile">
    <target name="clean" description="borrar archivos temporales">
        <delete dir="classes"/>
    </target>
    <target name="clobber" depends="clean" description="eliminar todos los archivos artefacto">
        <delete file="hello.jar"/>
    </target>
    <target name="compile" description="compilar el código java a un archivo class">
        <mkdir dir="classes"/>
        <javac srcdir="." destdir="classes"/>
    </target>
    <target name="jar" depends="compile" description="crear un archivo Jar para la aplicación">
        <jar destfile="hello.jar">
            <fileset dir="classes" includes="**/*.class"/>
            <manifest>
                <attribute name="Main-Class" value="HelloProgram"/>
            </manifest>
        </jar>
    </target>
</project>

Portabilidad[editar]

Una de las primeras ayudas de Ant fue solucionar los problemas de portabilidad de Make. En un Makefile las acciones necesarias para crear un objetivo se especifican como órdenes de Intérprete de comandos que son específicos de la plataforma, normalmente un shell de UNIX. Ant resuelve este problema proveyendo una gran cantidad de funcionalidades por él mismo, que pueden garantizar que permanecerán (casi) idénticas en todas las plataformas.

Por ejemplo, en el ejemplo build.xml debajo del objetivo clean borra el directorio classes y todo su contenido. En un Makefile esto normalmente se haría con la orden:

rm -rf classes/

rm (véase el manual de la consola Bash) es una orden específica de UNIX, que probablemente no estará disponible si el Makefile se usa en un entorno no Unix como Windows. En un archivo de construcción Ant se puede conseguir lo mismo usando una orden propia:

<delete dir="classes" />

Una discrepancia común entre diferentes plataformas es la manera en que las rutas de directorios se especifican. Unix usa la barra (/) para delimitar los componentes de una ruta, mientras que Windows usa la barra invertida (\). Los archivos de Ant permiten al autor elegir su convención favorita, barras o barras invertidas para directorios, comas o punto y coma para separar rutas. Ant lo convierte todo al formato apropiado para la plataforma actual.

Referencias[editar]

Bibliografía[editar]

Para ampliar conocimientos en el uso de ANT, por favor consultar el siguiente enlace:

http://ant.apache.org/manual/