CI/CD for C++ projects: setting up GitHub Actions
Modern C++ development requires not only writing quality code, but also setting up processes for its building, testing, and deployment. CI/CD (Continuous Integration / Continuous Deployment) is a practice that allows automating these processes, reducing the time between writing code and delivering it to users. In this article, we will look at how to set up CI/CD for C++ projects using GitHub Actions — a powerful and free tool built into GitHub.
GitHub Actions allows you to create automatic workflows that are triggered by certain events: push to a repository, creating a pull request, release, and others. For C++ projects, this is especially useful as it helps ensure that the code builds on different platforms and contains no regressions.
Basics of GitHub Actions for C++
GitHub Actions uses YAML files to describe workflows. Each workflow consists of one or more jobs that run on virtual machines (runners). For C++ projects, Ubuntu, Windows, and macOS runners are typically used. Let's look at the basic structure of a workflow file.
name: C++ CI
on: push: branches: [ main, develop ] pull_request: branches: [ main ]
jobs: build: runs-on: ubuntu-latest
steps: - uses: actions/checkout@v4
- name: Install dependencies run: | sudo apt-get update sudo apt-get install -y cmake g++ libboost-all-dev
- name: Configure CMake run: cmake -B build -DCMAKE_BUILD_TYPE=Release
- name: Build run: cmake --build build --config Release
- name: Test run: cd build && ctest --output-on-failureThis workflow runs on push to the main and develop branches, as well as when creating a pull request to main. It installs dependencies (CMake, g++, Boost), configures the project, builds it, and runs tests.
Multi-platform build for C++ projects
One of the main advantages of CI/CD for C++ is the ability to automatically check the build on multiple platforms. This is critically important, as C++ code can behave differently on different OSes and compilers. GitHub Actions makes it easy to organize a build matrix.
jobs: build: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] compiler: [g++, clang++] include: - os: ubuntu-latest compiler: g++ cmake_flags: -DCMAKE_CXX_COMPILER=g++-11 - os: ubuntu-latest compiler: clang++ cmake_flags: -DCMAKE_CXX_COMPILER=clang++-14 - os: windows-latest compiler: msvc cmake_flags: -DCMAKE_CXX_COMPILER=cl - os: macos-latest compiler: clang++ cmake_flags: -DCMAKE_CXX_COMPILER=clang++
runs-on: ${{ matrix.os }}
steps: - uses: actions/checkout@v4
- name: Install dependencies (Ubuntu) if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y cmake g++-11 clang-14 libboost-all-dev
- name: Install dependencies (macOS) if: matrix.os == 'macos-latest' run: | brew update brew install cmake boost
- name: Install dependencies (Windows) if: matrix.os == 'windows-latest' run: | choco install cmake boost-msvc-14.3
- name: Configure CMake run: cmake -B build ${{ matrix.cmake_flags }} -DCMAKE_BUILD_TYPE=Release
- name: Build run: cmake --build build --config Release
- name: Test run: | if [ "${{ runner.os }}" == "Windows" ]; then cd build && ctest -C Release --output-on-failure else cd build && ctest --output-on-failure fiIn this example