style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Gradle: Un ejemplo sencillo

Gradle es un sistema de automatización para construcción de proyectos, similar a Ant, Ivy, Maven, etc. Está construido en Groovy, pero no es solamente para construir proyectos en Groovy; puede usarse en proyectos Java sin ningún problema.

La arquitectura es muy interesante. Ha tomado muchas de las cosas buenas de Maven, Ivy y Ant, dejando atrás las desventajas de cada uno. Decidí echarle un ojo, y pensé que sería bueno empezar con algo sencillo: reescribir un script de Ant con el que creo varios jars.

Script original

Mi script original tiene varios targets definidos, y cada uno es para construir un proyecto distinto. Pero todos los proyectos son variantes del mismo sistema, solamente que unos tienen ciertos módulos y otros tienen unos distintos, aunque todos comparten ciertos módulos básicos. El script de hecho fue generado por un plugin viejo de JBoss que permitía empaquetar jars de distintas formas, pero luego lo fui personalizando. Se volvió muy tedioso manejar tanto XML.

El conjunto de proyectos es más o menos así (todos son proyectos de Eclipse):

+ ModuloRaiz1
|
+ ModuloRaiz2
|
+-+ carpeta1
|    |
|    + ModuloA
|    |
|    + ModuloB
|
+-+ carpeta2
|    |
|    + ModuloC
|    |
|    + ModuloD
|
+ ModuloE

Pero son como 30 módulos en vez de 5. Y bueno, el script de Ant es algo así, lo tengo dentro de ModuloRaiz1:

<project name="proyectote" default="todos">

<target name="todos" depends="p1,p2"/>

<target name="p1">
<jar destfile="p1a.jar">
  <zipfileset dir="bin" includes="**/*.class" />
  <zipfileset dir="../ModuloRaiz2/bin" includes="**/*.class,**/*.properties" />
  <zipfileset dir="../carpeta1/ModuloA/bin" includes="**/*.class" />
  <zipfileset dir="../ModuloE/bin" includes="**/*.class" />
</jar>
<jar destfile="p1b.jar">
  <zipfileset dir="bin" includes="**/*.class" />
  <zipfileset dir="../ModuloRaiz2/bin" includes="**/*.class,**/*.properties" />
  <zipfileset dir="../carpeta1/ModuloB/bin" includes="**/*.class" />
  <zipfileset dir="../ModuloE/bin" includes="**/*.class" />
</jar>
</target>

<target name="p2">
<jar destfile="p2c.jar">
  <zipfileset dir="bin" includes="**/*.class" />
  <zipfileset dir="../ModuloRaiz2/bin" includes="**/*.class,**/*.properties" />
  <zipfileset dir="../carpeta2/ModuloC/bin" includes="**/*.class" />
  <zipfileset dir="../ModuloE/bin" includes="**/*.class" />
</jar>
<jar destfile="p2d.jar">
  <zipfileset dir="bin" includes="**/*.class" />
  <zipfileset dir="../ModuloRaiz2/bin" includes="**/*.class,**/*.properties" />
  <zipfileset dir="../carpeta2/ModuloD/bin" includes="**/*.class" />
  <zipfileset dir="../ModuloE/bin" includes="**/*.class" />
</jar>
</target>

</project>

Sólo que en vez de 4 jars, son poco más de 10. El XML completo es muy latoso estarlo editando cuando hay que agregar o quitar módulos de un jar, y para crear un jar nuevo es peor, mucho copy-paste.

Versión Gradle

Con Gradle después de estar investigando un rato, me di cuenta que puedo reusar muchas cosas. Al ser un DSL sobre Groovy, me permite crear variables de cualquier tipo, a diferencia de que en Ant solamente se pueden definir propiedades que son cadenas, y las tareas son prácticamente closures donde pones código y se ejecutará en el momento adecuado. Es mucho más poderoso.

De modo que con Gradle puedo reutilizar muchas cosas. Mi idea inicial fue que debe haber una manera de definir un JAR con los módulos que todos los demás tienen en común, y a ese agregarle los filesets específicos para cada jar que necesito. Y como hay varios filesets que van en varios proyectos, deben de poder definirse por fuera de los jars, y solamente incluir la referencia en donde se necesite. Así que conceptualmente ya sabía lo que necesitaba. Ahora la bronca es implementarlo.

En Gradle no se usa XML, sino sintaxis más bien tipo Groovy. Se definen tareas y una tarea puede depender de otras, algo así:

task tarea1 << {
  println "Ejecutando tarea 1"
}

task tarea2(dependsOn:'tarea1') << {
  println "Ejecutando tarea 2"
}

Lo que estamos haciendo en este caso es definir la tarea1 y agregarle un closure. Y luego definir la tarea2, indicar que depende de la tarea1 y agregarle un closure. Todo esto lo ponemos en un archivo llamado build.gradle. Para ejecutarlo, simplemente hay que ejecutar el comando gradle con el nombre de la tarea que queremos ejecutar. Si ejecutamos tarea2, veremos algo así:

