Ant (5): creacrión de tareas propias
Creación de tareas en Ant
Descripción
Como ya hemos visto en otros artículos, Ant ofrece una gran cantidad de tareas predefinidas para realizar distintas acciones. Sin embargo, es posible que en algún momento necesitemos realizar algunas acciones concretas y no haya una acción que se ajuste a nuestras necesidades.
Para solucionar este problema, Ant proporciona al usuario la posibilidad de crear tareas personalizadas escritas en Java.
En este artículo se explica paso a paso la creación de distintos tipos de tareas para Ant.
Creando una tarea sencilla
Vamos a crear una tarea muy sencilla que funciona de manera similar a echo
. Se llamará Prueba1
y tendrá un atributo cadena de tipo String
.
En un fichero build.xml
, se usaría de la siguiente forma:
<target ...>
<Prueba1 cadena="unaCadena"/>
...
</target>
Cada tarea se implementa en (al menos) una clase Java. Todas las tareas extienden org.apache.tools.ant.Task
.
import org.apache.tools.ant.*;
public class Tarea1 extends Task {
...
}
Es importante destacar que el nombre de la tarea (Prueba1) y el nombre de la clase que la implementa (Tarea1) no tienen por qué coincidir.
Por cada atributo de la tarea, definiremos un atributo en la clase:
protected String cadena;
En este caso, ambos nombres sí deben coincidir.
Además, definiremos un método de tipo setter, que será invocado por Ant para darle valor al atributo Java:
public void setCadena (String cadena) {
this.cadena = cadena;
}
Existen algunas restricciones en cuanto al nombre del método: debe empezar por set
, la siguiente letra debe ser mayúscula y las demás minúsculas.
Por último, definimos un método execute
, de la siguiente manera:
public void execute () throws BuildException {
...
}
En el cuerpo del método pondremos todo aquello que queremos que la tarea haga.
Aquí está el ejemplo completo listo para ser compilado.
A continuación compilamos la tarea. El paquete org.apache.tools.ant
se encuentra en el fichero $ANT_HOME/lib/ant.jar
.
javac -classpath $ANT_HOME/lib/ant.jar Tarea1.java
Modificando y utilizando el fichero build.xml
Sólo queda añadir al principio del project
, una linea taskdef
para relacionar el nombre de la tarea con el nombre completo (incluyendo los paquetes, si los hay) de la clase que la implementa:
<project ...>
<taskdef name="Prueba1" classname="Tarea1"/>
...
</project ...>
Aquí está el primer build.xml
.
Para poder utilizar la nueva tarea, Ant debe ser capaz de encontrar la clase que la implementa. En la documentación oficial se recomienda ampliar la variable de entorno CLASSPATH
(en este caso, con el directorio donde está situada Tarea1.class
). Así, la nueva tarea podría utilizarse con:
ant -buildfile build1.xml
Alternativamente, es posible invocar a Ant sin tener que modificar el classpath actual. Suponiendo que Tarea1.class
esté en el directorio actual, podríamos usar algo como:
java -classpath $ANT_HOME/lib/ant.jar:. org.apache.tools.ant.Main -buildfile build1.xml
Ésta es la salida obtenida:
Buildfile: build1.xml
Prueba1:
[Prueba1]
[Prueba1] Tarea1 ...
[Prueba1] cadena = unaCadena
[Prueba1] Fin de Tarea1
[Prueba1]
[Prueba1]
[Prueba1] Tarea1 ...
[Prueba1] cadena = otraCadena
[Prueba1] Fin de Tarea1
[Prueba1]
BUILD SUCCESSFUL
Total time: 0 seconds
Por último, también es posible añadir nuestra tarea al conjunto de tareas predefinidas de Ant, de modo que no sea necesario indicar el elemento taskdef
. Para ello es necesario disponer del código fuente de Ant y añadir al fichero src/main/org/apache/tools/ant/taskdefs/defaults.properties
una linea indicando el nombre de la tarea, tal cual ha de ser usado y el nombre completo de la clase que la implementa. Por ejemplo:
unatarea=com.javahispano.tareas.unatarea
Por supuesto, es necesario que esta tarea sea accesible por Ant, bien modificando la variable de entorno CLASSPATH
, bien invocando org.apache.tools.ant.Main
con el parámetro classpath
adecuado.
La clase org.apache.tools.ant.Task
La clase Task
es la clase base de todas las tareas. En ella se definen varios atributos y métodos que conviene conocer. Entre otras cosas, es posible conocer atributos del proyecto (nombre, tareas predefinidas que puede utilizar, propiedades, etc.), de los objetivos del proyecto (cuáles son, qué tareas contienen, etc.) y de la tarea que se está ejecutando. Por ejemplo:
- Obtener el nombre del proyecto:
getProject().getName();
- Obtener el nombre del objetivo al que pertenece la tarea:
getTarget().getName();
- Obtener el nombre de la tarea:
getTaskName();
Aunque la mejor referencia es el API, aquí hay un ejemplo de cómo usar alguno de ellos. Aquí está el fichero build.
A modo de curiosidad, las propiedades definidas en el fichero build no se incluyen en las obtenidas con el Project.getUserProperties
sino con Project.getProperties
.
Más tipos de datos
En el primer ejemplo sólo hemos usado atributos del tipo String
, aunque Ant admite además tipos primitivos, la clase File
, la clase Class
y cualquier otra clase que tenga un constructor (público) con un único argumento de tipo String
(por ejemplo, StringBuffer
).
Los argumentos de tipo File
permiten indicar un fichero o un directorio (mediante una ruta absoluta o relativa). Por ejemplo, el atributo todir
de la tarea Copy
, internamente es de tipo File
.
Cuando en una tarea se declara un atributo de tipo File
, se instancia un objeto File
con el nombre de fichero indicado en el atributo correspondiente. Se tiene la garantía de que cuando se ejecuta el método execute
, los métodos set
ya se han ejecutado y los atributos de tipo File
ya han sido instanciados. El siguiente es un ejemplo de uso de este tipo de atributos en una tarea de un fichero build:
<target ...>
<unaTarea archivo="build.xml"/>
...
</target>
En la clase correspondiente, tendríamos algo como:
protected File archivo;
public void setArchivo (File archivo) {
this.archivo = archivo;
}
public void execute () throws BuildException {
System.out.println("Ruta absoluta: " + archivo.getAbsolutePath());
}
Así, el valor indicado en el atributo ("build.xml
") será utilizado en una invocación al método setArchivo
. Antes de ejecutar el método execute
, se habrá realizado una instanciación equivalente a:
new File(archivo)
de forma que archivo está correctamente inicializado con el valor indicado en la tarea del fichero build.
También es posible definir argumentos cuyo atributo correspondiente en la clase que implementa la tarea pertenezca a una clase que tiene un constructor que recibe como único parámetro un objeto String
.
Básicamente, se tienen las mismas garantías que en el caso anterior, de manera que cuando se ejecuta el método execute
, el atributo correspondiente ya ha sido instanciado con el valor dado en el argumento de la tarea.
Por ejemplo:
<target ...>
<unaTarea buffer="cadena"/>
...
</target>
y en la clase correspondiente:
protected StringBuffer buffer;
public void setBuffer (StringBuffer buffer) {
this.buffer = buffer;
}
public void execute throws BuildException () {
buffer.append(" cadena2");
System.out.println("StringBuffer:" + buffer.toString());
}
Sabemos que cuando se ejecute el método execute
, el atributo buffer
de la clase estará inicializado como si se hubiera ejecutado la siguiente instanciación:
new Stringbuffer(buffer)
La ejecución de esta tarea mostraría por pantalla la cadena "cadena cadena2
".
Aquí hay un ejemplo de uso de los atributos de tipo File
y StringBuffer
. Aquí está el correspondiente fichero build.
Tareas que admiten texto
Algunas tareas, como echo
, admiten un cuerpo no vacío, en el que hay texto:
<target ...>
<echo>
Primera linea del cuerpo
Una linea más
Otra linea
</echo>
</target>
La forma de implementar este tipo de tareas se basa en definir un método con el siguiente perfil:
public void addText (String)
No existen otras restricciones en cuanto a atributos necesarios o implementación del método anterior. Dentro del cuerpo de este método, podemos realizar cualquier tipo de acción, aunque lo más indicado es guardar el texto recibido en algún atributo, procesarlo de alguna manera si es necesario y utilizarlo posteriormente durante la ejecución del método execute.
Aquí hay un ejemplo de uso de tareas con texto interno. Aquí está el correspondiente fichero build.
Usando tipos enumerados
Algunas tareas tienen atributos enumerados, esto es, atributos que deben contener un valor de entre un conjunto de ellos. Por ejemplo, el atributo access
de la tarea Javadoc
es un atributo de tipo enumerado (puede tener los valores public
, protected
, package
o private
):
<target ...>
<javadoc
...
access="public"
/>
</target>
Es posible comprobar estos atributos de dos formas distintas. La primera es la forma obvia: comparar el valor recibido como argumento del método setter contra un conjunto de posibles valores:
protected String cadena;
public void setCadena (String enumerado) {
// Comprobar los posibles valores
if (enumerado.equals("valor1") || enumerado.equals("valor2") || ...)
this.cadena = enumerado;
else
throw new Exception ("Valor no permitido: " + enumerado);
}
Sin embargo, existe una forma más cómoda de proceder. Consiste en definir una tarea (por ejemplo interna) que extienda org.apache.tools.ant.types.EnumeratedAttribute
, como:
public static class MiTipoEnumerado extends EnumeratedAttributes {
public String [] getValues () {
return new String [] {"valor1", "valor2", "valor3"};
}
}
El método getValues
es abstracto en la clase EnumeratedAttributes
. En él se declaran los posibles valores. La clase EnumeratedAttributes
tiene otro método, setValue
, declarado como final
, donde se comprueba la pertenencia del valor indicado en el fichero build al conjunto de valores indicado en el método getValues
. De esta forma, se nos garantiza que el valor recibido en el método setEnumerado
es un valor correcto. En el caso de que el valor indicado no sea correcto, desde el método setValue
de EnumeratedAttributes
se lanzará una excepción señalando que el valor indicado no es válido.
Es importante que la clase se declare con los modificadores public
y static
. De otra forma, se obtendrá un error en tiempo de ejecución (concretamente, una excepción de tipo java.lang.InstantiationException
).
El argumento del método setter a usar será de este tipo:
public void setCadena (MiTipoEnumerado conjunto) {
// Obtener el valor indicado en el fichero build
this.cadena = conjunto.getValue();
}
Esta forma de proceder presenta la ventaja de que el control del valor utilizado se reduce únicamente a declarar el conjunto de valores permitidos. El resto corre a cargo de la clase EnumeratedAttributes
.
Según el caso, declarar la clase como interna puede ser suficiente. Por ejemplo, cuando el conjunto de valores es muy concreto y específico. En otros casos, puede ser útil definirla como pública, en un fichero aparte e incluso en un paquete aparte.
Aquí hay un ejemplo de uso con el correspondiente fichero build. La ejecución del único objetivo que hay produce un error de ejecución debido a que el valor "valor4
" no pertenece al conjunto de valores válidos declarado en la clase interna MiTipoEnumerado
.
Tareas con elementos anidados
La mayoría de tareas predefinidas aceptan elementos anidados. Por ejemplo, la tarea javac
admite un parámetro src
para indicar los ficheros fuente.
Para manejar estos elementos hay que implementar métodos createXXX
o addXXX
. Por la falta de documentación oficial en la distribución actual (1.4.1) y su complejidad, el uso de este tipo de elementos será descrito en un próximo artículo.
El ciclo de vida de una tarea
Cada tarea indicada en un fichero build sigue el siguiente ciclo de vida:
- La tarea es instanciada invocando el constructor sin argumentos, cuando Ant parsea el fichero
build.xml
. Todas las tareas indicadas contaskdef
son instanciadas, incluso si posteriormente no llegan a ser utilizadas. - La tarea obtiene referencias al projecto al que pertenece y a su location (posición dentro del fichero build) mediante los atributos
project
ylocation
heredados de la claseTask
. - Si el usuario indicó un atributo
id
en la instanciación de la tarea (en el fichero build), en el objeto proyecto se guarda (en tiempo de parseo) una referencia a esta tarea, mediante su métodoaddReference(String, Object)
. Es posible obtener estas referencias mediante el métodogetReferences
del objetoProject
. - La tarea obtiene una referencia al objetivo al que pertenece mediante el atributo
target
heredado de la claseTask
. - El método
init
es invocado en tiempo de parseo. - En tiempo de parseo, todos los elementos hijos de la tarea (en el fichero build, los elementos XML anidados) son creados mediante los métodos
createXXX
o instanciados y añadidos a esta tarea mediante los métodosaddXXX
. - En tiempo de ejecución, los métodos
setXXX
son ejecutados para dar valor a los atributos de la tarea. - En tiempo de ejecución, el método
addText
es ejecutado, para recuperar el texto contenido en el interior del elemento XML que representa a la tarea. - En tiempo de ejecución, los atributos de los elementos hijos son establecidos mediante sus correspondientes métodos
setXXX
. - El método
execute
es ejecutado al menos una vez.
En cuanto al último punto, es importante destacar que el método execute
puede invocarse varias veces. En el siguiente ejemplo, el método execute
de la tarea t
es ejecutado exactamente 2 veces:
<?xml version="1.0" encoding="iso-8859-1"?>
<project name="variasveces" default="tres">
<target name="uno">
<echo message="uno"/>
</target>
<target name="dos" depends="uno">
<echo message="dos"/>
</target>
<target name="tres" depends="dos">
<echo message="tres"/>
</target>
</project>
Integración de Ant con editores y entornos externos.
Durante la ejecución de Ant, se generan eventos que pueden ser dirigidos a otras aplicaciones. jEdit es un ejemplo de editor que puede trabajar estrechamente con Ant.
En un próximo artículo incluiremos información adicional sobre esta característica de Ant.
Puedes bajarte todo el código y los ficheros build de éste artículo en un solo fichero de aquí
Conclusión
Ant es una potente herramienta de desarrollo que proporciona una gran cantidad de tareas estándar para realizar multitud de acciones comunes. Su flexibilidad va más allá, al ofrecer un mecanismo sencillo pero potente de ampliación del juego básico de tareas.
En este artículo se han cubierto los primeros pasos para poder desarrollar nuestras propias tareas de Ant, a través de varios ejemplos sencillos que el lector sabrá ampliar y personalizar para adaptarlos a sus necesidades.
|
Reader Comments