Github Actions, C++ mit Boost und cmake, fast 50 % Beschleunigung mit Caching

Github Actions, C++ mit Boost und cmake, fast 50 % Beschleunigung mit Caching

Für ein persönliches Projekt verwende ich Github für das Quellcode-Hosting und Github Actions als automatisiertes Build- und Test-Tool. Github Actions kompiliert meinen cmake projectand führt alle Komponententests bei jedem Commit aus. Es speichert auch ein Erstellungsartefakt, das tatsächlich kompilierte Programm. Durch die Verwendung von Abhängigkeits-Caching und Make-Flags habe ich den Build-Prozess um 43 % beschleunigt, indem ich apt install libboost1.65-dev zwischengespeichert habe und geben Sie cmake ein ein -j2 Marke machen.

Dieser Artikel zeigt mein einfaches Setup zum Kompilieren eines C++-Projekts mit cmake und Boost auf Github-Aktionen. Nach der Kompilierung führt es alle Tests aus und lädt die kompilierte Binärdatei zum Download hoch. Für mein Ein-Mann-Projekt ist es übertrieben, aber wenn die Zusammenarbeit oder Builds auf Ihrem eigenen Computer lange dauern, ist es großartig, ein automatisiertes Build-/Testsystem zu haben.

Beachten Sie, dass sich die Erstellungszeit für ein kleines C++-Projekt von 1 Minute 48 Sekunden auf 47 Sekunden verringert hat. Die prozentuale Beschleunigung ist groß, aber wahrscheinlich finden Sie den Titel ein bisschen Clickbaity. Das Hauptaugenmerk dieses Artikels liegt darauf, zu zeigen, wie man ein einfaches C++-Projekt mit Boost unter Verwendung von Github-Aktionen erstellt.

Es zeigt auch, wie ein apt install zwischengespeichert wird und wie man cmake bereitstellt mit MAKEFLAGS um die zwei Kerne zu nutzen, die die kostenlose virtuelle Maschine von github builder hat.

Bei der Arbeit verwenden wir dafür Gitlab CI und es verkürzt die Kompilierungszeit des gesamten Projekts von 2 Stunden auf 20 Minuten, da riesige Build-Server mit Gitlab-Runnern laufen. Ein paar verschiedene Binärdateien werden für verschiedene Armarchitekturen erstellt, die Testsuite wird ausgeführt, Doxygen-Dokumente werden generiert, Codestilprüfungen werden durchgeführt und statische Analysen werden mit Sonarqube durchgeführt, alles aus einer Quelle. Mit einem Team von Entwicklern führt dies alles zu einer enormen Beschleunigung des Prozesses, Code zu überprüfen und bestimmte Dinge nicht zu vergessen.

Ich habe keinen eigenen Gitlab-Server (mehr) am Laufen, aber mir ist aufgefallen, dass Github auch eine Funktion wie Gitlab ci hat, aber sie nennen es Github-Aktionen, und es ist kostenlos für öffentliche Projekte, für private Projekte gibt es eine begrenzte Zeit, Aber 2000 Minuten sind genug für mich.

Einfaches cmake C++-Projekt mit Boost auf Github-Aktionen

Wenn Sie Ihren Quellcode auf Github hosten, können Sie Github Actions verwenden. Die meisten meiner persönlichen Projekte folgen dieser einfachen CMake-Struktur, die sich gut in meine bevorzugte IDE, CLion von JetBrains, integrieren lässt. Die Struktur hat auch Einheitentests mit GoogleTest.

Informationen zur Boost-Integration finden Sie in meinem anderen Artikel zur Integration in das Projekt-Setup. Unter Ubuntu müssen Sie auch die Entwicklungsbibliotheken installieren:

apt install libboost-dev-all

Auf der virtuellen Github-Linux-Maschine, die das Projekt erstellt, sind die meisten C++-Entwicklungstools installiert (wie gcc und die build-essential Paket), aber Boost fehlt. In der Datei, die Sie schreiben, die Ihre Erstellungsschritte angibt, können Sie auch sudo verwenden um Pakete über apt zu installieren , in unserem Fall boost .

Grundlegender Arbeitsablauf

Erstellen Sie im Stammordner Ihres Projekts einen Ordner für die Workflow-Dateien forgithub:

mkdir -p .github/workflows

Erstellen Sie in diesem Ordner einen .yml Datei für Ihren Workflow. Mein grundlegendes Beispiel zum Ausführen von cmake und mein Komponententest ist unten aufgeführt.

name: build and run tests
on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    # install dependencies
    - name: boost
      run: sudo apt-get update && sudo apt-get install -yq libboost1.65-dev
    # build project
    - name: mkdir
      run: mkdir build
    - name: cmake build
      run: cmake -Bbuild -H.
    - name: cmake make
      run: cmake --build build/ --target all
    # run tests
    - name: run test 1
      run: build/tst/Example1_tst
    - name: run test 2
      run: build/tst/Example2_tst

