Skip to content

A Basic Continuous Integration Pipeline with GitHub Actions

In a previous post, I built a basic CI pipeline with CTest that uploaded its results to a CDash board. It worked fine except that we had to start the pipeline manually from the command line. In this post, we define a pipeline with GitHub Actions that runs the CI pipeline whenever a developer pushes a code change to a GitHub repository.

GitHub Actions in a Nutshell

It’s a tiny nutshell, as I am “only” interested in running the CTest-based CI pipeline – at least for now. My plan is to improve the pipeline iteratively.

GitHub Actions is a platform for building continuous integration and continuous deployment (CI/CD) pipelines. We specify a CI/CD pipeline in a workflow. A workflow consists of one or more jobs. Each job executes one or more steps, predefined actions or custom scripts. Certain events on the repository (e.g., a push or merge request) trigger the execution of the workflow.

Jobs run on a computer in the GitHub cloud or on a local computer, that is, on GitHub-hosted runners or on self-hosted runners. GitHub-hosted runners execute the workflow in a virtual machine (Linux, Windows or macOS). The workflow would have to install additional software such as Qt or an embedded Linux SDK every time the workflow is run. This may quickly become time and money consuming, because billing is by the minute.

Hence, we probably fare better with a self-hosted runner. For my first steps with GitHub Actions, my Ubuntu 20.04 workstation will do. In a later step, I will probably move to Docker containers.

Setting up a Self-Hosted Runner

We go to the main page of the repository (for me: the private repository foss-compliance), navigate to the page Settings > Actions > Runners and press the button New self-hosted runner. We select Linux as the Runner image and x64 as the Architecture. We follow the instructions on the web page. The instructions differ in the repository URL, the runner version, the operating system and the configuration token.

Downloading and installing the runner:
$ mkdir actions-runner && cd actions-runner
$ curl -o actions-runner-linux-x64-2.292.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.292.0/actions-runner-linux-x64-2.292.0.tar.gz
$ echo "14839ba9f3da01abdd51d1eae0eb53397736473542a8fae4b7618d05a5af7bb5  actions-runner-linux-x64-2.292.0.tar.gz" | shasum -a 256 -c
$ tar xzf ./actions-runner-linux-x64-2.292.0.tar.gz

We are free to choose the installation directory of the runner. I installed it in/public/Tools/actions-runners, which I’ll refer to as $RUNNER_DIR.

Configuring the runner:
./config.sh --url https://github.com/bstubert/foss-compliance --token ABCDEFGHIJKLMNOPQRSTUVWXYZ123
[...]
# Authentication

√ Connected to GitHub

# Runner Registration

Enter the name of the runner group to add this runner to: [press Enter for Default] 

Enter the name of runner: [press Enter for godspeed] 

This runner will have the following labels: 'self-hosted', 'Linux', 'X64' 
Enter any additional labels (ex. label-1,label-2): [press Enter to skip] 

√ Runner successfully added
√ Runner connection is good

# Runner settings

Enter name of work folder: [press Enter for _work] 

√ Settings Saved.

For now, we use the default values for the four input requests by pressing Return. The runner will use $RUNNER_DIR/_work as its working directory.

Starting the runner:
$ ./run.sh 

√ Connected to GitHub

Current runner version: '2.292.0'
2022-06-06 08:40:19Z: Listening for Jobs

The runner is waiting for the first push to the repository.

Defining the Workflow for a CI Pipeline

We define the GitHub Actions workflow in the file .github/workflows/github-actions-ci-cd-pipeline.yml in the root directory of our repository.

name: CI/CD Pipeline
on: [push]
jobs:
  Commit-Stage:
    runs-on: self-hosted
    steps:
      - name: Checking out repository ${{ github.repository }}
        uses: actions/checkout@v3
      - name: Changing to working directory ${{ github.workspace }}
        run: cd ${{ github.workspace }}
      - name: Running FossCompliancePipeline with CTest
        run: ctest -S ./FossCompliancePipeline.cmake

The workflow CI/CD Pipeline has one job Commit-Stage, which runs on the self-hosted runner set up earlier. The job is triggered when a developer integrates code changes on any branch with git push. The job performs three steps.

  • The first step checks out the repository into $RUNNER_DIR/_work/foss-compliance/foss-compliance/ with version 3 of the predefined GitHub action checkout.
  • The second step changes to the directory where the repository was checked out – as given in the first step.
  • The third step runs the CTest-based pipeline for the foss-compliance project (build, test and coverage) and uploads the results to CDash – as described in my post Building a CI Pipeline with CTest and CDash.

When we push a change to the repository, GitHub Actions executes the workflow. We can watch the progress on the repository’s GitHub page under Actions > Workflows > CI/CD Pipeline.

The screenshot shows the list of workflow runs, where the first run is still in progress.
Overview of workflow runs with first run still in progress

Once finished, we click on the first workflow run “Named each step” to see some statistics about the job Commit-Stage.

The screenshot shows some statistics about the successful job Commit-Stage.
Statistics about the successful job Commit-Stage

We click on the button Commit-Stage to see the details of each step of the successful job.

The screenshot shows each step of the job Comit-Stage and the execution log for changing to the working directory and for running FossCompliancePipeline with CTest.
Execution log for each step of the job Commit-Stage

The page shows the status (here a check mark for success), the name and the running time for each step. When we open the folder for a step, we see the log of executing the step. GitHub Actions added three more steps to the three steps of our workflow file: Set up job (step 1 in the image above), Post Run actions/checkout@v3 (step 5), and Complete job (step 6).

Step 4 – Run FossCompliancePipeline with CTest – builds the foss-compliance project, runs all unit tests, calculates the coverage and uploads the results to the CDash board – as described in my post Building a CI Pipeline with CTest and CDash. The dashboard for the first run of the CI/CD Pipeline looks as follows. Every further run of the pipeline adds another row in the tables.

Shows the CDash board of the first run of the CI/CD Pipeline.
CDash board of GitHub Actions running the CI/CD Pipeline

Next Steps

Our rudimentary CI pipeline is running. It’s a good start, but not yet fit for the Continuous Delivery of a smart HMI running on a Qt embedded system. Here are some ideas how to improve the pipeline step by step.

  • The pipeline only takes care of continuous integration (CI) but not of continuous deployment (CD). It could use the CMake installation commands and CPack or Conan to create deployable packages.
  • Our pipeline lacks an Acceptance Stage complementing the Commit Stage (see also Chapters 8-10 in Dave Farley’s book Continuous Delivery Pipelines: How To Build Better Software Faster). The Commit Stage runs unit tests and builds the software. It ensures that we can integrate our code changes into the main branch with a high probability of not breaking anything. The Acceptance Stage runs acceptance tests on the software deployed to the embedded device. Running tests on the embedded device has some implications:
    • The pipeline must cross-compile the software against an embedded Linux SDK built with Yocto.
    • The pipeline must deploy the binaries to the device and run the acceptance tests on the device.
  • In bigger projects than foss-compliance, we must optimise the Commit and Acceptance Stage to run in less than 10 minutes and less than 60 minutes, respectively. Reducing include dependencies, stopping the build if unit tests fail, using ccache and using more processor cores are some means for optimisation.
  • For better reproducibility, we may want to run the pipeline in a Docker container instead of directly on a workstation.
  • The workstation running the CI/CD pipeline should start the runner as a (systemd) service. The same is true for the CDash server.
  • The pipeline could check for each push to main whether the code changes are covered by tests.
  • We may want to replace the CDash board by a more powerful dashboard like Grafana.

Resources