Skip to content

Nuget Package Publishing

sujeffreyl edited this page Dec 11, 2020 · 2 revisions

Create nuget packages for a project

This document describes what is necessary to create nuget packages for a project.

Guiding principles

  • Packages will be uploaded to nuget.org
  • Commits in the master branch will create a new pre-release nuget package. Pull requests will build nuget packages, but those are only available as artifacts on the CI machine.
  • We use AppVeyor to automatically build and upload nuget packages.
  • If a project is cross-platform the code should not contain platform specific #ifdefs. Instead the differences should be dealt with at run-time. This simplifies packaging significantly.
  • Similarly, there shouldn’t be different compiled versions for 64-/32-bit. The library should be compiled for AnyCPU. If it is necessary to distinguish between 64- and 32-bit code this should happen at run-time.
  • There are two exceptions to the above two rules:
  • Unmanaged libraries will very likely require different binaries for 64- and 32-bit as well as for the different platforms. However, if possible these binaries should still be distributed in a common nuget package.
  • For managed executables it might be necessary to compile for 64- or 32-bit. But note that AnyCPU is still often the better alternative unless there are specific reasons that can’t be solved easily otherwise. The other question is if a nuget package is the best way to distribute this executable.
  • Assemblies should be signed (strong-named). This allows them to be used in signed and unsigned projects, whereas an unsigned assembly can only be used in unsigned projects.

Additional tips

  • Create a CHANGELOG.md file where you document the relevant changes, and add the SIL.ReleaseTasks nuget package to the project. When building the nuget package, SIL.ReleaseTasks will extract the changes since the last version from the CHANGELOG.md file and add it to the nuget package so that it shows up as release notes on nuget.org.
  • Use the new sdk-style .csproj format for projects that create nuget packages. That way you get building nuget packages basically for free.
  • Use GitVersion to deal with semantic versioning. This solves quite a number of problems and also takes care of giving the nuget package the correct version. You might have to add a GitVersion.yml file to customize some aspects. It might be sufficient to copy an existing one, e.g. from libpalaso.
  • Add Microsoft.SourceLink.GitHub to the project and create (and upload) a symbols package. This enables source link for the nuget package and allows you to debug into the code of the nuget package when using that package somewhere.
  • After releasing a new version of a nuget package it is helpful to unlist the obsolete pre-release versions. This reduces the number of versions presented to the user. The SIL.NuGetCleaner tool helps with that.

Things to avoid

One of the challenges are dependencies that are not signed if you try to create a strong-named assembly. There are several workarounds. However, they have disadvantages and should be avoided:

  • Don’t use StrongNamer to sign dependent assemblies on the fly during a build. Clients that consume the nuget package won’t be able to access these assemblies, and they can’t use StrongNamer as well because that produces different results (unless you distribute the dependent signed assemblies in your nuget package)
  • Don’t use ILRepack/ilmerge to merge the dependencies into your assembly. This breaks symbol packages

Instead create a modified nuget package with a signed version of the dependency and upload that to nuget.org (naming the package e.g. -signed).

Prerequisites

  • In order to be able to manage the nuget package on nuget.org, you should create an account on nuget.org (login with a Microsoft account), and request an admin to add you to the sil-lsdev nuget organization (on slack). They will need your nuget username and you will receive an invitation through email.
  • In order to be able to set up the build on AppVeyor, you should create an account on appyevor.com, and request to be added to the SIL_Language_Technology account as collaborator. On appveyor.com (on the authorizations page), click Install AppVeyor App and configure SILlsdev to include the project(s) for which you intend to create nuget packages.

Creating packages

How to create nuget packages depends on whether this is a one-off package (e.g. for a dependent library that you don’t expect to change but need a nuget package for. One example would be a dependency that exists as a nuget package, but where the assembly isn’t signed.), or if this is an actively maintained project that we want to distribute as nuget packages.

Creating a one-off nuget package

To create a one-off nuget package, copy the files that should be included in the nuget package to the expected directory structure and create a .nuspec file. Then you can use nuget.exe to create the nuget package and upload that to nuget.org. Details are for example in this doc. If you want to modify an existing nuget package you’ll have to give it a different name (unless you have permissions to upload for that package, or want to submit a PR).

Modifying a project to build nuget packages

  • Switch to the new sdk-style .csproj format. At least VS 2017 doesn’t support creating a project with the new sdk-style format if the project uses .NET Framework, but it happily works if it finds an existing project in that format. The way I usually do it is that I copy and edit the code of a .csproj file of an existing similar project, e.g. SIL.Lexicon.csproj:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net461</TargetFrameworks>
    <RootNamespace>SIL.Lexicon</RootNamespace>
    <AssemblyTitle>SIL.Lexicon</AssemblyTitle><!--1-->
    <Configurations>Debug;Release</Configurations>
    <Description>Description will show up on nuget.org</Description><!--2-->
    <Company>SIL International</Company>
    <Authors>SIL International</Authors>
    <Product>libpalaso</Product>
    <Copyright>Copyright © 2010-2020 SIL International</Copyright>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <PackageProjectUrl>https://github.com/sillsdev/libpalaso</PackageProjectUrl>
    <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
    <OutputPath>../output/$(Configuration)</OutputPath>
    <PackageOutputPath>../output</PackageOutputPath>
    <SignAssembly>true</SignAssembly><!--3-->
    <AssemblyOriginatorKeyFile>../palaso.snk</AssemblyOriginatorKeyFile>
    <IncludeSymbols>true</IncludeSymbols><!--4-->
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <AppendToReleaseNotesProperty><!--5--><![CDATA[
See full changelog at https://github.com/sillsdev/libpalaso/blob/feature/nuget/CHANGELOG.md]]>
    </AppendToReleaseNotesProperty>
  </PropertyGroup>

  <ItemGroup><!--6-->
    <PackageReference Include="GitVersionTask" Version="5.0.1">
          <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" 
    PrivateAssets="All" />
    <PackageReference Include="SIL.ReleaseTasks" Version="[2.2.0,)" PrivateAssets="All" />
  </ItemGroup>
</Project>

The elements with the comments like "<!-- 1-->" need to be adjusted. If you already have a sdk-style project you might want to add the missing elements of the first property group and add the recommended packages. Notes:

  1. The AssemblyTitle is also used as the name of the nuget package.
  2. msbuild uses this and the following elements instead of the former AssemblyInfo.cs. The information also gets used for the nuget package. You should remove the previous AssemblyInfo.cs file.
  3. SignAssembly and AssemblyOriginatorKeyFile trigger the signing/strong-naming of the assembly.
  4. This and the following elements configure the creation of symbol packages which allow to debug into the nuget package.
  5. SIL.ReleaseTasks will append this text to the end of the release notes of the nuget package.
  6. See above under Additional tips for the explanation of these packages.

Building the nuget package

Building the nuget package locally can either be done from within the IDE (when on a project: Visual Studio: Build/Pack; Rider: Advanced Build Actions/Pack Selected Projects), or from the command line:

msbuild /t:Pack <solution>.sln

Automating building the nuget package

One way to do this is on AppVeyor (Go to Projects and click the New Project button.) It’s probably easiest to look at the settings of an existing job (e.g. libpalaso (feature/nuget)) and copy the settings from there.

Releasing a new version

See Release Procedure page on the libpalaso wiki.