Предлагаю вашему вниманию перевод статьи "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.
Комментарии (6)
rat1
15.08.2022 11:23+1Статья очень старая. На github уже давно есть шаблоны для cmake и сам cmake.
RussianWarShip Автор
16.08.2022 15:10Статья не столько о наличии Cmake, сколько о возможных способах получить больше, чем дает GitHub Actions по умолчанию. И я надеялся, что будет дискуссия – оставлять ли портянки из Cmake в .yml или лучше вынести их в отдельные Actions.
IvaYan
Из всей статьи я так и не понял, зачем тут Ninja. При том что мы уже используем CMake. Ну и зачем переводить пост трёхлетней давности, если даже переводчик признает что с тех пор что-то могло поменяться. Кстати, что именно?
RussianWarShip Автор
Поменялись версии операционных систем, доступных в GitHub Actions. Версия Cmake теперь минимум 23, а не 16. В маркете появилось много удобных дополнений, в частности для установки Qt.
Cmake сам не занимается сборкой, по умолчанию это делает make или nmake. С Ninja Кристиан получил одну и ту же сборочную систему на трех разных операционных системах.
IvaYan
А как же cmake --build? Команда вполне абстрагирует сборочную систему на разных ОС.
Playa
Ninja - это быстро ¯\_(ツ)_/¯