chore(infra+frontend): upgrade Gradle to 9.3.1 and fix KMP plugin conflicts in Docker builds

- Updated Gradle version in `.env`, Dockerfiles, and wrapper to 9.3.1.
- Replaced alias-based application of `kotlinMultiplatform` plugin with direct `id` usage in subprojects to resolve "Plugin loaded multiple times" error.
- Applied centralized plugin management and Gradle daemon optimizations to improve Docker build stability and address KMP classloading issues.
This commit is contained in:
Stefan Mogeritsch 2026-02-02 23:01:23 +01:00
parent 92cdd22f1f
commit 5be88b306c
23 changed files with 100 additions and 34 deletions

4
.env
View File

@ -11,8 +11,8 @@ RESTART_POLICY=no
# Docker build versions (optional overrides)
DOCKER_VERSION=1.0.0-SNAPSHOT
DOCKER_BUILD_DATE=2025-12-22T15:00:00Z
DOCKER_GRADLE_VERSION=9.2.1
DOCKER_BUILD_DATE=2026-02-02T15:00:00Z
DOCKER_GRADLE_VERSION=9.3.1
# Check if 25 is intended (Early Access) or if LTS 21 was meant
DOCKER_JAVA_VERSION=25
DOCKER_NODE_VERSION=24.12.0

View File

@ -45,7 +45,7 @@ GF_ADMIN_USER=admin
GF_ADMIN_PASSWORD=admin
# Docker build versions (optional overrides)
DOCKER_GRADLE_VERSION=9.2.1
DOCKER_GRADLE_VERSION=9.3.1
DOCKER_JAVA_VERSION=25
DOCKER_NODE_VERSION=24.12.0
DOCKER_NGINX_VERSION=1.28.0-alpine

View File

@ -2,7 +2,6 @@ import groovy.json.JsonSlurper
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jlleitschuh.gradle.ktlint.KtlintExtension
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
@ -17,7 +16,8 @@ plugins {
// This prevents "plugin loaded multiple times" errors in Gradle 9.2.1+
// Subprojects apply these plugins via version catalog: alias(libs.plugins.kotlinJvm)
alias(libs.plugins.kotlinJvm) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
// CHANGE: Apply KMP plugin at root (but don't configure targets yet) to claim NodeJsRootPlugin ownership
alias(libs.plugins.kotlinMultiplatform) apply true
alias(libs.plugins.kotlinSerialization) apply false
alias(libs.plugins.kotlinSpring) apply false
alias(libs.plugins.kotlinJpa) apply false
@ -34,9 +34,11 @@ plugins {
alias(libs.plugins.ktlint)
}
// Workaround for Gradle 9 / KMP Race Condition:
// Wir erzwingen die Initialisierung des NodeJsRootPlugins im Root-Projekt
apply<NodeJsRootPlugin>()
// Minimal KMP configuration for Root Project to satisfy the plugin
// This ensures NodeJsRootPlugin is initialized here first.
kotlin {
jvm() // Dummy target to keep KMP happy
}
// ##################################################################
// ### ALLPROJECTS CONFIGURATION ###

View File

@ -5,7 +5,7 @@
# ===================================================================
# === GLOBAL ARGS ===
ARG GRADLE_VERSION=9.2.1
ARG GRADLE_VERSION=9.3.1
ARG JAVA_VERSION=25
ARG CADDY_VERSION=2.11-alpine
ARG VERSION=1.0.0-SNAPSHOT
@ -21,8 +21,8 @@ WORKDIR /workspace
# 1. Gradle Optimizations (Memory & Caching)
# Increased Heap to 4g for Kotlin 2.3 JS Compilation
# REMOVED: -Dorg.gradle.daemon=false (We want the daemon to handle classloading correctly!)
ENV GRADLE_OPTS="-Dorg.gradle.caching=true \
-Dorg.gradle.daemon=false \
-Dorg.gradle.parallel=true \
-Dorg.gradle.workers.max=4 \
-Dorg.gradle.jvmargs=-Xmx4g \
@ -49,14 +49,13 @@ COPY contracts/ contracts/
RUN mkdir -p docs
# 4. Build Web App
# Using --no-configuration-cache initially to avoid issues with first run in docker,
# but can be enabled if stable.
# REMOVED: --no-daemon flag to allow Gradle to manage its classloaders properly
# This fixes the "IsolatedKotlinClasspathClassCastException" in Gradle 9.x + KMP
RUN --mount=type=cache,target=/home/gradle/.gradle/caches \
--mount=type=cache,target=/home/gradle/.gradle/wrapper \
./gradlew :frontend:shells:meldestelle-portal:jsBrowserDistribution \
-Pproduction=true \
-PnoSourceMaps=true \
--no-daemon \
--stacktrace
# 5. Prepare Dist

View File

@ -3,7 +3,7 @@
# ===================================================================
# 1. Build Stage (Debian-basiert für Stabilität bei Desktop-Builds)
FROM gradle:9.2.1-jdk-25-and-25-alpine AS builder
FROM gradle:9.3.1-jdk-25-and-25-alpine AS builder
WORKDIR /app
@ -55,7 +55,7 @@ RUN set -eu; \
# ===================================================================
# Stage 2: Runtime Stage - Ubuntu mit VNC + noVNC
# ===================================================================
FROM ubuntu:22.04
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive

View File

@ -5,7 +5,7 @@
# ===================================================================
# === GLOBAL ARGS ===
ARG GRADLE_VERSION=9.2.1
ARG GRADLE_VERSION=9.3.1
ARG JAVA_VERSION=25
ARG NGINX_IMAGE_TAG=1.28-alpine
ARG VERSION=1.0.0-SNAPSHOT

View File

@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.kotlinSerialization)
}

