~/codewithstu

Building Better NuGet Packages

Transcript

Hi, my name is Stu and in this video I'm going to show you how to build better NuGet packages. If you're new here, this channel is all about me breaking down different technologies into easily digestible chunks.

For this video, we're going to take a look at five different ways that you can improve your NuGet packages and then how to validate the improvements. We're going to be taking a look at basic NuGet package tags, license files, icons, deterministic builds, and Source Link.

Before we go and take a look at each tip in action, let's have a quick look around the solution that we're going to be using today. We have two separate projects: Project A and Project B. This helps us to do a before and after. Each one of the csproj files has nothing in it apart from targeting .NET 5. We also have a Directory.Build.props file which has the GeneratePackageOnBuild set to true, IsPackable set to true, and just redirects the NuGet packages into a central artifact folder. This artifact folder is then added to a NuGet config so we can reference it in the solution later.

So let's take a look at our first tip: setting basic NuGet package tag properties. To do this, we're going to be taking a look at Project A. One of the things that I most commonly see, especially on internal package feeds, is basic package metadata missing from the compiled package.

So let's take a look at seven common tags that you may want to consider setting for your package. The first one is the Description tag. This tells your consumers a one or two line summary about your package and what it is they're going to get from it. You can set this in a csproj by using the Description attribute inside of a PropertyGroup.

The next tag that we're going to look at is the PackageTags. Package tags allow you to search by this inside of most galleries such as nuget.

org to help further your package's SEO. You can set this in the csproj as shown. There are two things to note about the PackageTags property. The first is that it is delimited by the semicolon when used inside the csproj. The second is that a package tag needs to be a single word.

The next tag that we're going to take a look at is the ProjectUrl. This could be a link to your repository or a link to your project's website. Here's how you would set it inside of your csproj.

Next we're going to set the Authors tag. The Authors tag could be a collection of people that have worked on this NuGet package. Much like the PackageTags we've just gone through, the Authors is semicolon delimited, but it's not limited to a single word. Here's how you set this in your csproj.

If you're a company that's authoring the NuGet package, you may also want to make this known under your package metadata. To do this, you set the Company tag in your csproj. As with most corporate type things, you may also need to set a copyright for your NuGet package. To do this, you set the Copyright property inside the PropertyGroup in your csproj. Please note that this is limited to 4,000 characters.

The last package tag that you might want to set is the Title. This is a human-friendly title of the package which may be used in some NuGet UIs. To do this, we need to set the Title property in our csproj.

Now that we have most of the basic package metadata set, we can turn our focus to tip number two: the license. Adding the license that you're using to your NuGet package is really important so the consumers know about any conditions of use for your package. This can be accomplished either by setting an SPDX identifier or by including the full license text inside of your package.

To set the SPDX identifier, set the following csproj property. Here I have assumed I am using the MIT license, so I have set the value to MIT. You can find a full list of SPDX identifiers in the description below.

To tell NuGet to use a license file, set the following csproj property. Please note that if you use the license file, the location is the path inside of your NuGet package that should be searched. This means that you will also need to ensure that you have the file packed as part of the packaging process. For now, I'm just going to leave it as the license expression rather than the license file.

So now let's move on to our third tip: giving our NuGet package a logo. By including a logo inside of your NuGet package, you get to keep brand consistency. There are two different ways that you can include a logo. The first is embedding the logo inside of the package itself. This requires NuGet version 5.

3.0 or later. Alternatively, you can set a URL which the icon can be downloaded from. To set the icon of the package to a file that's inside of your NuGet package, use the following csproj property. If you wanted the package icon to come from a URL, then you would need to set this second property. Currently it's recommended that you have both of these properties for backwards compatibility purposes. In a future version of Visual Studio, you'll be able to see the package icon from the PackageIcon property. As I don't have a package icon available for this demo, I'm just going to remove these two lines for now.

Before we move on to deterministic builds and Source Link, let's build our package and have a look at what it looks like using NuGet Package Explorer. Going from top to bottom, you can see that our title has been set as well as the authors. Our package has the correct tags. We have details about the license, details of any copyright, and the description, all set correctly.

So let's now move on to our fourth tip, and that is deterministic builds. Deterministic builds are all about getting the compiler to emit the exact same executable or DLL, byte for byte, when given the same inputs. Using deterministic builds has a couple of different advantages.

