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:
- If we release from the master branch, a package with an explicit version number should show up on NuGet
- 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.:
1<PropertyGroup> 2 <TargetFramework>netcoreapp3.1</TargetFramework> 3 4 <LangVersion>8.0</LangVersion> 5 <Nullable>enable</Nullable> 6 <ApplicationIcon /> 7 <OutputType>Library</OutputType> 8 <StartupObject /> 9 10 <PackageId>NetCoreAdmin</PackageId> 11 <Authors>Christian Sauer</Authors> 12 <Company>codecentric</Company> 13 <Title>Net Core Admin</Title> 14 <PackageTags>ASP.Net Core;Spring Boot Admin;SBA</PackageTags> 15 <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. 16 See readme.md for examples and details</PackageDescription> 17 <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> 18 <RepositoryUrl>https://github.com/codecentric/net_core_admin</RepositoryUrl> 19 <GeneratePackageOnBuild>true</GeneratePackageOnBuild> 20 <IsPackable>true</IsPackable> 21 </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:
1<PackageReference Include="Nerdbank.GitVersioning" Version="3.1.91"> 2 <PrivateAssets>all</PrivateAssets> 3 <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> 4 </PackageReference>
also add a version.json in the root of your solution folder:
1{ 2 "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 "version": "0.1.9", 4 "publicReleaseRefSpec": [ 5 "^refs/heads/master$" 6 ], 7 "cloudBuild": { 8 "buildNumber": { 9 "enabled": true 10 } 11 } 12}
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:
1dotnet clean 2dotnet 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
1name: .NET Core 2 3on: 4 push: 5 branches: [ master, development ] 6 pull_request: 7 branches: [ master, development ] 8 9jobs: 10 build: 11 12 runs-on: ubuntu-latest 13 env: 14 working-directory: ./NactuatorSample # choose your solution folder here 15 16 steps: 17 - uses: actions/checkout@v2 18 with: 19 fetch-depth: 0 # avoid shallow clone so nbgv can do its work. 20 - name: Setup .NET Core 21 uses: actions/setup-dotnet@v1 22 with: 23 dotnet-version: 3.1.101 24 - name: Install dependencies 25 run: dotnet restore 26 working-directory: ${{env.working-directory}} 27 - name: Build 28 run: dotnet build --configuration Release --no-restore 29 working-directory: ${{env.working-directory}} 30 - name: Test 31 run: dotnet test --no-restore --verbosity normal 32 working-directory: ${{env.working-directory}}
This builds and tests our package.
Push to local registry
Add these steps:
1- name: Install gpr 2 run: dotnet tool install gpr --global 3 - name: gpr upload 4 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.
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:
1- name: Push generated package to NuGet 2 if: github.ref == 'refs/heads/master' 3 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.
More articles
fromChristian Sauer
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Christian Sauer
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.