Table of Contents
Background
Creating a GitHub workflow that uses the GNU Arm compiler is relatively simple if your have the right Actions. In the post I will show you have to install the arm-none-eabi-gcc compiler and build your application automatically when you check in your code to GitHub.
CI/CD and embedded software
Embedded software testing introduces some significant challenges in an end-to-end CI/CD pipeline. Depending on your particular device, there may be hardware restrictions (specialized hardware required to run testing) that can impact your integration steps, as well as the production devices can often be isolated from the development environment by being either offline, or accessible through low bandwidth connections.
Continuous integration using GitHub Actions
GitHub Actions are an awesome way to automate validation of your code changes and are an essential component when working with GitHub. One area where GitHub Actions can really help is to improve the continuous integration component of your development pipeline, specifically checking that your project can be built successfully.
Creating a build test workflow
To get started a create a workflow to perform the build, click on the “Actions” tab. Depending on your project, GitHub may suggest a starting point, in which case choose one of those if appropriate, otherwise you can skip this step and setup a workflow yourself.
In my case the project is using CMake, so I will select the “Configure” button for the CMake suggestion to give me a starting template.
Editing the workflow
The next step is to edit the workflow to execute the build. To do this successfully, we will need perform the following steps:
- Review triggers
- Create build strategy
- Checkout the code
- Install the toolchain
- Build the project
Review triggers
Triggers describe when the workflow will be executed. The template already filled in the details as show below to run the workflow when there is a push to the main
branch, as well as every time there is a push to a pull request (PR) on the main
branch.
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
In my case, this is what I'm looking for as I will be pushing changes to a branch, and once I create the PR, I want the validation to kick in before I eventually merge to main
.
Create build strategy
The build strategy is where the matrix of build types can be defined. In my case I want to build on both Windows and Ubuntu, and also build both the Debug and Release builds.
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
type: [Debug, Release]
Checkout the code
The first step is to checkout the code. The template already included a step using the checkout action, I extended this to recursively checkout out the submodules for my project.
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
Install the toolchain
Because I will be building for an Arm processor, I will install the arm-none-eabi-gcc compiler. The easiest way to do this is to use an existing GNU ARM Embedded Toolchain Action.
Secondly Ninja is considerably faster than the Make default, so I will use another GitHub Action to install this.
CMake comes preinstalled in the GitHub runner so this doesn't need to be installed explicitly.
- name: arm-none-eabi-gcc
uses: ryanwinter/arm-none-eabi-gcc@master
with:
release: '10-2021.10'
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v3
Build the project
The final step is to build the project. In this example I'm using CMake, however you could replace this with a different build system if that's your preference.
There are two commands, configure and build.
- Configure, using the following parameters:
Command | Description |
---|---|
-Bbuild | Set the build path |
-GNinja | Set the build system generator |
-DCMAKE_TOOLCHAIN_FILE="cmake/arm-gcc-cortex-m4.cmake" | Set the toolchain for a Cortex-M4. Read more on Arm toolchains. |
-DCMAKE_BUILD_TYPE="${{ matrix.type }}" | Set the build type which will use the type variable from the strategy outlined above. This will result in the build job executing twice, once for “Debug” and once for “Release”. |
2. Build, setting the build path to the same as the configure step.
- name: Build binary
run: |
cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE="cmake/arm-gcc-cortex-m4.cmake" -DCMAKE_BUILD_TYPE="${{ matrix.type }}"
cmake --build build
Final workflow
Putting it all together we end up with the following workflow:
name: CMake
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
type: [Debug, Release]
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: arm-none-eabi-gcc
uses: ryanwinter/arm-none-eabi-gcc@master
with:
release: '10-2021.10'
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v3
- name: Build binary
run: |
cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE="cmake/arm-gcc-cortex-m4.cmake" -DCMAKE_BUILD_TYPE="${{ matrix.type }}"
cmake --build build
The workflow in action
Whenever you create a pull request or merge a change to the main branch, the workflow will be triggered and the build job will be run four times, to cover the Windows/Ubuntu and Debug/Release combinations.
Previously run workflows can be found by clicking onto the “Actions” tab.
For a pull request, they can be found on the “Checks” tab, or the latest run can be seen inline on the “Conversation” tab
Clicking on each of these jobs will give you an outline of the steps and expanding each step will provide its output.
Next steps
Once you have your build workflow setup and running, there are a couple of additional things you can do to finish things off.
- Create a branch protection rule to stop a pull request from being merged up unless the workflow was successful.
- Create a status badge, which you can add to your readme, to indicate the status of the last run.