Herramientas de desarrollo: Travis CI

      No hay comentarios en Herramientas de desarrollo: Travis CI
netCoreTravis

Hace ya unos meses, hice mis primeras publicaciones sobre tecnología en el blog de un compañero de trabajo, y en ellas hablaba de una práctica que por desgracia, sigue siendo una gran desconocida cuando hablo con gente que se dedica al software. Me estoy refiriendo a la integración continua (CI).

Si bien es cierto, que las pruebas unitarias tienen una funcionalidad clara, no valen de nada si no las ejecutas. Esto puede pasar por muchas razones, pero una de ellas (al menos la que más me suele afectar a mi), es que trabajas sobre una funcionalidad que hay que pasar a la rama principal urgentemente y no hay tiempo de lanzar todas las pruebas unitarias. Durante el desarrollo de esa característica, compruebas que todo funciona bien, y subes tus cambios tranquilo, hasta que de repente… ¡¡Algo se ha roto en otro sitio!!

Gracias a herramientas como AppVeyor , Travis CI o Azure Pipelines, (los 3 son gratis si nuestro repositorio es público) podemos conseguir automatizar este trabajo, de modo que no tengamos que preocuparnos de compilar y ejecutar las pruebas. Ademas, estos sistemas nos aportan otra ventaja adicional, podemos compilar y ejecutar pruebas en diferentes entornos, como veremos a lo largo de esta entrada sobre Travis, y la siguiente que será sobre Azure Pipelines (y como se puede ver en mi colaboración en MascandoBits sobre AppVeyor).

Antes de continuar, es un buen recordar la entrada sobre Mocking (¡o leerla sin aun no lo has hecho!), ya ahora vamos a ver las ventajas de no depender de recursos externos para ejecutar las pruebas unitarias. De hecho, vamos a utilizar ese mismo repositorio para incluir la integración continua con Travis y Azure Pipelines al ser un proyecto NetCore (podemos ejecutarlo en Windows, Linux y MacOS) y tener pruebas unitarias.

Funcionamiento de los servicios CI

En primer lugar, podemos preguntarnos como funcionan estos sistemas de CI. Si utilizas GitHub es muy simple, están totalmente integrados, y con unos pocos clicks podemos hacer que cada vez que hagamos un push al repositorio (o nos manden un Pull Request), automáticamente se lance la compilación y ejecución de las pruebas del código, sin tener que preocuparnos de nada más. Ademas, estos servicios suelen trabajar usando un fichero de configuración «.yml», en el cual definimos todos los pasos que queremos ejecutar, de modo que es fácil reutilizar las configuraciones en nuestros diferentes proyectos con unos cambios mínimos. Una vez terminen, veremos en el historial de commits un indicador de si la integración ha ido bien o no, al igual que en los PR:

Historial Commits

Prueba PR

Ademas, estos servicios suelen permitirnos utilizar badges que podemos añadir a nuestro readme.md para saber siempre a simple vista el estado del proyecto:

Readme.md

Añadiendo Travis CI a nuestro repositorio

Para añadir nuestro repositorio a los trabajo de integración de Travis, en primer lugar, vamos a ir a su web y vamos a pulsar en el botón de registrarnos con GitHub. Nos lanzará una ventana para confirmar que autorizamos la consulta de datos desde GitHub, y una vez aceptemos, nos mostrará una ventana (¿vacía?) con nuestros proyectos. Lo que vamos a hacer, es pulsar sobre el botón «+» para añadir un nuevo repositorio:

Add Project

Sincronizamos nuestra cuenta para que se nos muestren los repositorios, y simplemente, activamos el repositorio que nos interesa:

Add Repo

Con esto, ya hemos indicado a Travis que queremos que lance el CI cada vez que detecte cambios en el repositorio, y Travis se encarga de configurarlo todo en GitHub para que le notifique esos cambios. A partir de este momento, cada vez que hagamos un push al remoto, se iniciara una compilación. En este momento, fallará al no tener el «.yml», asi que vamos a añadirlo.

Fichero .travis.yml

Vamos a crear un fichero en el repositorio que se llamará .travis.yml (el primer «.» delante de travis hay que ponerlo también). En el vamos a poner lo siguiente:

 
# Lenguaje que vamos a usar
language: csharp
dist: trusty
mono: none
# Version del SDK de NetCore que queremos utilizar
dotnet: 2.1.301
# Matriz de sistemas operativos sobre los que queremos lanzar el CI
os:
  - linux
  - osx
