Skip to Content
Course content

232: Continuous Integration for C++ Projects

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By now, you've probably felt the pain of the "it works on my machine" syndrome. You spend three hours polishing a feature, push it to the repository, and then realize you broke a dependency for your teammate because you're using a different compiler version or a local library they don't have. This is exactly why we use Continuous Integration (CI). In C++, CI isn't just a luxury; it's a necessity because our language is so sensitive to toolchain variations.

I want to show you how to set this up using a concrete example. We're going to build a small MathUtility library that handles basic vector operations and set up a GitHub Actions pipeline to ensure every commit is compiled and tested automatically.

Building the target project

First, let's get some code on the table. I'm keeping this simple: a Vector2D class. I've already structured this as a library with a separate test runner, which is the professional way to do it. If you put your tests in main(), you're just making more work for yourself later.

// Vector2D.hpp
#pragma once

struct Vector2D {
    double x, y;
    double magnitude() const { return std::sqrt(x*x + y*y); }
};

// Vector2D.cpp
#include "Vector2D.hpp"
#include 

// tests.cpp
#include "Vector2D.hpp"
#include 
#include 

int main() {
    Vector2D v{3.0, 4.0};
    assert(v.magnitude() == 5.0);
    std::cout << "All tests passed!" << std::endl;
    return 0;
}

I'm using CMake to manage this. My CMakeLists.txt defines the library and the test executable. It's a standard setup, nothing fancy, but it's what the CI runner will use to build the project.

Defining the automation pipeline

Now for the CI part. I'm using GitHub Actions because it's integrated directly into the repo. I need to create a YAML file in .github/workflows/ci.yml. This file tells GitHub: "Every time I push code, spin up a Linux VM, install a compiler, run CMake, and tell me if the tests passed."

name: C++ CI
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Dependencies
        run: sudo apt-get update && sudo apt-get install -y build-essential cmake
      - name: Configure CMake
        run: cmake -S . -B build
      - name: Build
        run: cmake --build build
      - name: Test
        run: ./build/tests

I'll push this to the cloud, expecting a satisfying green checkmark. But here's where I usually trip up—and it happened this time too.

Wait, why did the build fail?

I pushed the code, and within a minute, I got a red "X". The logs showed a bunch of errors like 'std::sqrt' is not a member of 'std' or issues with C++ standard library features I was using. I looked at my local machine and thought, "What are you talking about? It compiles perfectly here!"

The mistake? I forgot that my local machine is using GCC 12, but the GitHub runner's default environment might be defaulting to an older C++ standard (like C++11 or C++14) if I don't explicitly tell CMake which one I want. I was using a few C++17 features in my actual library that I'd added earlier, but I hadn't locked down the version in the build script.

I fixed it by adding one line to my CMakeLists.txt:

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

I pushed that change, and the pipeline turned green. It's a small detail, but it's a classic example of why CI is valuable: it catches the environment gaps that you are blind to on your own workstation.

The value of the green checkmark

Now, every time I make a change to Vector2D, I don't have to manually run the tests and hope I didn't break something. I just push. If the CI fails, I know exactly which commit caused the regression. This gives you the confidence to refactor your code aggressively, knowing that the "robot" is watching your back.




📋 Practical Task

Implementing a Multi-Compiler CI Matrix

In the lesson, we only tested our MathUtility on a single Linux runner. In the real world, C++ code needs to be portable. Your task is to modify the ci.yml workflow file to implement a build matrix.

Update the workflow so that it runs the build and test sequence simultaneously on three different operating systems: ubuntu-latest, windows-latest, and macos-latest. You will need to use the strategy: matrix key in your YAML configuration. Ensure that the "Test" step is updated to handle the fact that Windows executables end in .exe (hint: you can use cmake --build build && cd build && ctest or a similar platform-agnostic command if you have configured CTest in your CMake file).

Rating
0 0

There are no comments for now.

to be the first to leave a comment.