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

Construcción de proyectos existentes en Gradle

En una ocasión anterior escribí acerca de Gradle, un sistema de automatización para construcción de proyectos, de manera muy básica. En esta ocasión quiero profundizar un poco sobre una de sus funciones, la cual nos atañe a todos en esta comunidad: construir un proyecto Java.

Gradle tiene una arquitectura de plugins muy buena, y varios plugins muy buenos, por ejemplo el plugin para Java. El plugin para Java permite construir proyectos de manera MUY sencilla si se siguen ciertos lineamientos (y de manera también sencilla si no se siguen dichos lineamientos, como veremos más adelante). Por ejemplo, tener la típica estructura mavenesca en un proyecto ayuda mucho porque son los defaults para construir un proyecto Java:

src/main/java
src/main/resources
src/test/java
src/test/resources

Es decir, un directorio principal src en donde van todos los fuentes. De ahí se dividen dos secciones: main, con el código del proyecto, y test, con el código de pruebas unitarias (se ponen aparte porque así es más fácil separar posteriormente las clases del proyecto para empaquetarlo, excluyendo las pruebas). Dentro de cada uno de estos directorios, se tienen dos secciones: java, con las clases a compilar, y resources, donde van otros archivos necesarios para el proyecto pero que no son fuentes (imágenes, archivos de configuración XML o .properties, etc).

Si se tiene un proyecto con esta estructura, supongamos un "hola mundo", con su respectiva prueba unitaria usando jUnit, entonces el siguiente archivo build.gradle compilará todo el proyecto y ejecutará las pruebas unitarias:

//Esto es para usar el plugin de Gradle para Java
apply plugin:'java'
//El plugin para Java define un montón de cosas, entre ellas varias tareas
//invocamos aquí la principal para compilar todo por default
defaultTasks 'build'

//En esta sección incluimos los repositorios de Maven
repositories {
  mavenLocal()
  mavenCentral()
}

//Aquí se definen las dependencias para cada configuración.
//Importamos jUnit solamente para la compilacion de pruebas unitarias
dependencies {
  testCompile 'junit:junit:4.+'
}

Creo que es más corto y entendible que un script de Ant, y mucho más corto y entendible que un POM de Maven... de entrada, lo primero que se nota es la ausencia de indicaciones de compilación. Así es; el script no dice qué fuentes hay que compilar! Esto es porque por default, el plugin para Java define dos sourceSets, main y test, cada uno con su sección java y resources. Primero se compilarán los fuentes del conjunto main, teniendo disponible en el classpath el directorio src/main/resources; y luego se compilarán los fuentes de test, teniendo en ese classpath el resultado de la compilación de las clases de main y el directorio src/test/resources (y la dependencia jUnit que se definió para la configuración testCompile).

Para ejecutar las pruebas unitarias, se construirá un classpath que consiste en el resultado de la compilación de main, pero usando los resources del conjunto test; el resultado de la compilación de test; las dependencias definidas para la configuración testRuntime, que por default extiende testCompile.

Tareas adicionales

Pero compilar no es lo único que podemos hacer con esta configuración. Usando el plugin para Java, podemos hacer lo siguiente:

  • gradle javadoc para generar el Javadoc del proyecto (solamente de lo que haya en src/java/main por default), queda en build/docs/javadoc.
  • gradle test para ejecutar las pruebas unitarias, deja un reporte de los resultados en build/reports/tests formateado muy bonito en HTML y también en build/test-results en XML (esta tarea se ejecuta como parte de la tarea build).
  • gradle jar crea un JAR con las clases del proyecto (sin las de prueba) y los recursos de src/main/resources, y lo deja en build/libs (el nombre del proyecto por default es el nombre del directorio que contiene al build.gradle).

Así que como pueden ver, si se siguen algunas convenciones simples, construir un proyecto Java con Gradle es extremadamente simple.

Por cierto, si el proyecto tuviera dependencias adicionales, por ejemplo que para compilar las clases del proyecto necesitáramos de alguna biblioteca adicional, digamos SLF4J, se agrega en la sección de dependencias pero para la configuración de compilación. Cuando hay varias dependencias simplemente se deben listar separadas por comas. Y dado que las clases usan el API de SLF4J, necesitaremos una implementación al momento de ejecutar las pruebas unitarias (pero no para compilarlas):

