Sunteți pe pagina 1din 10

Groovy în construirea de aplicatii

Instrumente pentru construirea de aplicatii software

Procesul de obtinere a unui executabil pornind de la surse necesitã în general mai multe
operatii (comenzi) manuale, operatii care trebuie repetate dupã fiecare modificare în surse.
Acest proces se numeste contruire de aplicatii iar instrumentele care automatizeazã acest
proces se numesc “build tools”.
Aplicatiile au devenit tot mai complexe si nu mai este vorba de un singur fisier executabil
care trebuie produs, ci de o serie de “artefacte” (fisiere de configurare, fisiere cu teste, s.a).
Dintre operatiile care fac parte de obicei din construirea unei aplicatii mentionãm: stergerea,
crearea de directoare, stergerea, crearea si copierea de fisiere, compilare, linkeditare, testare,
împachetare si instalare (“deployment”). Operatiile sunt mai complexe pentru aplicatii si servicii
Web care includ si baze de date.
Primul instrument pentru construirea de aplicatii a fost “make” folosit pentru programe în
limbajul C si care folosea fisiere text ca “script” pentru descrierea operatiilor necesare.
Pentru construirea de aplicatii scrise în Java a fost scris “Ant” care este folosit si în prezent
pentru automatizarea operatiilor de obtinere a multor aplicatii. Ant foloseste fisiere XML pentru
descrierea operatiilor necesare construirii unei aplicatii.
Programul “Ivy” se foloseste împreunã cu Ant pentru descrierea dependentelor dintre fazele
construirii unei aplicatii si foloseste tot fisiere XML.
“Maven” este considerat ca fiind un instrument pentru gestionarea proiectelor software
(“project management tool”) si include facilitãti necesare dezvoltãrii de aplicatii mari, la care
contribuie mai multe persoane, care refolosesc pãrti din alte proiecte software, care pot genera
rapoarte si site-uri Web. Versiunile Maven folosite în prezent sunt 2.2 si 3.0 si folosesc tot fisiere
XML pentru descrierea operatiilor si dependentelor.
Utilizarea de fisiere XML tot mai complexe si mai mari ( de la “Ant” la “Maven”) s-a dovedit un
neajuns al acestor instrumente si de aceea s-a trecut mai recent la utilizarea unor limbaje
dinamice ca Ruby si Groovy în locul limbajului XML pentru fisierele de “build”. “Gant” este o
“fatadã” pentru utilizarea de sarcini “Ant”, iar “Gradle” este o alternativã la “Maven”; ambele
folosesc limbajul Groovy.
Mai exact, fisierele de “build” pentru Gant si Gradle sunt scrise într-un DSL intern Groovy, în
timp ce Ant si Maven foloseau fisiere scrise într-un DSL extern (bazat pe XML). Fisierele de
build pot fi imperative (pentru Ant), declarative (pentru Maven) sau mixte (Maven cu Ant).
Pe lângã cele patru instrumente considerate ca cele mai importante (Ant,Ivy,Maven,Gradle)
se mai folosesc si alte instrumente pentru construirea de proiecte (Buildr, IBM Rational Build
Forge, MSBuild s.a.).
Construirea de aplicatii este tot mai des parte dintr-un proces de integrare continuã
(“Continuous Integration”), care înseamnã actualizarea regulatã de cãtre participantii la un
proiect software a unui depozit de surse si executabile (“code repository”, “code base”).

Scurtã prezentare a programului Apache Ant

Construirea unei aplicatii este un “proiect” Ant; un proiect este descris într-un fisier numit
“build.xml”. Un proiect contine una sau mai multe actiuni (“target”), iar o actiune contine una sau
mai multe operatii (“task”), majoritatea fiind predefinite. In plus, un proiect poate folosi mai multe
proprietãti, incluse în “build.xml” sau citite dintr-un fisier separat “build.properties”.
Un proiect Ant are trei atribute:
- un nume ;
- actiunea cu care începe proiectul (“default”);
- directorul de bazã pentru alte “cãi” folosite în proiect (“basedir”).
Exemplu de proiect Ant:

1
<project name="MyProject" default="dist" basedir=".">
<description>
simple example build file
</description>
<!-- set global properties for this build -->
<property name="source" location="src"/>
<property name="build" location="build"/>
<property name="dist" location="dist"/>

