Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / automation

Writing CI Pipeline using GitHub Actions to Build C++ Project

4.50/5 (7 votes)
20 Apr 2020CPOL8 min read 24K  
This article gives a practical overview of using GitHub Actions to automate a CI pipeline to build C++ project which is configured using CMake.
A quick guide to understanding how GitHub Actions works. We will write a workflow file to automate the build process of a C++ sample project which is configured using CMake. This is not a step by step guide to follow, however, a good kick start for the ones who are starting to use GitHub Actions for building their C++ projects.

About GitHub Actions

GitHub Actions (not quite an older name as the release date is 13th Nov 2019) has gotten huge community attention due to its ease of use, flexible workflow customization, and reach to a countless number of programming languages supporting all the operating systems. The brand line, 'from idea to production', is quite explanatory. To simply understand it, take GitHub Actions as an integrated API built and maintained by GitHub to come along with your GitHub repository. It allows you to automate your workflow directly from your repository based on different events, such as on release, on push, on the pull, etc. GitHub manages the execution of your workflow files (I will explain workflow files momentarily), in such maintainable fashion; descriptive and colorful errors and warning provides logs for rich feedback on every step, save history with the related workflow file, etc.

GitHub Actions have proved to be a considerable candidate if you are to write CI/CD pipelines, as the base idea is that you can build, test and deploy your code directly from your git folder using your workflow file/s. A workflow file is a logical/physical representation of your CI/CD pipeline, which is a core block of GitHub Actions. To see the GitHub Actions in action, you need to have at least one workflow file in .github/workflows directory of your base repository. This workflow requires to be written in YAML format and saved with .yml and .yaml file extension. Workflow files contain the custom automated processes depending upon your project type and dependencies.

As mentioned, the processes written in the workflow files trigger once the desired event occurs. For example, on a pull request, it is required first to install the dependencies, build the project, run a few tests and only after successful execution of all these checks, the pull request will be marked as successful.

About the Article

Enough about GitHub Actions. Now that if you are convinced to use GitHub Actions to automate your build pipelines, follow along with the article as I will build a sample project written in C++ to demonstrate how to automate your build pipeline using GitHub actions. This demonstration can show you happening steps; however, you will not be comfortable with GitHub actions unless you will play with them yourself by automating different workflows on the tech stack of your choice. To know more about how your workflows work and help you to build your CI/CD pipeline, refer to this official guideline – "About GitHub Actions".

Project Structure

The project used for the demonstration is a simple 'Hello World' C++ program configured using CMake. I kept the project simple as the gist of the article is to write the workflow file instead of getting the head around the build process of a complex C++ project using CMake. For the purpose, I found the following repository on GitHub. Let's clone it first, Hello World.

 

Hold on, if you are not really familiar with the CMake tool for C++, yet you are to use it for building your C++ project, it is better to give it a thorough read from its official documentation [reference].

You will use Git version control to download and set up the source code:

$ git clone https://github.com/Iiqra/HelloWorldCmakeSample.git
$ cd cmake-hello-world/
$ mkdir build
$ cd build
$ cmake ..

It should run successfully like shown in the screenshot below:

Image 1

Figure 1: Configuring CMake files

After cloning the repository (simple Hello World program), I have created a dedicated folder for all the built files (in our case, this step is not that necessary. However, for big projects, this is always preferable). In the build folder, I have executed the CMake command to generate the build file using CMake as you can see the last three lines in the above image, indicating that configuration, generation, and the building of the project files have been successfully completed.

Thus, our build files contain a solution file (.sln), which will be used to build the project and generate the project binaries. I will use MSBuild to build the solution as I'm using on my Windows VM (you can see this build command in the below two images). You can use any alternative of your choice depending upon your preferences and operating system.

$ ls 
$ msbuild.exe CMakeHelloWorld.sln 
$ cd debug 
$ ./CMakeHelloWorld.exe

Now that the solution is built, we can have the binaries either in the debug folder or in the release folder depending upon our build configuration. As you can see in the image below, I have executed the CMakeHelloWorld.exe file, which prints the highlighted message as a result. However, the disclaimer is not functional yet, as the mentioned dependency is commented out for the manual execution of the execution file [Figure 5].

Image 2

Figure 2: Running Executable File
cmake_minimum_required (VERSION 2.8)
project (CMakeHelloWorld)

#version number
set (CMakeHelloWorld_VERSION_MAJOR 1)
set (CMakeHelloWorld_VERSION_MINOR 0)

#find dependencies
#find_package(cppzmq REQUIRED)


#include the subdirectory containing our libs
add_subdirectory (Hello)
include_directories(Hello)
#indicate the entry point for the executable
add_executable (CMakeHelloWorld Hello HelloWorld.cpp)

