Search code examples
c#nugetgithub-actionssemantic-versioning

Dotnet Semantic Versioning and Release with Github Actions


I have a dotnet project and am trying to build a CI/CD pipeline that does the following using Github Actions:

  • Builds the project on a PR or master change.
  • Tests the project on a PR or master change.
  • On a master change calculates a semver (ideally a changelog too but that's less important).
  • On a master change uses this semver, builds a nuget package, and publishes it to GitHub and Nuget.

So far, I have this workflow that builds and tests my project. This seems to work nicely. However, it also has a publish step, which never seems to be run (I guess the if is wrong(?)) and I'm really not sure where to start with the SemVer part of what I want.

name: .NET Build, Test and Publish

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

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      DISCOS_API_KEY: ${{ secrets.DISCOS_API_KEY }}
      DISCOS_API_URL: http://localhost:3000/api/
    steps:
    - uses: actions/checkout@v2
      with: 
        submodules: recursive
    - name: Build docker stack
      run: docker-compose -f src/DISCOSweb-Sdk/DISCOSweb-Sdk.Tests/docker-compose.yml up -d --force-recreate --build
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 6.0.x
    - name: Restore dependencies
      run: dotnet restore src/DISCOSweb-Sdk/DISCOSweb-Sdk.sln
    - name: Build
      run: dotnet build --no-restore src/DISCOSweb-Sdk/DISCOSweb-Sdk.sln
    - name: Test
      run: dotnet test --no-build --verbosity normal src/DISCOSweb-Sdk/DISCOSweb-Sdk.sln
  publish: # This stage is never run
    if: github.event.pull_request.merged == 'true'
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Publish
        id: publish_nuget
        uses: brandedoutcast/publish-nuget@v2
        with:
          PACKAGE_NAME: DISCOSweb-Sdk
          PROJECT_FILE_PATH: "**/DISCOSweb-Sdk.csproj"
          BUILD_CONFIGURATION: Release
          NUGET_KEY: ${{secrets.NUGET_KEY}}
          VERSION_STATIC: 1.0.0 # Replace with something better

I feel like this should be a relatively common and straightforward thing to do so any assistance would be really appreciated.


Solution

  • I managed to accomplish this by splitting my CI and CD workflows, and making use of some very helpful packages.


    CI Workflow

    This first workflow runs on PRs and pushes to master. It's worth noting that when branch protection is on, a push to master is synonymous with a merged PR.

    name: .NET Continuous Integration
    
    on:
      pull_request:
        branches: [ master ]
      push:
        branches: [ master ]
    
    jobs:
      test:
        name: Test Project (Mock API)
        runs-on: ubuntu-latest
        env:
          DISCOS_API_KEY: ${{ secrets.DISCOS_API_KEY }}
          DISCOS_API_URL: http://localhost:3000/api/
        steps:
        - uses: actions/checkout@v3
        - name: Setup .NET
          uses: actions/setup-dotnet@v2
          with:
            dotnet-version: 6.0.x
        - name: Build docker stack
          run: docker-compose -f ./src/DiscosWebSdk/DiscosWebSdk.Tests/docker-compose.yml up -d --force-recreate --build
        - name: Run tests against mock API
          run: dotnet test --logger GitHubActions ./src/DiscosWebSdk/DiscosWebSdk.sln
    

    CD Workflow

    There's a bit more to the CD workflow. It runs on pushes to master but only when there are changes made to the folder containing the actual source code for the SDK (that's what the paths block does).

    I then used the mathieudutour/github-tag-action@v6.0 action to tag the commit with a calculated semantic version (calculated using conventional commits).

    The package build is then tagged with this version using the -p:PackageVersion=${{ steps.tag_version.outputs.new_version }} option on dotnet nuget pack.

    The ncipollo/release-action@v1 also creates a release on Github.

    Finally, building and pushing the release to Github and Nuget is done using dotnet nuget push as usual.

    name: .NET Continuous Deployment
    
    on:
      push:
        branches: [ master ]
        paths: 
          - src/DiscosWebSdk/DiscosWebSdk/**
      workflow_dispatch:
    jobs:
    
      test:
        name: Test Project (Real API)
        runs-on: ubuntu-latest
        env:
          DISCOS_API_KEY: ${{ secrets.DISCOS_API_KEY }}
          DISCOS_API_URL: https://discosweb.esoc.esa.int/api/
        steps:
        - uses: actions/checkout@v3
        - name: Setup .NET
          uses: actions/setup-dotnet@v2
          with:
            dotnet-version: 6.0.x
        - name: Test against real API
          run: dotnet test --logger GitHubActions ./src/DiscosWebSdk/DiscosWebSdk.sln
    
      semantic-release:
        needs: test
        name: Create a Package Release
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3 # Need the full commit history for conventional commit
        - name: Setup .NET
          uses: actions/setup-dotnet@v2
          with:
            dotnet-version: 6.0.x
        - name: Bump version and push tag
          id: tag_version
          uses: mathieudutour/github-tag-action@v6.0
          with:
            github_token: ${{ secrets.GITHUB_TOKEN }}
        - name: Create a GitHub release
          uses: ncipollo/release-action@v1
          with:
            tag: ${{ steps.tag_version.outputs.new_tag }}
            name: Release ${{ steps.tag_version.outputs.new_tag }}
            body: ${{ steps.tag_version.outputs.changelog }}
        - name: Create Nuget Package
          run: dotnet build -c Release ./src/DiscosWebSdk/DiscosWebSdk/DiscosWebSdk.csproj && dotnet pack -c Release -p:PackageVersion=${{ steps.tag_version.outputs.new_version }} -o . ./src/DiscosWebSdk/DiscosWebSdk/DiscosWebSdk.csproj
        - name: Upload Package for Publishing
          uses: actions/upload-artifact@v3
          with:
            name: PackedLib
            path: ./*.nupkg
    
    
      github-publish:
        needs: semantic-release
        name: Publish to Github
        runs-on: ubuntu-latest
        steps:
        - name: Download built project  
          uses: actions/download-artifact@v3
          with:
            name: PackedLib
        - name: Setup .NET
          uses: actions/setup-dotnet@v2
          with:
            dotnet-version: 6.0.x
        - name: Push Package to GitHub
          run: dotnet nuget push --api-key ${{secrets.GITHUB_TOKEN}} --source "https://nuget.pkg.github.com/hughesjs/index.json" *.nupkg
    
    
      nuget-publish:
        needs: semantic-release
        name: Publish to Nuget
        runs-on: ubuntu-latest
        steps:
        - name: Download built project  
          uses: actions/download-artifact@v3
          with:
            name: PackedLib
        - name: Setup .NET
          uses: actions/setup-dotnet@v2
          with:
            dotnet-version: 6.0.x
        - name: Push Package to Nuget
          run: dotnet nuget push --api-key ${{secrets.NUGET_KEY}} --source "https://api.nuget.org/v3/index.json" *.nupkg