DevOps

GitHub Actions: первый workflow для сборки Qt-проекта

1 просмотров

Введение

Ранее мы рассмотрели минимальный концептуальный пример GitHub Actions — в этой статье разберём этот конкретный CI-сервис подробнее: структуру workflow-файлов, использование готовых, опубликованных действий (actions) сообщества, и кеширование зависимостей для ускорения повторных запусков.

Концепция

GitHub Actions определяет автоматизацию через YAML-файлы в директории .github/workflows/ репозитория — каждый workflow состоит из одной или нескольких «задач» (jobs), каждая выполняющаяся на отдельной виртуальной машине (runner) и состоящая из последовательности шагов (steps), включающих как простые shell-команды, так и переиспользуемые, опубликованные сообществом «действия» (actions), решающие типичные задачи (установка Qt, кеширование зависимостей) без необходимости писать соответствующую логику с нуля.

Пример кода

# .github/workflows/build.yml — более полный пример с готовым action для установки Qt
name: Build and Test

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Установка Qt через готовое действие
        uses: jurplel/install-qt-action@v3
        with:
          version: '6.6.0'
          modules: 'qtcharts qtnetworkauth'

      - name: Кеширование зависимостей vcpkg
        uses: actions/cache@v4
        with:
          path: ~/vcpkg/installed
          key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}
          # ключ кеша включает хеш файла зависимостей — кеш автоматически
          # становится недействительным при изменении списка зависимостей

      - name: Конфигурация и сборка
        run: |
          cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
          cmake --build build --config Release

      - name: Запуск тестов
        run: ctest --test-dir build --output-on-failure
# Матричная сборка — один workflow, выполняемый параллельно для нескольких комбинаций
jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        qt-version: ['6.5.0', '6.6.0']
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: jurplel/install-qt-action@v3
        with:
          version: ${{ matrix.qt-version }}
      # ... остальные шаги, общие для всех комбинаций матрицы ...

Пояснения к коду

uses: jurplel/install-qt-action@v3 — пример использования готового, опубликованного сообществом действия, абстрагирующего достаточно сложный процесс установки конкретной версии Qt с нужными модулями на разных операционных системах, что значительно проще и надёжнее самостоятельной реализации этой логики через серию shell-команд для каждой ОС отдельно. actions/cache с ключом, включающим hashFiles('vcpkg.json'), показывает практичное кеширование — повторная установка/сборка одних и тех же, не изменившихся зависимостей при каждом запуске CI значительно замедляет процесс, и кеш, автоматически инвалидируемый при изменении файла зависимостей (но не при каждом изменении кода самого приложения), даёт заметное ускорение повторных запусков без риска использования устаревшего кеша после реального изменения зависимостей. Матричная сборка (strategy.matrix) показывает декларативный способ выполнить один и тот же workflow параллельно для множества комбинаций параметров (ОС, версия Qt) без дублирования всей конфигурации workflow для каждой отдельной комбинации.

Подводные камни

  • Использование версии действия по плавающему тегу (@latest или @v3 без более точной фиксации, если действие часто обновляется с возможными breaking changes даже в рамках одной мажорной версии) без понимания риска — аналогично подводному камню Docker-образов, использование сторонних действий без достаточной фиксации версии создаёт риск неожиданного изменения поведения CI при обновлении используемого действия его авторами, и для критичных пайплайнов стоит рассмотреть фиксацию на конкретном коммите/тег действия, а не на широком, потенциально часто меняющемся диапазоне версий.
  • Чрезмерно широкая матрица сборки, тестирующая избыточное количество комбинаций параметров без реальной необходимости — каждая дополнительная комбинация в матрице (статья) пропорционально увеличивает общее время и (для платных CI-минут) стоимость выполнения всего workflow, и матрица должна охватывать реально значимые, поддерживаемые комбинации, а не все теоретически возможные сочетания параметров без разбора их практической значимости.
  • Отсутствие достаточно специфичного ключа кеша, приводящее либо к избыточно частой инвалидации кеша (ключ слишком специфичен, инвалидируется при изменениях, не влияющих реально на содержимое кеша), либо, что хуже, к использованию устаревшего, более не актуального кеша (ключ недостаточно специфичен, не реагирует на реальное изменение зависимостей) — правильный выбор того, что включать в ключ кеша, требует понимания, какие именно изменения реально должны инвалидировать конкретный кеш, и тестирования этого поведения, а не интуитивного, непроверенного выбора.
  • Игнорирование pull_request как отдельного, важного триггера workflow (настройка workflow только для push в основную ветку) — без запуска CI непосредственно для pull request (до его слияния в основную ветку) проблемы обнаруживаются только после уже произошедшего слияния, что противоречит самому смыслу раннего обнаружения проблем через CI и защищённых веток, требующих прохождения CI именно как условия для самого слияния, а не после него.