Neuigkeiten von trion.
Immer gut informiert.

Android CI-Builds mit Docker

Um Anwendungen erfolgreich bauen zu können, ist - je nach Anwendung - ein hoher Aufwand in die Einrichtung der Umgebung zu investieren. Beispielsweise für ein Android-Projekt muss Java installiert sein, es werden die Android-Tools mit dem Android-SDK benötigt und der Build muss passend konfiguriert werden. Dieses Setup wird aber nicht nur auf den Entwickler-Rechnern benötigt, auch die Countinuous-Integration-Umgebung des Buildservers muss entsprechende Werkzeuge bereitstellen. Um Reproduzierbarkeit zu gewährleisten, sollte sichergestellt sein, dass in allen diesen Umgebungen die gleichen Versionen der Tools genutzt werden.

In diesem Artikel werden wir uns damit beschäftigen, wie auf einfachem Wege eine zur Entwicklung von Android-Projekten geeignete Umgebung mit Hilfe von Docker erstellt werden kann. Am Beispiel von Gitlab-CI wird der Einsatz des entsprechenden Docker-Containers in einer CI-Umgebung gezeigt.

Docker als Container für Build-Tools

Um den Setupaufwand pro System zu reduzieren, ist Docker ein gut geeignetes Mittel: In einem Docker-Container können alle benötigten Abhängigkeiten zum Build eines Projektes vorinstalliert werden. Ein entsprechendes Docker-Image, welches zum Build von Android-Anwendungen genutzt werden kann, ist das trion/android-build-Image. Bei Anlage eines Android-Projektes mit Android-Studio ist zu beachten, dass eine Date local.properties generiert wird. Diese Datei enthält unter anderem Informationen über den Pfad zum lokalen Android SDK und ist umgebungsspezifisch. Die Angabe in dieser Datei steht dann im Konflikt zur Umgebung innerhalb des Docker-Containers und sollte daher vor der Ausführung eines Builds via Docker gelöscht werden und nicht in der Versionsverwaltung eingecheckt werden. Für lokale Builds wird Android-Studio die Datei bei Bedarf automatisch wieder anlegen.

Entfernen der durch Android-Studio angelegten local.properties
$ rm local.properties

Später in der CI-Umgebung wird es mit dieser Datei keine Probleme geben, da sie standardmäßig ohnehin nicht per Git verwaltet wird.
Einen lokalen Build können wir nun mit folgendem Befehl anstoßen.

Kommando zum Ausführen eines Android-Build im Docker-Container
$ docker run -u $(id -u) -e HOME=$(pwd) -e _JAVA_OPTIONS="-Duser.home=$(pwd)" --rm -v $(pwd):/$(pwd) -w /$(pwd) trion/android-build ./gradlew clean assembleRelease

Auch Unit-Tests lassen sich so leicht ausführen. Im Beispiel Projekt ist zusätzlich ein Task zur Ermittlungs der Testabdeckung mit JaCoCo definiert. Sofern die Tests, z.B. mit Robolectric, ohne Device und Android Emulator auskommen, lassen sie sich ebenfalls mit Docker ausführen.

Kommando zum Ausführen der Unit-Tests im Docker-Container
$ docker run -u $(id -u) -e HOME=$(pwd) -e _JAVA_OPTIONS="-Duser.home=$(pwd)" --rm -v $(pwd):/$(pwd) -w /$(pwd) trion/android-build ./gradlew jacocoTestReport

Die Code-Coverage-Reports werden dabei im Ordner app/build/reports/jacoco/jacocoTestReport/ abgelegt, die Datei app/build/reports/jacoco/jacocoTestReport/html/index.html enthält eine visuell aufbereitete Version des Coverage-Reports.

Nutzung von Docker mit Gitlab-CI

Um das Projekt auch auf einem (Gitlab-)CI-Server bauen zu können, müsste im herkömmlichen Fall die komplette Entwicklungsumgebung inkl. Android SDK auf dem Build-Node nachgebildet werden. Dieser Schritt entfällt mit obigem Docker-Image. Zur Konfiguration des CI-Builds wird in Gitlab-CI die .gitlab-ci.yml verwendet, welche sich im Root-Verzeichnis des Projektes befinden muss. Der folgende Code-Ausschnitt zeigt,wie der Android Build mit Docker umgesetzt werden kann.

.gitlab-ci.yml als Beispiel-Buildfile
stages:
- test
- release

variables:
  GRADLE_OPTS: "-Dorg.gradle.daemon=false"

before_script:
- export _JAVA_OPTIONS="-Duser.home=$(pwd)"

cache:
  key: ${CI_PROJECT_ID}
  paths:
  - .gradle/

test:
    stage: test
    image: trion/android-build
    tags:
      - x86_64
    script:
        - ./gradlew --info clean test --no-daemon
        - ./gradlew jacocoTestReport --no-daemon
        - cat app/build/reports/jacoco/jacocoTestReport/html/index.html

build:
    stage: release
    image: trion/android-build
    tags:
      - x86_64
    script:
        - ./gradlew --info clean assembleRelease --no-daemon
    artifacts:
        expire_in: 2 weeks
        paths:
            - app/build/outputs/apk/*.apk
            - app/build/outputs/apk/release/*.apk

Gitlab-CI arbeitet mit Stages, in unserem Fall legen wir eine test- und eine release-Stage an.

Um zu vermeiden, dass alle Gradle-Abhängigkeiten bei jedem Build komplett neu gezogen werden müssen, werden diese gecached. Dabei werden die gecachten Dateien unter einem für dieses Projekt einzigartigen, von Gitlab generierten Key abgelegt (key: ${CI_PROJECT_ID}). Um die oben eingeführten Docker Images nun zu nutzen, wird in jeder Stage, die das Image nutzen soll, das Property image auf den Wert trion/android-build gesetzt.

Das Property script gibt an, was genau in der jeweiligen Stage zu tun ist. In unserer Stage test führen wir etwa einen clean test für schnelles Entwicklerfeedback aus, dann werden die Tests noch einmal mit JaCoCo gestartet, um die Code-Coverage der Tests zu analysieren. Das Ergebnis der Code-Coverage-Analyse wird in der Datei app/build/reports/jacoco/jacocoTestReport/html/index.html abgelegt, welche im Anschluss an die Tests per cat ausgegeben wird, um die Ergebnisse auch in Gitlab nutzen zu können. Damit Gitlab das Resultat der Code-Coverage-Analyse verarbeiten kann, um zum Beispiel die Testabdeckung anzuzeigen, muss Gitlab mit einem regulären Ausdruck konfiguriert werden, der die Testabdeckung als Prozentzahl aus der Kommdozeilen-Ausgabe des Testkommandos ausließt. Dieser Regex lässt sich im grafischen UI von Gitlab einstellen, wie in der folgenden Abbildung gezeigt.

Figure 1. Regex zum Auslesen der Code-Covarge aus einem Gitlab-CI-Build

Der eigentliche Build spezifiziert dann noch das Property artifacts, welches dazu dient, das Build-Resultat für eine gewisse Zeit (in diesem Fall 2 Wochen) zum Download verfügbar zu machen. Hier werden 2 verschiedene Android-APK-Dateien zur Verfügung gestellt.

Um den Buildstatus seines Builds zu überprüfen, kann man sich außerdem das Build-Resultat in Form verschiedener Badges ausgeben lassen. In folgender Grafik werden etwa der Build-Status und die Coverage per Badge angezeigt.

Figure 2. Status Badges des Builds




Zu den Themen Android, Docker und Native Script bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Zur Desktop Version des Artikels