GitLab CI/CD für Android

Mit GitLab CI/CD kann eine Continuous-Integration- / Continuous-Delivery-Pipeline direkt innerhalb eines GitLab Repositories definiert und konfigurationslos an jedes beliebige Softwareprojekt angeflanscht werden. In diesem Workshop rekapitulieren wir die schrittweise Umsetzung unserer Build- und Release-Pipeline für ein bestehendes Android-Projekt. Dabei lernen wir GitLab CI/CD und die generelle Funktionsweise cloudbasierter CI/CD-Pipelines in der Praxis kennen.

Benötigte Software

GitLab CI/CD kann in jedem beliebigen GitLab Repository genutzt werden. Zum Durchführen der CI/CD-Schritte benötigst Du lediglich einen GitLab-Accountund einen lokal installierten Git-Client.

Um die einzelnen Schritte der Pipeline nicht nur auf dem GitLab-Server, sondern auch auf Deinem lokalen System ausführen und testen zu können, benötigst Du eine Installation des Android SDKs sowie des Build-Tools Gradle. Installationsanweisungen für alle Plattformen findest Du auf den jeweiligen Produktseiten.

1. GitLab CI/CD in ein bestehendes Android-Projekt einführen

Unser vorgegebenes Projekt repräsentiert eine Android App, die in Kotlin programmiert wurde. Zur Verwaltung der benötigten Abhängigkeiten sowie zum Ausführen aller relevanten Build Tasks wird Gradle verwendet. Build Tasks können beispielsweise das Bauen und Paketieren der Android-App oder das Ausführen definierter Unit- Tests, UI-Tests oder Code Quality Tools sein.

Das Projekt forken und klonen

Das GitLab Repository mit dem vorgegebenen Projekt befindet sich im Mayflower-GitLab. Über den auf der GitLab Web-Oberfläche angezeigten Button „Fork“ des angegebenen Repositories kann eine Kopie des Projekts in ein neues Repository Deines eigenen GitLab.Accounts überführt werden.

Anschließend steht das neu erstellte Repository zur Verfügung und Du kannst es auf Dein lokales System klonen. Erweiterungen am Projekt kannst Du nun jederzeit pushen. Mit Hilfe des Android Studios kann das Projekt geöffnet, gebaut und in einem Emulator oder auf einem physikalischen Gerät betrieben werden.

Relevante Gradle Tasks

Mit Gradle können vorgegebene und eigens konfigurierte Build Jobs im Projekt durchgeführt werden. Gradle verwendet die in den beiden Dateien build.gradle und app/build.gradle hinterlegte Projektkonfiguration. Hier sind alle erforderlichen Java-, Kotlin- und Android-Bibliotheken definiert und Anpassungen für zusätzliche Tools und Plugins hinterlegt.

Die gesamte Palette der in unserem Projekt zur Verfügung stehenden Gradle Tasks kann mit dem folgenden Befehl aufgelistet werden:

% ./gradlew tasks

Für unser Projekt sind in erster Linie die folgenden sechs Tasks relevant, die die beschriebenen Aufgaben ausführen:

Gradle TaskCLI AufrufBeschreibung
assemble./gradlew :app:assembleBauen der Anwendung
bundle./gradlew :app:bundlePaketieren der Anwendung
testDebug./gradlew :app:testDebugDurchführen aller Unit Tests
androidTestDebug./gradlew :app:androidTestDebugDurchführen aller UI-Tests
lint./gradlew :app:lintDurchführen des nativen Android Linters
detekt./gradlew :app:detektDurchführen des Kotlin Code-Style-Linters detekt

Die Installation und Aktualisierung aller von Gradle benötigten Abhängigkeiten erfolgt implizit bei jedem Aufruf des Gradle-Befehls. Daher gibt es im Gegensatz zu anderen Paketmanagern keinen eigenen gradle install oder gradle sync Befehl.

Die Anwendung lokal bauen

