Skip to content

Latest commit

 

History

History
135 lines (104 loc) · 8.71 KB

README.md

File metadata and controls

135 lines (104 loc) · 8.71 KB

Creating A Packaging Project

What is a packaging project?

A packaging project is a separate project that gathers multiple build outputs into a single NuGet package

When should I make one?

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.

Step By Step

  1. Create The Packaging Project
  2. Build Relevant Projects From The Packaging Project
  3. Gather Build Outputs
  4. Creating the NuGet Package
  5. Customizing Your Package Layout

1. Create The Packaging Project

  1. Create a new folder, call it packaging or whatever name you prefer.
  2. 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.
  3. Ensure your project uses the Microsoft.Build.NoTargets SDK. This SDK is intended for projects that aren't meant to be compiled.
  4. Define a TargetFramework property for your project. This TargetFramework will not affect your other projects. See this link (under TFM) for TargetFramework 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>

2. Build Relevant Projects From The Packaging Project

Building your packaging project should trigger builds for all relevant projects. MSBuild does this through ProjectReference items.

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

3. Gather Build Outputs

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>

Deciding what to pack

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.

Using OutputItemType

Official docs.

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.

  1. 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".

Extending OutputItemType

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.

Using ReferenceOutputAssembly

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.

Manually Gathering Build Outputs

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 vs. Generated Files

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.

4. Create the NuGet Package

Our packaging project builds its references and gathers their respective outputs. Now, it's time to create the NuGet package.

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

5. Customize the Package Layout

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>