CI/CD for C++: Setting Up a Pipeline from Scratch for a Real Project

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: When "It Works on My Machine" is a Disaster



Every C++ developer has heard the phrase at least once: "It works for me, strange that it crashes for you." Or they've said it themselves. Template compilation, linking, quirks of different standards (C++17, C++20), platform-dependent macros — C++ is full of surprises that surface when the environment changes.



A CI/CD pipeline (Continuous Integration / Continuous Deployment) for C++ is not a luxury, but a necessity. It automatically builds the project, runs tests, checks code quality, and, upon success, deploys the application to a server (or publishes a library). In this article, we will set up a full-fledged pipeline from scratch using GitHub Actions, CMake, and C++. You will get a ready-made configuration that you can adapt to your own project.



1. Project Preparation: Structure and Build System



Before writing the pipeline, you need to get the project in order. We will use CMake — the de facto standard for modern C++ projects. Our example will be a simple string processing utility with unit tests using Google Test.



Repository Structure

my_cpp_project/├── .github/│   └── workflows/│       └── ci.yml          # Pipeline file├── src/│   ├── main.cpp│   └── string_utils.cpp├── include/│   └── string_utils.hpp├── tests/│   ├── CMakeLists.txt│   └── test_string_utils.cpp├── CMakeLists.txt           # Root CMake├── Dockerfile               # For final deployment└── README.md


Root CMakeLists.txt

We build the library and the executable file. Note the export of tests:

cmake_minimum_required(VERSION 3.20)project(MyStringUtils VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Main libraryadd_library(string_utils STATIC src/string_utils.cpp include/string_utils.hpp)target_include_directories(string_utils PUBLIC include)

# Executable fileadd_executable(my_app src/main.cpp)target_link_libraries(my_app PRIVATE string_utils)

# Tests (optional, enabled via option)option(BUILD_TESTS "Build unit tests" ON)if(BUILD_TESTS) enable_testing() add_subdirectory(tests)endif()


Example Test (tests/test_string_utils.cpp)

#include <gtest/gtest.h>#include "string_utils.hpp"

TEST(StringUtilsTest, ToUpper) { EXPECT_EQ(to_upper("hello"), "HELLO"); EXPECT_EQ(to_upper("C++20"), "C++20");}

TEST(StringUtilsTest, Trim) { EXPECT_EQ(trim(" spaced "), "spaced"); EXPECT_EQ(trim("\\t\\r"), "");}


Now that the project is ready, let's move on to the main part — setting up the CI/CD pipeline for C++.



2. GitHub Actions: Writing Your First Pipeline



GitHub Actions is the most popular tool for CI/CD in Open Source. Its configuration is written in YAML and resides in the .github/workflows folder. Let's create the ci.yml file.



Basic Structure: Trigger on Push and PR

name: C++ CI/CD Pipeline

on: push: branches: [ main, develop ] pull_request: branches: [ main ]

jobs: build-and-test: runs-on: ubuntu-latest

steps: - name: Checkout code uses: actions/checkout@v4

- name: Install dependencies run: | sudo apt-get update sudo apt-get install -y cmake g++ libgtest-dev

- name: Configure CMake run: cmake -B build -DCMAKE_BUILD_TYPE=Release

- name: Build run: cmake --build build --parallel $(nproc)

- name: Run tests run: cd build && ctest --output-on-failure


This is a minimal but already functional CI/CD pipeline for C++. If the tests fail, the pipeline ends with an error, and you receive a notification. But we will go fu

Blogs

Book Recommendations