domingo, 21 de abril de 2013

Automatizando Builds con GXserver y CruiseControl.NET

Imagen de 'Automatizando Builds con GXserver y CruiseControl.NET'Una de las ventajas de utilizar un repositorio de control de cambios, como es GeneXus Server para el desarrollo con GeneXus, es la posibilidad de automatizar el proceso de armado de un proyecto a partir de los cambios que van haciendo los desarrolladores.

Esta es una guía de cómo poner esto en práctica.


Para qué automatizar el armado del proyecto

Tener un proceso automático es uno de los componentes básicos de Integración Continua, una práctica en el desarrollo de software que consiste en integrar frecuentemente (varias veces por día), el trabajo de los diferentes desarrolladores con el objetivo de detectar lo más pronto posible los problemas de integración. Los ambientes de trabajo de los diferentes desarrolladores se integran por medio de los commits que estos hacen a un repositorio central, y la integración es verificada por un proceso automático de armado del proyecto, que también puede incluir otros tests.

Pero sea en el contexto de Integración Continua, o de integraciones menos frecuentes, el solo hecho automatizar el armado a partir de lo que está en el repositorio tiene ventajas en sí mismo.

El armado de un sistema suele ser un proceso complejo que involucra múltiples etapas, desde obtener los últimos cambios del repositorio, generar y compilar todo lo necesario, ejecutar reorganizaciones, hasta mover los programas al ambiente de ejecución, por nombrar sólo los más visibles.

Si a esto le agregamos los detalles del armado de múltiples versiones del proyecto (que toman cosas de diferentes lugares, necesitan componentes de diferentes versiones, que dejan los resultados en otro lugar, etc.), y diferentes variantes de armado (con o sin tests, armando o no ciertos módulos, produciendo o no un setup, etc.), la complejidad puede crecer significativamente.

Automatizar este proceso implica, entre otras cosas:
  • No tener que dedicar a una persona para ejecutar cada etapa, verificar los resultados, decidir el siguiente paso a ejecutar, etc.,
  • No depender del conocimiento de una persona para ejecutar estos pasos correctamente y en el orden adecuado. ¿Qué pasa cuando esta persona se enferma o se toma vacaciones?
  • No correr el riesgo de que esa persona se equivoque, cosa que frecuentemente ocurre en los momentos de mayor estrés, es decir, cuando puede producir más daño.
Pero además, el tener la posibilidad de ejecutar todo el proceso de armado en forma totalmente desatendida y con un solo comando, nos habilita la posibilidad de dar el siguiente gran paso, que es automatizar también el propio disparo del proceso. Por ejemplo, uno podría querer que cada cierto tiempo se verificara si hubo nuevos commits en el repositorio, y que en caso afirmativo se ejecutara automáticamente el armado del proyecto. Para algunos podría querer hacer esto varias veces por día, para otros sólo durante las noches e incluso algunos solo durante los fines de semana. Aquí es donde entran en escena los llamados servidores de integración continua, de los cuales CruiseControl.NET es un ejemplo.

Combinando GeneXus Server y CruiseControl.NET

Un servidor de integración continua es un servicio que se encarga de monitorear cierto repositorio de software para ver si se produjeron cambios, en cuyo caso puede disparar un proceso de armado del proyecto.

En nuestro caso, lo que vamos a utilizar como servidor de integración continua es CruiseControl.NET. Este software va a monitorear con la frecuencia que indiquemos bases de conocimiento administradas por un GeneXus Server, y en caso de que se hayan producido nuevos commits, se encargará de actualizar una copia de la KB, y ejecutar el proceso de armado. Tanto la actualización como el armado se ejecutarán a través de tareas MSBuild, de forma que no sea necesario levantar GeneXus para ello.

Guía de Configuración

Proceso general

En primer lugar se necesita una máquina en la que estén instalados GeneXus, y CruiseControl.NET. También será necesario poder acceder desde allí a un GXserver, que podrá estar instalado en la misma máquina o no.

