A packaging project is a separate project that gathers multiple build outputs into a single NuGet package
A multi-targeted or multi-platform build that needs to package all of its outputs requires a packaging project. Other reasons include resolving build ordering issues or cleaning up project files by separating out build logic.
- Create The Packaging Project
- Build Relevant Projects From The Packaging Project
- Gather Build Outputs
- Creating the NuGet Package
- Customizing Your Package Layout
- Create a new folder, call it
packaging
or whatever name you prefer. - Create a
packaging.csproj
that matches the name of your folder (not required, but is convention).- NOTE: It is VERY important that your project's extension is
csproj
. The build process is affected by which extension you give it.
- NOTE: It is VERY important that your project's extension is
- Ensure your project uses the Microsoft.Build.NoTargets SDK. This SDK is intended for projects that aren't meant to be compiled.
- Define a
TargetFramework
property for your project. ThisTargetFramework
will not affect your other projects. See this link (underTFM
) forTargetFramework
values.
Your project should look something like this.
<!-- Version 3.5.6 is the latest version at the time of this writing. -->
<Project Sdk="Microsoft.Build.NoTargets/3.5.6">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
</Project>
Building your packaging project should trigger builds for all relevant projects. MSBuild does this through ProjectReference
items.
- Create a
ProjectReference
item for each project you want to build. This is enough to trigger builds for each project and the projects they reference.
<ItemGroup>
<ProjectReference Include="../ConsoleApp/ConsoleApp.csproj" />
<ProjectReference Include="../OtherLib/OtherLib.csproj">
</ItemGroup>
To better understand how packing items works, read Including Content In A Package. For most use cases, you'll either add your files to the Content
, or None
item types. None
refers to items that have no affect on the build process, whereas Content
items are already understood by the build process and are packed automatically.
Because None
items are not automatically packed, they'll need the metadata Pack=True
. Regardless of the item being packed, you'll need to specify a PackagePath
metadata if you want to customize the layout of your NuGet package.
<ItemGroup>
<None Include="../ClassLib/staticfile.foo" Pack="true" PackagePath="extras" />
</ItemGroup>
Realistically, you'll need to gather outputs in multiple ways. Which way you use depends on exactly what you need. Refer to this table to decide what's best for your needs.
Output Needed | Suggested Method(s) | Function | Notes |
---|---|---|---|
The output .dll of a ProjectReference | OutputItemType | Gathers TargetOutputs into new items. | This normally affects the build process because it passes the outputs to the compiler & friends. Thanks to Microsoft.Build.NoTargets , there's no need to worry about that here. |
exe, deps.json, or runtimeconfig.json | ReferenceOutputAssembly | Copies ProjectReference build output into the packaging project's bin/ directory. |
If you care about absolute minimal build steps/copies, you may want to Manually Gather Outputs instead. Sometimes a direct reference to an item is better than copying it over entirely. |
Generated Files Everything else |
1. Manually Gather Outputs 2. Extending OutputItemType |
There are MANY ways to gather the different types of outputs of a build. |
OutputItemType is described as Item type to emit target outputs into. By default, "Target outputs" means the output of the "Build" target, which is the project's dll. For details on adding more items into this output, see Extending OutputItemType.
- First, place OutputItemType metadata on your ProjectReferences. Give them whatever name you like.
<ItemGroup>
<ProjectReference Include="../ConsoleApp/ConsoleApp.csproj" OutputItemType="ConsoleAppOutput" />
<ProjectReference Include="../OtherLib/OtherLib.csproj" OutputItemType="OtherLibOutput" />
</ItemGroup>
Setting OutputItemType="ConsoleAppOutput"
tells the build to gather the output of that ProjectReference
into a new item named "ConsoleAppOutput".
If you'd like to extend what your ProjectReference
returns, try adding metadata Targets="MyTarget;Build"
to your project reference. You can then create a target named MyTarget
in that project that returns the items you want to pack. You might prefer this if you want each project to tell the packaging project what it wants packed. To read more on targets and how they return, see MSBuild Target Element remarks.
Including ReferenceOutputAssembly=true
on your ProjectReference
will tell the build to copy the exe/pdb/runtimeconfig.json/deps.json into the packaging project's bin/
directory. ReferenceOutputAssembly
is true by default when using Microsoft.NET.Sdk, whereas Microsoft.Build.NoTargets defaults it to false.
<ItemGroup>
<ProjectReference Include="../ConsoleApp/ConsoleApp.csproj" ReferenceOutputAssembly="true" />
</ItemGroup>
Note: this does not inform the build to copy the .dll
/.pdb
over. For the dll, see Using OutputItemType.
This is a "catch-all" method where you hard-code paths to files. The primary benefit here is performance, as it prevents extra copies into the packaging project's output directory. If you care deeply about build performance, this might be ideal for you.
<ItemGroup>
<Content Include="../OtherLib/$(OutDir)/someFile.foo"/>
</ItemGroup>
Static files (eg. Source files) are easy to deal with, simply add them to Content
or None
under any ItemGroup
. Generated files, however, must be added to an item type during execution of the build. For more on this, read this gist.
Our packaging project builds its references and gathers their respective outputs. Now, it's time to create the NuGet package.
- These two properties should allow your project to generate a NuGet package in the
bin/
directory.
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>My.Custom.Package</PackageId>
For more info on creating NuGet packages, see Create a NuGet package using MSBuild or Package Authoring Best Practices.
The final step is deciding where each item goes inside the NuGet package. You do this by specifying PackagePath
metadata on your Content
or None
items.
<ItemGroup>
<Content Include="@(ConsoleAppOutput)" PackagePath="app"/>
<Content Include="@(ClassLibOutput)" PackagePath="app"/>
<Content Include="@(OtherLibOutput)" PackagePath="lib/$(TargetFramework)" />
<None Include="../ClassLib/staticfile.foo" Pack="true" PackagePath="extras" />
</ItemGroup>
Note: You may see a NU5100
warning about dll's that aren't in a lib/
folder. If your package doesn't follow NuGet best practices, add NU5100
to the NoWarn
property.
<PropertyGroup>
<NoWarn>$(NoWarn);NU5100</NoWarn>
</PropertyGroup>