1. Warum Build-Systeme?

[THEORIE]

Sobald ein Kotlin-Projekt aus mehr als einer Datei besteht, reicht ein einzelner kotlinc-Aufruf nicht mehr. Es gibt mehrere Quelldateien mit Abhängigkeiten untereinander, dazu externe Bibliotheken, die erst heruntergeladen werden müssen, dazu Tests, die vor jedem Release laufen sollen. Das von Hand zu koordinieren wird schnell fehleranfällig — und niemand will bei jedem Build manuell prüfen, welche Datei sich seit dem letzten Mal geändert hat.

Ein Build-System ist Software, die genau das automatisiert: den Weg von Quellcode zu einem fertigen Artefakt, zum Beispiel einer ausführbaren JAR-Datei oder einer Bibliothek. Grob läuft das so ab:

Quellcode (*.kt)  ->  Bytecode (*.class)  ->  Archiv (*.jar)  ->  ausführbare Anwendung

Build-Systeme übernehmen dabei typischerweise:

  • Konfiguration einmal festlegen, dauerhaft nutzen — die Build-Konfiguration liegt als Datei im Projekt, nicht in den Handgriffen einer Person.
  • Wiederverwendung von Logik über mehrere Projekte oder Module eines Projekts hinweg.
  • Dependency-Management — externe Bibliotheken werden deklariert, nicht manuell heruntergeladen und verwaltet.
  • Testing und Verifikation als Teil des Builds, nicht als separater, oft vergessener Schritt.
  • Inkrementelle Builds — nur das neu bauen, was sich tatsächlich geändert hat, siehe die Vorhersage-Aufgabe oben.

Bekannte Build-Systeme im Java/Kotlin-Umfeld sind Apache Ant, Maven, Gradle und Bazel. Dieser Kurs konzentriert sich auf Gradle mit der Kotlin DSL (build.gradle.kts), weil es der Standard für neue Kotlin-Projekte ist — der "New Project"-Dialog von IntelliJ IDEA schlägt Gradle mit Kotlin DSL standardmäßig vor. Die nächste Lektion stellt Gradle Maven gegenüber, damit klar wird, wovon sich Gradle bewusst abgrenzt.

Weiterlesen

[VORHERSAGE]

Lies den Code genau durch, bevor du ihn ausführst.

vorhersage.kt
fun main() {
    val cache = mutableMapOf<String, Int>()

    fun build(name: String, input: Int): Int {
        val cached = cache[name]
        return if (cached == input) {
            println("$name: UP-TO-DATE")
            cached
        } else {
            println("$name: wird neu gebaut")
            cache[name] = input
            input * 2
        }
    }

    build("modulA", 5)
    build("modulA", 5)
    build("modulA", 7)
}

Was geben die drei Aufrufe von build() aus — welcher nutzt das zwischengespeicherte Ergebnis?

[AUFGABE]

Ein Build-System bringt Quellcode über mehrere Schritte zu einem lauffähigen Artefakt: erst wird kompiliert, dann werden die kompilierten Klassen in ein Archiv verpackt, erst danach kann die Anwendung laufen. Ruf compile(), packageJar() und run() in main() in genau der Reihenfolge auf, in der ein Build-System sie ausführen würde.

aufgabe.kt
fun compile() = println("Kotlin-Quellcode wird zu .class-Dateien kompiliert") fun packageJar() = println("Kompilierte Klassen werden in eine .jar-Datei gepackt") fun run() = println("Anwendung wird aus der .jar-Datei gestartet") fun main() { // TODO: compile(), packageJar() und run() in der richtigen Reihenfolge aufrufen }

Bevor du einen Hint bekommst: Was hast du schon probiert, und woran hängt es genau?

[VERSTÄNDNIS-CHECK]

1. Was automatisiert ein Build-System laut Definition in dieser Lektion?

2. Welcher Punkt gehört NICHT zu den typischen Aufgaben eines Build-Systems?