Luego de instalar el software conviene obtener desde GXserver una copia de la KB y ejecutar el build de forma manual, para asegurarse de que todo funciona correctamente. Luego de eso se puede proceder a la automatización del build a través de Cruise Control.

Paso 1 - Seleccionar la máquina

El primer paso es elegir la máquina en la que se va a instalar el servidor de Builds. Esta máquina tiene que tener la capacidad de tener instalado tanto GeneXus como CruiseControl.NET.

Requerimientos de instalación de GeneXus
Requerimientos de instalación de CruiseControl.NET

Para poder utilizar la consola web de CruiseControl.NET (WebDashboard) es necesario también tener instalado IIS con ASP.NET habilitado.

En caso de que el GXserver a utilizar no esté en la misma máquina, será necesario tener conectividad HTTPS con la máquina en la que esté instalado GXserver, ya sea a través de intranet o internet.

Paso 2 - Descargar e instalar GeneXus

Es necesario instalar GeneXus versión X Evolution 1 U7 en adelante.

Descargar GeneXus X Evolution 2
Manual de instalación de GeneXus

El programa instalación de GeneXus guarda la ruta del directorio de instalación en la variable de entorno GX_PROGRAM_DIR. Verificar el contenido de esta variable, y ajustarlo en caso de que fuera necesario.

Copiar los siguientes archivos en el directorio de instalación de GeneXus:

Artech.Samples.GXserverExtraTasks.dll
Artech.Samples.GXserverExtraTasks.targets

Paso 3 - Descargar e instalar el cliente de GXserver por línea de comando

Instalar los siguientes archivos en algún directorio local, por ejemplo C:\GXserverClient. En adelante nos referiremos a este directorio como [GXserverClient].

TeamDev.exe
TeamDev.exe.config
TeamDev.msbuild

Paso 4 - Descargar e instalar CruiseControl.NET.

Descargar CruiseControl.NET
Documentación de CruiseControl.NET

El setup presenta tres componentes para instalar. Es necesario como mínimo el CruiseControl.NET Server y se recomienda también instalar el Web Dashboard.



También se recomienda dejar marcadas las opciones de "Install CC.Net server as Windows service" y "Create virtual directory in IIS for Web Dashboard".



Como es usual, se puede elegir también el directorio de instalación, que por defecto es algo similar a C:\Program Files\CruiseControl.Net. En adelante, nos referiremos a este directorio de instalación como [CruiseControl]

Paso 5 - Agregar add-in de GXserver a Cruise Control

CruiseControl reconoce en su configuración por defecto varios sistemas de repositorio (SVN, CVS, etc.). Con este add-in, CruiseControl puede reconocer también a GXserver como un repositorio válido e interactuar con él.

Copiar CCNet.GXserver.Plugin.dll al directorio [CruiseControl]\server.

Paso 6 - Iniciar servidor de CruiseControl.NET

CruiseControl.NET puede ser ejecutado de dos maneras: como un servicio de Windows o como una aplicación de consola. Para el funcionamiento normal, lo recomendado es utilizar el servicio, pero en las primeras etapas de configuración, puede ser útil la aplicación de consola, ya que brinda información inmediata con mensajes en la consola y puede ser más fácil de detener y volver a ejecutar. Una vez que se ha estabilizado la configuración se puede pasar a utilizar el servicio.

Ambas aplicaciones se encuentran en el directorio [CruiseControl]\server, y se llaman ccservice.exe (el servicio) y ccnet.exe (la aplicación de consola), y cuentan con sus respectivos archivos de configuración ccservice.exe.config y ccnet.exe.config. En estos archivos se pueden configurar aspectos específicos a una u otra forma de ejecutar, y cada uno de ellos utiliza un tercer archivo de configuración (ccnet.config), usualmente compartido, donde en particular se establecen cuáles son los diferentes proyectos de armado (build projects).

Cada proyecto de armado indica un cierto repositorio de fuentes, qué cosas obtener de él, cada cuánto hay que consultarlo para saber si hubo cambios, dónde mantener una copia local de los fuentes, y qué ejecutar para realizar el build.

La ubicación por defecto del archivo ccnet.config es también en el directorio [CruiseControl]\server, aunque se puede especificar otra ubicación agregando una entrada como la siguiente en la sección <appSettings> de ccservice.exe.config y ccnet.exe.config:

<appSettings>
   <!-- Without this appSetting ccservice will look for ccnet.config
        in its own directory.
-->
   <add key="ccnet.config" value="c:\some\path\to\ccnet.config"/>
   ...
</appSettings>
A su vez, el contenido inicial del archivo ccnet.config es el siguiente:

<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
  <!-- This is your CruiseControl.NET Server Configuration file. Add
       your projects below!
-->

  <project name="MyFirstProject"
           description="demoproject showing a small config">
    <triggers>
      <!-- check the source control every X time for changes,
       and run the tasks if changes are found -->
     <intervalTrigger name="continuous"
                      seconds="30"
                      buildCondition="IfModificationExists"
                      initialSeconds="5"/>
    </triggers>

   <sourcecontrol type="nullSourceControl"
                  alwaysModified="true" />

    <tasks>
      <exec>
        <!-- if you want the task to fail,
             ping an unknown server
-->
       <executable>ping.exe</executable>
       <buildArgs>localhost</buildArgs>
       <buildTimeoutSeconds>15</buildTimeoutSeconds>
       <description>Pinging a server</description>
      </exec>
    </tasks>

    <publishers>
     <xmllogger />
     <artifactcleanup cleanUpMethod="KeepLastXBuilds"
                      cleanUpValue="50" />
    </publishers>

  </project>

</cruisecontrol>


Como se ejemplifica allí, en cada </project> declarado en el archivo ccnet.config, se pueden establecer una serie de elementos:
  • <triggers> - disparadores de la consulta por cambios en los fuentes. En el ejemplo, el disparo es simplemente por tiempo, y se consultará al repositorio si hay nuevos cambios cada 30 segundos, y se ejecutará el build si hay modificaciones.
  • <sourcecontrol> - el repositorio desde el cual se obtienen los fuentes. El tipo de repositorio puede ser alguno de los repositorios estándar ("svn", "cvs", etc.) o bien, como veremos más adelante, gracias al plugin que instalamos en el paso 4, también "gxserver". En el ejemplo se utiliza un tipo especial "nullSourceControl", que tiene una respuesta fija como que siempre hay nuevos cambios.
  • <tasks> - las tareas a ejecutar cuando se encuentran cambios en los fuentes. Usualmente aquí se utiliza una tarea que ejecute el proceso de build, y en el ejemplo esto es simulado con un comando ping, simplemente para que ejecute algo.
  • <publishers> - son tareas a ejecutar luego de finalizado el build y generalmente se utiliza para recolectar y publicar los resultados.
Esta configuración inicial no realiza el armado de ningún fuente, pero es útil para ejecutar el CruiseControl.NET y verificar si funciona hasta este punto. Una vez en funcionamiento, cualquier cambio en el archivo de configuración será detectado automáticamente.