dependencies {
  //Estas dependencias se aplican al momento de compilar el proyecto
  compile 'org.slf4j:slf4j-api:1.6.1', 'javax.mail:mail:1.4.1'
  //Al momento de compilar las pruebas, se usan las dependencias de 'compile'
  //y además estas
  testCompile 'junit:junit:4.+'
  //Y estas dependencias se utilizan únicamente al ejecutar las pruebas
  testRuntime 'org.slf4j:slf4j-simple:1.6.1'
}

Pintando fuera de la raya

Ahora veamos un caso un poco más interesante, y que considero más realista sobre todo para alguien que está considerando adoptar Gradle de manera gradual: ¿qué pasa si queremos compilar un proyecto existente, que no sigue estos lineamientos mavenescos? Por ejemplo un simple proyecto (llamémosle A) que tiene sus fuentes y recursos mezclados bajo src, compila todo a un directorio bin, y tiene una dependencia con otro proyecto que tiene una estructura similar (llamémosle B), ambos bajo la misma carpeta... algo así:

ProyectoA/
  src/
  bin/
ProyectoB/
  src/
  bin/

Pues podríamos compilar ambos con el mismo script... es cosa de configurar los sourceSets, que son los conjuntos de fuentes que se deben compilar. Como mencioné al principio, Gradle (bueno, realmente el plugin de Gradle para Java) define dos sourceSets, main y test, de modo que si tenemos algo así ya no tenemos que definir sourceSets en nuestro código, pero si tuviéramos que definirlo sería así:

sourceSets {
  main {
    java {
      srcDir 'src/main/java'
    }
    resources {
      srcDir 'src/main/resources'
    }
  }
  test {
    java {
      srcDir 'src/test/java'
    }
    resources {
      srcDir 'src/test/resources'
    }
  }
}

De modo que solamente tenemos que cambiar el sourceSet principal (por ahora nos vamos a enfocar a compilar el proyecto A, asumiendo que B está compilado):

sourceSets {
  main {
    java {
      srcDir 'src'
      include '**/*.java'
    }
    resources {
      //es el mismo directorio pero excluyendo los fuentes de Java
      srcDir 'src'
      exclude '**/*.java'
    }
    //Esto por default se construye a partir de las dependencias para
    //compilación, hay que agregarle el proyecto B
    compileClasspath += files('../ProyectoB/bin')
  }
  //Si tuviéramos pruebas unitarias en el layout estándar, habría que agregar
  //las clases del proyecto B al classpath de RUNTIME, no compilación,
  //del sourceSet de pruebas
  test {
    runtimeClasspath += files('../ProyectoB/bin')
  }
}

Con esta configuración, nuestro proyecto A compilará correctamente (si no hay pruebas unitarias, no se compilará nada, no hay problema; como no existe src/test/java, se salta esa parte).

Nótese que estamos invocando el método files(), el cual reside en la clase Project y nos convierte una lista de rutas (cadenas, java.io.File, etc) en una instancia de FileCollection. En la página del plugin y la documentación del API de Gradle hay más info al respecto.

Pero si queremos generar el Javadoc con el script como lo tenemos actualmente, veremos que hay problemas porque al momento de ejecutar esa tarea, no hay referencia a las clases del proyecto B; esto se debe a que el javadoc se crea en una tarea específica para el sourceSet main y solamente el resultado de la compilación y las dependencias configuradas en la sección dependencies. Así que necesitamos modificar eso.

El plugin para Java define una tarea de javadoc (y de jar y varias otras cosas) por cada sourceSet, utilizando el nombre del mismo. Así que hay una tarea mainJavadoc, que si bien no hemos definido nosotros, existe y podemos modificar. Lo único que necesitamos hacer es agregarle la configuración necesaria y dejar que el plugin haga lo demás:

task mainJavadoc(type:Javadoc) {
  //Aquí indicamos los fuentes a documentar
  source sourceSets.main.java
  //Y este es el classpath para el javadoc, que debe incluir las clases
  //compilas de ambos proyectos
  classpath = sourceSets.main.classes + sourceSets.main.compileClasspath
}

Una vez que hemos definido estas propiedades para el mainJavadoc, podemos crear el javadoc de nuestro proyecto sin problemas.

Conclusión

Como se puede ver, usar Gradle para construir proyectos Java es bastante simple; o mejor dicho, construir proyectos Java es bastante simple con Gradle, incluso si no se utiliza la estructura convencional de Maven para acomodar las clases. La filosofía de Gradle de separar las fases de configuración y ejecución nos permite solamente extender las definiciones existentes, en vez de tener que reescribirlas por completo solamente porque hay que cambiar algún valor; en este caso, sólo tuvimos que modificar algunas cosas de la fase de configuración, para que la fase de ejecución haga lo que necesitamos.

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.

