jueves, 9 de abril de 2015

Gradle - Versionamiento Automático

Introducción

En el post anterior se mostró cómo se puede configurar un proyecto (muy simple) para que use Gradle como herramienta de construcción. En esta ocasión se mostrará cómo usar Gradle para automatizar el versionamiento de los proyectos.

Se partirá usando como base el proyecto desarrollado en aquel post anterior, el cual se puede descargar desde: https://github.com/guillermo-varela/gradle-example/releases/tag/1.0.0

Quienes prefieran ver el resultado final, lo podrán descargar desde: https://github.com/guillermo-varela/gradle-example/releases/tag/1.0.1

Repositorio Maven

Tanto en los proyectos corporativos de las organizaciones como en proyectos de código abierto es relativamente común que los proyectos compilados y construidos (JAR, WAR, EAR, etc.) se almacenen en un repositorio Maven no solamente para tener el proyecto versionado, sino también en el caso de los proyectos que son librerías, para que puedan incluirse fácilmente en otros proyectos, indicando la versión específica que se quiere.

Para este ejemplo se usará una instalación local de Nexus, pero los conceptos pueden aplicarse a repositorios remotos, corporativos o públicos. Una vez descargado Nexus, para instalarlo se debe descomprimir el archivo obtenido en la carpeta de preferencia y dentro de la sub-carpeta "bin" ejecutar el comando que corresponda según el sistema operativo:

Linux/Mac: nexus install
Windows: nexus.bat install

Nota: En Windows los comandos de Nexus deben ejecutarse desde una línea de comandos como administrador.

Para iniciar Nexus se ejecuta el siguiente comando:

Linux/Mac: nexus start
Windows: nexus.bat start

Una vez iniciado, se puede acceder a la interfaz web mediante la URL: http://localhost:8081/nexus

Las credenciales por defecto como administrador son:
Usuario: admin
Contraseña: admin123

Figura 1 - Instalación local de Nexus

Gradle Maven

Es el plugin de Gradle que se usará para automatizar la publicación de las versiones del proyecto en el repositorio de Maven (el Nexus que se acabó de instalar).

Para usarlo, lo primero que se debe hacer es incluirlo y aplicarlo en el archivo de configuración del Gradle "build.gradle", agregando la siguiente instrucción:

apply plugin: 'maven'

Anteriormente se había visto cómo es posible crear propiedades dentro del mismo archivo "build.gradle", las cuales quedan disponibles como variables para el resto del archivo, como por ejemplo:

version = 1.0.0

En esta ocasión se creará el archivo "gradle.properties" en el cual se declararán algunas de estas propiedades y quedarán disponibles para usarlas dentro de "build.gradle":

version=1.0.1-SNAPSHOT
group=com.blogspot.nombre-temp

Aquí se incrementó la versión y se adicionó el sufijo "-SNAPSHOT", el cual es un estándar de Maven para indicar que es una versión que aún se encuentra en desarrollo y no hace parte de un "release" definitivo. Como su nombre en inglés lo indica, puede entenderse como la "foto" del proyecto en un determinado momento, a la cual Nexus le concatenará automáticamente la fecha en que se creó.

También se añadió la propiedad "group" la cual corresponderá al "groupId" del proyecto de Maven, mientras que "artifactId" será el mismo nombre del proyecto por defecto.

Ahora se deben indicar lo repositorios Maven a los que se deben subir las versiones, tanto "SNAPSHOT" como "RELEASE" (versiones definitivas) mediante la siguiente tarea de Gradle:
uploadArchives {
    repositories {
        mavenDeployer {
            snapshotRepository(url: "http://localhost:8081/nexus/content/repositories/snapshots/") {
                authentication(userName: "admin", password: "admin123")
            }
            repository(url: "http://localhost:8081/nexus/content/repositories/releases/") {
                authentication(userName: "admin", password: "admin123")
            }
        }
    }
}

