Введение: Когда "собралось на моей машине" — это катастрофа
Каждый C++ разработчик хотя бы раз слышал фразу: "У меня всё работает, странно, что у тебя падает". Или сам её произносил. Компиляция шаблонов, линковка, особенности разных стандартов (C++17, C++20), платформозависимые макросы — C++ полон сюрпризов, которые всплывают при смене окружения.
CI/CD pipeline (Continuous Integration / Continuous Deployment) для C++ — это не роскошь, а необходимость. Он автоматически собирает проект, прогоняет тесты, проверяет качество кода и, при успехе, разворачивает приложение на сервере (или публикует библиотеку). В этой статье мы настроим полноценный пайплайн с нуля, используя GitHub Actions, CMake и C++. Вы получите готовую конфигурацию, которую можно адаптировать под свой проект.
1. Подготовка проекта: структура и система сборки
Прежде чем писать пайплайн, нужно привести проект в порядок. Мы будем использовать CMake — стандарт де-факто для современных C++ проектов. Наш пример будет представлять собой простую утилиту для обработки строк с модульными тестами на Google Test.
Структура репозитория
my_cpp_project/
├── .github/
│ └── workflows/
│ └── ci.yml # Файл пайплайна
├── src/
│ ├── main.cpp
│ └── string_utils.cpp
├── include/
│ └── string_utils.hpp
├── tests/
│ ├── CMakeLists.txt
│ └── test_string_utils.cpp
├── CMakeLists.txt # Корневой CMake
├── Dockerfile # Для финального деплоя
└── README.md
Корневой CMakeLists.txt
Собираем библиотеку и исполняемый файл. Обратите внимание на экспорт тестов:
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)
# Основная библиотека
add_library(string_utils STATIC
src/string_utils.cpp
include/string_utils.hpp
)
target_include_directories(string_utils PUBLIC include)
# Исполняемый файл
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE string_utils)
# Тесты (опционально, подключаются опцией)
option(BUILD_TESTS "Build unit tests" ON)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
Пример теста (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\n\r"), "");
}
Теперь, когда проект готов, переходим к главному — настройке CI/CD pipeline для C++.
2. GitHub Actions: пишем первый пайплайн
GitHub Actions — самый популярный инструмент для CI/CD в Open Source. Его конфигурация пишется на YAML и лежит в папке .github/workflows. Создадим файл ci.yml.
Базовая структура: запуск при пуше и 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
Это минимальный, но уже рабочий CI/CD pipeline для C++. Если тесты падают, пайплайн завершается с ошибкой, и вы получаете уведомление. Но мы пойдём дальше — добавим линтеры, статический анализ и многоплатформенную сборку.
3. Улучшаем пайплайн: линтеры, статика и многоплатформенность
3.1. Линтер: clang-format и clang-tidy
Качество кода в C++ критично. Добавим проверку форматирования и статический анализ:
- name: Check code formatting (clang-format)
run: |
find . -name '*.cpp' -o -name '*.hpp' | xargs clang-format -style=file --dry-run --Werror
continue-on-error: true # Не блокируем сборку, но показываем ошибки
- name: Static analysis (clang-tidy)
run: |
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
run-clang-tidy -p build -header-filter='.*'
3.2. Многоплатформенная сборка (matrix)
C++ приложения часто требуют совместимости с разными ОС. Используем стратегию matrix:
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
compiler: [g++, clang++]
exclude:
- os: macos-latest
compiler: g++ # На macOS чаще используют clang
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set compiler (Linux/macOS)
if: runner.os != 'Windows'
run: |
echo "CXX=${{ matrix.compiler }}" >> $GITHUB_ENV
echo "CC=${{ matrix.compiler }}" >> $GITHUB_ENV
- name: Install dependencies (Ubuntu)
if: runner.os == 'Linux'
run: sudo apt-get install -y cmake g++ libgtest-dev
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: brew install cmake googletest
- name: Configure and Build
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
Теперь ваш CI/CD pipeline для C++ прогоняет сборку на трёх ОС с двумя компиляторами. Это отличная страховка от платформозависимых багов.
4. CD: автоматический деплой C++ приложения
После успешной сборки и тестов логично развернуть приложение. Для C++ это часто означает сборку Docker-образа или публикацию бинарного артефакта. Рассмотрим оба варианта.
4.1. Сборка Docker-образа
Создадим простой Dockerfile для мультистадийной сборки:
# Stage 1: Build
FROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y cmake g++ libgtest-dev
WORK