Ant Além do Básico: Técnicas, tasks e boas práticas
O objetivo desse artigo é mostrar recursos e tasks menos conhecidas e procurar desmistificar o uso geral do Ant.
Artigos Java Ant Além do Básico: Técnicas, tasks e boas práticas
Ant Além do Básico<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
Técnicas, tasks e boas práticas
Tire o máximo da ferramenta que torna simples muitas tarefas braçais ou complexas
O Apache Ant é uma ferramenta indispensável em praticamente qualquer projeto Java. Embora não seja um componente oficial da plataforma, o Ant já é um padrão de mercado para a automação de atividades de desenvolvimento – desde operações simples como a compilação de código, até tarefas mais complexas como a obtenção de arquivos via CVS, empacotamento de classes, execução de testes unitários e envio de resultados por e-mail (um bom exemplo do que o Ant é capaz de fazer é a geração do Tomcat a partir de um único arquivo – veja o quadro "Gerando o Tomcat 5").
Apesar de sua popularidade e maturidade (veja o quadro "Um pouco de história"), em muitos projetos é feito apenas uso limitado do Ant, utilizando-se apenas tasks básicas, e sem a preocupação de desenvolver buildfiles organizados. Conseqüentemente, o potencial da ferramenta é subutilizado e a manutenção de buildfiles torna-se um pesadelo, transformando o Ant mais em um problema que uma solução.
Pode-se argumentar que essa subutilização do Ant ocorre por falta de conhecimento de seus recursos, ou mesmo de tempo a ser gasto na sua configuração – afinal, trata-se de uma ferramenta de infra-estrutura. O objetivo desse artigo é minimizar o primeiro problema, mostrando recursos e tasks menos conhecidas e procurando desmistificar o uso geral do Ant. O artigo começa recapitulando conceitos básicos, e segue mostrando boas práticas, e técnicas avançadas, além de mostrar uma seleção de tasks de terceiros.
O uso básico do Ant já foi explorado em vários artigos da Java Magazine, como "Automação com Ant" na Edição 2 e "Seu ambiente Java completo", na Edição 3, além dos "Tira-dúvidas" sobre empacotamento (Edições 8 e 9). Mas é importante recapitularmos alguns conceitos – o leitor já familiarizado com o uso básico do Ant poderá pular, sem perdas, para a próxima seção.
Vamos começar lembrando o processo de instalação: feito o download do ZIP a partir do site do Ant (ant.apache.org), basta descompactar os arquivos e definir a variável de ambiente ANT_HOME para o diretório extraído do ZIP (por exemplo, c:\apache-ant-1.6.1 ou /usr/local/apache-ant-1.6.1). Depois resta apenas adicionar o caminho %ANT_HOME%/bin (ou ${ANT_HOME}/bin) ao path do sistema.
Já podemos passar aos conceitos, definindo os termos principais:
· build – termo genérico usado para denominar o processo de "construção" de um projeto.
· buildfile – arquivo XML com a configuração para um projeto/build; o nome padrão é build.xml, mas outros nomes podem ser usados.
· task – conjunto de comandos que realizam o trabalho real da build. Tasks são definidas por elementos XML no buildfile e podem ser de três tipos:
Ø core – tasks básicas incluídas na distribuição do Ant (exemplos: <javac>, <zip>, <copy>).
Ø optional – tasks que fazem parte da distribuição do Ant, mas requerem JARs adicionais que devem estar no diretório lib (alguns exemplos: <FTP>, <Telnet>, <JUnit> e <JunitReport>).
Ø custom – tasks customizadas definidas por terceiros, não incluídas no download do Ant; além de requererem JARs adicionais, devem ser referenciadas no buildfile com um elemento <taskdef>. Exemplos de tasks customizadas são as que automatizam o uso do XDoclet e do PMD. Uma seleção de tasks customizadas é apresentada ao final do artigo.
· target – task ou grupo de tasks responsável pela realização de uma atividade do projeto, como por exemplo, compilação de classes, criação de JARs, deployment num servidor de aplicações etc. Um projeto no Ant geralmente é constituído por vários targets, muitos deles interdependentes (por exemplo, a geração do JAR na maioria dos casos exige que as classes sejam compiladas antes). No elemento <target>, o atributo name define como o target será chamado pelos usuários do buildfile, e depends indica a seqüência de targets que precisam ser executados anteriormente.
· path – muitas tasks fazem uso de caminhos do sistema de arquivos; estes podem ser definidos como atributo da task (por exemplo, classpath na task <javac>), ou como uma sub-task – uma task chamada dentro de outra como, por exemplo, <path> ou <classpath> dentro de <javac>.
A Listagem 1 contém um buildfile básico, com targets para compilação de classes, geração de um JAR e execução da classe principal da aplicação (este exemplo trabalha sobre a classe trivial AloMundo da Listagem 2, incluída para deixar o exemplo completo).
Note que, visando simplificar o acompanhamento dos exemplos, usaremos buildfiles com nomes diferentes, em vez do padrão build.xml. Por isso será preciso especificar –f NomeArquivo ao executar o Ant. Além disso, lembre-se que, exceto quando se deseja executar o target padrão do buildfile (definido com o atributo default de <project>), você deve fornecer o nome do target como parâmetro. Por exemplo:
· ant -f buildSimples.xml compilarClasses
· ant -f buildSimples.xml criarJar executarAplicacao
Boas práticas e técnicas avançadas
Nesta seção são mostradas técnicas e recomendações que facilitam a construção e o gerenciamento de buildfiles, usando trechos do buildfile completo da Listagem 3 como exemplos (veja o quadro "A aplicação de exemplo").
Uso de propriedades
Uma boa prática fundamental é usar propriedades para a definição dos atributos de tasks, em vez de especificar valores diretamente no código. Com isso, se for preciso mudar o diretório onde as classes compiladas são geradas, por exemplo, basta alterar a definição da propriedade correspondente. E ao criar targets, fica mais fácil deduzir os possíveis valores para atributos de tasks, analisando o arquivo de propriedades.
Propriedades são definidas com elementos <property> e acessadas usando ${nomeDaPropriedade}. As formas mais comuns de definição de propriedades são:
§Usando os atributos name e value:
<property name="application.main"
value="jm14.ant.cli.Aplicacao"/>
§ Carregando as propriedades de um arquivo (tipicamente chamado build.properties) especificado com file ou url. Por exemplo:
A Listagem 4 apresenta um exemplo de arquivo de propriedades (usado no buildfile da Listagem 3).
Veja mais algumas características das propriedades:
· O valor de uma propriedade pode ser avaliado tanto no buildfile quanto no arquivo de propriedades. Isso é útil pois freqüentemente propriedades fazem uso de valores comuns. Por exemplo, dist.dir=${build.dir}/dist e jar.completo=${dist.dir}/${jar.nome}.
· Se for usada no buildfile uma propriedade não definida, o valor resultante será a literal representando a propriedade. Por exemplo, se saida.dir não estivesse definida, a task <mkdir dir="${saida.dir}"/> tentaria criar o diretório "${saida.dir}". Esse comportamento é bastante diferente de outras linguagens de script (como a Expression Language do JSTL ou a sintaxe de shell scripts) – nestas o valor de uma variável ou propriedade não definida é a string vazia.
· É possível misturar propriedades com valores literais no buildfile (por exemplo, <mkdir dir="${build.dir}/dist"/>), mas é recomendável que isso seja feito apenas no arquivo de propriedades (mantendo-se apenas propriedades "puras" no buildfile).
· O Ant permite acessar variáveis de ambiente do sistema, usando o atributo environment de <property>. Uma aplicação disso é em buildfiles que precisam saber onde o servidor de aplicações está instalado, com o valor variando para cada desenvolvedor. Por exemplo:
<property name="jboss.dir" value="${env.JBOSS_HOME}"/>
Target de inicialização
Outra boa prática é criar um target de inicialização da build, que deve ser especificado como dependência nos demais targets, e cuja finalidade é a preparação do ambiente. Tipicamente, o target de inicialização cria subdiretórios e inicializa propriedades a serem usados no decorrer do processo de build. O target prepararAmbiente (Listagem 3) é um exemplo.
As tasks que manipulam paths (como <path>, <classpath>, <fileset>, <dirset> e <filelist>) suportam o conceito de referência, pelo qual um path é criado e depois referenciado através de seu id no buildfile. As referências podem ser vistas como uma extensão do conceito de propriedades especificamente para paths, e são úteis no desenvolvimento de aplicações que requerem o mesmo path (ou paths parecidos mas com pequenas variações), em várias etapas do processo de build.
No seguinte exemplo são criados três classpaths: um comum a todos os targets, um para compilação e um apenas para execução:
<path id="classpath.comum">
<path id="classpath.compilacao">
<path refid="classpath.comum"/>
<pathelement location="lib/servlet.jar"/>
<path id="classpath.execucao">
<pathelement location="lib/hsqldb.jar"/>
Neste exemplo, o servlet.jar é necessário apenas na compilação e hsqldb.jar (que contém o driver JDBC usado pela aplicação), apenas em tempo de execução.
Muitas vezes nos vemos em situações em que uma task só pode ser executada se uma condição for satisfeita (por exemplo, pode ser requerida uma versão determinada do J2SE para a compilação). O Ant não oferece tasks condicionais genéricas como <if>, <else> e <for>, pois seus desenvolvedores acham que isso mudaria o foco do Ant tornando-a uma linguagem de script, por isso o modo de uso de lógica condicional é diferente de outras linguagens (e mais complexo).
Para usar condicionais em buildfiles, precisamos antes conhecer mais algumas características do Ant:
· Um target pode definir o atributo opcional unless, cujo valor é o nome de uma propriedade. Se esta propriedade não estiver definida, o target não será executado. De modo similar, pode-se usar o atributo if para que um target seja executado somente se uma propriedade estiver definida.
· O Ant fornece tasks condicionais que definem uma propriedade (usando o atributo property) apenas se uma condição for verdadeira. Na versão atual (1.6.1) as tasks condicionais são <available>, <uptodate> e <condition>. A task <condition> pode ser usada para agrupar diversas condições, definidas em sub-tasks. A condição será verdadeira somente se forem verdadeiras a combinação dessas condições (usando <and>, <or> e <not>). A Tabela 1 contém as tasks condicionais suportadas pelo Ant – note que, com exceção de <available>, <uptodate> e <checksum>, essas tasks só podem ser usadas dentro de <condition>.
· Para forçar o "fracasso" da build, usamos a task <fail>.
Conhecendo essas características, podemos implementar a lógica condicional seguindo este procedimento:
1. Declarar uma propriedade que atenda às condições desejadas. Por exemplo:
<target name="checaCondicoes">
<available classname="${jdbc.driver.class}"
property="jdbc.jar.presente"/>
2. Criar um target auxiliar que falhe caso a propriedade declarada não estiver definida. Exemplo:
<target name="checaJarJDBC" depends="checaCondicoes"
unless="jdbc.jar.presente">
<fail message="Arquivo do driver faltando!" />
3. No target principal (que depende da condição), adicionar o target auxiliar como dependência. Assim, se a condição não for satisfeita o target auxiliar falha e o principal não é executado: