martes, 28 de abril de 2015

Proyectos Gradle con múltiples módulos

Introducción

Al igual que Maven, Gradle tiene soporte para proyectos con múltiples módulos (multi-modulemulti-project). Esto permite tener un conjunto de configuraciones y librerías comunes, compartidas entre varios proyectos relacionados. Esto puede usarse tanto en aplicaciones Java como en librerías de clases o aplicaciones Web, inclusive en proyectos que no son Java.

Para demostrar esta funcionalidad se desarrollarán unos proyectos muy simples:
  • common: tendrá la clase "Util" con un método que imprimirá en consola una cadena indicada como parámetro, intercambiando las mayúsculas y las minúsculas.
  • hello-world: tendrá la clase "Example" con un método "main" usando la clase "Util" para imprimir el texto "Hello World".
  • hola-mundo: tendrá la clase "Example" con un método "main" usando la clase "Util" para imprimir el texto "Hola Mundo". 
En este escenario el proyecto "common" será una librería de clases Java, la cual será usada por los proyectos "hello-world" y "hola-mundo" para imprimir textos de manera "estandarizada".

El proyecto completo se podrá descargar desde: https://github.com/guillermo-varela/gradle-multi-project-example

Proyecto Raíz/Padre (Root/Parent)

Este es el proyecto que contiene a los demás sub-proyectos (o módulos). Para empezar se creará la siguiente estructura de carpetas y archivos:
gradle-multi-project-example
..build.gradle
..gradle.properties
..settings.gradle
..common/
..hello-world/
..hola-mundo/

Las carpetas "common", "hello-world" y "hola-mundo" corresponden a los sub-proyectos, mientras que los demás archivos tendrán la configuración de Gradle común a todos estos.

Archivo build.gradle

plugins {
  id 'net.researchgate.release' version '2.0.2'
}

subprojects {
    apply plugin: 'java'

    project(':hello-world') {
        apply plugin: 'application'
    }

    project(':hola-mundo') {
        apply plugin: 'application'
    }

    sourceCompatibility = 1.7
    targetCompatibility = 1.7

    repositories {
        mavenCentral()
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

Al principio se indica que todo el proyecto usará el plugin release para automatizar el versionamiento, como se indicó en un post anterior.

Luego se tiene la sección "subprojects" en la cual se indican las configuraciones y tareas que se aplicarán a los sub-proyectos. En este caso se indica que todos serán proyectos Java, compilando con el JDK 1.7 y usarán el repositorio Maven Central. También se indica que solamente los proyectos "hello-world" y "hola-mundo" serán aplicaciones ejecutables. Esto es preferible que se encuentre dentro de la configuración de Gradle de cada sub-proyecto, pero se dejó así para demostrar cómo aplicar configuraciones diferenciadas según el proyecto.

Ya al final del archivo se indica que se usará el Wrapper de Gradle en su versión 2.3.

Arhivo gradle.properties

version=1.0.0-SNAPSHOT
Nota: es importante notar que todos los sub-proyectos usarán la misma versión que se indica en el proyecto raíz, dado que de momento el plugin release no soporta tener diferentes números de versión: https://github.com/researchgate/gradle-release#multi-project-builds

Archivo settings.gradle

include ":hello-world", ":hola-mundo", ":common"

En este archivo se indican cuales son los sub-proyectos a tener en cuenta.

Proyecto common

Cuando se usan múltiples proyectos con Gradle no es obligatorio tener un archivo de configuración en todos los proyectos, si toda la configuración necesaria está en el proyecto raíz. Sin embargo en este caso se tendrá la identificación del proyecto en el JAR generado y una dependencia.

Archivo build.gradle

jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Multi-Project Example - Common', 'Implementation-Version': version
    }
}

dependencies {
    compile 'org.apache.commons:commons-lang3:3.3.2'
}

Archivo Util.java

package com.blogspot.nombre_temp.gradle.multi_project.common;

import org.apache.commons.lang3.StringUtils;

public class Util {
  public static void println(String text) {
    System.out.println(StringUtils.swapCase(text));
  }
}

Esta clase (ubicada en la ruta "gradle-multi-project-example/common/src/main/java/com/blogspot/nombre_temp/gradle/multi_project/common/Util.java") es la que usarán los otros proyectos para imprimir cadenas.

Proyecto hello-world

Archivo build.gradle

mainClassName = 'com.blogspot.nombre_temp.gradle.multi_project.hello_world.Example'

jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Multi-Project Example - Hello World', 'Implementation-Version': version
        attributes 'Main-Class': mainClassName
    }
}

dependencies {
    compile project(':common')
}

Dado que buena parte de la configuración de Gradle está en el proyecto raíz, aquí solamente se indica cual será la clase con el método "main" (punto de entrada de la aplicación) y se indica que se tendrá como dependencia el proyecto "common". Nótese que para declarar la dependencia con ese proyecto se usó "compile project(':common')" en lugar de una nombre completo de un repositorio Maven como en otros casos. Adicionalmente no fue necesario incluir manualmente la dependencia de Apache Commons Lang, ya que esta se trae por trasitividad al tener la dependencia con "common".

