Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSBuild multiple dll in a single NuGet package

I have a Visual Studio 2017 solution that contains two projects:

Foo.csproj
Foo.Core.csproj

Both of these projects target multiple frameworks: net452;netstandard1.2

Foo.csproj includes a project reference to Foo.Core.csproj:

<ItemGroup>
    <ProjectReference Include="..\Foo.Core\Foo.Core.csproj" />
</ItemGroup>

When I generate a NuGet package for Foo.csproj, I want the nupkg file to include both of these assemblies.

What is currently happening is that the NuGet package that gets created has Foo.dll and then a NuGet dependency on Foo.Core (which doesn't exist).

How can I generate a single NuGet package using msbuild that will include both assemblies?

For reference this is the command I am currently using (which is not working how I want it to):

msbuild /p:restore,pack Foo.csproj
like image 432
Dismissile Avatar asked Jul 07 '17 17:07

Dismissile


1 Answers

This is currently not directly supported by NuGet out of the box. You can follow this GitHub issue for updates.

However, there are a few ways to create such NuGet package.

  1. Use the "Nugetizer 3000"

This is an newly developed tool to build NuGet packages from projects and works by installing the NuGet.Build.Packaging nuget package. You can find some documentation on it on its GitHub wiki page but since it is a very new project, there isn't much documentation or community knowledge around it yet(!) (but the team developing it is very helpful, you could file GitHub issues if you get stuck).

  1. Adding a custom target in the project (2.0.0 tooling / VS 2017 15.3+): Create an item in the csproj that will include the referenced project's output DLL

This approach is very hacky as it relies on an internal MSBuild item that the pack targets use. It works by first marking the <ProjectReference> to not be referenced from the created nuget package like this:

<ProjectReference Include="..\libA\libA.csproj" PrivateAssets="All"/>

Then you can add this to the project to include the generated libA.dll in the nuget package:

<PropertyGroup>
  <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeP2PAssets</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="IncludeP2PAssets">
  <ItemGroup>
    <BuildOutputInPackage Include="$(OutputPath)\testprivatelib.dll" />
  </ItemGroup>
</Target>

Note that this requires you to add all the <PackageReference> items of the referenced project to the project you generate the package from since they would be missing from the generated package since you effectively disabled the transitive reference behaviour.

  1. Create a custom .nuspec file

At the time of writing, this is probably the most "supported" way, but also the most complex. NuGet allows you to disable the automatic generation of the resulting .nuspec file and automatic collection of files by setting the <NuspecFile> property in your project, along with a <NuspecProperties> property that allows you to pass replacement tokens for parsing the .nuspec file.

This works by modifying the project file like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard1.4</TargetFramework>
    <NuspecFile>$(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec</NuspecFile>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\LibB\LibB.csproj" />
  </ItemGroup>

  <Target Name="SetNuspecProperties" BeforeTargets="GenerateNuspec">
    <PropertyGroup>
      <NuspecProperties>$(NuspecProperties);id=$(AssemblyName)</NuspecProperties>
      <NuspecProperties>$(NuspecProperties);config=$(Configuration)</NuspecProperties>
      <NuspecProperties>$(NuspecProperties);version=$(PackageVersion)</NuspecProperties>
      <NuspecProperties>$(NuspecProperties);description=$(Description)</NuspecProperties>
      <NuspecProperties>$(NuspecProperties);authors=$(Authors)</NuspecProperties>
    </PropertyGroup>
  </Target>
</Project>

This will automatically look for a .nuspec file with the same name as the project (somelib.csproj => somelib.nuspec) and pass some properties along to it. The properties are created in a target in order to be able to access fully resolved and defaulted properties like PackageVersion.

The .nuspec file could look like this:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <authors>$authors$</authors>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>$description$</description>
    <dependencies>
      <group targetFramework=".NETStandard1.4">
        <dependency id="NETStandard.Library" version="1.6.1" exclude="Build,Analyzers" />
      </group>
    </dependencies>
  </metadata>
  <files>
    <file src="bin\$config$\netstandard1.4\*.dll" target="lib\netstandard1.4\" />
  </files>
</package>

Note that you must add all referenced NuGet packages as a <dependency> element in the .nuspec file since these are no longer automatically generated from the <PackageReference> items in your project file. Refer to the NuSpec Reference for more details.

I have recently created an example project on GitHub demonstrating the use of a custom .nuspec file for exactly this purpose.

like image 106
Martin Ullrich Avatar answered Sep 22 '22 08:09

Martin Ullrich