Working with NuGet packages and GitHub

No Comments

NuGet Packages are a critical part of any .Net Core project, especially if you plan to release it as a library.

Unfortunately, the ecosystem surrounding NuGet packages is a maze of different tools and incomplete documentation.

This article offers guidance on setting up a working NuGet workflow on GitHub.

I will show how to version a GitHub repository, how to upload a package using GitHub actions and how to consume it.

Most importantly, I want to version the library like this:

  1. If we release from the master branch, a package with an explicit version number should show up on NuGet
  2. If we build something on the develop branch or in a PR to Master, we want to upload a package preview to GitHub Packages, e.g. for testing.

How to start

This article assumes that you have a .Net core class library you want to version. For example, create one with dotnet new classlib in an empty folder.

How to version

Any good package needs a bit of information, which has to be added to the csproj file, e.g.:

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>

    <LangVersion>8.0</LangVersion>
    <Nullable>enable</Nullable>
    <ApplicationIcon />
    <OutputType>Library</OutputType>
    <StartupObject />

    <PackageId>NetCoreAdmin</PackageId>
    <Authors>Christian Sauer</Authors>
    <Company>codecentric</Company>
    <Title>Net Core Admin</Title>
    <PackageTags>ASP.Net Core;Spring Boot Admin;SBA</PackageTags>
    <PackageDescription>Allows to integrate a ASP.NET Core Server with Spring Boot Admin. It allows to easily see the logs, configured routes, configured dependencies and memory usage.
    See readme.md for examples and details</PackageDescription>
    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
    <RepositoryUrl>https://github.com/codecentric/net_core_admin</RepositoryUrl>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <IsPackable>true</IsPackable>
  </PropertyGroup>

Most of these mandatory properties are explained here.

But I left off one set of properties intentionally: PackageVersion. While you can add an explicit version number here or via AssemblyVersion attribute in the code, it is an anti-pattern in my opinion. Sure, it is quick and easy, but a single fixed version number is not optimal in times of continuous integration – ideally you will create a new package per build and incrementing this number manually is a major source of errors and pain. Using some home-grown script is another possibility, but then you need to solve the question how to assign the number on each build. Sure, you can do that – but that's a lot of work for not much gain.

So we will use the tool nbgv to help us version our library. While the documentation is more confusing than helpful, nbgv can create an incrementing unique version number for each commit.

To achieve this you need to add nbgv as a NuGet dependency:
In the csproj:

 <PackageReference Include="Nerdbank.GitVersioning" Version="3.1.91">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

also add a version.json in the root of your solution folder:

{
  "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
  "version": "0.1.9",
  "publicReleaseRefSpec": [
    "^refs/heads/master$"
  ],
  "cloudBuild": {
    "buildNumber": {
      "enabled": true
    }
  }
}

Detailed information on what version.json does can be found here, but most important are the version field and publicReleaseRefSpec. version is part of the final version number – it includes the most important part of the version number, using the SemVer format. You are expected to manage this part for yourself, because no tool is able to determine if you introduced a breaking change or not.

If you build on a non-master branch, nbgv will automatically postfix your version number with a unique id, e.g.: 0.1.9-g1adb19c798 (the part after '-' denotes a preview Package which is not automatically used by Visual Studio). When you build on master, nbgv will automatically use 0.1.9 as the version number. The postfix is derived from the Git commit and guaranteed to be unique, so no collisions should occur.

You can further automate incrementing of major/minor versions by using the cmd-utility of nbgv, please see GitHub for details.

Finally, CLEAN and then build the project:

dotnet clean
dotnet build

There should be a new nupkg file in [ProjectFolder]\bin\Debug.

You can now push your project to GitHub.

With this configuration in place, we can configure our GitHub actions so that we get a nice preview package on each merge request and a 'normal' package on each release. All we need to do is increase the version number when we merge into the master branch.

How to set up our GitHub action

Ideally, I would like this setup:

  • on each push to a merge request: build a new package and push it to the GitHub Package feed. These packages are for developers and testing.
  • on a push to master: release to NuGet. This release is intended for a general audience.

But there are several bugs in GitHub Package feeds which make this task more complicated than it needs to be. Particularly, NuGet push does not work correctly with GitHub Packages at the moment. Therefore, we will need to get a GitHub tool running for the first step. Please watch this GitHub issue for changes: https://github.community/t/github-package-registry-not-compatible-with-dotnet-nuget-client/14392/7.

I assume you are somewhat familiar with GitHub Actions – if you use them for the first time, please read the documentation first.

General layout of our GitHub Action

Location: .github\workflows\dotnetcore.yml

name: .NET Core

on:
  push:
    branches: [ master, development ]
  pull_request:
    branches: [ master, development ]

jobs:
  build:

    runs-on: ubuntu-latest
    env:
      working-directory: ./NactuatorSample # choose your solution folder here

    steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.101
    - name: Install dependencies
      run: dotnet restore
      working-directory: ${{env.working-directory}}
    - name: Build
      run: dotnet build --configuration Release --no-restore
      working-directory: ${{env.working-directory}}
    - name: Test
      run: dotnet test --no-restore --verbosity normal
      working-directory: ${{env.working-directory}}

This builds and tests our package.

Push to local registry

Add these steps:

    - name: Install gpr
      run: dotnet tool install gpr --global
    - name: gpr upload
      run: find -name "*.nupkg" -print -exec gpr push -k ${{secrets.GH_PACKAGE}} {} \;
  • Create a new GitHub Access Token (in your user account) with read/write packages permissions.
  • Add this token to the projects secrets as GH_PACKAGE
  • Finally, push this change to GitHub. It should automatically invoke your new action and push the package.
  • Look at Code -> Packages and see your Package.

github package

Push to NuGet

Pushing to nuget.org can be done using .Net Core standard tooling, most importantly the cmd tool nuget.
But you need an API key for nuget.org to do so – so please register there and add the API key to your GitHub Secrets, e.g. with the name Nuget.

Then add these steps:

    - name: Push generated package to NuGet
      if: github.ref == 'refs/heads/master'
      run: dotnet nuget push ${{env.working-directory}}/Nactuator/bin/Release/*.nupkg --skip-duplicate --api-key ${{ secrets.Nuget }} --source https://api.nuget.org/v3/index.json

Note the if condition – we only want to push to NuGet if the master branch is built.
Commit and push and the GitHub Action should run after you merged your pull request to master.

Please note that NuGet needs some time to index the new package, so it can take an hour or two until the package shows up.

Use package from GitHub

Please see this Guide from GitHub.

Outlook

We have successfully pushed packages to a registry, but ideally we would be able to debug them, too – SourceLink is designed to do that and I will investigate that in a blog post soon.

Comment

Your email address will not be published. Required fields are marked *