<target name="init">
<!-- Create the time stamp -->
<tstamp/>
<!-- Create the build directory structure used by compile -->
<mkdir dir="${build}"/>
</target>

<target name="compile" depends="init"


description="compile the source " >
<!-- Compile the java code from ${source} into ${build} -->
<javac srcdir="${source}" destdir="${build}"/>
</target>

<target name="dist" depends="compile"


description="generate the distribution" >
<!-- Create the distribution directory -->
<mkdir dir="${dist}/lib"/>
<!-- Put everything in ${build} into the MyProject-20080627.jar file -->
<jar jarfile="${dist}/lib/MyProject-20080627.jar" basedir="${build}"/>
</target>

<target name="clean"
description="clean up" >
<!-- Delete the ${build} and ${dist} directory trees -->
<delete dir="${build}"/>
<delete dir="${dist}"/>
</target>
</project>

In exemplul anterior proiectul “MyProject” contine 4 actiuni (“targets”), prima se executã


actiunea numitã “dist” (“distribution”) deoarece are atributul (“default”); se foloseste directorul
curent (în care se aflã “build.xml”) ca director de bazã (“basedir”).
Exemplul anterior foloseste mai multe operatii (“tasks”) predefinite: “delete”, “mkdir”, “javac”
si “jar”. Unele corespund unor comenzi din sistemul de operare (mkdir, delete, copy, s.a.) iar
altele sunt apeluri de executabile (javac) sau operatii specifice “ant” (jar).
O actiune depinde în general de realizarea anterioarã a altor actiuni: “dist” depinde de
“compile”, care depinde de “init”. Actiunea “clean” va fi executatã separat de celelalte. Când se
apeleazã “ant” se poate specifica numele a una sau mai multor actiuni (“target”) din proiect:
ant clean
ant compile deploy run
In general se formeazã un lant de dependente care determinã succesiunea în care se vor
executa mai multe actiuni dintr-un proiect. Executia unei actiuni poate fi conditionatã, ca în
exemplul urmãtor (“echo” este operatia Ant care afiseazã un mesaj la consolã ):

<target name="myTarget" depends="myTarget.check" if="myTarget.run">


<echo>Files foo.txt and bar.txt are present.</echo>
</target>

<target name="myTarget.check">
<condition property="myTarget.run">
2
<and>
<available file="foo.txt"/>
<available file="bar.txt"/>
</and>
</condition>
</target>

Se pot defini noi operatii “task” în mai multe feluri:


- folosind operatia <taskdef> într-o actiune cu numele “use”.
- definind o clasã Java care extinde clasa org.apache.tools.ant.Task
- folosind operatiile <exec> si <java> pentru executarea de programe/clase

Operatiile au de obicei mai multe argumente (atribute de marcaje XML) în formatul urmãtor:
<name attribute1="value1" attribute2="value2" ... />
Datoritã numãrului mare de operatii predefinite în Ant este putin probabil sã fie necesarã
definirea de noi operatii (“tasks”) ca extinderi de tip “plugin” pentru Ant.
Fiecare proprietate are un nume si o valoare; de exemplu proprietatea cu numele “source”
are valoarea “src” si este folositã în comanda de compilare:
<javac srcdir="${source}" destdir="${build}"/>
Inainte de executia comenzii se înlocuieste numele cu valoarea proprietãtii în optiuni (în
parametri comenzii); exemplu:
javac –sourcepath src –d build *.*
Ant poate folosi, pe lângã operatiile sale proprii, operatii Ivy si Maven.
In general un fisier “build.xml” contine urmãtoarele actiuni “top-level” (nesubordonate altora):
- build (etapele construirii unei aplicatii)
- test (testare cu JUnit)
- clean (stergere fisiere si directoare create de actiunea “build”
- deploy (transfer arhivã aplicatie pe sistemul unde se exploateazã)
- publish (publicare surse si binare pe un site Web de distributie)
- fetch (obtinerea ultimei versiuni dintr-un arbore cvs)
- docs/javadocs (creare documentatie pentru aplicatie)
- all : clean, fetch,build,test,docs,deploy
Exemple de actiuni interne:
- init (initializare proprietãti, citire fisiere de proprietãti, s.a.)
- compile (compilare surse)
- link/jar (creare executabile)

Un exemplu de utilizare Ant

Acest exemplu este extras din documentatia Ant (Apache Ant User Manual : Tutorials : Hello
World with Ant) unde este extins treptat de la simpla construire a unei aplicatii cu o singurã
clasã Java, la utilizarea log4j pentru jurnalizare si apoi la utilizare JUnit pentru testare si creare
de rapoarte în urma testelor.
Sursa clasei se aflã în subdirectorul “src” al directorului de lucru si aratã astfel:

package oata;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}

Sursa clasei de test se aflã tot în directorul “src” si aratã astfel:

public class HelloWorldTest extends junit.framework.TestCase {


public void testNothing() {

3
}
public void testWillAlwaysFail() {
fail("An error message");
}
}

Subdirectorul “lib” contine arhiva cu clasele “junit” iar subdirectorul “build” este creat de Ant
ca urmare a interpretãrii actiunilor din “build.xml”. Folderul “build” are 3 subdirectoare :
“classes”, “jar” si “junitreport”.

<project name="HelloWorld" basedir="." default="main">


<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="classes.dir" value="build/classes"/>
<property name="jar.dir" value="build/jar"/>
<property name="main-class" value="oata.HelloWorld"/>
<property name="lib.dir" value="lib"/>
<property name="report.dir" value="build/junitreport"/>
<path id="classpath">
<fileset dir="lib" includes="**/*.jar"/>
</path>

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

<target name="compile">
<mkdir dir="${classes.dir}"/>
<javac srcdir="src" destdir="${classes.dir}" classpathref="classpath"/>
</target>

<target name="jar" depends="compile">


<mkdir dir="${jar.dir}"/>
<jar destfile="${jar.dir}/apache-ant.jar" basedir="${classes.dir}">
<manifest>
<attribute name="Main-Class" value="${main-class}"/>
</manifest>
</jar>
</target>

<target name="run" depends="jar">


<java fork="true" classname="${main-class}">
<classpath>
<path refid="classpath"/>
<path id="application" location="${jar.dir}/apache-ant.jar"/>
</classpath>
</java>
</target>

<target name="junit" depends="jar">


<mkdir dir="${report.dir}"/>

<junit printsummary="yes">
<classpath>
<path refid="classpath"/>

4
</classpath>
<formatter type="xml"/>

<batchtest fork="yes" todir="${report.dir}" >


<fileset dir="src" includes="*Test.java"/>
</batchtest>
</junit>
</target>

<target name="junitreport">
<junitreport todir="${report.dir}">
<fileset dir="${report.dir}" includes="TEST-*.xml"/>
<report todir="${report.dir}"/>
</junitreport>
</target>

<target name="main" depends="clean,run"/>


<target name="all" depends="main,junit" />

</project>

Gant : Ant fãrã XML

Fisierele “build.xml” folosite de Ant sunt de tip imperativ si solicitã executarea unor actiuni;
ele sunt interpretate de cãtre programul Ant. Caracteristica de limbaj procedural interpretat este
proprie oricãrui limbaj de scripting, deci fisierele XML de “build” ar putea fi înlocuite cu un limbaj
de scripting dacã acesta contine deja sau poate fi extins cu posibilitatea de a descrie operatii
Ant (“task”), dependente dintre operatii si de a folosi operatiile predefinite din Ant.
Groovy este un limbaj de scripting extensibil si deci poate îndeplini acest rol, cu câteva
avantaje fatã de fisierele XML folosite de Ant:
- Scripturile necesare construirii de aplicatii sunt mai usor de scris si de citit ;
- Scripturile pot include orice secvente de cod Groovy, pe lângã taskuri Ant;
- Eventualele erori din script sunt detectate încã din faza de compilare Groovy (parte din
procesul de interpretare).
Gant este un program cu acelasi rol ca si Ant dar care foloseste fisiere de build scrise în
Groovy cu anumite extensii (deci un DSL intern lui Groovy). Gant se poate utiliza direct sau
indirect din Grails, prin comenzi specifice Grails.
Un script Gant este un script Groovy care contine definitii de actiuni, apeluri de operatii Ant
predefinite si operatii asupra altor obiecte predefinite. Douã obiecte predefinite sunt mai des
folosite:
- includeTargets : includere de actiuni din alte scripturi sau clase definite corespunzãtor;
- includeTool : includere clase care oferã diferite servicii folosite în scriptil Gant. Exemplu:

ant.property ( environment : 'environment' )


ant.taskdef ( name : 'groovyc' , classname : 'org.codehaus.groovy.ant.Groovyc' )
includeTargets << gant.targets.Clean
cleanPattern << '**/*~'
includeTool << gant.targets.Ivy

Se pot include si fisiere sursã astfel:

Exemplu de script Gant (fisier “build.gant”):

includeTargets << gant.targets.Clean


5
cleanPattern << [ '**/*~' , '**/*.bak' ]
cleanDirectory << 'build'

target ( stuff : 'A target to do some stuff.' ) {


println ( 'Stuff' )
depends ( clean )
echo ( message : 'A default message from Ant.' )
otherStuff ( )
}
target ( otherStuff : 'A target to do some other stuff' ) {
println ( 'OtherStuff' )
echo ( message : 'Another message from Ant.' )
clean ( )
}
setDefaultTarget ( stuff )

Un “target” Ant este aici un functor, având ca parametri un nume de target si o descriere (un
sir). Metoda “depends” poate avea ca argument un nume de target si este folositã, ca si în Ant,
pentru a specifica secventa de executie a unor actiuni “target”, care poate fi alta decât ordinea
definirii lor în fisierul “build”; executia începe cu actiunea implicitã, care apare ca parametru în
metoda “setDefaultTarget”.
Un fisier “build.gant” poate refolosi actiuni din alte fisiere în douã moduri:
- prin includerea unor surse Groovy (sub-scripturi). Exemplu:
includeTargets << new File('source/org/codehaus/groovy/gant/targets/clean.gant' )
- prin “evaluarea” unor surse Groovy:
evaluate (new File(‘clean.gant’))
- prin includerea de fisiere Groovy compilate (de clase Groovy). Exemplu:
includeTargets << gant.targets.Clean

Forme posibile pentru definirea unei actiuni:


target ( name : target-name ) target-closure
target ( name : target-name , description : target-description ) target-closure
target ( target-name : target-description ) target-closure

Definitia “target-closure” poate contine orice cod Groovy. Se pot folosi operatii predefinite Ant
prin apeluri de metode de forma “ant.echo”, “ant.mkdir”,.. sau direct ca “echo”,”mkdir”,...
target (first: ‘First target’) { echo (‘prima actiune’) }

Exemplu de apel a unei actiuni dintr-o altã actiune:


target (second:’Second target’) {
first()
echo ( ‘a doua actiune’)
}

De obicei înlãntuirea de actiuni se face cu ajutorul metodei “depends”, care va executa


actiunea (‘first’) de care depinde actiunea curentã (‘second’) numai dacã aceasta (‘first’) nu a
mai fost executatã anterior. Exemplu:
target (second:’Second target’) {
depends (first)
echo (message: ‘a doua actiune’)
}

Numele de actiuni sunt chei într-un dictionar folosit ca argument de functorul “target” si sunt
de tip “String” (chiar dacã nu este necesarã utilizarea de ghilimele pentru aceste nume); totusi
observatia este importantã atunci când aceste nume (si chiar actiunile) sunt create dinamic
(ceea ce cu Ant si XML nici nu este posibil). Exemplu:

6
aTargetName = 'something'
target ( ( aTargetName ) : 'A target called' + aTargetName + '.' ) {
println ( 'Executing ' + aTargetName )
}

Alt exemplu de script Gant simplu:

target (default: "First target ") {


depends(clean, compile)
jar()
}
target (clean:"Delete stuff") { ant.delete(dir:"build") }
target (compile:"Compile stuff") {
ant.mkdir(dir:"build/classes")
ant.javac(srcdir:"src", destdir:"build/classes")
}
target (jar:"Package stuff") {
ant.jar(basedir:"build/classes", destfile:"build/myproject.jar")
}

Obiectul predefinit “ant” poate fi dat factor comun la apelarea unei secvente de operatii,
folosind metoda “sequential” astfel:

target ('default': "Performs compilation of Java sources") {


compile()
}
target (compile : "Implementation of compilation phase") {
println "Compiling sources.."
ant.sequential {
mkdir(dir:"${basedir}/web-app/WEB-INF/classes")
path(id:"classpath") {
fileset(dir:"lib")
fileset(dir:"${grailsHome}/lib")
fileset(dir:"${grailsHome}/dist")
fileset(dir:"${basedir}/web-app/WEB-INF/classes")
}
javac(srcdir:"${basedir}/src/java",
destdir:"${basedir}/web-app/WEB-INF/classes",
classpathref:"classpath",debug:"on",
deprecation:"on", optimize:"off")
}
}

Exemplu de definire a unui “task” Gant:

sourceDirectory = 'source'
buildDirectory = 'build'
includeTargets << gant.targets.Clean
cleanPattern << '**/*~'
cleanDirectory << buildDirectory
ant.taskdef ( name : 'groovyc' , classname : 'org.codehaus.groovy.ant.Groovyc' )
target ( compile : 'Compile source to build directory.' ) {
javac ( srcdir : sourceDirectory , destdir : buildDirectory , debug : 'on' )
groovyc ( srcdir : sourceDirectory , destdir : buildDirectory )
}

Pentru a executa un script Gant trebuie folosit programul Gant, care este un fisier “bat” ce
contine o comandã de forma:
java – jar gant-1.9.3_groovy-1.7.3.jar
7
Gant nu face parte din distributia Groovy dar face parte din distributia Grails. Gant se poate
descãrca în douã variante:
- Self-contained: contine si bibliotecile Groovy necesare
- Necesitã o instalare Groovy separatã
Fisierul care contine un script Gant trebuie sã aibã numele “gant.bat”, dar comanda “gant”
poate avea ca argumente numele unor actiuni din fisierul “gant.bat”. Exemplu:
gant clean

Existã si un script Groovy care transformã fisiere “build.xml” în fisiere “build.gant”.

Utilizarea de scripturi Gant în Grails

Grails este un framework complet (“full-stack”) pentru aplicatii Web, bazat pe limbajul Groovy
si care înlocuieste configurarea prin respectarea unor conventii, la fel ca si Ruby on Rails. Grails
nu aduce noi tehnologii Web, dar simplificã considerabil utilizarea unor tehnologii existente si se
bazeazã pe câteva produse de succes cum sunt Spring si Hibernate (pentru ORM).
Dezvoltarea unei aplicatii Web cu ajutorul lui Grails se poate face:
- Intr-un IDE (NetBeans si IDEA-JetBrains);
- In mod linie de comandã, folosind comenzi specifice Grails.
Fiecare comandã Grails lanseazã un script Gant sau Groovy predefinit si poate avea
argumente. Exemple de comenzi Grails uzuale:

grails create-app [myapp] // creare director aplicatie


grails generate-all // generare clase controler si pagini GSP pentru clasele domeniu
grails run-app // executare aplicatie
grails war // creare arhivã pentru instalare aplicatie pe alt server

Comanda “grails create-app” creeazã un directo cu numele “myapp” si cu structura:

myapp
grails-app // sursele aplicatiei
lib // biblioteci folosite de aplicatie
scripts // scripturi groovy pentru comenzi consola
src // alte surse (auxiliare)
target // clase rezultate din compilari
test // surse teste
web-app // resurse statice (css, imagini,..)
application.properties

Sursele aplicatiei se aflã în subdirectorul “grails-app”, care are o structurã ce reflectã schema
MVC (Model-View-Controller):

conf // configurãri
controllers // servleti controler
domain // clase domeniu (model)
services // clase cu servicii ptr controlere
views // pagini afisate (gsp,jsp,html)

Scripturile Grails se pot afla si în alte subdirectoare (unde sunt cãutate la executia unei
comenzi); în afara scripturilor predefinite orice programator îsi poate defini alte scripturi Gant.
Scripturile pentru comenzile predefinite (create-app, run-app, s.a.) se aflã în subdirectorul
“scripts” din distributia Grails (de ex. C:\grails-1.3\scripts) si au un nume putin modificat fatã de
numele folosite în comenzi. Exemple de comenzi si numele scripturilor executate:
grails create-script CreateScript.groovy
grails run-app RunApp.groovy
grails generate-all GenerateAll.groovy
8
Pentru a crea un script ce poate fi apelat printr-o nouã comandã Grails se va folosi comanda:
grails create-script MyScript