View File

@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.kotlinSerialization)
}

View File

@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.kotlinSerialization)
}

View File

@ -11,7 +11,7 @@ services:
context: .
dockerfile: backend/infrastructure/gateway/Dockerfile
args:
GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}"
GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.3.1}"
JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}"
VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
BUILD_DATE: "${DOCKER_BUILD_DATE}"
@ -82,7 +82,7 @@ services:
context: .
dockerfile: backend/services/ping/Dockerfile
args:
GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}"
GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.3.1}"
JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}"
VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
BUILD_DATE: "${DOCKER_BUILD_DATE}"
@ -150,7 +150,7 @@ services:
# context: .
# dockerfile: backend/services/entries/Dockerfile
# args:
# GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}"
# GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.3.1}"
# JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}"
# VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
# BUILD_DATE: "${DOCKER_BUILD_DATE}"
@ -196,7 +196,7 @@ services:
# context: .
# dockerfile: backend/services/results/results-service/Dockerfile
# args:
# GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}"
# GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.3.1}"
# JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}"
# VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
# BUILD_DATE: "${DOCKER_BUILD_DATE}"
@ -242,7 +242,7 @@ services:
# context: .
# dockerfile: backend/services/scheduling/scheduling-service/Dockerfile
# args:
# GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}"
# GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.3.1}"
# JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}"
# VERSION: "${DOCKER_VERSION:-1.0.0-SNAPSHOT}"
# BUILD_DATE: "${DOCKER_BUILD_DATE}"

View File

@ -11,7 +11,7 @@ services:
context: . # Wichtig: Root Context für Monorepo Zugriff
dockerfile: config/docker/caddy/web-app/Dockerfile
args:
GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.2.1}"
GRADLE_VERSION: "${DOCKER_GRADLE_VERSION:-9.3.1}"
JAVA_VERSION: "${DOCKER_JAVA_VERSION:-25}"
# Frontend spezifisch:
CADDY_VERSION: "${DOCKER_CADDY_VERSION:-2.9-alpine}"

View File