Nota: Se usan los repositorios "snapshots" y "releases" porque ya vienen en la instalación de Nexus, sin embargo pueden crearse repositorios dentro de Nexus con cualquier nombre.

En este punto ya es posible ejecutar manualmente la nueva tarea de Gradle y subir el proyecto a Nexus. Si se deja en la versión el sufijo "-SNAPSHOT", el proyecto se subirá automáticamente al repositorio "snapshots", de lo contrario se usará "releases" por defecto. Para esto se usará el comando "gradlew uploadArchives":

gradlew uploadArchives

:compileJava
:processResources UP-TO-DATE
:classes
:jar
:startScripts
:distTar
:distZip
:uploadArchives
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1-SNAPSHOT/gradle-example-1.0.1-20150408.053732-1.jar to repository remote at http://localhost:8081/nexus/content/repositories/snapshots/
Transferring 1K from remote
Uploaded 1K
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1-SNAPSHOT/gradle-example-1.0.1-20150408.053732-1.zip to repository remote at http://localhost:8081/nexus/content/repositories/snapshots/
Transferring 377K from remote
Uploaded 377K
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1-SNAPSHOT/gradle-example-1.0.1-20150408.053732-1.tar to repository remote at http://localhost:8081/nexus/content/repositories/snapshots/
Transferring 420K from remote
Uploaded 420K

BUILD SUCCESSFUL

Total time: 15.108 secs

Figura 2 - Proyecto versionado en Nexus

Como puede verse en la figura 2, en Nexus quedan almacenados los archivos de la construcción del proyecto (JAR, ZIP, TAR) con la versión indicada (1.0.1-SNAPSHOT).

Repositorio Git

Para quienes tenga el proyecto desarrollado desde cero localmente, y no hayan creado un repositorio de Git, es un buen momento para crear uno. No necesariamente se debe crear un repositorio remoto en servidores como GitHub o Bitbucket, un repositorio local sirve para los propósitos de este post, y de hecho es una buena práctica en el desarrollo de software, ya que permite versionar los proyectos y devolver cambios más fácilmente.

Quienes sean completamente nuevos usando Git o sistemas de control de versiones en general, pueden revisar el siguiente tutorial, el cual no solamente explica cómo funciona sino también el porqué es un tema tan importante: http://readwrite.com/2013/09/30/understanding-github-a-journey-for-beginners-part-1

Para crear el repositorio local de Git lo único que se necesita es ejecutar desde la raíz del proyecto el siguiente comando:

git init
Initialized empty Git repository

Luego se debe modificar (o crear) el archivo ".gitignore", también en la raíz del proyecto:

.gradle/
build/

Estas dos líneas indican que las carpetas ".gradle" y "build" no se incluirán en el repositorio de código. En general no se recomienda incluir en los repositorios carpetas y archivos que se generen como parte del proceso de compilación/construcción del proyecto, mas no como parte del código como tal, ya que estos pueden generarse desde el equipo en el cual se esté trabajando, ahorrando espacio en el servidor remoto (en caso de tenerlo).

Un artículo que explica cómo se pueden determinar los archivos a incluir en un repositorio de código se puede encontrar en: http://blog.jooq.org/2014/09/08/look-no-further-the-final-answer-to-where-to-put-generated-code

Para indicar que los demás archivos se deben incluir en el repositorio se ejecuta el siguiente comando, incluyendo el punto del final:

git add .

Y finalmente para efectivamente incluir los archivos en el repositorio se ejecuta:

git commit -m "First commit."

Para marcar versiones definitivas (release) del proyecto en el repositorio Git se usará la funcionalidad de Tagging, la cual permite marcar un punto en la historia del repositorio como importante y por cada Tag creado crea una copia del estado del proyecto en ese momento y permite su descarga específica. Por ejemplo, quienes usaron el enlace para descargar el proyecto desde GitHub, pudieron ver que se está referenciando el tag "1.0.0".

Manualmente puede crearse un tag con el nombre "1.0.0" y el comentario "First tag" mediante el siguiente comando:

git tag -a "1.0.0"