Comanda care va executa acest script va fi:


grails my-script

Pentru a include un script predefinit într-un nou script trebuie specificat directorul unde se
aflã distributia Grails (valoarea variabilei de sistem GRAILS_HOME). Exemplu:

grailsHome = ant.project.properties."environment.GRAILS_HOME"
includeTargets << new File ( "${grailsHome}/scripts/Package.groovy" )
task('default':"My Script") {
depends( package )
}

O altã operatie Gant utilã este “classpath”, care stabileste cãile din interiorul directorului unei
aplicatii Grails (grails-app,lib,classes) astfel ca eventualele stergeri de fisiere sã nu genereze
exceptii:

grailsHome = ant.project.properties."environment.GRAILS_HOME"
includeTargets << new File ( "${grailsHome}/scripts/Init.groovy" )
task('default':"My Funky Script") {
depends( classpath )
// reference a Grails class here
}

Fragment dintr-un script pentru comanda “grails create-app” (creare director si subdirectoare
aplicatie):

import grails.util.GrailsNameUtils
import grails.util.Metadata

includeTargets << grailsScript("_GrailsPlugins")


includeTargets << grailsScript("_GrailsInit")
includeTargets << grailsScript("IntegrateWith")

grailsAppName = ""
projectType = "app"

target (createApp: "Creates a Grails application for the given name") {


depends(parseArguments, appName)
initProject()
// Create a message bundle to get the user started.
touch(file: metadataFile)
// Set the default version number for the application
updateMetadata( "app.version": grailsAppVersion ?: "0.1",
"app.servlet.version": servletVersion)
installDefaultPluginSet()
event("StatusFinal", ["Created Grails Application at $basedir"])
}

def resetBaseDirectory(String basedir) {


// Update the build settings and reload the build configuration.
grailsSettings.baseDir = new File(basedir)
grailsSettings.loadConfig()

// Reload the application metadata.

9
metadataFile = new File("$basedir/${Metadata.FILE}")
metadata = Metadata.getInstance(metadataFile)
// Reset the plugin stuff.
pluginSettings.clearCache()
pluginsHome = grailsSettings.projectPluginsDir.path
}

target(createPlugin: "The implementation target") {


depends(parseArguments, appName)
metadataFile = new File("${basedir}/application.properties")
projectType = "plugin"
initProject()
// Rename the plugin descriptor.
pluginName = GrailsNameUtils.getNameFromScript(grailsAppName)
if (!(pluginName ==~ /[a-zA-Z-]+/)) {
println ‘’’Error: Specified plugin name [$grailsAppName] is invalid.
Plugin names can only contain word characters separated by hyphens.’’’
exit 1
}

ant.move(
file: "${basedir}/GrailsPlugin.groovy",
tofile: "${basedir}/${pluginName}GrailsPlugin.groovy", overwrite: true)
// Insert the name of the plugin into whatever files need it.
ant.replace(dir:"${basedir}") {
include(name: "*GrailsPlugin.groovy")
include(name: "scripts/*")
replacefilter(token: "@plugin.name@", value: pluginName)
replacefilter(token: "@plugin.short.name@", value: GrailsNameUtils.getScriptName(pluginName))
replacefilter(token: "@plugin.version@", value: grailsAppVersion ?: "0.1")
replacefilter(token: "@grails.version@", value: grailsVersion)
}
// install default plugins into plugin project
installDefaultPluginSet()
event("StatusFinal", [ "Created plugin ${pluginName}" ])
}

target(initProject: "Initialise an application or plugin project") {


depends(createStructure, updateAppProperties)
grailsUnpack(dest: basedir, src: "grails-shared-files.jar")
grailsUnpack(dest: basedir, src: "grails-$projectType-files.jar")
integrateEclipse()
// make sure Grails central repo is prepped for default plugin set installation
grailsSettings.dependencyManager.parseDependencies {
repositories {
grailsCentral()
}
}
}

Resurse:

http://ant.apache.org/
http://ant.apache.org/ivy/
http://gant.codehaus.org/

10

S-ar putea să vă placă și