Использование GitHub Actions с C++ и CMake

от автора

Предлагаю вашему вниманию перевод статьи «Using GitHub Actions with C++ and CMake» от Cristian Adam, написанной около трех лет назад. За это время в GitHub Actions появилось много улучшений и некоторые приемы в статье могут показаться велосипедостроением. Тем не менее, это остается хорошим вводным обзором.


В этом посте я хочу показать файл конфигурации GitHub Actions для проекта C++, использующего CMake.

GitHub Actions это предоставляемая GitHub инфраструктура CI/CD. Сейчас GitHub Actions предлагает следующие виртуальные машины (runners):

Виртуальное окружение

Имя рабочего процесса YAML

Windows Server 2022

windows-latest

Ubuntu 20.04

ubuntu-latest или ubuntu-20.04

Ubuntu 18.04

ubuntu-18.04

macOS Catalina 10.15

macos-latest

Каждая виртуальная машина имеет одинаковые доступные аппаратные ресурсы:

  • 2х ядерное CPU

  • 7 Гб оперативной памяти

  • 14 Гб на диске SSD

Каждое задание рабочего процесса может выполняться до 6 часов.

К сожалению, когда я включил GitHub Actions в проекте C++, мне предложили такой рабочий процесс:

./configure make make check make distcheck 

Это немного не то, что можно использовать с CMake.

Hello World

Я хочу собрать традиционное тестовое приложение C++:

#include <iostream>  int main() {   std::cout << "Hello world\n"; } 

Со следующим проектом CMake:

cmake_minimum_required(VERSION 3.16)  project(main)  add_executable(main main.cpp)  install(TARGETS main)  enable_testing() add_test(NAME main COMMAND main) 

TL;DR смотрите проект на GitHub.

Build Matrix

Я начал со следующей матрицы сборки:

name: CMake Build Matrix  on: [push]  jobs:   build:     name: ${ { matrix.config.name } }     runs-on: ${ { matrix.config.os } }     strategy:       fail-fast: false       matrix:         config:         - {             name: "Windows Latest MSVC", artifact: "Windows-MSVC.tar.xz",             os: windows-latest,             build_type: "Release", cc: "cl", cxx: "cl",             environment_script: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat"           }         - {             name: "Windows Latest MinGW", artifact: "Windows-MinGW.tar.xz",             os: windows-latest,             build_type: "Release", cc: "gcc", cxx: "g++"           }         - {             name: "Ubuntu Latest GCC", artifact: "Linux.tar.xz",             os: ubuntu-latest,             build_type: "Release", cc: "gcc", cxx: "g++"           }         - {             name: "macOS Latest Clang", artifact: "macOS.tar.xz",             os: macos-latest,             build_type: "Release", cc: "clang", cxx: "clang++"           } 

Свежие CMake и Ninja

На странице установленного ПО виртуальных машин мы видим, что CMake есть везде, но в разных версиях:

Виртуальное окружение

Версия CMake

Windows Server 2019

3.16.0

Ubuntu 18.04

3.12.4

macOS Catalina 10.15

3.15.5

Это значит, что нужно будет ограничить минимальную версию CMake до 3.12 или обновить CMake.

CMake 3.16 поддерживает прекомпиляцию заголовков и Unity Builds, которые помогают сократить время сборки.

Поскольку у CMake и Ninja есть репозитории на GitHub, я решил скачать нужные релизы с GitHub.

Для написания скрипта я использовал CMake, потому что виртуальные машины по умолчанию используют свойственный им язык скриптов (bash для Linux и powershell для Windows). CMake умеет выполнять процессы, загружать файлы, извлекать архивы и делать еще много полезных вещей.

- name: Download Ninja and CMake   id: cmake_and_ninja   shell: cmake -P {0}   run: |     set(ninja_version "1.9.0")     set(cmake_version "3.16.2")      message(STATUS "Using host CMake version: ${CMAKE_VERSION}")      if ("${ { runner.os } }" STREQUAL "Windows")       set(ninja_suffix "win.zip")       set(cmake_suffix "win64-x64.zip")       set(cmake_dir "cmake-${cmake_version}-win64-x64/bin")     elseif ("${ { runner.os } }" STREQUAL "Linux")       set(ninja_suffix "linux.zip")       set(cmake_suffix "Linux-x86_64.tar.gz")       set(cmake_dir "cmake-${cmake_version}-Linux-x86_64/bin")     elseif ("${ { runner.os } }" STREQUAL "macOS")       set(ninja_suffix "mac.zip")       set(cmake_suffix "Darwin-x86_64.tar.gz")       set(cmake_dir "cmake-${cmake_version}-Darwin-x86_64/CMake.app/Contents/bin")     endif()      set(ninja_url "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-${ninja_suffix}")     file(DOWNLOAD "${ninja_url}" ./ninja.zip SHOW_PROGRESS)     execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ninja.zip)      set(cmake_url "https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-${cmake_suffix}")     file(DOWNLOAD "${cmake_url}" ./cmake.zip SHOW_PROGRESS)     execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./cmake.zip)      # Save the path for other steps     file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/${cmake_dir}" cmake_dir)     message("::set-output name=cmake_dir::${cmake_dir}")      if (NOT "${ { runner.os } }" STREQUAL "Windows")       execute_process(         COMMAND chmod +x ninja         COMMAND chmod +x ${cmake_dir}/cmake       )     endif() 