El que puedas configurar para

El que puedas configurar para que se adapte a tu estructura esta muy bien. Ant lo hace, pero se queda corto con el manejo de dependencia, Maven es muy rígido ( al menos en apariencia )

Lo único malo que encuentro es que hay que aprender otro DLS más ( por muy simple que este sea ) aunque claro,es cuestión de practicar un poco.

Al parecer vamos saliendo poco a poco de la era XML.

De hcho esa es la idea

Salir del XML ha sido tema para mejorar la forma e que se decriben las tareas. Sobre Maven, pues se me sigue haciendo accesible para tener proyectos dependientes pero eso si, ni discuto que es bien bien pero bieen engorroso y claro que ejemplos como este de Gradle hacen parecer a sus precursores como unos ogros que no dejan de ser buenos pero si OBSOLETOS... Habria que ver si aparte de las ventajas con el DLS representa una mejoraen rendimiento usar Gradle

Cuando yo inicie mis practicas java me daba muchisima flojera tenerque hacer todo el proceso de compilacion as que (como usaba windows) hacia mi archivo .bat que tenia las instrucciones para compilar y copiar archivos y demas con comandos MS-DOS para que nomas le diera click y tan tan,,, hoy en dia ha sido lo mas facil que he probado para construir proyectos... (recuerden que eran practicas de prmeros pasos con Java y no necesitaba dependencias de otros proyectos, librerias ni otras chacharas... si encuentro el script lo publico aunqe no tengamucho chiste porque eran como 5 lineas)

Imagen de ezamudio

Otro DSL

Pues es otro DSL, pero si estás algo familiarizado con la sintaxis de builders que usa Groovy, sigue las mismas formas.

A cambio de esa dificultad (que realmente es muy pequeña), tienes muchísima flexibilidad, puedes seguir aprovechando tus conocimientos de Ant (porque puedes usar tareas de Ant, como mostré en la entrada previa de Gradle que publiqué), en fin, tiene las cosas buenas de Maven, Ant y Ivy, sin tener las cosas feas de Maven, Ant y Ivy.

Imagen de ezamudio

scripts

jdd, la bronca de hacer esos scripts es que en proyectos reales siempre vas a tener alguna dependencia, así sea con puro JEE o bibliotecas muy básicas para logging o driver de JDBC, etc. Estar manteniendo esos scripts de manera no estándar es una pesadilla, porque el día que haces el script y funciona sientes que es lo mejor que existe, pero un par de años después que necesitas modificarlo, quieres matar al autor del script (o sea, tú mismo) porque el bastardo no dejó ni un comentario y no funciona fuera del equipo donde se escribió originalmente, hay que actualizar a patín todo el classpath para jalar las versiones nuevas de bibliotecas que ahora usas, etc...

DUDA SOBRE GRADLE

Buenas tardes señores, les escribo desde Panamá, es solo para preguntarles, una cosa. El archivo build.gradle que es el equivalente el POM.xml, quien lo tiene que crear, algún comando o lo tengo que crear yo en un block de notas y escribir las primeras sentencias del proyecto, quiero saber como empiezo, estoy acostumbrado que con maven solo yo creaba un directorio, luego dentro de ese directorio ejecutaba el comando de maven para que me creara la estructura general del proyecto y alli también se me generaba el archivo de configuración pom.xml. Ahora es lo mismo con gradle, o yo tengo que crear todos los directorios y carpetas del proyecto a mano e incluso el buil.gradle. No entiendo tampoco como empiezo, la parte del mismo build.gradle y lo de hacer el script sí, pero, me pierdo cuando ustedes empiezan por hablar del archivo build.gradle y no dicen de donde sale.

graciaas y espero sus respuestas

Imagen de ezamudio

de un editor

El archivo build.gradle lo creas tú en un editor de texto. No hay creación automática porque Gradle es un versátil y se pueden hacer todo tipo de tareas y configuraciones, no solamente compilar proyectos de Java; por eso no hay un comando para crear un build.gradle. Piensa que es como ant, que no hay un comando para crear un build.xml sino que tienes que hacerlo tú, o usar alguna herramienta externa a Ant para crearlo.

Por otra parte no tendría mucho caso tener un comando para crear un build.gradle de default para compilar un proyecto Java, pues solamente sería esto:

apply plugin:'java'
defaultTasks 'build'

repositories {
  mavenLocal()
  mavenCentral()
}

dependencies {
}