Los tags creados en un repositorio pueden listarse mediante el comando:
git tag -l
1.0.0

Automatización del Versionamiento

Gradle-Release es el plugin que permite automatizar el versionamiento del código del proyecto en repositorios como Git, SVN, Mercurial, etc. Adicionalmente puede integrarse con el plugin de Maven anteriormente mencionado para que dicho versionamiento también sea automatizado.

Para usarlo, lo primero que se debe hacer es incluirlo y aplicarlo en el archivo de configuración del Gradle "build.gradle". Esto se debe hacer al principio del archivo y la sintaxis dependerá de la versión de Gradle que se esté usando:

Gradle 2.0 ó anterior:
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'net.researchgate:gradle-release:2.0.2'
    }
}
apply plugin: 'net.researchgate.release'

Gradle 2.1 ó posterior:
plugins {
  id 'net.researchgate.release' version '2.0.2'
}

Dado que en este ejemplo el proyecto está usando el Wrapper de Gradle con la versión 2.3, se asumirá que se usa la última sintaxis.

Para integrar este plugin con el de Maven, basta indicar que la creación de la versión "Release" en el repositorio de código (el tag) dependerá de que se haga lo mismo en Maven, relacionando las tareas de Gradle mediante la siguiente instrucción al final del archivo "build.gradle":

createReleaseTag.dependsOn uploadArchives

Este plugin se encarga de verificar, compilar, construir, versionar y publicar el proyecto por nosotros mediante el comando "gradlew release", sin embargo si se ejecuta en este momento el resultado tendrá un error:

gradlew release

:release
:gradle-example:initScmPlugin
:gradle-example:checkCommitNeeded FAILED
:release FAILED
Release process failed, reverting back any changes made by Release Plugin.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':checkCommitNeeded'.
> You have unversioned files:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
?? gradle.properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 7.419 secs

Básicamente se está indicando que se tienen cambios en el proyecto que aún no han sido incluidos en el repositorio de Git (commit). Este plugin dispone de múltiples configuraciones que permiten alterar su funcionamiento y en este caso se podría crear la propiedad "failOnCommitNeeded" con el valor "false" para que permita crear la nueva versión sin tener todos los cambios en el repositorio.

En este caso simplemente se incluirán los nuevos archivos (add), se guardarán los cambios (commit) y se publicarán en el repositorio remoto de Git (push) en caso de usar uno:

git add .
git commit -m "Maven and release plugins"
git push

Una vez hecho esto, ya debe ser posible la generación del nuevo release:

gradlew release
:release
:gradle-example:initScmPlugin
:gradle-example:checkCommitNeeded
:gradle-example:checkUpdateNeeded
:gradle-example:unSnapshotVersion
> Building 0% > :release > :gradle-example:confirmReleaseVersion
??> This release version: [1.0.1]
:gradle-example:confirmReleaseVersion
:gradle-example:checkSnapshotDependencies
:gradle-example:compileJava UP-TO-DATE
:gradle-example:processResources UP-TO-DATE
:gradle-example:classes UP-TO-DATE
:gradle-example:jar
:gradle-example:startScripts
:gradle-example:distTar
:gradle-example:distZip
:gradle-example:assemble
:gradle-example:compileTestJava UP-TO-DATE
:gradle-example:processTestResources UP-TO-DATE
:gradle-example:testClasses UP-TO-DATE
:gradle-example:test UP-TO-DATE
:gradle-example:check UP-TO-DATE
:gradle-example:build
> Building 0% > :release > :gradle-example:preTagCommit
Username for 'https://github.com': XXX
Password for 'https://XXX@github.com':
:gradle-example:preTagCommit
:gradle-example:uploadArchives
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1/gradle-example-1.0.1.jar to repository remote at http://localhost:8081/nexus/content/repositories/releases/
Transferring 1K from remote
Uploaded 1K
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1/gradle-example-1.0.1.tar to repository remote at http://localhost:8081/nexus/content/repositories/releases/
Transferring 420K from remote
Uploaded 420K
Uploading: com/blogspot/nombre-temp/gradle-example/1.0.1/gradle-example-1.0.1.zip to repository remote at http://localhost:8081/nexus/content/repositories/releases/
Transferring 377K from remote
Uploaded 377K
> Building 0% > :release > :gradle-example:createReleaseTag
Username for 'https://github.com': XXX
Password for 'https://XXX@github.com':
:gradle-example:createReleaseTag
> Building 0% > :release > :gradle-example
:updateVersion
??> Enter the next version (current one released as [1.0.1]): [1.0.2-SNAPSHOT]
:gradle-example:updateVersion
> Building 0% > :release > :gradle-example:commitNewVersion
Username for 'https://github.com': XXX
Password for 'https://XXX@github.com':
:gradle-example:commitNewVersion