Mit dem Ausführen der beiden Tasks assemble und bundle können wir unsere App in den beiden Standard-BuildConfigs debug und release erstellen und paketieren. Lokal können wir das innerhalb unseres Projektordners mit den folgenden beiden Befehlen nachvollziehen:

% ./gradlew :app:assemble --info
% ./gradlew :app:bundle --info

Durch die Angabe der Option --info erhalten wir mehr Ausgaben zur Laufzeit von Gradle. Wir verwenden diese Option bei allen zukünftigen Aufrufen.

Die gebauten Applikationen befinden sich nach Abschluss der Gradle Tasks unterhalb app/build/outputs/bundle.

Warum der Gradle Wrapper?

Der Gradle Wrapper und das Projekt vorhandene Skript ./gradlew ist eine Institution von Gradle und führt alle gewünschten Gradle-Tasks mit der im Projekt fest spezifizierten Version 7.3.3 aus. Diese Einstellung ist in der Datei gradle/wrapper/gradle-wrapper.properties im Feld distributionUrl hinterlegt:

distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip

Damit ist sichergestellt, dass alle in unserem Projekt eingesetzten Gradle-Features und -Plugins mit der im Projekt hinterlegten Version funktionieren. Führen wir den Gradle Wrapper innerhalb unseres Projektordners aus, so läuft dieser immer in dieser festgelegten Version:

    % ./gradlew –version
 
------------------------------------------------------------
Gradle 7.3.3
------------------------------------------------------------
 
Build time:   2021-12-22 12:37:54 UTC
Revision:     6f556c80f945dc54b50e0be633da6c62dbe8dc71
 
Kotlin:       1.5.31
Groovy:       3.0.9
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
 
...

Die lokal installierte Version von Gradle kann von der im Projekt benötigten divergieren, was leicht zu Fehlern und Abstürzen der einzelnen Gradle Tasks führen kann. Daher verwenden wir in unserem Workshop bei allen Aufrufen den Gradle Wrapper anstelle des direkten Gradle-Befehls.

Neue CI/CD Pipeline mittels yml

Die Ausführung der für unser Projekt relevanten Tasks wollen wir nun automatisieren, indem wir sie schrittweise in eine neue CI/CD-Pipeline überführen. Hierfür benötigt GitLab CI/CD lediglich eine einzelne neue Datei – .gitlab-ci.yml – innerhalb unseres Projektverzeichnisses, in der alle benötigten Informationen über unsere gewünschte Pipeline hinterlegt werden:

image: openjdk:11-jdk
 
variables:
  # should match 'compileSdkVersion'
  ANDROID_COMPILE_SDK: "29"
  # should match 'buildToolsVersion'
  ANDROID_BUILD_TOOLS: "29.0.3"
  # pick cli version from https://developer.android.com/studio/index.html section 'CLI Tools Only'
  ANDROID_SDK_TOOLS: "6514223"
 
before_script:
  - apt-get --quiet update --yes
  - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
 
  # setup Android home for moving/exporting the downloaded sdk into it
  - export ANDROID_HOME="${PWD}/android-home"
  - install -d $ANDROID_HOME
  # install official Android SDK tools from official source
  - wget --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
  - pushd $ANDROID_HOME
  - unzip -d cmdline-tools cmdline-tools.zip
  - popd
  - export PATH=$PATH:${ANDROID_HOME}/cmdline-tools/tools/bin/
 
  # setup gradle home directory
  - export GRADLE_USER_HOME=${PWD}/.gradle
 
  # setup Android SDK manager
  - sdkmanager --version
  - yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses || true
  - sdkmanager --sdk_root=${ANDROID_HOME} "platforms;android-${ANDROID_COMPILE_SDK}"
  - sdkmanager --sdk_root=${ANDROID_HOME} "platform-tools"
  - sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;${ANDROID_BUILD_TOOLS}"
 
cache:
  key: ${CI_PROJECT_ID}
  paths:
    - .gradle/

Vorlagen für die erforderlichen und teilweise ausladend wirkenden Initialisierungsblöcke können über das GitLab CI/CD Template Repository beschafft werden.

Nach dem Committen und Pushen dieser Datei hat GitLab CI/CD die neu definierte Pipeline erkannt und die Inhalte der yml-Datei als korrekt validiert. Wir können den Lauf unserer Pipeline auf der Web-Oberfläche unter dem Menüpunkt “CI/CD“ einsehen. Initial wird uns hier der folgende Fehler angezeigt: jobs config should contain at least one visible job

Das hat den Grund, dass unsere Pipeline bisher noch keinen definierten Job ausführt; diesen führen wir im nächsten Schritt zu unserem yml hinzu. Werfen wir vorab einen Blick auf die bisherigen vier definierten Sektionen der Datei:

Aufbau der yml-Datei

Mit image: openjdk:11-jdk wird das Docker-Image angegeben, auf der der CI/CD-Laufzeitcontainer für diese Pipeline basiert. In unserem Fall starten wir mit einer Java Umgebung in der das Java SDK in der Version 11 vorliegt.

Im Bereich valiables: werden Variablen definiert, die wir innerhalb unserer yml-Datei beliebig oft einsetzen können. Das ermöglicht uns eine zentrale und redundanzfreie Definition wichtiger, veränderbarer und wiederkehrender Werte.

In before_script: werden Iniitalisierungsaufgaben definiert, die vor dem Durchlauf eines jeden CI/CD-Jobs ausgeführt werden sollen. Dabei handelt es sich um Shell-Befehle, die sequentiell und zeilenweise abgearbeitet werden und eine Initialisierung der Android-Umgebung auf der im Container laufenden Linux-Maschine durchführen.

Jeder Job, den wir im weiteren Verlauf unseres Workshops erstellen werden,, läuft in seiner eigenen Linux-Umgebung. Somit muss zu Beginn jeden Jobs die JDK11- und Android/Gradle-Umgebung wiederholt erstellt und initialisiert werden.

Mit Hilfe des Keywords cache kann GitLab CI/CD gleichbleibende Installationsordner erkennen und zwischen Containerinstanzen wiederverwenden. In unserem Fall wird das von Gradle generierte Cache-Verzeichnis .gradle als Cache-Verzeichnis für unsere Pipeline angegeben. Das beschleunigt den Durchlauf aller weiteren Jobs erheblich.

Unser erster CI/CD-Job: BuildAndPackageApp

Die beiden Gradle-Aufrufe zum Bauen und Paketieren der App wollen wir nun in den ersten CI/CD-Job unserer Pipeline überführen; damit werden diese beiden Tasks fortan bei jedem Git Push automatisiert ausgeführt. Unseren ersten Job fügen wir unter dem Namen BuildAndPackageApp zu unserer Pipeline-Datei .gitlab-ci.yml hinzu:

...
 
BuildAndPackageApp:
  stage: build
  script:
    - ./gradlew :app:assemble --info
    - ./gradlew :app:bundle --info
  artifacts:
    paths:
      - app/build/outputs/bundle

Starten der Pipeline

Pushen wir diese Erweiterung nun in unser Repository, so wird unser erster Job serverseitig ausgeführt. Nun können wir unsere Pipeline und deren ersten Job in der GitLab-Weboberfläche unter „CI/CD“ einsehen und dort auch den Durchlauf unseres Jobs inspizieren:

Pipeline Stages

Jobs können in verschiedene Stages geclustert werden. Dabei führt GitLab CI/CD alle Jobs einer Stage parallel aus und geht beim erfolgreichen Abschluss aller Jobs einer Stage sequentiellzur nächsten Stage über. Wir verwenden in unserem Workshop die drei vorgegebenen Stages buildtest und deploy und siedeln unseren ersten Job daher auch in der ersten Stage build an.

Build-Artefakte

Nach dem Gradle-Durchlauf stehen die gebauten Android-Apps im Unterverzeichnis app/build/outputs/bundle/ im Container zur Verfügung. Geben wir diesen Pfad im Feld artifacts als Build-Artefakte an, so steht das Verzeichnis nach dem Durchlauf des Jobs in der GitLab-CI/CD-Oberfläche zum Download zur Verfügung.

