Utilisation de GitHub Actions avec C++ et CMake

Dans cet article, je vais fournir un fichier yaml de configuration GitHub Actions pour les projets C++ utilisant CMake.

GitHub Actions est une infrastructure CI/CD fournie par GitHub. GitHub Actions Propose actuellement les machines virtuelles suivantes (exécuteurs) :

Environnement virtuelÉtiquette de flux de travail YAML
Windows Server 2019windows-latest
Ubuntu 18.04ubuntu-latest ou ubuntu-18.04
Ubuntu 16.04ubuntu-16.04
macOS Catalina 10.15macOS-latest

Chaque machine virtuelle dispose des mêmes ressources matérielles :

  • Processeur à 2 cœurs
  • 7 Go de mémoire RAM
  • 14 Go d’espace disque SSD

Chaque tâche d’un workflow peut s’exécuter jusqu’à 6 heures d’exécution.

Malheureusement, lorsque j’ai activé GitHub Actions sur un projet C++, ce flux de travail m’a été présenté :

./configure
make
make check
make distcheck

Ce n’est pas quelque chose que vous pouvez utiliser avec CMake cependant sourire

Hello world 

Cistian adam a construit le programme C++ hello world suivant :

#include <iostream>

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

Avec le projet CMake suivant :

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 voir le projet sur GitHub.

Matrice de construction

Cistian adam a commencé avec la matrice de construction suivante :

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++"
          }

Latest CMake and Ninja

In the software installed on the runners page we can see that CMake is installed on all runners, but with different versions:

Virtual environmentCMake Version
Windows Server 20193.16.0
Ubuntu 18.043.12.4
macOS Catalina 10.153.15.5

This would mean that one would have to limit the minimum CMake version to 3.12, or upgrade CMake.

CMake 3.16 comes with support for Precompile Headers and Unity Builds, which help reducing build times.

Since CMake and Ninja have GitHub Releases, I decided to download those GitHub releases. smile

I used CMake as a scripting language, since the default scripting language for runners is different (bash, and powershell). CMake can execute processes, download files, extract archives.

- 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()

Configure step

Now that I have CMake and Ninja, all I have to do is configure the project like this:

- 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()

Cistian adam a définit les variables d’environnement et pour MSVC, il a dû exécuter le script, pour obtenir toutes les variables d’environnement et les définir pour le script d’exécution de CMake.CCCXXvcvars64.bat

Étape de construction

L’étape de génération implique l’exécution du paramètre CMake avec :--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()

Cistian adam a définit la variable, pour voir à quelle vitesse la compilation est dans les coureurs respectifs.NINJA_STATUS

Pour MSVC,il a réutilisé le script de l’étape Configurer.environment_script_output.txt

Étape Exécuter les tests

Cette étape appelle avec le nombre de cœurs passés en argument :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()

Étapes d’installation, d’emballage et de téléchargement

Cette étape implique l’exécution de CMake avec , puis la création d’une archive avec CMake, et en le téléchargeant en tant qu’artefact de build.--installtar.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 } }

il n’a pas utilisé CMake comme langage de script, car cela implique simplement d’appeler CMake avec des paramètres, et le les shells par défaut peuvent gérer cela sourire

Gestion des rejets

Lorsque vous marquez une version dans git, vous souhaitez également que les artefacts de build soient promus en tant que versions :

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

Le code pour ce faire est ci-dessous, est déclenché si le refpath git contient :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

This looks complicated, but it’s needed since needs to be called only once, otherwise it will fail. See issue #14issue #27 for more information.actions/create-release

Même si vous pouvez utiliser un flux de travail pendant 6 heures, le expire dans une heure. Vous pouvez soit créer un jeton personnel, soit Téléchargez les artefacts manuellement dans la version. Voir cette communauté GitHub pour plus d’informations.secrets.GITHUB_TOKEN

Fermeture

L’activation de GitHub Actions sur votre projet CMake est aussi simple que de créer un fichier avec le contenu de .github/workflows/build_cmake.ymlbuild_cmake.yml.

Mohamed SAKHRI
Mohamed SAKHRI

Je suis le créateur et rédacteur en chef de Tech To Geek, via ce petit blog, je partage avec vous ma passion pour la technologie. Mon expertise couvre divers systèmes d’exploitation, notamment Windows, Linux, macOS et Android, en mettant l’accent sur l'ajout de guides pratiques .

Articles: 1751

Mises à jour de la newsletter

Saisissez votre adresse e-mail ci-dessous et abonnez-vous à notre newsletter

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *