Cuando en mi anterior post explicaba las ventajas de usar un servidor de integración continua, decía que el primer paso para mejorar el proceso de integración es automatizar el proceso de compilación. En este post voy a explicar con un ejemplo sencillo cómo podemos automatizar este proceso usando MSBuild.

Para este ejemplo vamos a tener una solución de Visual Studio con dos proyectos: nuestra aplicación y un proyecto de tests, y nuestro script se va a encargar de realizar los siguientes pasos:

Limpiar los resultados de ejecuciones anteriores. Compilar la solución. Ejecutar los tests. Generar un paquete NuGet con el resultado.

El objetivo será que alguien que se baje el proyecto desde el control de código fuente pueda realizar todos estos pasos ejecutando un único comando, sin necesidad de instalar manualmente ningún componente adicional en la máquina (más allá el SDK de .NET). De esta forma conseguiremos que la compilación siempre se realice en un entorno controlado, con versiones adecuadas de cada dependencia y sin posibilidad de introducir errores en el proceso por saltarnos algún paso manual.

Estructura de Carpetas

Cuando diseñamos un script de estas características, es importante definir una estructura de carpetas razonable. Esto, además de ayudarnos a crear el script, nos permitirá adaptarlo fácilmente a nuevos proyectos siempre que mantengamos la misma estructura de carpetas.

La estructura de carpetas que vamos a utilizar es la siguiente:

La carpeta src será la carpeta donde se encontrará la solución de Visual Studio y los paquetes NuGet utilizados. Toda la gestión de paquetes NuGet la haremos desde Visual Studio, donde además de instalarlos deberemos hablitar la opción de Restaurar Paquetes NuGet (Enable NuGet Package Restore). Podríamos hacerlo desde el propio script de compilación usando NuGet por línea de comandos, pero resulta más cómodo hacerlo desde Visual Studio.

En la carpeta build generemos los archivos compilados (.exes y .dlls) y la usaremos como carpeta de trabajo durante la compilación.

La carpeta results contendrá en resultado final del proceso. En nuestro caso será un paquete NuGet y un fichero xml con los resultados de la ejecución de los tests.

En la carpeta raíz del proyecto tendremos el script de compilación propiamente dicho ( sample.build ) y un fichero build.cmd para lanzar el proceso cómodamente.

MSBuild: Targets y Tasks

MSBuild utiliza archivos XML para configurar el proceso de compilación. Estos ficheros se organizan alrededor del concepto de Target s. Un Target representa un proceso que se puede invocar de forma independiente y que, a su vez, puede depender de otros procesos.

En nuestro ejemplo, cada una de las fases de la compilación (limpiar, compilar, empaquetar, etc.) será un Target . Además crearemos un Target que dependa de todos los demás de manera que al invocarlo se ejecute el proceso completo.

La estructura del fichero incluyendo sólo los Target s es la siguiente:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Default"> <Target Name="Default" DependsOnTargets=" Clean; Build; RunTests; CreatePackage"/> <Target Name="Clean">...</Target> <Target Name="Build">...</Target> <Target Name="RunTests">...</Target> <Target Name="CreatePackage">...</Target> </Project>

Para invocar un Target concreto por línea de comandos, debemos usar el parámetro /t . Si no indicamos nada, se ejecutará el Target por defecto (en este caso Default ). Esto nos permite probar el script paso a paso:

msbuild.exe sample.build ← Esto ejecuta el target por defecto msbuild.exe sample.bulid /t:RunTests ← Esto sólo ejecutaría el target RunTests

Para definir las acciones a realizar en cada Target se utilizan las Task s. Las Task s son operaciones que nos ofrece MSBuild para manejar archivos y directorios, compilar proyectos, ejecutar comandos externos, etc. Además de la que incluye MSBuild puedes crear tus propias Task s o usar alguna de las librerías existentes, como MSBuild Extension Pack o MSBuild Community Tasks).

Al crear el script podemos definir propiedades y grupos de ficheros usando los elementos Property e ItemGroup . Las propiedades funcionan de manera similar a las variables, y podemos utilizarlas para almacenar la ruta hasta ficheros, parámetros de compilación, etc. Los grupos de ficheros sirven, como era de esperar, para indicar un conjunto de ficheros que luego podremos referenciar en las Task s.

Aunque no voy a entrar en detalles para no alargar mucho el post, veamos cómo se implementa uno de los pasos: la ejecución de los tests:

<PropertyGroup> <BuildDir>$(MSBuildProjectDirectory)\build\</BuildDir> <ResultsDir>$(MSBuildProjectDirectory)\results\</ResultsDir> <NUnit>$(MSBuildProjectDirectory)\src\packages\NUnit.Runners.2.6.2\tools

unit-console.exe</NUnit> </PropertyGroup> <Target Name="RunTests"> <Exec Command='"$(NUnit)" "$(BuildDir)\Tests.dll"' WorkingDirectory='$(ResultsDir)'/> </Target>

En el PropertyGroup declaramos una serie de propieades con la ruta hasta el ejecutable de NUnit-Console.exe que se encargará de lanzar los tests, la carpeta donde se encuentran los ficheros compilados y la carpeta donde se almacenan el resultado de los tests.

El Target RunTests utiliza la Task Exec para ejecutar NUnit-Console.exe pasándole como parámetros el assembly con los tests e indicando como carpeta de trabajo la carpeta de resultados. Como podéis ver, podemos usar string interpolation con las propiedades que hemos definido antes para construir los argumentos de la Task .

En el script de compilación completo podéis ver cómo se implementan el resto de Target s.

Resumen

Espero que este post haya servidor para desmitificar un poco el uso de MSBuild, podéis encontrar el código completo del proyecto en mi cuenta de github para jugar con él y ver cómo funciona.

Es importante tener una buena estructura de carpetas que podamos reutilizar de proyecto a proyecto, porque eso nos ayudará también a reutilizar el propio script de compilación. Lo normal es acabar con un script bastante genérico que luego se pueda adaptar a otros proyectos mediante la definición de propiedades y nuevos targets.

MSBuild es una herramienta muy potente y con él podemos automatizar muchas más cosas de las que se ven en este ejemplo. El mayor inconveniente que tiene (para mi gusto) es el uso de XML, que hace que resulte todo un poco más lioso, pero una vez que te acostumbras merece la pena.