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.
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.
$ 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.
$ 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-Buildfilestages:
- 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.
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.
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.