Crear un paquete nuget y publicarlo puede ser sencillo, pero también es una operación que nos puede llevar más tiempo del previsto. La idea de este post es automatizar (en la medida de lo posible) la creación y publicación de un paquete nuget en un feed de Azure DevOps.
Además, y asumiendo que “lo perfecto es enemigo de lo bueno”, he procurado que tanto el paquete como la pipeline sean lo más sencillas posibles. Ya habrá tiempo después para que cada uno personalice el resultado.
TL;DR Usaremos cookiecutter (un paquete de Python) para generar un paquete nuget listo para ser publicado. Tendremos que crear en Azure DevOps un Environment (para la aprobación manual de la publicación), así como una pipeline y algunas variables para versionar el paquete con SEMVER.
Cookiecutter es un paquete de Python para generar proyectos, el equivalente a Yeoman de npm. He usado cookiecutter simplemente porque ahora estoy usando más Python que Javascript, pero la idea es la misma, automatizar la creación de todo lo necesario para crear y publicar un paquete de nuget.
Asumiendo que tienes cookiecutter instalado (instalarlo es tan sencillo como ejecutar pip install cookiecutter
), generaremos todo lo necesario con cookiecutter https://github.com/panicoenlaxbox/cookiecutter-csharpnugetpackage
, que nos preguntará por:
- Nombre del paquete
- Nombre de la organización de Azure DevOps
- Nombre del proyecto de Azure DevOps
- Nombre del feed de Azure DevOps
Estoy asumiendo que estás usando un feed de proyecto (en vez de un feed de organización) y, aunque es la recomendación oficial, ciertamente es muy opinable.
Creating new organization-scoped feeds is not recommended.
En caso de que estes usando un feed de organización, los cambios a realizar después de haber generado el proyecto sólo atañen a la url del feed en el fichero nuget.config
.
Los highlights del proyecto generado son:
- Soporte a Source Link, Using Source Link in .NET projects and how to configure Visual Studio to use it, gracias Luis Ruiz Pavón.
- Pipeline con control de versión SEMVER, idea tomada de Fun with Azure DevOps NuGet package versioning!
- Pipeline negociada con Sergio Navarro Pino y Alejandro García Miravet, sin ellos no hubiera sido posible este post.
- Generación de un informe de cobertura de código con ReportGenerator.
Lo único que no he metido es el fichero Directory.Build.props porque depende del tamaño de la base de código del paquete, a veces me gusta y a veces no.
En cuanto a los TFM del proyecto, me remito a este par de tweets que lo explican muy bien y así me ahorro la explicación:
Así, que la pregunta real es: ¿Qué clientes quieres soportar para tu classlib?
— Eduard Tomàs (@eiximenis) November 18, 2020
* Solo net5 -> Target a net5
* net5 y netcore3.x -> Target a netstandard 2.1
* net5, netcore 3.x,2.x, netfx4.6 -> Netstandard 2.0
* Otros -> Evaluar netstandard 1.x y/o multi-targeting
netcoreapp is a platform, like net4x or UWP. netstandard is about sharing code across applications. Your unit tests are an app, not a shared library. That’s why they can’t be netstandard (or its older brother, the PCL). Your apps need a target platform; so do your tests.
En local, el único cambio reseñable es configurar el fichero nuget.config
para poder usar nuestro feed. Aunque en el repositorio de GitHub está bien explicado, básicamente deberías ejecutar la siguiente instrucción:
dotnet nuget add source https://pkgs.dev.azure.com/YOUR_ORGANIZATION/YOUR_TEAM_PROJECT/_packaging/YOUR_PROJECT_FEED/nuget/v3/index.json --name WHATEVER_YOU_WANT --username YOUR_USER_NAME --password YOUR_PAT --configfile nuget.config
Si quieres ver en local el informe de cobertura de código:
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=..\TestResults\Coverage\
dotnet reportgenerator -reports:tests\TestResults\Coverage\**\*.cobertura.xml -targetdir:tests\TestResults\reportgenerator -reporttypes:HtmlInline_AzurePipelines
Llegado a este punto, es hora de publicar nuestro paquete. Yo estoy asumiendo que ya tienes una organización de Azure DevOps creada, un proyecto, un feed de proyecto e incluso un entorno con aprobación manual. Por eso, para crear la pipeline solamente tendrás que cambiar el id del feed de proyecto (YOUR_PROJECT_FEED_ID) y el nombre del entorno (YOUR_ENVIRONMENT_NAME) en el fichero azure-pipelines.yml.
Para el control de versiones con SEMVER, necesitamos crear también las siguientes variables:
Name | Value |
---|---|
Major | 0 |
Minor | 0 |
PackageVersionType | |
Patch | $[counter(format('{0}.{1}', variables['Major'], variables['Minor']), 0)] |
PackageVersion | $(Major).$(Minor).$(Patch)$(PackageVersionType) |
La propiedad Version
del .csproj
no se usará.
Si no especificamos ningún valor durante la stage de Deployment, las versiones de los paquetes irán siendo 0.0.0
, 0.0.1
y así sucesivamente. Siempre podemos modificar la variable Major
o Minor
(tanto a nivel general como para la ejecución concreta de una pipeline) y además con PackageVersionType
podemos escribir -algo
(guión incluido) y obtendremos versiones de prerelease. En realidad, a Nuget cualquier texto seguido del guión le vale para considerar nuestra paquete una versión prerelease.
Respecto a PackageVersionType
, si la rama que dispara la pipeline no es main
y la variable no tiene un valor, se establecerá a -alpha
.
Finalmente, al descargar tu paquete y abrirlo con Nuget Package Explorer, deberías ver algo parecido a esto (todo verdecito y apuntando al commit de turno):
Claro está que la estrategía de publicación de un paquete está sujeta a debate y, bien podría valer lo aquí expuesto o no valer para nada. En cualquier caso, la siguiente vez que tenga que crear un paquete espero ahorrarme unos valiosos minutos/horas copiando y pegando de mi OneNote.
Un saludo!