Build-Artefakte können auch zur Bereitstellung von Log-Dateien für bestimmte Tools oder Prozesse verwendet und zudem auch an nachfolgende CI/CD-Jobs oder an externe Codeanalyse-Tools weitergegeben werden.

2. Unit Tests ausführen

Im Projekt wird das Test-Framework JUnit 4 eingesetzt. Es ermöglicht die Abdeckung unseres Produktionscodes mit Unit Tests und die Generierung einer Test Code Coverage. Mit einem funktionierenden Test-Setup können wir zudem künftige Erweiterungen an unserer Applikation testgetrieben entwickeln.

Alle Unit-Tests sind im Projekt unterhalb des Ordners app/src/test definiert. Abgedeckt wird hier eine unabhängige Serviceklasse unserer Anwendung. Das Zusammenspiel mit anderen Klassen und Komponenten wird dabei mit dem Mocking-Framework Mockito sichergestellt.

Unit Tests lokal ausführen

Durch den Aufruf des Tasks test können alle Unit-Tests ausgeführt werden. Der anschließend aufgerufene Task jacocoTestReport generiert aus den Daten des Testlaufs zudem Coverage-Reports, mit denen die Testabdeckung unseres Projektcodes gemessen werden kann.

% ./gradlew -Pci --console=plain :app:testDebug --info
% ./gradlew -Pci --console=plain :app:jacocoTestReport --info

Die Test- und Coverage-Reports werden unterhalb app/build/reports/ und app/build/test-results generiert.

CI-Job Nummer Zwei: UnitTests

In einer Continuous-Integreation-Pipeline stellt das Ausführen automatisierter Tests wohl einen der häufigsten Anwendungsfälle dar. Sie sind ein wichtiger Faktor bei der Sicherstellung einer hohen Code-Qualität.

Unser zweiter Job ruft nun die beiden Gradle-Befehle zum Ausführen der Unit Tests und der Generierung der Code Coverage in seinem script-Bereich auf. Nach dem Abschluss beider Gradle Tasks werden die generierten Test- und Coverage-Reports als Build-Artefakte exportiert.

...
 
UnitTests:
  stage: test
  script:
    - ./gradlew -Pci --console=plain :app:test --info
    - ./gradlew -Pci --console=plain :app:jacocoTestReport --info
  artifacts:
    paths:
      - app/build/reports/tests
      - app/build/test-results
      - app/build/reports/jacoco

Bedingter Start des Jobs

Unser zweiter Job UnitTests ist angewiesen, in der Stage test zu laufen. Somit wird er nur dann ausgeführt, wenn der zuvor in der Stage build definierte Job BuildAndPackageApp erfolgreich abgeschlossen wurde. Es benötigt dafür keine explizite Deklaration in der yml-Datei. Dadurch wird implizit auf einen unnötigen – weil zum Scheitern verurteilten – Start der Tests verzichtet, sofern die Anwendung zuvor gar nicht korrekt kompiliert oder gebaut werden konnte.

3. UI-Tests ausführen

Das Android-Framework Espresso erlaubt die instrumentalisierte Ausführung von Android-Apps und ermöglicht so die Definition und Durchführung von UI-Tests. Diese Tests laufen in Echtzeit und interagieren mit den UI-Elementen unserer Anwendung, die auf einem Android-Emulator oder -Gerät betrieben wird. Zu Beginn eines jeden Tests wird die Anwendung übrigens neu gestartet.

UI-Tests haben aus allen genannten Gründen eine deutlich höhere Ausführungszeit und sind aufwändiger zu erstellen und zu warten.

UI-Tests lokal ausführen

Die UI-Tests befinden sich im Projekt unterhalb app/src/androidTest. Sie testen verschiedene Szenarien und Aspekte unserer Applikation ab. Über den Task connectedAndroidTest können alle UI-Tests ausgeführt werden. Wichtig ist, dass im Vorfeld der Android-Emulator oder ein angeschlossenes Gerät über den Device Manager des Android Studios gestartet werden muss.