Para iniciar el servidor, vamos a ejecutar el ccnet.exe desde una ventana de comandos (alternativamente podemos ejecutar la opción CruiseControl.NET en el menú de inicio de Windows.

Luego de iniciar el servidor, para verificar que todo está funcionando bien, se puede acceder a la URL del servidor: [http://localhost/ccnet], en donde aparecerá la consola (Web Dashboard) de CruiseControl.NET:

Paso 7 - Crear la copia de trabajo de la KB

En cada item de armado en CruiseControl.NET, a los cuales se les llama proyectos (projects) se va a especificar cuál es la KB en GXserver de que se trata, qué versión (si hay más de una), qué environment, etc.. Para cada uno de esos proyectos, se va a especificar también la ubicación de una KB local, que se utilizará como copia de trabajo para actualizar con lo que haya en GXserver y realizar el proceso de build.

Se recomienda centralizar todas las copias de trabajo que vayan a ser utilizadas con CruiseControl.NET en una misma estructura de directorios, de forma tal que para cada proyecto exista un directorio, dentro del cual esté como hijo el directorio de la KB de trabajo propiamente dicha.

Por ejemplo, si se va a automatizar el proceso de Build de dos KBs, una KB llamada Bobsville (en su versión principal y en otra llamada "Version 1.0"), y otra KB llamada Sunflower Valley (en su versión principal y en otra llamada "Versión 2.2"), el árbol de directorios podría ser el siguiente:

CruiseControlData
  |
   --- Bobsville
  |      |
  |       --- Principal
  |      |      |
  |      |       --- WorkingCopy
  |      |
  |       --- Version1.0
  |             |
  |              --- WorkingCopy
  |
   --- Sunflower Valley
  |      |
  |       --- Principal
  |      |      |
  |      |       --- WorkingCopy
  |      |
  |       --- Version2.2
  |             |
  |              --- WorkingCopy
  |
  .
  .
  .
Los proyectos que corresponden a una misma KB del server (Bobsville y Sunflower Valley en este caso) aparecen allí agrupados bajo un mismo directorio, aunque esto no es estrictamente necesario. Lo que sí es importante, por razones que se verán más adelante, es que para cada proyecto de armado, exista un directorio específico al proyecto, dentro del cual esté a su vez el directorio de la KB de trabajo.

A los efectos de continuar con el proceso de configuracíón de la integración continua, vamos a considerar que automatizaremos en primer lugar el armado de la versión principal de la KB Bobsville, para lo cual crearemos la copia de trabajo en el directorio C:\CruiseControlData\Bobsville\Principal\WorkingCopy. La KB Bobsville se puede obtener del GXserver de Sandbox de la Evolution 2, es decir en la URL http://sandbox.genexusserver.com/xev2. A continuación se puede ver el contenido de la ventana de Create Knowledge Base from GeneXus Server al momento de crear esta copia de trabajo.

Paso 8 - Primer update y build manual

Luego de crear la copia de trabajo es necesario hacer un Update y un Build, lo cual a su vez requiere definir (o crear) la base de datos operativa, especificar y generar todo por primera vez. Este proceso podría evitarse si se parte de una KB de trabajo existente que ya esté armada, y se copia para aquí con todos sus archivos ya generados.

En cualquier caso, lo importante es verificar que es posible hacer un Update y un Build en forma manual, resolviendo cualquier problema que pudiera surgir, para estar seguros de que está todo bien configurado con respecto a GeneXus y GXserver, y que a partir de este punto solo es necesario proceder a la automatización.

Paso 9 - Configurar el proyecto de armado

Para configurar un proyecto de armado es necesario editar el archivo ccnet.config, para agregar en el XML un nuevo elemento de tipo <project>.

En primer lugar, vamos a declarar algunas variables, cuyo valor depende de cada instalación, y que se van a referenciar después desde la declaración de los datos del proyecto. De esta forma, esas variables pueden ser compartidas para varios proyectos y es más sencillo actualizarlas. Copiar lo siguiente inmediatamente después del elemento raíz del XML (<cruisecontrol xmlns:cb="urn:ccnet.config.builder">):

<!-- GeneXus installations -->
<cb:define GxXEv1U8Dir="C:\Program Files\Artech\GeneXus\GeneXusXEv1U8" />
<cb:define GxXEv2U2="C:\Program Files\Artech\GeneXus\GeneXusXEv2U2" />
<cb:define GxDefault="$(GxXEv2U2)" />

<!-- GXserver credentials -->
<cb:define GXserverUser="GXtechnical\user" />
<cb:define GXserverPassword="**********" />

<!-- MSBuild executable -->
<cb:define MSBuildExe="C:\Windows\Microsoft.NET\Framework\v3.5\msbuild.exe" />

<!-- CruiseControl configuration -->
<cb:define CruiseControlServerDir="C:\Program Files\CruiseControl.NET\server" />
<
cb:define CruiseControlDataDir="C:\CruiseControlData" />
<cb:define GXserverClientDir="C:\GXserverClient" />
<cb:define TeamDevExePath="$(GXserverClientDir)\TeamDev.exe" />
Naturalmente que es necesario modificar algunos de los valores de estas variables (aquellos resaltados con fondo amarillo) con lo que corresponda en cada caso. Luego, a continuación de estas declaraciones de variables, hay que agregar lo siguiente que es la declaración del proyecto de armado:

<project name="Bobsville Principal">
  <sourcecontrol type="gxserver">
    <serverUrl>http://sandbox.genexusserver.com/xev2</serverUrl>
    <serverUsername>$(GXserverUser)</serverUsername>
    <serverPassword>$(GXserverPassword)</serverPassword>
    <serverKbAlias>Bobsville</serverKbAlias>
    <serverKbVersion>Bobsville</serverKbVersion>
    <getAllKbVersions>false</getAllKbVersions>
    <workingDirectory>$(CruiseControlDataDir)\Bobsville\Principal\WorkingCopy</workingDirectory>
    <workingVersion></workingVersion>
    <workingEnvironment></workingEnvironment>
    <localSettings>$(CruiseControlDataDir)\Bobsville\Principal\settings.local</localSettings >
    <dbaseServerInstance></dbaseServerInstance>
    <createDbInKbFolder>true</createDbInKbFolder>
    <dbaseUseIntegratedSecurity>true</dbaseUseIntegratedSecurity>
    <dbaseServerUsername></dbaseServerUsername>
    <dbaseServerPassword></dbaseServerPassword>
    <dbaseName></dbaseName>
    <executable>$(TeamDevExePath)</executable>
    <teamDevTasks>"$(GXserverClientDir)\TeamDev.msbuild"</teamDevTasks>
    <msbuildExecutable>$(MSBuildExe)</msbuildExecutable>
    <autoGetSource>true</autoGetSource>
    <cleanCopy>false</cleanCopy>
    <tagOnSuccess>false</tagOnSuccess>
  </sourcecontrol>
  <triggers>
    <intervalTrigger seconds="600"/>
  </triggers>
  <tasks>
    <msbuild>
      <executable>$(MSBuildExe)</executable>
      <projectFile>$(GXserverClientDir)\TeamDev.msbuild</projectFile>
      <buildArgs>
        /v:Normal
        /t:Build
        /p:WorkingDirectory="$(CruiseControlDataDir)\Bobsville\Principal\WorkingCopy"
        /p:ServerUsername=$(GXserverUser)
        /p:ServerPassword=$(GXserverPassword)
        /p:GX_PROGRAM_DIR="$(GxDefault)"
        /p:MSBuildExtensionsPath="c:\Program Files\MSBuild"
      </buildArgs>
      <logger>$(CruiseControlServerDir)\ThoughtWorks.CruiseControl.MsBuild.dll</logger>
      <timeout>18000</timeout>
    </msbuild>
  </tasks>
  <publishers>
    <xmllogger />
    <artifactcleanup cleanUpMethod="KeepLastXBuilds" cleanUpValue="50" />
    <modificationHistory />
  </publishers>
</project>
Al salvar el contenido del archivo ccnet.config, el cambio será detectado por CruiseControl.NET y si volvemos a la consola (Web Dashboard), veremos ahora nuestro nuevo proyecto. A partir de ahora, cada vez que CruiseControl.NET detecte que hay un nuevo commit (va a estar consultando a GXserver cada 10 minutos, según lo que pusimos en <intervalTrigger>) ejecutará el build de la KB, y podremos ver los resultados desde la propia consola.

¿Todavía por aquí?

Si llegaron hasta este punto, habiendo seguido todos los pasos de arriba, me interesa saber cómo les fue. ¿Problemas, errores, sugerencias?

¿Funcionó todo bien? Si es así, los invito a firmar en el Libro de Visitas. Busquen en la KB Bobsville un objeto con ese nombre, anótense allí y hagan commit para que todos nos enteremos.

Por supuesto, siéntanse libres también de colaborar en el desarrollo de la KB Bobsville. ♦

Imagen: Assembly line robots, publicada por Annette Davis, bajo licencia CC BY-NC-ND 3.0.