BuildID mit Gradle
Gerade in den Zeiten von DevOps, Continuous Integration und Continuous Delivery wird es immer wichtiger zu wissen, wer, wann
ein Binary gebaut hat und auf welchem Stand des Quellcodes es beruht. Das klassische "projekt-yxz-1.2-snapshot"
von Maven hilft hier nicht weiter, denn mit dieser Bezeichnung kann es beliebig viele Binaries geben, und einen
Rückschluss auf den zu Grunde liegenden Commit in Git lässt es auch nicht zu. Die Frage kann sich auf dem CI-Server oder Testsystem
stellen, um einen dort aufgetretenen Fehler zuzuordnen oder auch beim lokalen Entwickeln in kurzen Zyklen,
wenn man sicher sein will, dass ein Deployment des neusten Builds wirklich stattgefunden hat.
Abhilfe schafft hier die BuildID, die immer eindeutig und automatisch bei jedem Build in das Binary
kopiert wird. Dort kann es dann zur Laufzeit ausgelesen und z.B. im Footer einer Web-Anwendung oder im Info-Menüpunkt eines Rich-Clients
angezeigt werden. Entscheidend ist der Automatismus: die BuildID wird immer während des Builds eindeutig vom Build-Script
generiert, und kann daher nicht vergessen und muss nicht von Hand nachgezogen werden. Auch bei Releases nicht.
Mit einem Gradle Task ist das kein Problem.
task buildId << {
buildDir.mkdirs()
// generate timestamp and user for build_id
def build_id=new Date().format('yyyyMMdd HHmm ')+System.properties.'user.name'
try {
// get git status und ref
def gitref="git rev-parse --short HEAD".execute().text.trim()
def gittag="git describe --tags --always $gitref".execute().text.trim()
//if(gitref!=gittag) { gitref="$gittag $gitref"}
def gitdirty="git status --porcelain".execute().text.isEmpty()?" ":"*"
build_id+=" $gittag$gitdirty"
}
catch (Exception x) {
println "Warning: no git executable, using simple buildId"
}
println "buildId: $build_id"
// and write build_id to file
new File("$buildDir/build_id.txt").withWriter { out -> out.println build_id }
}
Das obige Beispiel erzeugt eine BuildID mit einem aktuellen Timestamp und dem Username. Dazu kommt noch
der aktuelle Commit aus Git und mit dem Dirtyflag noch die Information, ob der Stand im Workspace verändert
wurde (""uncommited changes"). Wenn es einen Release Tag gibt, wird statt des SHA1-Codes das Tag verwendet:
20140520 1622 mw V_1_2 // Gebaut von mw, Release Tag V_1_2, nicht dirty
20140521 1141 mw a5f734* // Binary basiert auf Commit a5f734, dirty
Das Ganze funktioniert auch für Rollbacks, wenn z.B. im Release V_1_3 ein kritischer Bug aufgetreten ist
und nun auf Release V_1_2 zurückgerollt werden soll. Die folgenden beiden Zeilen bauen das alte Release
mit der passenden BuildID.
git checkout V_1_2
gradle clean war
Die BuildID kann dann von Gradle beim Build von Java Anwendungen in das META-INF Verzeichnis von JAR, WAR oder EAR Archiven kopiert werden:
war.dependsOn buildId
war {
metaInf {
from "$buildDir/build_id.txt"
}
}
Und steht der Anwendung zur Laufzeit wie folgt zur Verfügung und kann z.B. im Footer angezeigt werden:
InputStream str = getClass().getResourceAsStream("/META-INF/build_id.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(str));
String buildId = br.readLine();
Mein Referenzprojekt dazu auf github:
JTrack-EE7: RESTful Webservices, Gradle, Testautomatisierung mit Spock, JUnit und Arquillian