:tarea1
Ejecutando tarea 1
:tarea2
Ejecutando tarea 2

BUILD SUCCESSFUL

Total time: 2.362 secs

No voy a poner el equivalente en Ant porque para empezar no sé ni cómo hacer el equivalente de println en ant; tampoco sabía cómo era en Gradle, solamente lo puse porque así funciona en Groovy... y funcionó sin problemas.

Pero bueno. Ahora el script "real" en Gradle. Primero, quiero definir los filesets. En Gradle se pueden ejecutar tareas de ant y usar objetos de ant, de manera muy sencilla. Entonces pongo esto al principio de mi build.gradle para definir los filesets de los módulos específicos para cada jar (omito los raíces que son comunes a todos porque esos los definiré en el jar básico):

def mod_a = ant.fileset(dir:'../carpeta1/ModuloA/bin', includes:'**/*.class')
def mod_b = ant.fileset(dir:'../carpeta1/ModuloB/bin', includes:'**/*.class')
def mod_c = ant.fileset(dir:'../carpeta2/ModuloC/bin', includes:'**/*.class')
def mod_d = ant.fileset(dir:'../carpeta2/ModuloD/bin', includes:'**/*.class')
def mod_e = ant.fileset(dir:'../ModuloE/bin', includes:'**/*.class,**/*.properties')

Luego, para generar un jar básico, voy a hacer una función. Puedo definir funciones en Gradle, igual que haría un método en Groovy; también puede recibir parámetros, y devolver cualquier objeto. Y los GStrings de Groovy funcionan de manera normal. Así que pongo esto:

def jarBase(String nombre) {
  File destDir = new File('directorioDestino')
  if (!destDir.exists()) {
    destDir.mkdir()
  }
  ant.jar(destFile:"directorioDestino/${nombre}.jar") {
    fileset(dir:'bin', includes:'**/*.class')
    fileset(dir:'../ModuloRaiz2/bin', includes:'**/*.class,**/*.properties')
  }
}

Esta función verifica si existe el directorio destino y si no, lo crea; luego genera un jar con el nombre que le indiquemos, en el directorio destino y lo devuelve. Recordemos que en Groovy, el resultado de la última sentencia ejecutada es también el valor de retorno del método o closure donde se ejecuta. De modo que en el método jarBase, la última sentencia ejecutada ant.jar() es el valor de retorno.

Y ahora sólo queda definir las tareas para crear los jars. Voy a crear igual 2 tareas, así como tenía los 2 targets de ant. Y en cada tarea voy a crear 2 jars. Pero ahora es cuando se pone interesante porque puedo obtener el jar y agregarle más filesets:

task t1 << {
  //Obtengo el jar
  def j = jarBase('p1a')
  //Creo una lista con los filesets adicionales y los agrego
  [ mod_a, mod_e ].each {
    //Puedo usar closures de manera normal. Aquí j es un objeto Jar de Ant
    //en el javadoc encontré el método addFileset.
    j.addFileset(it)
  }
  //Ahora debo ejecutarlo para que se genere el JAR
  j.execute()
  //Y ahora el otro JAR
  j = jarBase('p1b')
  [ mod_b, mod_e ].each { j.addFileset(it) }
  j.execute()
}

task t2 << {
  def j = jarBase('p2c')
  [ mod_c, mod_e ].each { j.addFileset(it) }
  j.execute()
  j = jarBase('p2d')
  [ mod_d, mod_e ].each { j.addFileset(it) }
  j.execute()
}

Ahora si ejecuto "gradle t1" me genera p1a.jar y p1b.jar igual que antes. Pero el script completo no sólo es bastante más compacto que el de ant; es mucho más legible. Y eso es lo que más me gustó porque este script de vez en cuando lo tengo que modificar, para agregar más jars o agregarle o quitarle módulos a uno de los jars existentes.

Incluso, ya después se me ocurrió una versión más compacta. Si modifico el método que crea el jar, puedo hacer cada tarea de una sola llamada, porque el método ya no sólo creará el jar, sino que le agregará los filesets específicos:

def jarBase(String nombre, List fsets) {
  File destDir = new File('directorioDestino')
  if (!destDir.exists()) {
    destDir.mkdir()
  }
  def jb = ant.jar(destFile:"directorioDestino/${nombre}.jar") {
    fileset(dir:'bin', includes:'**/*.class')
    fileset(dir:'../ModuloRaiz2/bin', includes:'**/*.class,**/*.properties')
  }
  fsets.each { jb.addFileset(it) }
  jb.execute()
}

Y con eso, las tareas pueden quedar tan simples como esto:

task nuevo << {
  jarBase('nuevo', [ mod_a, mod_c, mod_d, mod_e ])
}

Con este sencillo ejemplo, es evidente que Gradle tiene mucho más poder que Ant. Sé que en ant se pueden definir tareas custom, pero eso implica crear una clase en Java y compilarla, lo cual puede ser muy engorroso porque será una clase que extienda y utilice varias clases del API de Ant. En Gradle se pueden utilizar tareas custom de Ant de manera sencilla. Y bueno, definir tareas complejas es muy sencillo porque simplemente se programa algo en Groovy dentro de la tarea.

