C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
232: Continuous Integration for C++ Projects
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).
There are no comments for now.