Wir arbeiten seit längerer Zeit an der Modernisierung unseres Buildsystems auf Basis von Gradle. Eines der wichtigsten Ziele ist die Reduzierung der Build- und Testzeiten. Durch die Features von Gradle und den Einsatz von leistungsfähiger Hardware haben wir in Summe die Performance des Buildsystems vervielfacht.

Wo stehen wir?

Verlauf der Buildzeiten

Bei den reinen Buildzeiten (Compile und Publish) sind wir mittlerweile im Schnitt bei 10 Minuten.  Damit sind wir zufrieden, das ist auch für die Größe unseres System (> 800 Module, > 5 Mio LoC) durchaus angemessen. Aber bei den Tests liegen wir im Schnitt immer noch über einer Stunde.

Wie kommen wir hier weiter? Es gibt verschiedene Möglichkeiten:

  • Noch mehr Hardware anschaffen bzw. weitere Skalierungsmöglichkeiten in der Cloud nutzen
  • Tests stark reduzieren oder auch aufwändige Integrationstests durch schnellere Unit-Tests ersetzen (Stichwort: Testpyramide)
  • Abhängigkeiten der Module reduzieren

Warum taucht hier die Pflege von Abhängigkeiten auf?

Auf die ersten beiden Punkte möchte ich nicht weiter eingehen, beide sind naheliegend um die Testlaufzeiten weiter zu reduzieren. Die Pflege von Abhängigkeiten fällt einem wiederum vielleicht nicht als Erstes ein. Die Abhängigkeiten zu reduzieren ist natürlich grundsätzlich aus Architektursicht eine gute Idee. So werden auch auf Modul- bzw. Projektebene gewisse Architektur-Regeln sichergestellt. Es hat aber auch ganz praktische, unmittelbare Auswirkungen auf das Build-Verhalten. Eines der wesentlichen Features von Gradle ist ein echter inkrementeller Build. Ein Modul wird nur dann gebaut oder getestet, wenn es an dem Modul – oder Modulen von denen es abhängig ist –  Änderungen gegeben hat.

 

Warum ist das ein Problem?

Wir hatten in unserem alten Buildsystem (auf Basis von Apache Ant) unsere Abhängigkeiten mit Fokus auf Wartbarkeit gepflegt. Das heißt, wir haben sehr viel Gebrauch von transitiven Abhängigkeitsdefinitionen gemacht. Das hatte im alten Buildsystem auch keine entsprechenden Konsequenzen, da es keinen mit Gradle vergleichbaren direkten inkrementellen Build gab.

Beispielhafte Buildabhängigkeiten als Graph

Mit der Einführung von Gradle haben wir zunächst die Abhängigkeiten 1:1 übernommen. Im zweiten Schritt haben wir angefangen, die Abhängigkeiten erneut zu überprüfen. Durch die vielen transitiven Abhängigkeitsdefinitionen gibt es jede Menge Abhängigkeiten, die eigentlich unnötig sind. Somit baut Gradle auch Module mit, die nicht mitgebaut werden müssten. Die Reduzierung der Abhängigkeiten kann hier einen enormen Effekt haben, gerade bei unnötigen Abhängigkeiten, die sehr weit „innen“ in der Anwendungsstruktur liegen und entsprechende Auswirkungen haben. Dies bekommt man insbesondere bei Änderungen zu spüren, an denen der (unnötige) Build von Modulen mit aufwändigen Tests hängt.

Fazit

Es lohnt sich in die Pflege der Abhängkeiten zu investieren. Es macht Sinn hier kontinuierlich die Abhängigkeiten im Blick zu haben und die Auswirkungen auf das Buildverhalten zu analysieren. Dazu bietet sich z.B. Gradle Enterprise oder vergleichbare Werkzeuge an. Nur dann kann man das Potential von Gradle voll entfalten und profitiert von noch besseren Build- und Testzeiten.