Firstly, it allows verification that no vulnerabilities or backdoors have been introduced during the compilation process. By promising identical results are always generated from a given source, this allows multiple parties to come to a consensus on the correct result, highlighting any deviations as suspect and worthy of scrutiny in the process.

Another key advantage is knowing that your binary output is always the same. If you have confidence that your binary output is always the same given the same source code, then the debugging experience becomes a little bit better. This is because local symbols will match those generated on the build server, meaning that you can reuse breakpoints for debugging.

You can easily set up deterministic builds inside of your csproj by setting the Deterministic property to true. By enabling this feature, you may also get some performance improvements for your builds, but your mileage may vary here. Please note that you will not be able to use deterministic builds if you use a wildcard version number in either the AssemblyVersion attribute or the AssemblyFileVersion attribute.

To complete the deterministic story, I thoroughly recommend that you enable Source Link. Source Link allows MSBuild to know certain details about the project that you're attempting to build, such as the Git commit hash that you're currently on. It also embeds the sources of the build so that you can debug into the projects when they're referenced as a NuGet package.

To enable Source Link, we need to include a NuGet package for our version control provider. Even though I'm going to add the GitHub-specific NuGet package, there are also packages for Azure Repos, Azure DevOps, GitLab, and Bitbucket. So let's add this NuGet package now.

To get a complete Source Link experience, I would thoroughly recommend enabling the following properties. The first is EmbedUntrackedSources. This instructs the build system to embed project source files that are not currently tracked by source control.

The next is the ContinuousIntegrationBuild. This is used within the build system to enable settings that only apply to official builds as opposed to local builds on the developer machine. An example of such a setting is a deterministic source path. This means that all source paths do not depend on the exact location on disk or operating system used to compile the project. This is used with the Deterministic flag above.

Next is the DebugType set to embedded. This means that all the debug information is compiled inside of the DLL, so we don't have to include the PDBs as a separate item. Alternatively, if you don't want embedded debug symbols, then you can use the AllowedOutputExtensionsInPackageBuildOutputFolder property. This tongue twister of a property tells the build system to include the PDBs as part of the generated NuGet package, which isn't the default. Here's how you set that. As you can see, I've included the MSBuild property inside of the value so that I am only extending what is currently there.

The last property that you may want to consider using is the PublishRepositoryUrl. This ensures that the repository URL is set and published in the final nuspec. This allows URLs to be given out for private repositories and public repositories. If you do not want this private information public, then do not set this property.

Now that we've set all of these properties, we can build our project and then open up the project inside of NuGet Package Explorer to view the details. I'm also going to open up Project B so that we can compare before and after.

The first thing that we can see is that the health is now green on the left hand side, which is Project A. We have valid Source Link information, deterministic builds, and compiler flags have all been set correctly. As noted earlier, our title and authors have been set correctly along with the tags, copyright, and description properties. You can also see details about the current commit for the repository underneath the Repository tab.

If we expand the lib folder and the net5.0 folder and click on Project A, you can see that we have the PDB sources in there as well. Now if we compare this to Project B on the right hand side, we do not have any debug information associated with this project at all.

But we don't have to just use the NuGet Package Explorer to verify these changes. Another way of verifying that we have set everything up correctly is to use the.

dotnet validate global tool from Claire Novotny. So let's go and take a look at how to set this up.

Let's take a look at how to install the dotnet validate tool as a global tool. I'm going to open up a new command line for Project A and I'm going to call dotnet tool install use the global flag which is -g and then dotnet-validate. Then because it's a pre-release version, I'm going to explicitly mark the version that I want which is 0.0.1-preview.42.

Now that we have the dotnet validate tool installed globally, I'm going to switch across to our artifacts folder. From here I can validate a NuGet package individually by calling dotnet validate package and we want to have a local package and then I can provide the package name or a path to the package. So in this case, I want Project A. I'm going to press tab to complete the version number and the extension for me.

If I hit enter now, you'll see that I have valid output results. The Source Link is valid, we have a deterministic build, and we have the right compiler flags present. Now let's compare this against Project B. Because we haven't set anything up for Project B, we are missing the symbols, we're missing the determinism, and we're also missing the correct compiler flags. You can find this.

dotnet validate tool underneath the NuGet Package Explorer repository on GitHub. This output is going to be tidied up by the looks of the issues to make it easier to use through things like build systems, so it's definitely a project worth keeping an eye on.

If you enjoyed this video, consider subscribing to the YouTube channel for more content like this.

// share_this