Nota: de ser posible la única relación entre los sub-proyectos en las configuración de Gradle debe ser a nivel de dependencias, no de configuraciones (por ejemplo que un proyecto no use o modifique tareas de Gradle de otro proyecto), esto con el fin de mantener los proyectos lo menos acoplados posible.

Archivo Example.java

package com.blogspot.nombre_temp.gradle.multi_project.hello_world;

import com.blogspot.nombre_temp.gradle.multi_project.common.Util;

public class Example {
  public static void main(String[] args) {
    Util.println("Hello World");
  }
}

Como se explicaba inicialmente, esta clase (ubicada en la ruta "gradle-multi-project-example/hello-world/src/main/java/com/blogspot/nombre_temp/gradle/multi_project/hello_world/Example.java") contiene el método "main" y usa la clase "Util" para imprimir la cadena "Hello World".

Proyecto hola-mundo

Es casi idéntico al proyecto "hello-world", pero imprimiendo "Hola Mundo".

Archivo build.gradle

mainClassName = 'com.blogspot.nombre_temp.gradle.multi_project.hola_mundo.Example'

jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Multi-Project Example - Hola Mundo', 'Implementation-Version': version
        attributes 'Main-Class': mainClassName
    }
}

dependencies {
    compile project(':common')
}

Archivo Example.java

package com.blogspot.nombre_temp.gradle.multi_project.hola_mundo;

import com.blogspot.nombre_temp.gradle.multi_project.common.Util;

public class Example {
  public static void main(String[] args) {
    Util.println("Hola Mundo");
  }
}

Construcción y Ejecución

Para construir todos los proyectos, basta con abrir una instancia de Terminal (línea de comandos) y ejecutar el comando "gradle wrapper" para inicializar el Wrapper de Gradle y luego "./gradlew build" (o "gradlew.bat build" si se está usando Windows).
:common:compileJava
:common:processResources UP-TO-DATE
:common:classes
:common:jar
:common:assemble
:common:compileTestJava UP-TO-DATE
:common:processTestResources UP-TO-DATE
:common:testClasses UP-TO-DATE
:common:test UP-TO-DATE
:common:check UP-TO-DATE
:common:build

:hello-world:compileJava
:hello-world:processResources UP-TO-DATE
:hello-world:classes
:hello-world:jar
:hello-world:startScripts
:hello-world:distTar
:hello-world:distZip
:hello-world:assemble
:hello-world:compileTestJava UP-TO-DATE
:hello-world:processTestResources UP-TO-DATE
:hello-world:testClasses UP-TO-DATE
:hello-world:test UP-TO-DATE
:hello-world:check UP-TO-DATE
:hello-world:build

:hola-mundo:compileJava
:hola-mundo:processResources UP-TO-DATE
:hola-mundo:classes
:hola-mundo:jar
:hola-mundo:startScripts
:hola-mundo:distTar
:hola-mundo:distZip
:hola-mundo:assemble
:hola-mundo:compileTestJava UP-TO-DATE
:hola-mundo:processTestResources UP-TO-DATE
:hola-mundo:testClasses UP-TO-DATE
:hola-mundo:test UP-TO-DATE
:hola-mundo:check UP-TO-DATE
:hola-mundo:build

BUILD SUCCESSFUL

Total time: 5.544 secs

Como puede verse los tres proyectos se compilaron y se construyeron según las configuraciones realizadas. En el caso de "hello-world" y "hola-mundo" se generaron también los archivos comprimidos con los ejecutables de cada aplicación.

Para ejecutar las aplicaciones se puede usar el comando "./gradlew run", también desde el proyecto raíz:
:common:compileJava UP-TO-DATE
:common:processResources UP-TO-DATE
:common:classes UP-TO-DATE
:common:jar UP-TO-DATE

:hello-world:compileJava UP-TO-DATE
:hello-world:processResources UP-TO-DATE
:hello-world:classes UP-TO-DATE
:hello-world:run
hELLO wORLD

:hola-mundo:compileJava UP-TO-DATE
:hola-mundo:processResources UP-TO-DATE
:hola-mundo:classes UP-TO-DATE
:hola-mundo:run
hOLA mUNDO

BUILD SUCCESSFUL

Total time: 4.303 secs

El resultado es que primero se compila el proyecto "common" (en este caso aparece "UP-TO-DATE" ya que no fue necesario generar el JAR porque se acabó de ejecutar build) ya que se tiene la dependencia hacia este y posteriormente se ejecutan los proyectos "hello-world" y "hola-mundo" (se ejecutan en orden alfanumérico).

Referencias

http://gradle.org/docs/current/userguide/multi_project_builds.html

Más sobre Gradle

http://nombre-temp.blogspot.com/2016/01/tutorial-gradle.html

No hay comentarios.:

Publicar un comentario