% ./gradlew :app:connectedAndroidTest --info

Anschließend werden die UI-Tests automatisiert durchgeführt. Das Laufen der Tests kann im Emulator beobachtet werden.

Auch hier werden nach dem Abschluss der UI-Tests diverse Test-Reports erstellt, die sich unter app/build/reports/androidTests und build/outputs/androidTest-results einsehen lassen.

Unser dritter CI/CD-Job: Ausführen der UI-Tests

Da alle Befehle in unserer CI/CD-Pipeline innerhalb eines Docker-Containers ausgeführt werden, muss im Vorfeld des Ausführens der UI-Tests ein Emulator im Headless-Mode gestartet werden. Der Emulator muss zudem ohne Hardwarebeschleunigung laufen, da diese im Container nicht zur Verfügung steht. Wir fügen nun unseren dritten Job UI-Tests hinzu, mit dem unsere UI-Tests automatisiert durchgeführt werden sollen:

...
 
UI-Tests:
  stage: test
  allow_failure: true
  script:
    # setup sdkmanager
    - sdkmanager --list | grep system-images
    - sdkmanager --sdk_root=${ANDROID_HOME} --install "system-images;android-29;default;arm64-v8a"
    # create new android virtual device
    - echo "no" | avdmanager --verbose create avd --name "MyAvd" --force --package "system-images;android-29;default;arm64-v8a" --abi "arm64-v8a" &
    # start new headless emulator from avd
    - ${ANDROID_HOME}/emulator/emulator -avd "MyAvd" -gpu "off" -accel "on" -no-audio -no-boot-anim -no-window
 
    # run AndroidTests on connected headless running emulator
    - ./gradlew :app:connectedAndroidTest --info
 
  artifacts:
    paths:
      - app/build/reports/androidTests
      - app/build/outputs/androidTest-results

Wichtiger Hinweis

Dieser Schritt konnte bisher in unserer Pipeline noch nicht realisiert werden. Alle drei Architekturtypen für den Emulator auf einem aktuelleren API-Level (x86, x86_64 und arm64-v8a) verweigern den Start im Container aufgrund einer unpassenden Architektur. Daher beendet sich dieser Job mit einem Fehler. Aktuell probieren wir es mit dem armeabi-v7a, hier startet der Emulator zwar aber meldet wiederholt Fehler.

Scheitern erlaubt.gitlab-ci.yml

Das Scheitern eines Jobs führt standardmäßig zum Scheitern der gesamten Pipeline. Wir können das Scheitern eines Jobs aber auch geflissentlich ignorieren indem wir für den Job die Option allow_failure mit dem Wert true angeben. Somit wird der Job zwar mit dem Status „Fehlgeschlagen“ versehen, unsere Pipeline wird nach Abschluss aller Jobs aber trotzdem mit dem Status „Bestanden“ ausgezeichnet.

4. Statische Code Analyse ausführen

Der Einsatz von Lintern hilft bei der Sicherstellung eines einheitlichen Code-Styles und warnt vor potentiellen Fehlern im Programmcode oder in den Android Ressourcen-XMLs. Für die Durchführung der statischen Codeanalyse setzen wir In unserem Projekt zwei verschiedene Linter ein: Den nativen Android Linter sowie den Kotlin Code-Style-Linter detekt.

Android Linter

Das Regelwerk des Android Linters kann in der Datei lint.xml umfangreich angepasst werden. Mit dem Aufruf des folgenden Tasks wird der Android Linter gestartet und ausführliche Linter-Reports in unterschiedlichen Formaten im Verzeichnis app/lint/reports generiert:

 % ./gradlew :app:lint -Pci --console=plain -PbuildDir=lint –-info 

Der Durchlauf des Tasks meldet zahlreiche Warnungen und auch Fehler, da beispielsweise in einer Android-Ressourcen-Datei ein hardgecodedes String-Literal verwendet wird. Trotzdem wird die Ausführung des Tasks als „Erfolgreich“ beendet.