# Comando que queremos ejecutar ANTES de compilar  
install:
- dotnet restore
# Script de compilacion
script:
  # Compilamos el proyecto
- dotnet build
  # Ejecutamos las pruebas unitarias
- dotnet test PruebasUnitarias/PruebasUnitarias.csproj
# No queremos que nos notifique por email los resultados
-notifications:
-  email: false

Si nos fijamos, simplemente le estamos indicando al servicio que es lo que queremos que haga. Le indicamos el lenguaje, le indicamos la versión del SDK, y los sistemas operativos sobre los que trabajar.
En el script de instalación (previo a compilar), ejecutamos un restore, para descargar los paquetes Nuget necesarios (sino, vamos a tener un fallo de compilación), y después indicamos que queremos lanzar dotnet build para compilar, y dotnet test «proyecto de pruebas» para ejecutar los test. Os dejo el enlace a la documentación para que podáis consultarla si queréis hacer cosas más concretas.

Una vez que hemos añadido el fichero, commit y para el repositorio, con lo cual, se iniciará automáticamente el trabajo. Si todo va bien, una vez que termine, si vamos a travis, veremos algo como esto:

CI End

Como comentaba antes, Travis nos permite generar badges para saber el estado del repo, esto se hace tan fácilmente como pulsando sobre el badge, y nos mostrara una ventana para seleccionar donde lo queremos poner. Simplemente, tenemos que seleccionar donde lo vamos a colocar, y nos dará el código copy-paste.

badge url

Con esto, ya tenemos una primera aproximación a la Integración Continua con Travis CI. En las siguientes entradas de esta serie sobre CI/CD, veremos Azure Pipelines, y hablaremos sobre el despliegue continuo (CD), que es tan facil como el CI, y nos permite por ejemplo, automatizar la publicación de paquetes nuget, webs, subir los binarios a un FTP, etc…

SourceLink: Depuración de código bajo demanda

Después de la última entrada en colaboración con VariableNotFound, volvemos a la normalidad, y hoy vengo a hablaros de una herramienta relativamente nueva, que me parece muy interesante conocer para nuestros proyectos, esta herramienta es SourceLink.

¿Y que es esta herramienta?, te puedes estar preguntando, pues fácil, es una herramienta de descarga de código bajo demanda, la cual nos permite que si quien desarrolla el paquete hace los deberes (hay que dar tiempo de adaptación también eh, ¡no nos pongamos nerviosos!), nosotros podamos entrar a depurar el código fuente, porque este se nos descargue automáticamente desde el repositorio. Ojo, esto lo que nos permite es entrar y seguir la ejecución, no modificar el paquete en sí mismo, pero esto al menos, nos permite saber si el fallo es nuestro o del autor. (Y creedme, ¡esto es mucho mejor que tener que descargarse el proyecto entero y depurarlo de verdad!)

Dicho esto, vamos a meternos en faena… ¿Como puedo habilitar SourceLink en mi Visual Studio?

Habilitar SourceLink en Visual Studio

Esto en realidad es muy fácil, simplemente tenemos que ir al menú «Herramientas→Opciones», y dentro de la ventana que nos abre, bajar hasta «Depuración→General» y buscar «Habilitar compatibilidad con vínculos de origen», una vez lo encontremos, tenemos que activarlo. Unas pocas lineas más arriba, también debemos desmarcar «Habilitar solo mi código»:

La imagen muestra las opciones que hay que habilitar y deshabilitar

Con esto tan simple, ya lo hemos activado, pero ahora, vamos a ver cómo funciona. Por ejemplo, yo he utilizado un paquete Nuget que hice en su día para WoL, y que hace unas semanas actualicé para dar soporte a SourceLink. Para ello, creamos un proyecto, e instalamos el paquete con el comando:

PM-> Install-Package EasyWakeOnLan

Y por ejemplo, podemos utilizar este código:


string Mac = null;
//Instance the class
EasyWakeOnLanClient WOLClient = new EasyWakeOnLanClient();
//Wake the remote PC
WOLClient.Wake(Mac);

Este código, a priori debería lanzar una excepción, ya que no le indicamos una Mac válida. A modo de comparación, vamos a ejecutarlo primero sin SourceLink:

Como podíamos prever, la librería lanza una excepción, y para nosotros, toda la información se limita a la llamada de la librería, pero si volvemos a ejecutar con SourceLink habilitado, vemos que, al llegar a la línea, nos muestra el mensaje:

SourceLinkDownload

Y cuando pulsamos en «Descarga el origen y continuar depurando», vemos que el error se lanzaba en la primera línea, al intentar operar con la variable «Mac» siendo su valor null:

InternalError

Hay que decir además, que no es necesario que se produzca una excepción para poder entrar a depurar el paquete, si entramos dentro del método como lo haríamos con uno nuestro, también nos permite descargar el código y depurar el paquete.

Como se puede ver, si eres consumidor de paquetería Nuget, esta herramienta es muy interesante, y yo recomiendo tenerla activada, ya que muchos paquetes hoy en día ya lo soportan, y puede ser de ayuda para encontrar el problema. Si eres el desarrollador de un paquete Nuget, esta opción también es interesante, ya que te pueden dar información más detallada sobre la issue que hay en tu código.

Más adelante hablaremos sobre cómo crear y publicar un paquete Nuget en Nuget.org o en un repositorio privado, y veremos más en profundidad que hacer para dar soporte a esta maravillosa herramienta que es SourceLink.

Crear y utilizar librerías multiplataforma con C++ y NetCore (Parte 1)

C++ y NetCore

Son ya varias entradas hablando sobre NetCore, y la potencia de un entorno multiplataforma, pero… ¿Que pasa si por necesidades de rendimiento, necesitamos aun más potencia y es requisito ejecutar código nativo en C++ y NetCore?

Pues precisamente de eso vengo a hablaros hoy, y ademas es una entrada muy especial para mi, ya que vamos a presentarla como una colaboración con el compañero José M. Aguilar de Variable Not Found, el cual, he tenido la suerte de tener como profesor de unos cursos ASP.NET MVC 5 y ASP.NET MVC Core, los cuales recomiendo sin duda (de leer su blog no digo nada por razones obvias…). Y precisamente por esa colaboración, esta entrada se va a presentar en 2 partes:

  • La primera aquí, donde vamos a hablar de como compilar código C++ multiplataforma.
  • La segunda parte en Variable Not Found, donde explicaremos las opciones para consumir las librerías sin perder la capacidad de ejecutar NetCore multiplataforma.

Hechas las presentaciones, vamos a meternos en faena.¿ Que necesitamos para poder ejecutar C++ y NetCore? Pues en primer lugar, necesitaremos una librería C++ que sea multiplataforma. Vamos a crear una librería sencilla, la cual nos permita obtener un string y hacer una suma. Después, vamos a compilarla en Windows, en Linux y en MacOS (versiones: Windows 10, Debian 9.5, MacOS High Sierra 10.13.6)

Creando el proyecto C++

Esta vez vamos a cambiar un poco la manera de trabajar, esto es porque para conseguir independencia del IDE, en C++ se utilizan los CMake, por lo que nuestro proyecto va a constar de 3 archivos:

  1. Nativo.h
  2. Nativo.cpp
  3. CMakeLists.txt

Nativo.h

En este fichero vamos a definir los prototipos de las funciones que expondrá la librería, además de hacer los ajustes necesarios para conseguir una librería multiplataforma. Para ello, crearemos una carpeta para el proyecto, y dentro de esta, un fichero que se llame Nativo y tenga de extensión «.h». Dentro del fichero, pondremos el siguiente código: 

 
#pragma once

//Utilizamos directivas de preprocesado para definir la macro de la API
//Esto hay que hacerlo porque en Windows y en NoWindows se declaran diferente
#ifdef _WIN32
#  ifdef MODULE_API_EXPORTS
#    define MODULE_API extern "C" __declspec(dllexport) 
#  else
#    define MODULE_API extern "C" __declspec(dllimport)
#  endif
#else
#  define MODULE_API extern "C"
#endif
//Declaracion de los métodos nativos
MODULE_API void GetStringMessage(char *str, int strsize);

MODULE_API int Suma(int a, int b);

En él, vemos que la gran mayoría, son definiciones y solo dos líneas son las declaraciones de las funciones que expone la librería pero… ¿Y por qué todas esas definiciones?
Básicamente, por lo que a mi modo de ver, es la herencia de la época (por suerte superada ya) de «Absorbe, Expande y Destruye», por lo cual, en Windows, el código nativo en C++, declara «__declspec» en sus APIs, haciendo incompatible el código nativo con el resto de plataformas UNIX, además de cambiar la extensión de la librería (pero eso lo hacen todos y es irrelevante, porque NetCore ya tiene eso en cuenta). Para más información, podéis echarle un ojo a este enlace.
Pero veámoslo, lo que estamos haciendo, es definir una macro, la cual en función de si se compila en Windows o no, añade «__declspec(dllexport)/__declspec(dllimport)» o lo deja en blanco, de modo que cuando compilemos en la plataforma concreta la librería, corra sin problemas.