Шаг настройки

Теперь, когда у меня есть CMake и Ninja, все, что мне нужно сделать, это настроить проект таким образом:

- name: Configure   shell: cmake -P {0}   run: |     set(ENV{CC} ${ { matrix.config.cc } })     set(ENV{CXX} ${ { matrix.config.cxx } })      if ("${ { runner.os } }" STREQUAL "Windows" AND NOT "x${ { matrix.config.environment_script } }" STREQUAL "x")       execute_process(         COMMAND "${ { matrix.config.environment_script } }" && set         OUTPUT_FILE environment_script_output.txt       )       file(STRINGS environment_script_output.txt output_lines)       foreach(line IN LISTS output_lines)         if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$")           set(ENV{${CMAKE_MATCH_1} } "${CMAKE_MATCH_2}")         endif()       endforeach()     endif()      file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/ninja" ninja_program)      execute_process(       COMMAND ${ { steps.cmake_and_ninja.outputs.cmake_dir } }/cmake         -S .         -B build         -D CMAKE_BUILD_TYPE=${ { matrix.config.build_type } }         -G Ninja         -D CMAKE_MAKE_PROGRAM=${ninja_program}       RESULT_VARIABLE result     )     if (NOT result EQUAL 0)       message(FATAL_ERROR "Bad exit status")     endif() 

Я установил переменные окружения CC и CXX, а для MSVC мне пришлось выполнить скрипт vcvars64.bat, получить все переменные окружения и установить их для выполняющегося скрипта CMake.

Шаг сборки

Шаг сборки включает в себя запуск CMake с параметром --build:

- name: Build   shell: cmake -P {0}   run: |     set(ENV{NINJA_STATUS} "[%f/%t %o/sec] ")      if ("${ { runner.os } }" STREQUAL "Windows" AND NOT "x${ { matrix.config.environment_script } }" STREQUAL "x")       file(STRINGS environment_script_output.txt output_lines)       foreach(line IN LISTS output_lines)         if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$")           set(ENV{${CMAKE_MATCH_1} } "${CMAKE_MATCH_2}")         endif()       endforeach()     endif()      execute_process(       COMMAND ${ { steps.cmake_and_ninja.outputs.cmake_dir } }/cmake --build build       RESULT_VARIABLE result     )     if (NOT result EQUAL 0)       message(FATAL_ERROR "Bad exit status")     endif() 

Что бы увидеть скорость компиляции на разном виртуальном окружении, я установил переменную NINJA_STATUS.

Для переменных MSVC я использовал скрипт environment_script_output.txt, полученный на шаге настройки.

Шаг запуска тестов

На этом шаге вызывается ctest с передачей числа ядер процессора через аргумент -j:

- name: Run tests   shell: cmake -P {0}   run: |     include(ProcessorCount)     ProcessorCount(N)      execute_process(       COMMAND ${ { steps.cmake_and_ninja.outputs.cmake_dir } }/ctest -j ${N}       WORKING_DIRECTORY build       RESULT_VARIABLE result     )     if (NOT result EQUAL 0)       message(FATAL_ERROR "Running tests failed!")     endif() 