Wenn Sie committen und pushen, sollten Sie in der Lage sein, die Aktion auf Github nachzuschlagen:

Das war einfach, oder? Ein Remote-Server erstellt Ihr Programm und führt die Unittests aus. Wenn Sie dies auf Ihrer lokalen Workstation tun würden, wären die Schritte ungefähr so:

#build code
cd to/project/folder
cd build
cmake ..
make
# run tests
tst/Example1_tst
tst/Example2_tst

Apt-Installationsabhängigkeiten zwischenspeichern

In meinem Fall die apt update && apt install libboost-1.65-dev dauert fast 15 Sekunden. Wenn Sie mehr Pakete haben, dauert dies länger und wird auch jedes Mal ausgeführt, ändert sich aber fast nie. Also ein bisschen Zeit- und Ressourcenverschwendung.

Dieser Beitrag auf Stackoverflow enthält ein ausführliches Beispiel zum Caching von apt Schritte. Mein Beispiel ist eine vereinfachte Version. Ersetzen Sie diesen Schritt in Ihrer Workflow-Datei:

- name: boost
  run: sudo apt-get update && sudo apt-get install -yq libboost1.65-dev

Mit dem folgenden Codestück:

- name: Cache boost
  uses: actions/[email protected]
  id: cache-boost
  with:
    path: "~/boost"
    key: libboost1.65-dev
