Correr scripts en NuGet en VS 2017

Si alguna vez publicaron un proyecto .NET como paquete NuGet y necesitaron ejecutar scripting personalizado al instalar el paquete, eso era posible antes usando el mecanismo install.ps1/uninstall.ps1.

Sin embargo, el soporte para esto fue retirado en Visual Studio 2017, el cual incluye NuGet 4.x. El script Init.ps1 sigue siendo soportado, pero el mismo es invocado cada vez que se abre la solución, lo cual puede no ser apropiado para la mayoría de los casos de uso. Otro problema con el nuevo manejo de NuGet en Visual Studio 2017 es que el archivo .nuspec, que se usaba para definir metadata del paquete y archivos extra a empaquetar, ya no está; la metadata en VS 2017 ahora se maneja a través de las propiedades del proyecto, en su nueva sección Packaging:

Una vez ingresada en las propiedades del proyecto, esta información es almacenada en el archivo .csproj:

El problema con este nuevo enfoque es, ¿cómo definir qué archivos empaquetar en el .nupkg final usando sólo el .csproj?

Existe un enfoque alternativo para hacer scripting en NuGet que involucra embeber targets MSBuild en el paquete; NuGet los detectará e inyectará en el proyecto que usa el paquete, y los invocará al compilar. Este proceso no es tan directo y no está del todo bien documentado, por ende este post.

Paso 1: Crear archivo(s) .targets file usando la estructura de directorios convencional de NuGet

En primer lugar, debido a las convenciones de NuGet, estos archivos deben ir dentro de la carpeta build (para que NuGet los detecte al compilar un proyecto que use el paquete). Si el proyecto se compila para múltiples frameworks, será necesaria una estructura como la siguiente:

Paso 2: Definir los .targets

Aquí es donde se realiza el scripting, usando MSBuild. Comencemos con un script tipo "Hola Mundo"::

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="CustomScriptTest" AfterTargets="Build">
    <Message Text="Custom Bitprim build step invoked for .NET 4.6.1 build" />
  </Target>
</Project>

Nótese que la forma en que esto se inserta en el proceso de compilación del proyecto cliente es a través de la propiedad "AfterTargets". En la misma, se da como valor el nombre de un target predefinido del proceso de build donde queremos insertar nuestro script. Para ver todos los valores posibles, se puede aumentar la verbosidad de MSBuild (Tools - Options - Projects and Solutions - Build and run - MSBuild output verbosity), y se verá al compilar los nombres de todos los targets MSBuild que conforman el proceso de build.

El script MSBuild puede ser tan complejo como sea necesario, utilizando todas las tareas y propiedades de MSBuild. Si MSBuild no resultara suficiente, se puede escribir un script en cualquier otro lenguaje (Python, por ejemplo) en un archivo aparte, empaquetarlo, y hacer que el target lo invoque usando la tarea Exec de MSBuild.

Paso 3: Empaquetar los archivos .targets

Sin acceso al archivo .nuspec, es necesario usar la nueva sintaxis .csproj para indicarle a NuGet qué archivos extra van dentro del paquete. En nuestro caso, queremos agregar los archivos .targets. Esto puede hacerse de la siguiente forma:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>

    <Version>0.2.1</Version>
    <Authors>Bitprim</Authors>
    <Company>Bitprim</Company>
    <Description>C# binding for Bitprim Bitcoin platform</Description>
    <PackageProjectUrl>www.bitprim.org</PackageProjectUrl>
    <RepositoryUrl>https://github.com/bitprim/bitprim-cs</RepositoryUrl>
    <RepositoryType>Git</RepositoryType>
    <PackageTags>Bitcoin, C#, Litecoin, Bitcoin Cash</PackageTags>
    <PackageReleaseNotes>Basic chain queries</PackageReleaseNotes>
    <NeutralLanguage>en-US</NeutralLanguage>
    <AssemblyVersion>0.1.1.0</AssemblyVersion>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <FileVersion>0.1.1.0</FileVersion>
  </PropertyGroup>

  <ItemGroup>
    <!-- pack the target files -->
    <Content Include="build\net461\bitprim.targets" PackagePath="build\net461\bitprim-cs.targets" />
    <Content Include="build\netcoreapp2.0\bitprim.targets" PackagePath="build\netcoreapp2.0\bitprim-cs.targets" />
 </ItemGroup>

</Project>

Luego de compilar con estos cambios, es posible usar NuGet Package Explorer para comprobar que los archivos fueron empaquetados correctamente.

Paso 4: Probarlo

Ahora que el archivo .nupkg ha sido creado, es posible publicarlo a nuget.org o un feed local y consumirlo desde otro proyecto. Luego de instalar el paquete desde la consola PM, la dependencia será agregada al proyecto de la manera usual. Luego, al compilar el proyecto que depende de ese paquete, debería verse la salida del script en la ventana de output de Visual Studio.

Comments

  1. Hay dos formas de crear paquetes Nuget entonces?

    Antes de VS 2017 - Nuget 4

    y con VS 2017 - Nuget 4

    ReplyDelete
  2. Aunque parte de los cambios introducidos por VS 2017 son simplemente ayudas desde la UI para facilitar el proceso; salvo la deprecation de los scripts de instalación, gran parte del flow sigue siendo igual debajo de los helpers de VS.

    ReplyDelete

Post a Comment

Popular posts from this blog

VB.NET: Raise base class events from a derived class

Apache Kafka - I - High level architecture and concepts

Upgrading Lodash from 3.x to 4.x