BUILD SUCCESSFUL

Total time: 1 mins 12.978 secs

Han sucedido varias cosas con la ejecución de este comando:

  1. El plugin "Gradle Release" primero tomó el número la versión del proyecto indicado en el archivo "gradle.properties", automáticamente le quitó el sufijo "-SNAPSHOT" (ya que estamos generando una versión release) y pidió confirmar si esta es la versión a generar.
  2. Compilación, construcción y generación los archivos ejecutables del proyecto.
  3. Genera un commit en el repositorio Git.
  4. Dado que en este caso se estaba usando un repositorio Git remoto (GitHub), se solicitaron las credenciales para subir los cambios (push).
  5. Se cargaron los archivos de distribución (JAR, TAR, ZIP) en el repositorio "releases" de Nexus con la versión generada (1.0.1).

    Figura 3 - Versión release en Nexus

  6. Creación del tag en el repositorio Git. El tag usa como nombre la versión del proyecto. Nuevamente, como en este caso se tiene un repositorio remoto, se solicitaron las credenciales y se hizo el push.
    Figura 4 - Tags en GitHub

  7. Se presentó una sugerencia para el siguiente número de versión del proyecto y se pidió confirmarlo. Es de notar que el plugin automáticamente volvió a adicionar el sufijo "-SNAPSHOT",  ya que la versión release ya fue generada y publicada.
  8. Nuevo commit y push en el repositorio Git, dejando el proyecto listo para continuar su desarrollo.

En resumen, solamente ejecutando el comando "gradlew release" se ha podido generar una versión nueva del proyecto, publicando los archivos necesarios en los repositorios Maven y Git.

Como dato adicional, para quienes consideren que la manera de incrementar el número de la versión que usa el plugin es suficiente y no quieren confirmar la versión cada vez (o están usando un servidor de integración continua), pueden ejecutar lo siguiente:
gradlew release -Pgradle.release.useAutomaticVersion=true

O también indicarse desde el principio las versiones:
gradlew release -Pgradle.release.useAutomaticVersion=true -PreleaseVersion=1.0.0 -PnewVersion=1.1.0-SNAPSHOT


Para tener en cuenta

Quizás algunos piensan que se ha realizado demasiado trabajo para un proyecto tan pequeño. En primer lugar, un proyecto real es mucho más grande y complejo que el ejemplo presentado para estos posts, el cual se ha mantenido muy simple para centrar mejor la atención en los temas de Gradle, y a medida que los proyectos son más grandes se hacen más complejos de administrar, sin embargo los archivos de configuración de Gradle varían muy poco, por lo que es un esfuerzo de una sola vez, que en el mediano/largo plazo ahorra mucho trabajo.

Referencias

http://gradle.org/docs/current/userguide/maven_plugin.html
https://github.com/researchgate/gradle-release
http://readwrite.com/2013/09/30/understanding-github-a-journey-for-beginners-part-1
https://books.sonatype.com/nexus-book/reference/install.html
http://blog.jooq.org/2014/09/08/look-no-further-the-final-answer-to-where-to-put-generated-code

Más sobre Gradle


No hay comentarios.:

Publicar un comentario