- name: Install boost
  env:
    CACHE_HIT: ${{steps.cache-boost.outputs.cache-hit}}
  run: |
    if [[ "$CACHE_HIT" == 'true' ]]; then
      sudo cp --force --recursive ~/boost/* /
    else
      sudo apt-get update && sudo apt-get install -yq libboost1.65-dev
      mkdir -p ~/boost
      for dep in libboost1.65-dev; do
          dpkg -L $dep | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/boost/
      done
    fi

Was dies im Grunde tut, ist, wenn Boost noch nicht installiert ist, es zu installieren und dann dpkg zu verwenden um alle neu installierten Dateien in einen Ordner zu kopieren. Beim nächsten Mal lädt die virtuelle Maschine diesen artifact herunter und extrahieren Sie es einfach auf / . Der Effekt ist derselbe, die Bibliotheken werden installiert, die Zeit dafür beträgt jedoch nur 1 Sekunde statt 15 Sekunden.

Wenn Sie eine neuere Version des Pakets installieren müssen, sagen Sie libboost-1.71-dev , ersetzen Sie den Paketnamen durch den neueren und Sie sind fertig.

Wenn Sie mehrere Pakete installieren müssen, stellen Sie sicher, dass es sich um die tatsächlichen Pakete handelt, nicht um ein Metapaket (ein Paket ohne Dateien, nur Abhängigkeiten). Metapakete haben keine zu kopierenden Dateien, daher schlagen die Schritte fehl. Sie können die Ubuntu- oder Debian-Paketseite verwenden, um zu überprüfen, ob libboost-dev beispielsweise ein Metapaket ist (10 kB Paketgröße, keine tatsächlichen Dateien), während libboost1.71-dev ein tatsächliches Paket ist. Größere Dateigröße und viele enthaltene Dateien.

Mit dieser ersten Verbesserung wird der nachfolgende Build schneller, insbesondere wenn Sie viele Abhängigkeiten installieren müssen. Eine weitere Optimierung, die wir vornehmen können, ist die Bereitstellung von makeflag um mehr Ressourcen während des Bauens zu verbrauchen.

makeflags für cmake bereitstellen

In einem cmake-Projekt können alle Build-Schritte mit cmake selbst durchgeführt werden, anstatt mit dem Build-System, für das cmake generiert (wie make/ninja), wenn Ihre cmake-Version 3.15 oder höher ist:

cd to/project/folder
cmake --build build/
sudo cmake --install build/

Kein separates make , der letzte cmake-Befehl umschließt das. Du kannst es auch einfach auf die altmodische Art machen:

cd to/project/folder/build
cmake ..
make all
sudo make install

Verwenden Sie den cmake Befehle funktionieren nicht nur für Makefiles , sondern auch für ninja oder jedes andere Build-System cmake erzeugen kann.

Aber in unserem Beispiel verwenden wir Makefiles und um die zwei Kerne zu verwenden, die die githubvirtual-Maschine hat (statt nur einem Kern), müssen wir ein Flag für make bereitstellen .

Wenn Sie es mit der Kommandozeile machen würden, würden Sie das tun:

make -j2 all

Wobei -j# ist die Anzahl der Kerne, die Sie zum Erstellen verwenden möchten. Jetzt können wir mit cmake kompliziertere Dinge in unserem CMakeLists.txt tun , aber das würde unser einfaches Beispiel unübersichtlich machen. Mit Github Actions können Sie Umgebungsvariablen und make festlegen kann den MAKEFLAGS verwenden Umgebungsvariable. Wenn wir das so einstellen, dass es -j2 enthält , sogar über cmake , wird die Fahne durchgereicht.

Ersetzen Sie in unserer YAML-Datei für GitHub-Aktionen den folgenden Schritt:

- name: cmake make
  run: cmake --build build/ --target all

Mit folgendem Code. Sie können auch nur die letzten beiden Zeilen hinzufügen, anstatt den ganzen Block zu ersetzen.

- name: cmake make
  run: cmake --build build/ --target all
  env:
    MAKEFLAGS: "-j2"

In meinem Fall beschleunigte die Verwendung von zwei Kernen den Build-Prozess um weitere 27 Sekunden. Wenn Ihr Projekt größer ist, wird die Verbesserung auch größer sein.

Build-Artefakte hochladen

Eine der anderen nützlichen Funktionen ist die Möglichkeit, bestimmte Dateien herunterzuladen, die erstellt wurden. Github nennt sie build artifacts und Sie können sie über die Webseite herunterladen:

Bei der Arbeit verwenden wir dies über Gitlab, um für einige verschiedene ARM-Architekturen zu kompilieren. Nicht jeder hat ein Crosscompiler-Setup, aber sie können einfach ihre frisch erstellte Binärdatei herunterladen und auf tatsächlicher Hardware ausführen. Die meisten unserer Tests werden mit Einheitentests automatisiert, aber es gibt Randfälle, zum Beispiel die Interaktion mit tatsächlicher Hardware (denken Sie an Ventile, Pumpen, Hochspannungsrelais).

Wenn Sie nicht crosskompilieren, ist es immer noch nützlich, es ermöglicht anderen Leuten, eine Binärdatei zu erhalten, ohne sie kompilieren zu müssen. Ein Tester könnte sich anmelden, die Binärdatei für seinen speziellen Feature-Zweig herunterladen und zum Testen verwenden.

Buildartefakte sind ebenfalls reproduzierbar. Sie können einen Build eines Zweigs von vor 6 Monaten auslösen und erhalten diese Binärdatei genauso makellos wie damals.

Fügen Sie Folgendes am Ende Ihrer YML-Datei hinzu. Die Pfade sind für unser Beispiel.

# upload artifact, example binary
- name: Upload Example binary
  uses: actions/upload-artifact@v1
  with:
    name: upload binary
    path: build/src/Example

Sie können damit verrückt werden, es mit Github-Releases für bestimmte Branches koppeln und mehr automatisieren, aber das ist für unseren Beispielfall nicht möglich.

Die endgültige Yaml-Datei

Die Yaml-Datei mit allen Verbesserungen ist unten aufgeführt:

name: build and run tests
on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    # install and cache dependencies
    - name: Cache boost
      uses: actions/[email protected]
      id: cache-boost
      with:
        path: "~/boost"
        key: libboost1.65-dev
    - name: Install boost
      env:
        CACHE_HIT: ${{steps.cache-boost.outputs.cache-hit}}
      run: |
        if [[ "$CACHE_HIT" == 'true' ]]; then
          sudo cp --force --recursive ~/boost/* /
        else
          sudo apt-get update && sudo apt-get install -yq libboost1.65-dev
          mkdir -p ~/boost
          for dep in libboost1.65-dev; do
              dpkg -L $dep | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/boost/
          done
        fi
    # build project
    - name: mkdir
      run: mkdir build
    - name: cmake build
      run: cmake -Bbuild -H.
    - name: cmake make
      run: cmake --build build/ --target all
      env:
        MAKEFLAGS: "-j2"
    # run tests
    - name: run test 1
      run: build/tst/Example1_tst
    - name: run test 2
      run: build/tst/Example2_tst
    # upload artifact, game binary
    - name: Upload Example binary
      uses: actions/upload-artifact@v1
      with:
        name: upload binary
        path: build/src/Example

Schlussfolgerung

In diesem Artikel wurde sowohl die automatische Build-Einrichtung eines C++ Project auf Githubactions, wie man Build-Artefakte hochlädt und zwei Verbesserungen, um einen solchen Build zu beschleunigen. In meinem Fall sind die Verbesserungen prozentual signifikant, aber nicht so beeindruckend, wenn man sich die tatsächlichen Zahlen ansieht. Bei größeren Projekten oder bei Abrechnung nach Laufzeit könnten die Verbesserungen einen größeren Effekt haben.


No