Además de Ant, Gradle puede utilizar muchas cosas de Ivy y Maven, tales como manejo de dependencias, ejecución de pruebas unitarias, etc. Esto es apenas una introducción muy simple para los que no conocen este sistema o no sabían de su existencia. Espero les sea de utilidad.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

Es tremendamente agradable no

Es tremendamente agradable no saber como hacer algo, escribirlo como supones que debe de funcionar y ver que trabaje bien a la primera!

¿Como es la instalación? Supongo que será como la de Ant: descomprimir un archivo y modificar el path.

Nota, el build.xml de ant,

Nota, el build.xml de ant, también es un DSL. Quizá más feo, quizá "externo" pero al fin y al cabo un DSL.

Imagen de ezamudio

build.xml

No ps sí, es un DSL, basado en XML... mejor un build system en klingon.

La instalación de Gradle efectivamente es bajar un zip, descomprimirlo, agregar a tu ambiente la variable GRADLE_HOME y agregar a tu path $GRADLE_HOME/bin

Disculpa mi ignorancia (que

Disculpa mi ignorancia (que hasta pudiera considerarse pereza XD), pero, ¿Gradle también maneja dependencias cómo Maven ó es más bien un complemento de Ant para hacer la automatización de procesos de gestión de proyecto más simple ó es ambos?

Imagen de ezamudio

También

Gradle también maneja dependencias como Maven y como Ivy. No es complemento de Ant, más bien un sustituto (aunque puedes llamar scripts existentes de ant desde Gradle).

No soy un experto en Gradle, yo mismo estoy empezando a usarlo, pero de lo que he visto, se ve mucho mejor que Ant y Maven. Conforme investigue más y lo empiece a usar ya podré escribir más cosas.

Imagen de Raul

Tambien voy empezando con

Tambien voy empezando con gradle! y no puedo decir si es mas o mejor que otros pues el primero sistema de cnstruccion de proyectos que utilizo, muy buen ejemplo zamudio!!
ademas pues no se me hace tane tediosos el uso de gradle!

Imagen de ezamudio

no es tedioso

Es que no es para nada tedioso. Yo no he usado Ivy, pero he usado maven y ant, y a pesar que pueden ser muy buenos para automatizar muchas cosas (j8583 y jAlarms los tengo en maven), puede ser muy engorroso estar lidiando con dependencias, versiones, los targets que hay que correr, etc. Con Maven todo tiene que estar organizado de cierta forma, o se vuelve una pesadilla la construcción del proyecto. Con ant todo puede estar como quieras, pero por lo mismo hay que configurar muchas cosas. Y tanto maven como ant, es puro XML.

Habría que revisar como esta

Habría que revisar como esta eso de las dependencias, a mi en lo personal me agrada Maven y pues se me hace bastante sencillo y eso de las dependencias es "de lo mejor" (que conozco) no se si Gradle se cuelgue de los mismos repositorios que usa Maven pero pues en cuanto a la forma de trabajar las librerías y versiones se me hace agradable (no tedioso).

Con respecto a Ant, claro que si es bien fastidioso tener que hacer esos copy / paste y tener muchas cosas repetidas para cambiar solo unas cosillas aunque también no descarto su poder pues Gradle se ve mejor que Ant pero eso no hace malo a Ant... yo me acuerdo cuando lo vi por vez primera, era algo así de "no maaaa que rifado" (como hoy lo digo con Gradle)

Bueno la cosa es que yo si me aviento a aprender Gradle pero todavía no me atrevería a cambiar a Ant/Maven aunque le doy mi voto de confianza por ser Groovy. Ya había oído cosas muy buenas y por lo que veo aquí está muy accesible de usar

En cuanto a lo compacto si me sorprendió. Yo recuerdo haber visto un Gradle de una sola linea que hacia lo mismito que varias de Ant

Imagen de ezamudio

repos

Usa los mismos repos de maven y de ivy, y me refiero a no sólo centrales sino los adicionales también.

Yo no digo que ant sea malo, también lo uso bastante, pero en comparación, la sintaxis de Gradle me gusta mucho más que el XML. Puedes usar Gradle para puro script tipo ant pero sin XML, eso estoy haciendo yo ahora; lo importante para mí es saber que cuando lo llegue a necesitar, tengo una opción más flexible, poderosa y sencilla que ant y maven juntos. Así como Maven nació como una mejora sobre Ant, Gradle puede verse como una mejora sobre Maven. Cada uno toma lo mejor y aprende de los errores de sus antecesores.

Imagen de benek

Consideraciones

Hace un tiempo publiqué desde el twitter de @javamexico una noticia relacionada, el equipo de desarrollo de Hibernate estaría dejando de lado Maven para utilizar Gradle. Afortunadamente expusieron las razones del cambio, se las dejo para que las lean si les interesa saber un poco más de las ventajas: http://community.jboss.org/wiki/Gradlewhy

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">