Groovifique seu build!
Este blog deixou de ser mantido, mas o autor continua escrevendo aqui. Não deixe de assinar o novo feed!
Eu sei que fiquei um bom tempo sem escrever nada, mas tentarei resolver tal situação com este post, que será longo e interessante! Por isso, economizarei nos “lero-leros” e compensarei nos códigos.
Descreverei a seguir o funcionamento do processo de build de um projeto que venho desenvolvendo. Mesmo este projeto não sendo grande, o processo de empacotamento dos arquivos é meio complicado. Apenas para te dar uma idéia, neste projeto estou usando Maven para controle de dependências e build básico do projeto; também são usados scripts Groovy para preparação do sistema de Ajuda e para geração dos artefatos finais (distribuição dos binários, códigos-fonte e Java Web Start) que são disponibilizados aos usuários.
Pretendo mostrar como podemos criar facilmente um build script de razoável complexidade usando Groovy.
Problema 1: executando script Groovy através do Maven
Neste post eu mostrei o código de um script Groovy que criei para fazer a indexação das páginas de Ajuda da aplicação. Tudo funciona muito bem, mas eu ainda tinha que executar o script manualmente.
Já que estou usando o Maven, seria muito bom se eu conseguisse configurá-lo de tal forma que este executasse o script Groovy de forma automática. E, de fato, é algo bem simples de ser feito:
-
-
<?xml version="1.0" encoding="UTF-8"?>
-
<project>
-
<!– informações do projeto –>
-
<build>
-
<plugins>
-
<plugin>
-
<groupId>org.codehaus.mojo.groovy</groupId>
-
<artifactId>groovy-maven-plugin</artifactId>
-
<executions>
-
<execution>
-
<phase>generate-resources</phase>
-
<goals>
-
<goal>execute</goal>
-
</goals>
-
<configuration>
-
<source>${pom.basedir}/src/main/groovy/IndexHelp.groovy</source>
-
</configuration>
-
</execution>
-
</executions>
-
</plugin>
-
</plugins>
-
</build>
-
</project>
-
Com isso, sempre que o Maven executar a fase generate-resources (que é relacionada à geração de arquivos a serem incluídos nos pacotes), o script IndexHelp.groovy é executado.
Embora exista a possibilidade de se incluir os scripts Groovy diretamente no XML, eu não recomendaria por questões de organização.
Problema 2: criar uma nova release é chato!
Atualmente, cada nova release deste projeto é composta de três pacotes:
- Pacote binário (que contém apenas os arquivos JAR e outros arquivos com informações sobre o projeto (README, CHANGELOG etc). É compactado no formato ZIP;
- Pacote completo contendo os fontes da aplicação. É basicamente uma alternativa ao checkout do VCS, também disponibilizado em um arquivo ZIP;
- Distribuição Java Web Start, onde o usuário baixa o descritor JNLP e o Java Web Start se encarrega de baixar ou atualizar os arquivos necessários do servidor web.
Abaixo segue um esquema sobre os procedimentos necessários para se criar cada um dos pacotes: (se você zela pela sua saúde, não leia)
- Pacote binário
- Criar um diretório em um local qualquer;
- Copiar JARs gerados pelo Maven no diretório criado;
- Copiar os arquivos de informações (README, CHANGELOG etc) no diretório criado;
- Empacotar o diretório criado em um arquivo ZIP;
- Remover o diretório criado;
- Gerar um checksum MD5 para conferir a integridade do arquivo após o upload do mesmo no servidor.
- Pacote com os fontes
- Criar um diretório em um local qualquer;
- Copiar toda a estrutura de arquivos do projeto no diretório criado;
- Remover arquivos de log, arquivos ocultos e arquivos criados pelo VCS;
- Remover os arquivos binários (JAR, .class);
- Empacotar o diretório criado em um arquivo ZIP;
- Remover o diretório criado;
- Gerar um checksum MD5 para conferir a integridade do arquivo após o upload do mesmo no servidor.
- Java Web Start
- Criar uma Keystore e uma chave para assinar digitalmente os arquivos JAR (caso ainda não tenha sido criado);
- Siga os passos 1 e 2 descritos no primeiro item;
- Copiar os descritores JNLP no diretório criado;
- Assinar os arquivos JARs com a chave gerada no primeiro passo;
- Empacotar o diretório criado em um arquivo ZIP (para diminuir o tamanho do arquivo e aumentar o desempenho do upload);
- Remover o diretório criado;
- Gerar um checksum MD5 para conferir a integridade do arquivo após o upload do mesmo no servidor.
Se você ainda não meteu uma bala na própria cabeça, me diga uma coisa: isso é chato ou não é? (se você acha que não é, procure um médico… agora).
Nem é preciso dizer que fazer manualmente todo esse processo é bastante difícil e chato. Mas então, como fazemos para resolver esta situação? Creio que o nome que veio na sua cabeça é aquele: Ant.
Não irei entrar no mérito da questão se o Ant é bom ou não, mas eu particularmente não sou muito chegado ao Ant. Sei lá, acho que a sintaxe XML poderia ser mais enxuta. Mas nem tudo está perdido, pois existe uma solução para este problema.
Gant - escrevendo targets Ant com Groovy
Confesso que, num primeiro momento, achei este projeto um tanto quanto estranho, mas vi que trata-se de uma ferramenta que combina com perfeição o poder do Ant com a simplicidade do Groovy.
Scripts Gant são simples scripts Groovy, mas mais poderosos pois são capazes de declarar targets no estilo Ant com 0% de gordura trans XML.
Para saber como instalar o Gant em seu sistema, visite o site do projeto.
Problema 3: gerando o pacote com os binários
Finalmente um pouco de ação!
Bom, a primeira coisa que fiz foi criar um arquivo de build vazio, chamado build.gant (que é o nome padrão), no diretório raíz do projeto (no mesmo local onde fica o arquivo pom.xml).
Os trechos de código mostrados adiante foram adaptados a partir do build script original do projeto, que é mais polido. Este se encontra disponível caso você queira ver como funciona.
Primeiramente, eu precisei coletar algumas informações do usuário:
-
-
app = ‘xxx’ // nome da aplicação
-
out = ‘xxx’ // diretório onde os arquivos resultantes serão colocados
-
version = ‘xxx’ // versão a ser gerada
-
-
target(prepareFolder : ‘Prepare the release output folder’) {
-
app = cString(‘Application name’, app)
-
out = cString(‘Folder where the generated files will be placed’, out)
-
Ant.mkdir(dir:out)
-
version = cString(‘Release version’, version)
-
}
-
-
print "$msg [$defaultValue]: "
-
value
-
}
-
Neste trecho de script definimos um target para criar o diretório que receberá os arquivos gerados. O método Ant.mkdir é uma task do Gant que permite a criação de diretórios, e seu funcionamento é idêntico ao funcionamento da task Ant homônima.
O target que gera o pacote binário pode ser visto a seguir:
-
-
target(bin : ‘Generate the binary package’) {
-
depends(prepareFolder)
-
def folder = "$out/$app-$version-bin"
-
def zip = "${folder}.zip"
-
-
Ant.echo ‘Generating the binary package’
-
Ant.delete(dir:folder)
-
Ant.delete(file:zip)
-
Ant.mkdir(dir:folder)
-
Ant.copy(toDir:folder) {
-
fileset(dir:‘obexftp-frontend-core/target/executable-netbeans.dir’)
-
fileset(dir:‘info’, excludes:‘**/*SOURCES*.txt’)
-
}
-
Ant.zip(destFile:"${folder}.zip") {
-
zipfileset(dir:folder)
-
}
-
md5("${folder}.zip")
-
Ant.delete(dir:folder)
-
Ant.echo ‘Binary package generated successfully’
-
}
-
-
Ant.echo "Generating the Checksum of the ‘$file’ file…"
-
Ant.exec(executable:‘md5sum’, output:"${file}.md5sum.txt") {
-
arg(value:‘-b’)
-
arg(value:file)
-
}
-
}
-
Muito simples!
Temos um target chamado bin que depende de um outro chamado prepareFolder. Esse target ainda faz a cópia dos arquivos desejados para uma pasta temporária, os empacota em um arquivo ZIP, gera o checksum MD5 desse arquivo e remove o diretório temporário.
Já podemos perceber que é possível de se criar qualquer target ou código que use as tasks do Ant sem que seja necessário manjar tudo do Gant; basta abrir a documentação das tasks do Ant no seu browser e as utilizar seguindo essas regrinhas de sintaxe. É fácil!
Problema 4: gerando o pacote com os fontes
Não há muita novidade aqui, como você pode ver no exemplo de script abaixo:
-
-
target(source : ‘Generate the source package’) {
-
depends(prepareFolder)
-
def folder = "$out/$app-$version-src"
-
def zip = "${folder}.zip"
-
-
Ant.echo ‘Generating the source package’
-
Ant.delete(dir:folder)
-
Ant.delete(file:zip)
-
Ant.mkdir(dir:folder)
-
fileset(dir:‘.’, excludes:‘**/target/** **/.** **/log/**’)
-
}
-
Ant.zip(destfile:"${folder}.zip") {
-
zipfileset(dir:folder)
-
}
-
md5("${folder}.zip")
-
Ant.delete(dir:folder)
-
Ant.echo ‘Source package generated successfully’
-
}
-
A diferença aqui é que usamos vários patterns no parâmetro excludes, em fileset.
E não pense que esses códigos não funcionam, pois eles funcionam sim! O fato de eu estar apenas colocando alguns trechos não muda nada!
Problema 5: gerando a distribuição Java Web Start
Aqui a coisa fica um pouco mais cabeluda, mas nada que complique a vida de um usuário Gant.
Se uma aplicação precisa rodar com um nível restritivo mais baixo, todos os arquivos JAR desta aplicação devem ser assinados digitalmente para que o Java Web Start possa saber que tais arquivos JAR não foram alterados por terceiros.
Para fazer isso, o primeiro passo é gerar uma keystore e uma chave para que possamos assinar os JARs do projeto:
-
-
alias = ‘myself’
-
keystore = ‘myKeystore’
-
storepass = ‘***’
-
keypass = ‘***’
-
-
target (jws : ‘Generate the Java Web Start package’) {
-
depends(prepareFolder)
-
def folder = "$out/$app-$version-jws"
-
def zip = "${folder}.zip"
-
-
readJwsInfo()
-
Ant.echo ‘Generating the Java Web Start package’
-
Ant.delete(dir:folder)
-
Ant.delete(file:zip)
-
Ant.mkdir(dir:folder)
-
Ant.copy(toDir:folder) {
-
fileset(dir:‘obexftp-frontend-core/target/executable-netbeans.dir’)
-
fileset(dir:’src/main/jws’)
-
fileset(dir:’src/main/resources’, excludes:‘**/*.xcf’)
-
}
-
signJars(folder)
-
Ant.zip(destFile:"${folder}.zip") {
-
zipfileset(dir:folder)
-
}
-
md5("${folder}.zip")
-
Ant.delete(dir:folder)
-
Ant.echo ‘Java Web Start package generated successfully’
-
}
-
-
alias = cString(‘Key alias’, alias)
-
keystore = cString(‘Keystore file’, keystore)
-
storepass = cString(‘Keystore password’, storepass)
-
keypass = cString(‘Key password’, keypass)
-
}
-
-
Ant.signjar(params) {
-
fileset(dir:folder, includes:‘**/*.jar’)
-
}
-
}
-
Perceba que a utilização das tasks seguem um padrão de sintaxe bastante peculiar. O que pode complicar um pouco é saber quais tasks aceitam quais parâmetros, mas tudo isso pode ser consultado diretamente na documentação do Ant.
Legal! Com algumas poucas tasks fizemos tudo o que precisávamos. Ou melhor, quase tudo…
Problema 6: testando a distribuição Java Web Start
Da forma que está, ainda seria necessário fazer a implantação da distribuição Java Web Start (no servidor de produção) para testá-la! Mas lembre-se que estamos criando o build script em Groovy, e, por isso, tal questão pode ser facilmente resolvida.
Um jeito simples que encontrei foi definir o codebase (local onde a distribuição Java Web Start é implantada) em tempo de build.
Primeiramente, abri os arquivos JNLP e substituí o valor do parâmetro codebase por um token %CODEBASE%:
-
-
<?xml version="1.0" encoding="UTF-8"?>
-
<jnlp spec="1.0+" codebase="%CODEBASE%">
-
<!– conteúdo do arquivo –>
-
</jnlp>
-
Então, durante o build, o script pede para o usuário preencher o codebase:
-
-
jwsCodebase = ‘http://projeto.com/javawebstart’
-
target (jws : ‘Generate the Java Web Start package’) {
-
// …
-
Ant.copy(toDir:folder) {
-
fileset(dir:‘obexftp-frontend-core/target/executable-netbeans.dir’)
-
fileset(dir:’src/main/jws’)
-
fileset(dir:’src/main/resources’, excludes:‘**/*.xcf’)
-
}
-
replaceCodebase(folder)
-
signJars(folder)
-
// …
-
}
-
-
jwsCodebase = cString(‘Java Web Start codebase’, jwsCodebase)
-
alias = cString(‘Key alias’, alias)
-
// …
-
}
-
-
Ant.replace(dir:folder, token:‘%CODEBASE%’, value:jwsCodebase)
-
}
-
Fazendo isso, podemos criar uma distribuição Java Web Start cujo codebase aponte para uma URL qualquer. Então, basta mandar os arquivos para o endereço correspondente e fazer o teste da aplicação. Se o download e verificação ocorrerem normalmente, basta gerar a versão oficial da distribuição Java Web Start e proceder com a implantação.
Mas, como rodar um target?
Crie vergonha nessa cara e use o help!
$ cd myProject/ $ ls build.gant build.gant $ gant -h
Conclusão
Não há nada que não possa ser feito com Groovy e um pouco de imaginação. E, o que é melhor, com pouco esforço.
Imagem modificada por: Daniel F. Martins
Tags: ant, build, distribuição, gant, groovy, java, java web start, maven, script, testes

21 de agosto de 2007 às 4:17 pm
Opa! Gostei do titulo do post.
Só um detalhe, Daniel. A geração da chave para assinar os pacotes jws também pode ser feita no processo de build, basta usar a target genkey.
valeuz…
21 de agosto de 2007 às 4:42 pm
Verdade Marcos, bem lembrado! Não pensei na geração da Keystore através do Gant, pois já tenho uma Keystore que uso aqui e coloquei o procedimento de criação apenas para mostrar como pode ser feito.
Um target que faz a criação da Keystore poderia ser escrito da seguinte forma:
Claro que é possível fazer isso com menos linhas de código, mas acho que assim fica mais legível.
Valeu!
21 de agosto de 2007 às 4:44 pm
Acabei de achar um outro exemplo bastante interessante de build script Gant.