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