For anyone who has been cutting-edge with .Net for a while will know that the nuget.exe runs under mono. And that you can use nuget.exe to package *.nuspec files into *.nupkg files.

Anyone who has been doing dotnet cross-platform will also know that you can use dotnet pack on both Windows and Linux to package your project into a NuGet package containing the dll. It even does references and multi-targeting correctly out of the box.

But did you know that you can also use dotnet pack to package any arbitrary nuspec file as well?

First, let’s look at a simple nuspec file that only has package references (an aggregate package).

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <id>MyNewApp</id>
    <version>1.0.0</version>
    <authors>csMACnz</authors>
    <description>An aggregate package example</description>
    <tags>utils example</tags>

    <dependencies>
      <dependency id="Newtonsoft.Json" version="11.0.2" />
      <dependency id="Beefeater" version="0.5.0" />
    </dependencies>
  </metadata>
  <files>
    <!-- empty files tag avoids copying all files -->
  </files>
</package>

We could use nuget.exe pack but that wouldn’t work on Linux. Instead, we create a csproj file designed to do the pack for us in a standard dotnet pack kind of way.

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

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <NuspecFile>newapp.nuspec</NuspecFile>
    <NuspecProperties></NuspecProperties>
    <NuspecBasePath></NuspecBasePath>
  </PropertyGroup>

</Project>

The magic is these three properties (only one required, really):

  • <NuspecFile> - Required to tell dotnet to use the hand-crafted nuspec file
  • <NuspecProperties> - (Optional) ability to generate and send nuspec replacement tokens
  • <NuspecBasePath> - (Optional) relative base path to use for pack files (defaults to project folder)

For more details on all the things you can put into a nuspec file, check out the docs: https://docs.microsoft.com/en-us/nuget/reference/nuspec

But we don’t stop there. If you run this you will see that we are still building our empty project. We can do a couple of things here.

  • <NoBuild>true</NoBuild> - Adding this will stop the build step being run during pack
  • <GeneratePackageOnBuild>true</GeneratePackageOnBuild> - We can instead tell dotnet build to run the pack as well.

One of these two scenarios may suit you better. It depends.

Using the above settings, you can basically produce any kind of NuGet package you want.

For one last piece of tidy-up, you can disable the compile and remove the dll from the build output entirely:

  • <SkipCompilerExecution>true</SkipCompilerExecution> - (Optional) disable compile entirely
  • <CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory> - stop the dll output to the output folder.

Unfortunately, at this stage I am still yet to find a reasonable way to remove the need for <TargetFramework>, but something I am still experimenting with. There is a chance that if you are really customising your MSBuild commands, you would end up leveraging the dotnet restore, and that requires a TargetFramework anyway, so not a big deal.