# Indicate which libraries to include during the link process.
target_link_libraries (CMakeHelloWorld Hello)

install (TARGETS CMakeHelloWorld DESTINATION bin)

The reason for commenting out this dependency is that the cppzmq will be installed using vcpkg and will take some time to be installed. That is why it is better to make this step part of the CI pipeline instead.

Writing Workflow File

Thus far, you are aware of the project structure and have also seen that how we can build it manually (excluding one step of installing vcpkg dependency, which is, of course, better to be directly installed from the commands written in workflow file). Therefore, you are good to grasp the concepts written in the following workflow file.

name: CI
# Workflow file for windows
on:
  push:
    branches:
      - master
  pull_request: {}

jobs:
  Windows:
    name: build-windows
    runs-on: [self-hosted, windows]

    steps:
    - name: Checkout
      uses: actions/checkout@v1
      with:
          submodules: recursive

    - name: Installing vcpkg (windows)
      run: |
        cd ..
        git clone https://github.com/Microsoft/vcpkg.git
        cd vcpkg
        git checkout --force 2020.01
        .\bootstrap-vcpkg.bat
        .\vcpkg.exe install cppzmq:x64-windows

    - name: Running cmake (windows)
      run: |
        cd ${{ github.workspace }}
        mkdir build
        cd build
        cmake .. -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}
                  /../vcpkg/scripts/buildsystems/vcpkg.cmake

    - name: Build Solution (windows)
      run: |
        cd build
        MSBuild.exe CMakeHelloWorld.sln

As mentioned initially, explaining the above workflow file is not meant to be covered in this article. I recommend going through the official documentation for this sake. However, for the sake of completion, I better describe just the main steps:

Starting off with the name of my workflow, which is CI in this case. Then the event comes, I have set 'push (on the master branch)' as the main event when this workflow will be triggered, there can be multiple events as well, i.e., [push, pull_request]. That way, whenever something will be pushed from this repository on the master branch, the following build-windows job will start its execution.

Next, we require the type of machine on which the job will run. Your job will run on a fresh instance of the virtual machine every time if you have chosen to go with GitHub-hosted runners, otherwise, you have an option to run your job on the self-hosted runner as well. The provided labels for GitHub runners are windows -latest or ubuntu-latest, etc. For a self-hosted runner, we use 'self-hosted' as the label. Moreover, we can also use a label array, as in my case – self-hosted, windows.

Then comes the integral component – steps. Steps are the sequential tasks inside a job, which are so like different commands performing different actions. The step has some properties, names, uses clause, and run command. Please read about steps in detail from Job Steps.

All the steps are explanatory; I'm installing vcpkg (after cloning it from its git repo) to install the dependencies, which is cppzmq in the provided CMake file. Then, I'm performing all the steps which been performed manually in the first half of the article, configuring CMake, building solution files, and then running the execution file.

Here is the time to trigger this workflow by triggering the event on which we set the job to be executed, that is push event.

Image 3

Figure 3: Pushing changes on GitHub Repo

Modification is required to push events to occur, and for that, I have added comments (refer to figure 7). Now switch to your repository's action tab and check the action with the recent commit you made.

Image 4

Figure 4: Workflow file view

This is how the action window looks like; the first commit got failed since there was no runner running at the time of the first commit. However, there was the .yaml file present in the .github/ workflow folder with the push event which got triggered eventually on our first commit. However, the second commit 'Added comment in the CI file' got executed and passed successfully. On the action's console window, you can see the colorful logging of all the steps and underline details of each command which you wrote.

The below screenshot showing the workflow console window is the well explanatory; every step is successfully executed with its underline build output details. Moreover, you can access this console file from the action tab from the in-used repository here for further details and looking deep into the steps.

Image 5

Figure 5: Expanded vcpkg logs on the console window

Concluding Thoughts

Before summing up, there are two must-knows which I want you to notice:

  1. You require to be the owner of the repository if you want to run a runner from your machine and get the workflow files running in your repository. Therefore, make sure after cloning my repository for a kick start, you can init-add-commit-push again in your personal repository, then launch the runner on your own machine [Setup Runner].
  2. There are a few already written and tested actions available in the GitHub marketplace for most of the tasks you can get help from while automating your workflows such as run-cmake action and run-vcpkg action. However, to get started with the overall functionality of GitHub actions, I have preferred to write the raw commands instead of using these packages.

All in all, we have gone through the fundamentals of GitHub Actions. We have written one workflow file to automate our pipeline to build a simple C++ program that is configured to be built with CMake settings. Also, we have seen the execution of the workflow file on the repository's actions tab to observe how to build logs and steps are represented.

History

  • 20th April, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)