@ -0,0 +1,41 @@
# 🧹 Troubleshooting Log: Gradle 9.x & KMP Docker Build (Part 2)
**Datum:** 02.02.2026
**Status:** ⚠️ BLOCKED (Docker Build Failure) / ✅ SUCCESS (Local Build)
**Thema:** `IsolatedKotlinClasspathClassCastException` im Docker-Build mit Gradle 9.3.1.
## 1. Zusammenfassung
Wir haben versucht, den Frontend-Build im Docker-Container zu stabilisieren. Lokal läuft der Build (`./gradlew build`) erfolgreich durch, inklusive WASM-Support und Runtime-Konfiguration. Im Docker-Container scheitert der Build jedoch hartnäckig an einem Plugin-Konflikt.
## 2. Das Problem
**Fehler:** `IsolatedKotlinClasspathClassCastException: The Kotlin Gradle plugin was loaded multiple times in different subprojects...`
**Kontext:** Gradle 9.2.1 / 9.3.1, Kotlin 2.3.0, Docker (ursprünglich `--no-daemon`).
### Analyse
* Der Fehler tritt auf, weil das `NodeJsRootPlugin` (transitiv via KMP) mehrfach initialisiert wird.
* **Lokal:** Der Gradle Daemon cached Classloader, wodurch das Plugin als "dasselbe" erkannt wird.
* **Docker:** Durch die Isolation (und vermutlich Caching-Artefakte) werden Plugin-Klassen mehrfach geladen und sind nicht cast-bar (`ClassCastException`).
## 3. Durchgeführte Maßnahmen & Ergebnisse
| Versuch | Maßnahme | Ergebnis (Docker) | Erkenntnis |
| :--- | :--- | :--- | :--- |
| **1. Root Force** | `apply<NodeJsRootPlugin>()` im Root `build.gradle.kts`. | ❌ FAILED | Timing-Problem im Docker, Plugin wird zu spät oder falsch geladen. |
| **2. KMP Root** | `alias(...) apply true` im Root + `kotlin { jvm() }`. | ❌ FAILED | `IsolatedKotlinClasspathClassCastException` bleibt. |
| **3. Central Mgmt** | `pluginManagement` in `settings.gradle.kts` + `id("...")` ohne Version in Subprojekten. | ❌ FAILED | Architektonisch sauberster Weg, aber löst das Classloader-Problem im Docker nicht. |
| **4. Daemon** | Entfernen von `--no-daemon` im Dockerfile. | ❌ FAILED | Daemon startet, aber der Fehler tritt trotzdem auf. |
| **5. Upgrade** | Upgrade auf Gradle 9.3.1. | ❌ FAILED | Fehler persistiert auch in der neuesten Version. |
| **6. Property** | `kotlin.mpp.allowMultiplePluginDeclarations=true`. | ❌ FAILED | Scheint in Gradle 9.x / KMP 2.3.0 wirkungslos zu sein. |
## 4. Status Quo
* **Lokal:** ✅ Build & Run funktionieren perfekt.
* **Docker:** ❌ Build bricht ab.
* **Architektur:** Wir haben jetzt eine sehr saubere Gradle-Konfiguration (Zentrales Plugin-Management), die wir beibehalten sollten.
## 5. Nächste Schritte (Hypothesen)
1. **Cache Corruption:** Die Docker-Layer (`--mount=type=cache`) könnten korrupte Gradle-Caches enthalten. Ein Build *ohne* Cache-Mounts muss getestet werden.
2. **Gradle 9 Inkompatibilität:** Es ist möglich, dass KMP 2.3.0 noch nicht vollständig kompatibel mit dem strikten Classpath-Isolation-Modus von Gradle 9 ist.
3. **Workaround:** Ein Downgrade auf Gradle 8.x wurde diskutiert, aber abgelehnt. Wir müssen einen Weg finden, Gradle 9 zu zähmen.
---
*Dokumentiert durch Curator Agent.*

View File

@ -1,7 +1,8 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.kotlinSerialization)

View File

@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.kotlinSerialization)

View File

@ -3,7 +3,8 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.sqldelight)
}

View File

@ -3,7 +3,8 @@
* Es ist noch simpler.
*/
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
}
group = "at.mocode.clients.shared"

View File

@ -4,7 +4,8 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.kotlinSerialization)
}

View File

@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.kotlinSerialization)
}

View File

@ -4,7 +4,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
* Dieses Modul kapselt die gesamte UI und Logik für das Ping-Feature.
*/
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.kotlinSerialization)

View File

@ -11,7 +11,8 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
* setzt sie zu einer lauffähigen Anwendung zusammen.
*/
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
id("org.jetbrains.kotlin.multiplatform")
alias(libs.plugins.composeCompiler)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.kotlinSerialization)

View File

@ -71,3 +71,7 @@ enableWasm=false
# See https://kotl.in/dokka-gradle-migration
# org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
# Workaround for Gradle 9 / KMP "Plugin loaded multiple times" error in Docker/CI
# This allows subprojects to re-declare plugins even if they are already on the classpath
kotlin.mpp.allowMultiplePluginDeclarations=true

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -10,6 +10,15 @@ pluginManagement {
maven("https://us-central1-maven.pkg.dev/varabyte-repos/public")
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } // Added snapshots for plugins
}
plugins {
// Centralized Plugin Version Management
// This allows subprojects to apply plugins without versions, avoiding conflicts.
id("org.jetbrains.kotlin.multiplatform") version "2.3.0"
id("org.jetbrains.kotlin.plugin.serialization") version "2.3.0"
id("org.jetbrains.compose") version "1.10.0"
id("org.jetbrains.kotlin.plugin.compose") version "2.3.0"
id("app.cash.sqldelight") version "2.2.1"
}
}
plugins {