Es demasiado simple, no?

Imagen de luxspes

Dependencias locales

Precisamente acabo de gradlear el proyecto que estoy haciendo actualmente. Y precisamente tuve el problema de no querer obtener por el momento las dependencias desde un repositorio en internet.

Ciertamente se puede hacer como lo describio ezamudio:

compileClasspath += files('../ProyectoB/bin')

pero en realidad, de acuerdo con la documentacion de Gradle, el modo recomendado de hacer esto es asi:

dependencies { 
        compile fileTree(dir: '../ProyectoB/build/libs', includes: ['*.jar'])          
}

asi las dependencias quedan dentro del area de dependencias, y cuando uno desea actualizar y utilizar repositorios de maven, el cambio esta localizado a esa parte.

Definitivamente me esta gustando Gradle, lo que hacia con 200 lineas en Ant, lo hago con 20 en Gradle (y Maven, bueno, Maven es sencillamente un horror, a menos que hagas los proyectos exactamente como los quiere Maven)

Imagen de ezamudio

Dependencias

La bronca es que para poner esa sección que dices en dependencies es porque tienes un JAR. En mi ejemplo (basado en un caso de la vida real), no tengo un Jar, solamente un directorio con clases compiladas (si te fijas, Gradle no toca el ProyectoB, o sea no compilo nada ahí ni creo un jar ni nada, solamente lo menciono en esa línea para incluir el directorio con las clases que tiene ya compiladas).

DUDA SOBRE GRADLE [GRACIAS]

OK. Muchas gracias. la verdad tenía esa inquietud, a veces cuando uno se acostumbra a tener algo piensa que todo tiene que ser equivalente.

muchisimas gracias.

saludos.

Imagen de luxspes

Jars: En Gradle son demasiado faciles

La bronca es que para poner esa sección que dices en dependencies es porque tienes un JAR.

Es cierto, necesitas un JAR, aunque gracias a Gradle, yo no lo llamaria una bronca.

En mi ejemplo (basado en un caso de la vida real), no tengo un Jar, solamente un directorio con clases compiladas (si te fijas, Gradle no toca el ProyectoB, o sea no compilo nada ahí ni creo un jar ni nada, solamente lo menciono en esa línea para incluir el directorio con las clases que tiene ya compiladas).

De acuerdo, aunque, gracias a Gradle, hacer que el proyecto B tenga un JAR es tan facil, que.... bueno... por que no tener un JAR?, el JAR te trae orden, estructura, y para tenerlo todo lo que necesitas en un build.gradle para el proyecto B con el siguiente contenido:

 
apply plugin: 'java'

Y eso es todo es imposible hacer mas facil hacer un "JAR". Por que no meter al proyecto B en en un JAR si es tan facil?

Por otro lado me pregunto... (suponiendo que uno se quiera ahorrar los menos de 30 teclazos necesarios para meter al proyecto B en un JAR... se valdria en Gradle hacer algo como esto?:

 
dependencies {  
        compile fileTree(dir: '../ProyectoB/bin', includes: ['*.class'])          
}

Sospecho que no por la estructura "carpeta x package" de java...

Por cierto, perdon por que no lo comente antes, pero muy buen articulo.

Imagen de ezamudio

No funciona

Eso de fileTree con el directorio bin y que incluya los .class solamente, no funciona, porque lo que hace es meter CADA archivo .class al classpath, de modo que no encuentra ninguna clase.

Ahora, la triste historia de por qué en mi caso tuve que meter el directorio bin del ProyectoB al classpath directamente, en vez de armar un jar y meterlo en dependencias:

1. El proyecto A necesita un par de clases del proyecto B, nada más.
2. El proyecto B depende de los proyectos C, D, E, F y G.
3. El proyecto C depende de los proyectos H, J, K.
4. El proyecto D depende de los proyectos F, H, G.
5. El proyecto E depende de los proyectos Z, W, X y K.
etc etc etc etc.

Entonces jamás voy a poder compilar el proyecto B con script de una línea. Ni con dos, de hecho, suponiendo que agregara la linea buildDir='bin' para que tome ese directorio como el de resultados, porque Eclipse al compilar, pone directamente las clases compiladas bajo bin, pero Gradle espera ver un classes bajo bin...

Aquí estamos hablando de dependencias internas. Por supuesto que para dependencias externas (bibliotecas que bajaste de internet pero que no quieres que se vuelvan a bajar via maven porque no quieres medio internet en tu disco), la manera recomendada es que agregues el fileTree en dependencies.

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