Build a .NET library with GitHub Actions
Tags: dotnet, csharp, devops, github
Published: 2020-01-05 15:20:40

GitHub Actions are a great free tool to have continuous integration of your opensource .NET library for free, directly into GitHub, saving you from setting up other tools and linking accounts.
Setting it up can feel a daunting task but, if you follow this guide you are going to be able to set it up in 10 minutes!

## What will I achieve

Following these steps, you will be able to build and run tests, targeting multiple versions of `.NET Core`, reporting then the status to the Pull Requests.
Finally, when you release a new version (either creating a release on GitHub or pushing a new tag), it will prepare your `NuGet` packages and upload them on [NuGet.org](https://www.nuget.org).

![GitHub Actions](https://thepracticaldev.s3.amazonaws.com/i/omyz7kikni6pit89hn2x.png)

## What are the prerequisites

This guide assumes that you are building either .NET Standard or .NET Core libraries.
Tests are run multitargeting multiple versions of .NET Core, although that is not a requirement.

It also assumes that to release new versions of the library you will create a new tag (it can be done from the `Releases` tab on `GitHub`) with the version number as tag name, e.g. `1.2.3`. To release a prerelease version you will use a prerelease name as `tag`, e.g. `1.2.3-preview.1`.

As this build step will rely on `dotnet pack` to package your libraries, you need to make sure you are excluding all the test project, samples and other libraries you don't want to package and publish to NuGet adding the property `<IsPackable>false</IsPackable>` to the `csproj`s or a `Directory.Build.props` file.

_Note: when using a `Directory.Build.props` file, as these builds will run on Linux agents, the casing in the file name matters!_

Here is a sample `csproj` file for a project to not be generated as `NuGet` package:

```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.1" />
  </ItemGroup>

</Project>
```

## Ok, show me the code

Create a file in a folder `.github/workflows` named `build.yml` with the following content:

```yml
name: Build

on:
  push:
    branches: master
    tags: '**'
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Dump GitHub context
      env:
        GITHUB_CONTEXT: ${{ toJson(github) }}
      run: echo "$GITHUB_CONTEXT"
    - name: Checkout
      uses: actions/checkout@v1
    - name: Setup .NET Core 2.1
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 2.1.607
    - name: Setup .NET Core 3.0
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.101
    - name: .NET Core SxS
      run: |
        rsync -a ${DOTNET_ROOT/3.0.101/2.1.607}/* $DOTNET_ROOT/
    - name: Build
      run: dotnet build --configuration Release
    - name: Test
      run: dotnet test --no-build --configuration Release
  pack:
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'push'
    steps:
    - name: Checkout
      uses: actions/checkout@v1
    - name: Setup .NET Core 3.0
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.101
    - name: Determine version
      run: echo "::set-env name=VERSION::$(git describe --tags --dirty)"
    - name: Pack
      run: dotnet pack --output ./artifacts --configuration Release -p:Version=$VERSION
    - uses: actions/upload-artifact@v1
      with:
        name: artifacts
        path: ./artifacts
  publish:
    runs-on: ubuntu-latest
    needs: pack
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
    steps:
    - name: Setup .NET Core 3.0
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.0.101
    - uses: actions/download-artifact@v1
      with:
        name: artifacts
        path: ./artifacts
    - name: Publish packages
      run: dotnet nuget push ./artifacts/**.nupkg --source nuget.org --api-key ${{secrets.NUGET_TOKEN}}
```

Then go into your [NuGet.org](https://www.nuget.org) account, navigate to [API keys](https://www.nuget.org/account/apikeys) and create a new key.
If for example your project is called `MELT` and it is Generating a package called `MELT` and one called `MELT.AspNetCore`, you could call the key `GitHub Actions MELT` and setup the glob pattern as `MELT*`.

_Note there is no `.` before the `*`, otherwise, the `MELT` base package will not match._

![Create a API key on NuGet.org](https://thepracticaldev.s3.amazonaws.com/i/uz5f990u496by38a1c71.png)

Then go into the `Settings` of your GitHub project, then on the left navigation menu select `Secrets`, then `Add a new key` and name the key `NUGET_TOKEN`.

![Add a secret to GitHub](https://thepracticaldev.s3.amazonaws.com/i/1nra4571kud3yye18muz.png)


Done!

Now you just have to push the `build.yml` to master and, after that, all the new PR (or new pushes to pre-existing ones) will start to build and attach the status to the PR.

When a PR is merged to master, it will also create the packages (with a temporary pre-release version, not strictly following SemVer however that doesn't matter as this packages are not published) and upload them as artifacts in the build results in `GitHub Actions` so that you can check which packages have been created and download them if you need to verify something.

![Download artifacts from GitHub Actions](https://thepracticaldev.s3.amazonaws.com/i/5dov52cyplcfxodov655.png)

If you are happy, you can then create a release, so that the actual packages with the correct version number will be generated and uploaded to [NuGet.org](https://www.nuget.org).

![GitHub create release](https://thepracticaldev.s3.amazonaws.com/i/kvdcn82bamjvk3u3mp4w.png)

*Happy building!*

----

## Appendix: change version of .NET Core

This article assumes you are running the tests against multiple versions of .NET Core, specifically .NET Core 3.0 and 2.1, so it is installing the .NET Core SDK 2.1.607 and 3.0.101. If you need to change those version, you have to both update the version in the steps to install the SDK as well as in the `rsync` command in the `.NET Core SxS` step at line 29 of the `build.yml` file shown above, which is a [workaround](https://github.com/actions/setup-dotnet/issues/25#issuecomment-557570168) to overcome the [current limitation](https://github.com/actions/setup-dotnet/issues/25) to install multiple versions of .NET Core in `GitHub Actions`.

----

_Originally published at [alessio.franceschelli.me](https://alessio.franceschelli.me) on December 4, 2019._