Lint found 2 errors, 18 warnings. First failure:
 
app/src/main/res/layout/fragment_first.xml:72: Error: Hardcoded string "Test Transaction", should use @string resource [HardcodedText]
        android:text="Test Transaction"

Kotlin Code-Style-Linter „detekt“

Der Linter detekt prüft den Code Style unserer Kotlin-Quellcode-Dateien und zeigt Verletzungen am vordefinierten Code-Style auf. Der gewünschte Style wird in der Datei detekt-config.yml definiert und kann dort sehr feingranular angepasst werden.

Um den Linter lokal zu starten, können wir den folgenden Task ausführen. Auftretende Fehler und Warnungen werden dabei auf der Konsole ausgegeben und Linter-Reports im Verzeichnis build/reports/detekt generiert:

 % ./gradlew :app:detekt –-info 

Auch dieser Task meldet aktuell zahlreiche Verletzungen des gewünschten Code-Styles in unseren Kotlin-Dateien. Beispielsweise wird eine Überschreitung der maximalen Zeilenlänge von 120 Zeichen in einer Testklasse moniert. Trotzdem endet der Task erfolgreich.

4.3. Unser vierter CI/CD-Job: Statische Code Analyse durchführen

Den Durchlauf beider Linter können wir nun automatisieren und so eine Continuous Inspection in unsere CI/CD-Pipeline integrieren. Unser neuer Job StaticCodeAnalysis ruft beide Tasks auf und exportiert danach alle generierten Linter-Reports als Build-Artefakte..gitlab-ci.yml

...
 
StaticCodeAnalysis:
  stage: test
  script:
    - ./gradlew -Pci --console=plain :app:lint -PbuildDir=lint --info
    - ./gradlew :app:detekt --info
  artifacts:
    paths:
      - app/lint/reports
      - app/build/reports/detekt

Technische Schulden

Warum also scheitern die Tasks nicht, obwohl die beiden Linter Warnungen und sogar Fehler melden? Weil in den entsprechenden Gradle-Builddateien dieses Verhalten explizit als gewünscht konfiguriert wurde.

In der Gradle-Builddatei app/build.gradle ist definiert, dass Fehler des Android Linters nicht zum Scheitern des Tasks führen:

android {
    lintOptions {
        abortOnError false
    }
}

Und auch für detekt wurde ein Config-Flag in der Gradle-Buildkonfiguration hierfür gesetzt:

detekt {
    ignoreFailures = true
}

Beide Lösungen stellen eine bewusst eingegangene technische Schuld dar, die zu einem späteren Zeitpunkt durch Fixen aller Linter-Meldungen und Entfernen der beiden genannten Config-Direktiven noch behoben wird. 

Oder auch nicht.

5. Erstellen eines GitLab Release Items

Ein GitLab Release repräsentiert eine veröffentlichte Version unseres Programms und wird anhand einer getaggten Git Revision erstellt. Einem Release können auch Release Notes und mehrere ausgewählte Build-Artefakte beigefügt werden. Die GitLab Releases werden dabei ausschließlich auf dem GitLab-Server angelegt und können nicht lokal erstellt oder getestet werden. Somit existiert hierfür auch kein Gradle Task.


CI/CD-Job Nummer Fünf: Erstellen eines GitLab Releases beim Pushen eines Git Tags

Wir wollen unser Release-Management “Git Aware gestalten und die CI/CD-Pipeline beim Pushen eines Git Tags automagisch ein GitLab Release Item erstellen lassen. Das können wir in einem neuen Job definieren. Das eigens hierfür vorhergesehene Feld release ermöglicht die Angabe aller relevanter Angaben für das zu erstellende Release:

...
 
