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.

Create a new workflow

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:

  1. Review triggers
  2. Create build strategy
  3. Checkout the code
  4. Install the toolchain
  5. 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.

  1. Configure, using the following parameters:
CommandDescription
-BbuildSet the build path
-GNinjaSet 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”.
CMake configure parameters

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.

Workflow jobs

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.

  1. Create a branch protection rule to stop a pull request from being merged up unless the workflow was successful.
  2. Create a status badge, which you can add to your readme, to indicate the status of the last run.