Github action build scripts for various languages

I’ve been through a bit of a wave of writing my unit of measurement library code for various programming languages, originally starting in F# through C# and Java to Go, Rust, Swift, Typescript and Python. Each time I’ve needed/wanted to create a Github actions build workflow for each language. To be honest Github gives you all the information you need, but I’m going to list what I’ve got and what worked for me etc.

Creating the workflow file

I’ve covered much of this before, but I’ll recreate here for completeness.

You’ll need to start by following these steps

  • Create a .github folder in the root of your github repository
  • Within the .github folder create a workflows folder
  • Within the workflows folder create a file – it can be named whatever you like, let’s use build.yml (yes it’s a YAML file)

The workflow configuration

Note: YAML is a format where whitespace is significant. In the code snippets below I will left justify the code, basically breaking the format but making it easier to read. At the end of the post I’ll put all the snippets together to show the correct format, if you’re just here for the code, then I’d suggest scrolling to the bottom of the post.

Your build.yml (if you followed my naming) will start with the name of the workflow, so let’s simply name it Build, i.e.

name: Build

Next up we need to list the events that cause the workflow to start, usually this will be things like “on push” or “on pull_request”. So now we add the following to the .yml file

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
  workflow_dispatch: # Manual Run

As Github has moved away from using master for the root branch to main obviously change master to main in the above. I tend to (at least initially) also include a way to manually run the workflow if I need to, hence include workflow_dispatch.

So we have a name of the workflow and the events that trigger the workflow, now we need the tasks or in Github action terms, the jobs to run. So we’ll just add the following, to our .yml

jobs:
   build:

Now for the next step, we can either simply list the VM OS to run on using

runs-on: ubuntu-latest

Or, as all the languages listed at the start of this post are cross platform, we might want to try building them on each OS we want to support, in this case we create a strategy and matrix of OS’s to support. It’d also be good to see in the build what OS we’re building for so we’ll create the following

name: Build on ${{ matrix.os }}
strategy:
  matrix:
    os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}

In the above, we’ll try to run our build on Mac, Ubuntu and Windows latest OS environments. If you only care about one environment you can use the previous runs-on example or just reduce the os array to the single environment.

Note: If you take a look at virtual-environments, you’ll see a list of supported TAML labels for the OS environment.

Within the matrix we can declare variables that are used in runs-on but also for steps, so for example if we’re wanted to list the version(s) that we want to support of a toolchain or language, we could do something like

matrix:
  os: [macos-latest, ubuntu-latest, windows-latest]
  go-version: [1.15.x]

So now we have matrix.go-version available for deciding which version of Go (for example) we’ll install.

We’ve setup the environments now we need to actually list the steps of our build. The first step is to checkout the code, so we’ll have something like this

steps:
- uses: actions/checkout@v2

Next we’re create each step of the build process, usually with a name for each step to make it easier to see what’s happening as part of the build workflow. At this point we’ll start to get into the specifics for each toolchain and language. For example for F# and C# we’ll start by setting up dotnet. For Swift we can just start calling the swift CLI. So let’s go through each toolchain/language setup I have for my unit conversion libraries/packages.

.NET steps

For .NET we’ll need to setup .NET with the version(s) we want to build against. Then install any required nuget dependencies, then build our code, run any tests and if required package up and even deploy to NuGet (which is covered in my post Deploying my library to Github packages using Github actions).

- name: Setup .NET Core SDK 6.0.x
  uses: actions/setup-dotnet@v1
  with:
    dotnet-version: '6.0.x'
- name: Install dependencies
  run: dotnet restore
- name: Build
  run: dotnet build --configuration Release --no-restore
- name: Test
  run: dotnet test FSharp.Units.Tests/FSharp.Units.Tests.fsproj --no-restore --verbosity normal
- name: Create Package
  run: dotnet pack --configuration Release

Note: Ofcourse change the FSharp.Units.Tests/FSharp.Units.Tests.fsproj to point to your test project(s).

Java steps

For Java we’re just going to have a setup step and then use Maven to build and run tests

- name: Setup JDK 8
  uses: actions/setup-node@v1
  with:
    java-version: '8'
    distribution: 'adopt'
- name: Build with Maven
  run: mvn --batch-mode --update-snapshots verify
[code]

<strong>Go steps</strong>

With Go we're using a previously declared <em>go-version</em> to allow us to target the version of Go we want to setup, ofcourse we could do the same for .NET, Java etc. Next we're installing dependencies, basically I want to install golint to lint my code. Next up we build our code using the Go CLI then vet and lint before finally running the tests.

[code]
- name: Setup Go
  uses: actions/setup-go@v2
  with:
    go-version: ${{ matrix.go-version }}
- name: Install dependencies
  run: |
    go version
    go get -u golang.org/x/lint/golint
- name: Build
  run: go build -v ./...
- name: Run vet & lint
  run: |
    go vet ./...
    golint ./...
- name: Run testing
  run: go test -v ./...

Rust steps

For Rust we’ll keep the steps pretty simple

- name: Build
  run: cargo build --verbose
- name: Run tests
  run: cargo test --verbose

Swift steps

- name: Build
  run: cargo build --verbose
- name: Run tests
  run: cargo test --verbose

Typescript steps

- name: Use Node.js
  uses: actions/setup-node@v1
  with:
    node-version: '12.x'
- name: Install dependencies
  run: yarn
- run: yarn run build
- run: yarn run test

Python steps

- name: Set up Python
  uses: actions/setup-python@v2
  with:
    python-version: '3.x'
- name: Test with pytest
  run: |
    python -m unittest