Search Results for

    Show / Hide Table of Contents

    Quickstart

    NuGetizer is a drop-in replacement for the .NET SDK built-in Pack (a.k.a. "SDK Pack"), which instantly supercharges your ability to customize and extend the packing process in a consistent and easy to understand process designed and centered around best practices in MSBuild design and extensibility.

    Yes, this means you'll never need to write a .nuspec by hand ever again, no matter how complicated or advanced your packing scenarios are.

    Since packing is an exercise in getting the resulting .nupkg to contain exactly what you expect it to contain, NuGetizer provides a .NET global tool that allows you to quickly and iteratively test your packing scenarios, without incurring in lengthy builds, zipping and subsequent unzipping of the .nupkg for each attempt and so on.

    Install the tool like any other .NET global tool with:

    dotnet tool install -g dotnet-nugetizer
    

    Afterwards, from a directory containing your solution or project, just run nugetize. This will provide a quick render of the package metadata, dependencies and contents, such as:

    nugetize output

    Tip

    For added convenience in exploring NuGetizer's capabilities, you can run all the examples in this documentation directly in your browser in a GitHub Codespace

    Getting started is really easy with powerful (yet flexible) built-in package contents inference and a few properties and item metadata that are named and used consistently in a wide variety of common MSBuild items:

    Item Metadata Description
    Pack Whether to pack an item. (true|false)
    PackagePath The relative path of the file within the package. Implicitly means Pack=true.
    PackFolder The known folder within the package where the file will be placed. Implicitly means Pack=true. Useful in combination with FrameworkSpecific.
    FrameworkSpecific Whether the PackFolder should be considered as framework-specific. (true|false)

    If an item is framework-specific, the project's TargetFramwework will be used to calculate the final PackagePath automatically (i.e. /lib/netstandard2.0/Lib.dll or /contentFiles/cs/net7.0-android/Api.cs).

    For example, to prevent a <PackageReference> from becoming a package dependency, you can just add Pack="false" to it. Likewise, to include an arbitrary file in the package, you can just add Pack=true to a <None> item:

    <ItemGroup>
        <None Include="sample.md" Pack="true" />
    </ItemGroup>
    

    If you want to include the item at a specific path, you would instead use PackagePath:

    <ItemGroup>
        <None Include="sample.md" PackagePath="docs/samples/sample.md" />
    </ItemGroup>
    

    Or pack an entire folder:

    <ItemGroup>
        <None Include="docs/**/*.*" PackFolder="docs" />
    </ItemGroup>
    

    The following are some of the project properties affecting package inference defaults:

    Properties Description
    PackBuildOutput Whether to pack the project's build output. (Default true)
    BuildOutputFrameworkSpecific Whether the project-s build output shoud be considered framework-specific.
    PackSymbols true if PackBuildOutput=true (*)
    PackFolder The known folder within the package where the project's build output will be placed. (Default lib)
    PackContent Whether to pack Content items. (Default false)
    PackNone Whether to pack None items. (Default false)
    PackReadme Whether to pack a readme.md file alongside the project. (Default true)
    PackDependencies Whether to process package references. (Default '', treated as true)
    PackFrameworkReferences Whether to pack framework Reference items. (Default true if PackFolder=lib, false if PackDependencies=false)
    PackProjectReferences Whether to process project references. (Default true)

    Default value for PackContent is actually smarter: it defaults to true if the Content items have a non-empty CopyToOutputDirectory value.

    Default value for PackNone is equally smarter too: it defaults to true if project's PackFolder is build or buildTransitive, since that's the default item type for .props and .targets files in the .NET SDK and in build-targeting projects, it's the more sensible default.

    BuildOutputFrameworkSpecific default value depends on the PackFolder and is the same as for FrameworkSpecific item metadata:

    PackFolder FrameworkSpecific
    content (*) true
    lib true
    dependency (**) true
    frameworkReference (**) true
    build false
    all others (***) false

    * Since the plain content folder is deprecated as of NuGet v3+, we use content to mean contentFiles throughout the docs, targets and implementation. They are interchangeable in NuGetizer and always mean the latter.

    ** dependency and frameworkReference are pseudo folders containing the package references and framework (<Reference ...) references.

    *** tool(s), native, runtime(s), ref, analyzer(s), source/src, any custom folder.

    Project References

    NuGetizer allows each project to determine its contribution to a package by using the items and properties mentioned above. This feature works across project references too (unless PackProjectReferences=false).

    Given the following projects:

    Core.csproj:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFrameworks>net472;net60</TargetFrameworks>
        <PackageId>Core</PackageId>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
      </ItemGroup>
    
      <ItemGroup>
        <ProjectReference Include="..\common\Common.csproj" />
      </ItemGroup>
    
    </Project>
    

    Common.csproj:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
      </ItemGroup>
    
    </Project>
    

    The Core package would contain:

    package contents for project reference

    As you can see, Common.csproj's contribution (a netstandard library) has now become part of the referencing project/package. Neither project changed the default PackFolder, so both are contributing lib content. But since the packing project is Core, it multi-targets and lib content is framework-specific by default, both end up as the lib contents of each of the targeted frameworks.

    If you add a PackFolder=build property to Common.csproj (and perhaps add a targets file and build tasks too):

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PackFolder>build</PackFolder>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
        <PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.2.0" />
      </ItemGroup>
    
    </Project>
    

    the package contents would instead become:

    project reference with build contents

    Finally, if you set a PackageId on Common.csproj, its content would now be part of a package dependency instead, since the project would be producing its own package now:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PackageId>Core.Build</PackageId>
        <PackFolder>build</PackFolder>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
        <PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.2.0" />
      </ItemGroup>
    
    </Project>
    

    project reference as dependency

    And you can turn the dependency into a non-framework specific one (since it's build targets/tasks and might apply to more than just the targeted frameworks) by annotating the project reference:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFrameworks>net472;net60</TargetFrameworks>
        <PackageId>Core</PackageId>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
      </ItemGroup>
    
      <ItemGroup>
        <ProjectReference Include="..\common\Common.csproj" FrameworkSpecific="false" />
      </ItemGroup>
    
    </Project>
    

    project reference as non-framework specific dependency

    Finally, you could exclude the project reference entirely from packing by setting <ProjectReference ... Pack="false" />, like for any other item type.

    Package References

    Package references become package dependencies by default (unless PackDependencies=false), which match the target framework of the project. This is a sensible default for lib projects, as we have seen in the previous section.

    Sometimes, you need to ship your dependency's files alongside your main library for things to work properly. In such cases, you can use the built-in PrivateAssets=all metadata on the PackageReference, which causes the resolved assets from the dependency to become part of the primary (build) output of the project.

    For example, a build targets/tasks project like:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PackageId>Jsonator</PackageId>
        <PackFolder>build</PackFolder>
     </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.2" PrivateAssets="all" />
      </ItemGroup>
    
    </Project>
    

    Would result in a package with:

    package reference as private assets

    This also works transitively. For a more complex dependency, such as Microsoft.Extensions.Configuration which brings other dependencies in turn:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PackageId>Jsonator</PackageId>
        <PackFolder>build</PackFolder>
     </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.2" PrivateAssets="all" />
        <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" PrivateAssets="all" />
      </ItemGroup>
    
    </Project>
    

    We would get the following package contents:

    transitive package dependencies as private assets

    Content and CopyToOutputDirectory

    The simplest way to include arbitrary files alongside your project main build output is to set CopyToOutputDirectory=PreserveNewest. This will cause the items to end up in your output directory:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PackageId>Quickstart</PackageId>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
      </ItemGroup>
    
      <ItemGroup>
        <Content Include="Sample.txt" CopyToOutputDirectory="true" />
      </ItemGroup>
    
    </Project>
    

    Intuitively, they will also end up alongside your primary build output package folder:

    content with copy to output directory

    Warning

    It's highly advisable that you never use CopyToOutputDirectory=Always, since it can impact incremental build performance quite significantly and there's rarely a good reason for using it over PreserveNewest.

    If you change the pack folder for the project, the content files will "follow" it. This project includes a Scriban template for code generation alongside the build tasks and targets which could use it to emit some code at build time, for example:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PackageId>Quickstart</PackageId>
        <PackFolder>build</PackFolder>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
        <PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.2.0" />
        <PackageReference Include="Scriban" Version="5.6.0" PrivateAssets="all" />
      </ItemGroup>
    
      <ItemGroup>
        <Content CopyToOutput="true" Update="*.sbntxt" CopyToOutputDirectory="true" />
      </ItemGroup>
    
    </Project>
    

    Note how we're updating the <Content> items and using a wildcard, since files ending in txt are already added by the .NET SDK as content:

    content with copy to output directory

    NuGetizer is effectively setting (implicitly) the PackFolder of your items to that of the project, whenever you use CopyToOutputDirectory=PreserveNewest. This also works for other item types, such as None (as long as they are packable, that is, they have a Pack=true metadata too, or the project specifies PackNone=true).

    In addition, the packed files for these items will point to the location in the project's output folder, rather than the source location. This makes it easier to have custom build behavior that might modify the item after copying to the output directory (such as updating a version number in targets). You can see this by opening the generated .nuspec in the NuGetizer output.

    ContentFiles

    Beyond the previous very simple scenario, however, content files in a package have much more flexibility through attributes that affect packaging and their inclusion in consumers' projects. NuGetizer supports these in an intuitive way through item metadata on <Content> MSBuild items, namely: BuildAction, CopyToOutput and Flatten item metadata.

    Tip

    The packing attribute CopyToOutput is different than CopyToOutputDirectory. When you use the latter, you are implicitly changing the item's PackFolder from Content to the project's. Therefore, the content packing attributes will not apply.

    The package path of content files should also contain the code language and target framework they apply to, to control more precisely their inclusion in consuming projects, such as contentFiles/cs/netstandard2.0/MyApi.cs (to reference the file only for projects with C# language, targeting netstandard2.0 or a compatible framework). You can control those with CodeLanguage and TargetFramework item metadata:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PackageId>Quickstart</PackageId>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
      </ItemGroup>
    
      <ItemGroup>
        <!-- This how-to file might showcase C# code which is TF-agnostic -->
        <Content Include="HowTo.txt" 
                 BuildAction="None" 
                 CopyToOutput="PreserveNewest"
                 CodeLanguage="cs"
                 TargetFramework="any" />
      </ItemGroup>
    
    </Project>
    

    content with metadata

    By default, the target framework subfolder matches the project's. This can be changed by adding TargetFramework="any" metadata on the item, as shown above.

    One common scenario is creating source-only packages, where the API is provided as source code directly to consuming projects. To make this process straightforward, NuGetizer defaults Compile items' metadata to PackFolder="content" BuildAction="Compile" CodeLanguage="$(DefaultLanguageSourceExtension)" TargetFramework="$(TargetFramework)". This means that by default, all the relevant item metadata for packing compile is already in place. All you need to do is turn packing on for those items by setting PackCompile=true in the project:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PackageId>Quickstart</PackageId>
        <PackCompile>true</PackCompile>
        <PackBuildOutput>false</PackBuildOutput>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
      </ItemGroup>
    
    </Project>
    
    Tip

    In this scenario, it's most common to also set PackBuildOutput=false to avoid packing the compiled version.

    content with compile

    Since the packing is separate from the project building itself, you still have a project that compiles, can be referenced by unit tests and so on. This makes it very easy to create multi-targeting source-only packages reliably!

    Packaging Projects

    More often than not, a non-trivial package is made up of multiple projects, contributing multiple types of assets (i.e. main library project, analyzers/source generators project, build/tasks project, additional tools). Trying to accomodate every scenario from within a single "top-level" library project can be tricky and not very productive or maintainable.

    NuGetizer supports the SDK-style .msbuildproj project file type which has built-in support in Visual Studio. It can use the Microsoft.Build.NoTargets SDK as follows:

    <Project Sdk="Microsoft.Build.NoTargets/3.7.0">
    
      <PropertyGroup>
        <PackageId>Quickstart</PackageId>
        <TargetFramework>netstandard2.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
      </ItemGroup>
    
    </Project>
    
    Note

    The requirement of a TargetFramework comes from the underlying SDK and the .NET SDK targets themselves, but this kind of project will not build any output.

    empty packaging project

    In addition to referencing any of the projects shown in this Quickstart, packaging projects can also reference other packaging projects in turn for complex packing scenarios too.

    The following is a fairly comprehensive example of a packaging project, including references to build/tasks project, code analysis projcet, multi-targeting main library and additional tools:

    <Project Sdk="Microsoft.Build.NoTargets/3.7.0">
    
      <PropertyGroup>
        <PackageId>Quickstart</PackageId>
        <TargetFramework>netstandard2.0</TargetFramework>
        <PublishRepositoryUrl>true</PublishRepositoryUrl>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="NuGetizer" />
        <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" 
                          PrivateAssets="all" />
      </ItemGroup>
    
      <ItemGroup>
        <ProjectReference Include="..\Analyzer\Quickstart.CodeAnalysis.csproj" />
        <ProjectReference Include="..\Build\Quickstart.Tasks.csproj" />
        <ProjectReference Include="..\Lib\Quickstart.csproj" />
        <ProjectReference Include="..\Tools\Quickstart.csproj" />
      </ItemGroup>
    
    </Project>
    

    Which produces the following content:

    packaging project with multiple references

    • Improve this Doc
    In This Article
    Back to top Generated by DocFX