Шаги установки, упаковки и загрузки

Эти шаги включают запуск CMake с --install, последующий вызов CMake для создания архива tar.xz и загрузку архива как артефакта сборки.

- name: Install Strip   run: ${ { steps.cmake_and_ninja.outputs.cmake_dir } }/cmake --install build --prefix instdir --strip  - name: Pack   working-directory: instdir   run: ${ { steps.cmake_and_ninja.outputs.cmake_dir } }/cmake -E tar cJfv ../${ { matrix.config.artifact } } .  - name: Upload   uses: actions/upload-artifact@v1   with:     path: ./${ { matrix.config.artifact } }     name: ${ { matrix.config.artifact } } 

Я не стал использовать CMake в качестве языка сценариев для простых вызовов CMake с параметрами, оболочки по умолчанию прекрасно с этим справляются.

Обработка релизов

Когда вы помечаете релиз в git, вы также хотите, чтобы артефакты сборки прикрепились к релизу:

git tag -a v1.0.0 -m "Release v1.0.0" git push origin v1.0.0 

Ниже приведён код для этого, который сработает, если git refpath содержит tags/v:

release:   if: contains(github.ref, 'tags/v')   runs-on: ubuntu-latest   needs: build    steps:   - name: Create Release     id: create_release     uses: actions/create-release@v1.0.0     env:       GITHUB_TOKEN: ${ { secrets.GITHUB_TOKEN } }     with:       tag_name: ${ { github.ref } }       release_name: Release ${ { github.ref } }       draft: false       prerelease: false    - name: Store Release url     run: |       echo "${ { steps.create_release.outputs.upload_url } }" > ./upload_url    - uses: actions/upload-artifact@v1     with:       path: ./upload_url       name: upload_url  publish:   if: contains(github.ref, 'tags/v')   name: ${ { matrix.config.name } }   runs-on: ${ { matrix.config.os } }   strategy:     fail-fast: false     matrix:       config:       - {           name: "Windows Latest MSVC", artifact: "Windows-MSVC.tar.xz",           os: ubuntu-latest         }       - {           name: "Windows Latest MinGW", artifact: "Windows-MinGW.tar.xz",           os: ubuntu-latest         }       - {           name: "Ubuntu Latest GCC", artifact: "Linux.tar.xz",           os: ubuntu-latest         }       - {           name: "macOS Latest Clang", artifact: "macOS.tar.xz",           os: ubuntu-latest         }   needs: release    steps:   - name: Download artifact     uses: actions/download-artifact@v1     with:       name: ${ { matrix.config.artifact } }       path: ./    - name: Download URL     uses: actions/download-artifact@v1     with:       name: upload_url       path: ./   - id: set_upload_url     run: |       upload_url=`cat ./upload_url`       echo ::set-output name=upload_url::$upload_url    - name: Upload to Release     id: upload_to_release     uses: actions/upload-release-asset@v1.0.1     env:       GITHUB_TOKEN: ${ { secrets.GITHUB_TOKEN } }     with:       upload_url: ${ { steps.set_upload_url.outputs.upload_url } }       asset_path: ./${ { matrix.config.artifact } }       asset_name: ${ { matrix.config.artifact } }       asset_content_type: application/x-gtar 

Это выглядит сложным, но это необходимо, так как actions/create-release можно вызвать однократно, иначе это действие закончится ошибкой. Это обсуждается в issue #14 и issue #27.

Несмотря на то, что вы можете использовать рабочий процесс до 6 часов, токен secrets.GITHUB_TOKEN действителен один час. Вы можете создать личный токен или загрузить артефакты в релиз вручную. Подробности в обсуждении сообщества GitHub.

Заключение

Включить GitHub Actions в вашем проекте на CMake становится проще, если создать файл .github/workflows/build_cmake.yml с содержимым из build_cmake.yml.

Вы можете посмотреть GitHub Actions в проекте Кристиана Адама Hello World GitHub.

Оригинальный текст опубликован на странице под лицензией CC BY 4.0.


ссылка на оригинал статьи https://habr.com/ru/post/682458/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *