NuGet scripting in VS 2017

If any of you ever published a .NET project as a NuGet package and needed some custom scripting to run when the package was installed, this used to be possible using the install.ps1/uninstall.ps1 mechanism.

However, support for this was dropped in Visual Studio 2017, which bundles NuGet 4.x. The Init.ps1 script is still supported, but it runs every time the solution is opened, which might not be appropiate for most use cases. Another problem with the new VS 2017 NuGet way of doing things is that the .nuspec file, which was used to define metadata and files to package, is now gone; metadata is now handled via the project's properties, in the new Packaging section:

In turn, this information is saved in the .csproj project file:

The problem with this is, how to define which files to package inside the resulting .nupkg file using only .csproj?

There is an alternate approach to NuGet scripting which involves embedding MSBuild targets in the package; NuGet will inject them in the project which uses the package, and run them on build time. This process is not as straightforward and is not well documented, hence this post.

Step 1: Create .targets file using NuGet conventional folder structure

First of all, due to NuGet conventions, these files must go inside the build folder (so that NuGet picks them up when building a project which uses the package). If your project targets multiple frameworks, you might have a folder structure such as this one:

Step 2: Define the .targets

This is where the scripting will take place, using MSBuild. Let's start with a "Hello world" sort of script:

<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>

Notice that the way that this hooks into the client project's build chain is via the "AfterTargets" property. Here, we supply a predefined target from the build process we want our script to hook into. To view all the possible values, you can crank up MSBuild's verbosity (Tools - Options - Projects and Solutions - Build and run - MSBuild output verbosity), and you will see all the MSBuild targets that make up the build process.

This can be as complex as necessary, using all the available MSBuild tasks and properties. If MSBuild is not powerful enough for your needs, you can write your script in any language you want (say, Python), package it inside the package, and have this target call it using the MSBuild Exec task.

Step 3: Package the .targets files

Without access to the .nuspec file, we need to use the new .csproj syntax to tell NuGet which extra files go inside the package. In our case, we want the .targets files. This can be done this way:

<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>

After building with these changes, you can use NuGet Package Explorer to verify that the files were packaged correctly.

Step 4: Test it

Now that the .nupkg has has been created, you may publish it to nuget.org or you local feed and consume it from another project. After installing the package from the PM console, the dependency will be added to your project as usual. Then, when you build the project which consumes the package, you should see the output from your custom script.

Comments

  1. Have you found that you can still use .nuspec files with the new .csproj format in VS2017? The documentation suggests that if a .nuspec file is found it will use that over what packaging info is found in the project metadata?

    ReplyDelete
    Replies
    1. Thanks, hadn't seen that! I will try it out if I cannot do with the project metadata.

      Delete

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