Continuous Integration mit Java und JavaScript, Teil 1: Die Kunst der Versionierung

Das Build-System

Die Basis der Baustelle ist das Build-System

Ein Build-System, auch Build-Tool genannt, ist ein Programm zum automatisierten Erzeugen von Software. Ein System, wie Apache Ant, Gnu Make, Apache Maven oder npm baut Software, in dem es für das jeweilige Zielsystem eine Reihe von Aktionen durchführt. Das sind typischerweise simple Aufrufe externer Programme in einer definierten Reihenfolge. Dabei verfolgen die Tools unterschiedliche Ansätze. Ant und Make arbeiten beispielsweise Tasks ab, die in Abhängigkeit zueinander stehen. Für die Quellen und die erzeugten Artefakte existiert in den beiden Werkzeugen keine Vorlage (Modell), sodass immer eine Abfolge zum Erstellen des Artefakts angegeben werden muss. Im Gegensatz dazu definiert das Build-Tool Maven ein Projektmodell. Das bedeutet, das Bauen wird in erster Linie konfiguriert. Für das Anwendungsbeispiel im zweiten Teil kam ein Zwitter zum Einsatz: Das Build-Tool Gradle ist sowohl Task-basiert, kennt jedoch je nach verwendeten Plug-in auch Projektmodelle und vereint damit die Vorteiler zweier Welten: Flexibilität und die Vermeidung von Redundanz.

Ein weiteres Merkmal eines Build-Systems ist der Grad der Abhängigkeit zum Betriebssystem und die damit verbundene Versionsstabilität. Das bedeutet, wie stabil ein Build-Tool sich selbst in einer geforderten Version installiert. Gradle-Anwender können dazu die geforderte Versionsnummer angeben,worauf sich das Werkzeug selbst in der angegebenen Version aus einem Repository lädt und sich um die Installation kümmert. Die Versionsstabilität des Build-Tools ist ein wichtiger Baustein für das Erstellen reproduzierbarer Builds.

Im Vorfeld sollte für die Auswahl des Build-Tools genügend Zeit eingeplant werden. Technisch spricht übrigens nichts dagegen, Java-Quellcode mit Java-fremden Tools wie npm oder Gnu Make zu bauen. Meist ist ein Build-Tool auf ein Zielsystem hin optimiert. Somit entscheiden Details hinsichtlich benötigter Plug-ins, Dependency-Mechanismen oder der Unterstützung eines bestimmten Repositories.

Kontrolle über die Versionen behalten

Beim Anlegen eines Build-Systems müssen die Administratoren entscheiden, wo die erstellten Artefakte für nachfolgende Schritte abgelegt und auf welche Art benötigte Artefakte wie Bibliotheken eingebunden werden. Die einfachste Variante ist das Speichern der Artefakte auf ein (Netz-)Laufwerk und/oder das Einchecken der benötigen Bibliotheken in das Versionskontrollsystem (CVS, SVN, Git). Der Nachteil an dieser Vorgehensweise ist, dass sich bestehende Arbeiten im Internet nicht wieder verwenden lassen und die Aktualisierung der benutzten Bibliotheken aufwendig ist.

Eine Alternative besteht in der Nutzung eines Repository-Systems für den Zugriff oder die Ablage von Artefakten. Ein solches Werkzeug verwaltet zusätzlich Metadaten zu den Artefakten wie Abhängigkeiten, Versionssummer, Vorgängerversion, Autor und Lizenz. Anhand der Meta-Daten kann ein Unternehmen sicherstellen, dass beispielsweise eine bestimmte Lizenzart aus- oder eingeschlossen wird. Da ein Repository-System den Zugriff zwischenspeichert, wird nebenbei stets eine redundante Kopie archiviert, die bei einem Ausfalls eines entfernten Systems die Reproduzierbarkeit des Builds sicherstellt. Die Kopie hilft zudem für die Fälle, bei denen sich der Lieferant entscheidet, eine benötigte Version aus dem Internet wieder zu entfernen. Idealerweise unterstützt das Build-System ein hausinternes Repository, um Ausfälle von externen Diensten, wie die von GitHub im April 2015, zu überbrücken.

Voneinander abhängig

Die dritte Problemkategorie beim Erstellen reproduzierbarer Builds stellt die Verwaltung der Abhängigkeiten dar. Das kann sich auf eine einzelne Version oder einen Versionswertebereich beziehen. Manche Systeme unterstützen orthogonale Eigenschaften wie Betriebssystem oder CPU-Architektur. So ist npm in der Lage, Abhängigkeiten anhand des Betriebssystems zu konfigurieren. Im Scala-Umfeld sind Pakete anhand des verwendeten Scala-Compilers zu unterscheiden, und das Portage System unterstützt durch sogenannte USE Flags allgemeine orthogonale Notwendigkeiten wie Videounterstützung, IPv6 oder unterstützte Sprachen. Letztlich hat sich beim Bauen von Software die grobe Unterteilung der Dependencies in die Kategorien compile, test und runtime als ausreichend erwiesen. Sowohl Maven Repositories als auch npm kennen diese Ordnung. Letztlich sollte das Team prüfen, wie ein Build-System mit den Artefakten eines anderen umgehen kann.