CreateRelease:
  stage: deploy
  rules:
    - if: $CI_COMMIT_TAG
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  before_script: []
  script:
    - echo "Create GitLab Release item v.$CI_COMMIT_TAG"
  release:
    name: 'Release $CI_COMMIT_TAG'
    description: 'v.$CI_COMMIT_TAG Release Notes: $CI_COMMIT_MESSAGE'
    tag_name: '$CI_COMMIT_TAG'
    ref: '$CI_COMMIT_TAG'
    assets:
      links:
        - name: 'Static Code Analysis Reports'
          url: 'https://git.mayflower.de/mayflower/android-payworks-mobile-sdk-app/-/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=StaticCodeAnalysis'
        - name: 'Unit Test Reports'
          url: 'https://git.mayflower.de/mayflower/android-payworks-mobile-sdk-app/-/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=UnitTests'
        - name: 'Build Application Packages'
          url: 'https://git.mayflower.de/mayflower/android-payworks-mobile-sdk-app/-/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=BuildAndPackageApp'

Bedingte Job Ausführung

Unser neuer Release-Job wird durch das Hinzufügen der if-Regel im Feld rules nur dann gestartet, wenn ein Git Tag gepusht wird. Zudem siedelt der Release-Job in der Stage deploy an und wird somit nur dann ausgeführt, wenn alle vorherigen Jobs erfolgreich abgeschlossen wurden.

Keine Initialisierung der Umgebung

Im Gegensatz zu allen bisherigen Jobs muss in diesem Job kein Gradle Task ausgeführt werden. Somit wird für die Ausführung des Release-Jobs auch keine Android-Umgebung benötigt – die Initialisierung kann also gänzlich entfallen. Das erreichen wir, indem wir das global gesetzte Feld before_script für unseren Release-Job mit einem leeren Array überschrieben.

Testen des Jobs

Zum Testen unseres neuen Jobs CreateRelease committen wir die Erweiterung unserer yml-Datei und fügen ein Git Tag mit einer Versionsnummer zu dieser Revision hinzu. Pushen wir anschließend dieses Tag, so können wir den Durchlauf unseres neuen Jobs und die Erstellung eines neuen Release Items im GitLab beobachten:

% git tag 1.0.0 HEAD
% git push origin 1.0.0

Releases werden auf der GitLab-Weboberfläche unter dem Menüpunkt Deployments > Releases angezeigt. Nach dem Abschluss des Release-Jobs werden dem Benutzer hier nun auch die aktuellsten Build-Artefakte zur Verfügung gestellt. Der Einfachheit halber werden hier von jedem Job die aktuellsten Build-Artefakte zum Download angeboten, da GitLab CI/CD alle anderen Build-Artefakte standardmäßig verwirft. Um fest versionierte Artefakte dauerhaft zu speichern und anzubieten ist eine aufwändigere Lösung erforderlich.

In einer CI/CD-Pipeline fällt dieser Prozessschritt unter den Begriff Continuous Delivery.

Pipeline vollständig

Damit ist unsere Build- und Release-Pipeline vorerst komplett. In der CI/CD-Übersicht unseres Projekts sind alle Jobs in ihren drei Build-Phasen nun auf einen Blick ersichtlich:

In GitLab fällt die CI/CD-Pipeline aus der Tüte

GitLab CI/CD ermöglicht uns die Umsetzung einer vollständigen CI/CD-Pipeline, ohne dass hierfür zusätzliche Services oder externe Build-Server angeschafft werden müssen. Die konfigurationsfreie und cloudbasierte Nutzung ermöglicht Entwicklern einen schnellen Einstieg in das Thema CI/CD und bietet zudem eine hohe Developer Experience. Darüberhinaus zeichnet sich die Definition unserer Pipeline durch eine zielgerichtete und puristische yml-Syntax aus.

Ich freue mich sehr, wenn ich Dir einen schnellen Einstieg in die Kernkonzepte von GitLab CI/CD und in die praktische Arbeit mit CI/CD-Pipelines geben konnte. Für Feedback oder Rückfragen kannst Du mich gerne via christopher.stock@mayflower.de kontaktieren.

Für neue Blogupdates anmelden:


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.