Nativo.cpp

En ese fichero, vamos a colocar el cuerpo de los métodos, y es el que más familiar nos va a resultar:

 
#include "Nativo.h"
#include <iostream>
#include <algorithm>

void GetStringMessage(char* str, int strsize) {
	//Comprobamos que el tamaño del buffer que nos indican en mayor que 0
	if (strsize > 0) {
		//Definimos el mensaje
		const char result[] = "Mensaje generado desde C++";
		//Obtenemos cual va a ser la longitud maxima que podemos utilizar
		const auto size = std::min(sizeof(result), strsize) - 1;
		//Compiamos al buffer la cadena
		std::copy(result, result + size, str);
		//Indicamos el final de cadena
		str[size] = '\0';
	}
}

int Suma(int a, int b) {
	return a + b;
}

En él, vemos que se incluyen el fichero de prototipos (.h) y 2 librerías estándar, después de esto, se definen los cuerpos de los dos métodos que expone nuestra API.

CMakeLists.txt

En este fichero, indicaremos a CMake las instrucciones que debe ejecutar para generar el proyecto:

 
# Version mínima de CMake
cmake_minimum_required(VERSION 3.0)
#Nombre del proyecto
project(EjemploNativo)
#Añadimos los ficheros y le decimos que sera una librería compartida
add_library(EjemploNativo SHARED Nativo.cpp Nativo.h)
#Quitamos los prefijos (esto quita el "lib" que añade)
set_target_properties(EjemploNativo PROPERTIES PREFIX "")
#Indicamos el nombre de la salida
set_target_properties(EjemploNativo PROPERTIES OUTPUT_NAME EjemploNativo)

En él, vemos que le vamos a indicar el nombre del proyecto, los ficheros que contiene, y el nombre del binario de salida.

Compilando el proyecto

Para generar nuestro binario, utilizaremos CMake y el compilador que tengamos instalado en nuestro equipo (Visual Studio en Windows, GCC en Linux o XCode en MacOS habitualmente), para ello, lo primero será descargar CMake desde su web o mediante apt-get (en Linux).
Una vez que lo tengamos instalado (en el proceso de instalación, seleccionaremos la opción de añadir al PATH, para poder utilizar CMake por consola), vamos a la ruta donde esta el fichero CMakeLists.txt, y lanzamos una consola (o terminal, depende del OS), y ejecutamos los siguientes comandos:

 
#Para Windows (utilizo Visual Studio 2017, sería necesario indicar el vuestro)
cmake -G"Visual Studio 15 2017 Win64" 

#Para Linux o MacOS
cmake .

#Para compilar, independientemente de la plataforma
cmake --build . --target EjemploNativo --config Debug & cmake --build . --target EjemploNativo --config Release

Si nos fijamos, en Windows le tenemos que indicar el generador aunque solo tengamos un Visual Studio instalado, cosa que en Linux y MacOS no es necesario. Yo he utilizado Visual Studio 2017, pero dejo el enlace a la lista de generadores disponibles.

Si todo ha ido bien, deberíamos ver una salida como esta en nuestras terminales (Pongo imágenes de Windows 10 con PowerShell, Debian 9.5 con terminal y MacOS HighSierra con terminal):

cmake Windows
cmake linux
cmake MacOS

Como se puede ver, dentro de nuestros directorios, ya tenemos nuestra librería «EjemploNativo.XXX»

De este modo, ya hemos conseguido hacer compilación multiplataforma con nuestro código nativo. El siguiente paso, es consumir esas librerías multiplataforma desde nuestra aplicación NetCore, consiguiendo unir C++ y NetCore. Pero para eso, tenéis que visitar el blog del compañero José M. Aguilar donde publico la segunda parte de la entrada. En caso de que encontreis problemas durante la prueba de NetCore en Linux, hace poco hablamos sobre como depurar sobre SSH.

Como siempre, dejo el enlace al código fuente en Github, por si queréis saltaros la parte de escribir el código.