refactor(gradle, desktop): Build-Konfiguration bereinigt, Ports optimiert und UI-Logik konsolidiert

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-05-09 14:27:17 +02:00
parent 280db663c7
commit 8d176ce955
10 changed files with 123 additions and 107 deletions
+2 -1
View File
@@ -90,7 +90,7 @@ subprojects {
jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED") jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED")
// Suppress ByteBuddy/Mockito dynamic agent loading warnings (Java 21+) // Suppress ByteBuddy/Mockito dynamic agent loading warnings (Java 21+)
jvmArgs("-XX:+EnableDynamicAgentLoading") jvmArgs("-XX:+EnableDynamicAgentLoading")
// Increase test JVM memory with a stable configuration jvmArgs("--enable-native-access=ALL-UNNAMED")
minHeapSize = "512m" minHeapSize = "512m"
maxHeapSize = "2g" maxHeapSize = "2g"
// Parallel test execution for better performance // Parallel test execution for better performance
@@ -166,6 +166,7 @@ subprojects {
jvmArgs("-Xshare:auto", "-Djdk.instrument.traceUsage=false") jvmArgs("-Xshare:auto", "-Djdk.instrument.traceUsage=false")
jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED") jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED")
jvmArgs("-XX:+EnableDynamicAgentLoading") jvmArgs("-XX:+EnableDynamicAgentLoading")
jvmArgs("--enable-native-access=ALL-UNNAMED")
maxHeapSize = "2g" maxHeapSize = "2g"
dependsOn("testClasses") dependsOn("testClasses")
} }
@@ -2,12 +2,6 @@ package at.mocode.frontend.core.localdb
import org.koin.dsl.module import org.koin.dsl.module
/**
* Thin wrapper around SQLDelight `AppDatabase` creation.
*
* The platform-specific part is the `DatabaseDriverFactory` (expect/actual),
* which provides the appropriate SQLDelight driver (JVM sqlite driver, JS WebWorkerDriver, ...).
*/
class DatabaseProvider( class DatabaseProvider(
private val driverFactory: DatabaseDriverFactory private val driverFactory: DatabaseDriverFactory
) { ) {
@@ -17,9 +11,6 @@ class DatabaseProvider(
} }
} }
/**
* Koin module to provide the SQLDelight database for all frontend targets.
*/
val localDbModule = module { val localDbModule = module {
single<DatabaseDriverFactory> { DatabaseDriverFactory() } single<DatabaseDriverFactory> { DatabaseDriverFactory() }
single<DatabaseProvider> { DatabaseProvider(get()) } single<DatabaseProvider> { DatabaseProvider(get()) }
@@ -45,6 +45,7 @@ class JvmP2pSyncService : P2pSyncService {
} }
// Prozessweiter, portbasierter Guard // Prozessweiter, portbasierter Guard
println("[P2P Server] Versuche Port $port zu reservieren...")
if (!startedPorts.add(port)) { if (!startedPorts.add(port)) {
println("[P2P Server] Bereits gestartet (Prozess) auf Port $port idempotent, kein neuer Bind") println("[P2P Server] Bereits gestartet (Prozess) auf Port $port idempotent, kein neuer Bind")
return return
@@ -79,7 +80,6 @@ class JvmP2pSyncService : P2pSyncService {
} }
}.start(wait = false) }.start(wait = false)
currentPort = port currentPort = port
println("[P2P Server] Gestartet auf Port $port")
} catch (e: Exception) { } catch (e: Exception) {
// Start fehlgeschlagen -> Port-Lock wieder freigeben // Start fehlgeschlagen -> Port-Lock wieder freigeben
startedPorts.remove(port) startedPorts.remove(port)
@@ -33,6 +33,8 @@ import androidx.compose.ui.unit.sp
import at.mocode.frontend.features.device.initialization.domain.DeviceInitializationValidator import at.mocode.frontend.features.device.initialization.domain.DeviceInitializationValidator
import at.mocode.frontend.features.device.initialization.domain.model.AppThemeSetting import at.mocode.frontend.features.device.initialization.domain.model.AppThemeSetting
import at.mocode.frontend.features.device.initialization.domain.model.NetworkRole import at.mocode.frontend.features.device.initialization.domain.model.NetworkRole
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.milliseconds
@Composable @Composable
private fun DiscoveryRadar( private fun DiscoveryRadar(
@@ -94,7 +96,7 @@ fun DeviceInitializationScreen(
// Automatische Discovery starten // Automatische Discovery starten
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.startDiscovery() viewModel.startDiscovery()
roleSelectorFocus.requestFocus() delay(100.milliseconds); withFrameMillis { roleSelectorFocus.requestFocus() }
} }
Surface( Surface(
@@ -48,12 +48,6 @@ actual fun DeviceInitializationConfig(
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val (_, sharedKeyFocus, backupPathFocus, clientNameFocus, clientRoleFocus) = remember { FocusRequester.createRefs() } val (_, sharedKeyFocus, backupPathFocus, clientNameFocus, clientRoleFocus) = remember { FocusRequester.createRefs() }
LaunchedEffect(Unit) {
if (settings.deviceName.isEmpty()) {
deviceNameFocus.requestFocus()
}
}
Card( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.large, shape = MaterialTheme.shapes.large,
@@ -66,7 +60,7 @@ actual fun DeviceInitializationConfig(
value = settings.deviceName, value = settings.deviceName,
onValueChange = { viewModel.updateSettings { s -> s.copy(deviceName = it) } }, onValueChange = { viewModel.updateSettings { s -> s.copy(deviceName = it) } },
label = "Gerätename", label = "Gerätename",
helpDescription = "Ein eindeutiger Name für diesen PC (z.B. 'Richter-Springplatz').", helpDescription = "Ein eindeutiger Name für diesen PC (z.B. Richter-Springplatz).",
placeholder = "z.B. Meldestelle-PC-1", placeholder = "z.B. Meldestelle-PC-1",
isError = settings.deviceName.isNotEmpty() && !DeviceInitializationValidator.isNameValid(settings.deviceName), isError = settings.deviceName.isNotEmpty() && !DeviceInitializationValidator.isNameValid(settings.deviceName),
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_NAME_LENGTH} Zeichen erforderlich.", errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_NAME_LENGTH} Zeichen erforderlich.",
@@ -77,24 +71,43 @@ actual fun DeviceInitializationConfig(
compact = true compact = true
) )
// NETZWERK-INTERFACES (EXPERTEN-MODUS)
val interfaces = remember { val interfaces = remember {
NetworkInterface.getNetworkInterfaces().toList() NetworkInterface.getNetworkInterfaces().toList()
.filter { it.isUp && !it.isLoopback && it.inetAddresses.hasMoreElements() } .filter { it.isUp && !it.isLoopback && it.inetAddresses.hasMoreElements() && !it.name.startsWith("br-") && !it.name.startsWith("docker") && !it.name.startsWith("veth") }
.map { ni -> .map { ni ->
val friendlyName = when { val friendlyName = when {
ni.displayName.contains("wlan", ignoreCase = true) || ni.displayName.contains("wi-fi", ignoreCase = true) || ni.name.contains("wlan", ignoreCase = true) -> "🌐 WLAN" ni.displayName.contains("wlan", ignoreCase = true) || ni.displayName.contains(
ni.displayName.contains("eth", ignoreCase = true) || ni.displayName.contains("ethernet", ignoreCase = true) || ni.name.contains("eth", ignoreCase = true) || ni.name.contains("en", ignoreCase = true) -> "🔌 Ethernet" "wi-fi",
ignoreCase = true
) || ni.name.contains("wlan", ignoreCase = true) -> "🌐 WLAN"
ni.displayName.contains("eth", ignoreCase = true) || ni.displayName.contains(
"ethernet",
ignoreCase = true
) || ni.name.contains("eth", ignoreCase = true) || ni.name.contains(
"en",
ignoreCase = true
) -> "🔌 Ethernet"
else -> "💻 " + ni.displayName else -> "💻 " + ni.displayName
} }
val address = ni.inetAddresses.asSequence().firstOrNull { !it.isLinkLocalAddress && it.hostAddress.indexOf(':') == -1 }?.hostAddress val address = ni.inetAddresses.asSequence()
.firstOrNull { !it.isLinkLocalAddress && it.hostAddress.indexOf(":") == -1 }?.hostAddress
?: ni.inetAddresses.nextElement().hostAddress ?: ni.inetAddresses.nextElement().hostAddress
val isConnected = !ni.isLoopback && ni.isUp && ni.interfaceAddresses.any { val isConnected = !ni.isLoopback && ni.isUp && ni.interfaceAddresses.any {
it.address.isSiteLocalAddress || it.address.hostAddress.startsWith("192.168") || it.address.hostAddress.startsWith("10.") it.address.isSiteLocalAddress || it.address.hostAddress.startsWith("192.168") || it.address.hostAddress.startsWith(
"10."
)
} }
InterfaceInfo(id = "$friendlyName ($address)", name = friendlyName, address = address, hardwareName = ni.name, isConnected = isConnected) InterfaceInfo(
id = "$friendlyName ($address)",
name = friendlyName,
address = address,
hardwareName = ni.name,
isConnected = isConnected
)
} }
} }
@@ -120,12 +133,17 @@ actual fun DeviceInitializationConfig(
Surface( Surface(
onClick = { if (!uiState.isLocked) viewModel.updateSettings { s -> s.copy(networkInterface = info.id) } }, onClick = { if (!uiState.isLocked) viewModel.updateSettings { s -> s.copy(networkInterface = info.id) } },
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f), color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant.copy(
alpha = 0.3f
),
border = if (isSelected) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null, border = if (isSelected) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(10.dp).background(if (info.isConnected) Color(0xFF4CAF50) else Color(0xFFF44336), CircleShape)) Box(
Modifier.size(10.dp)
.background(if (info.isConnected) Color(0xFF4CAF50) else Color(0xFFF44336), CircleShape)
)
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Text(info.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold) Text(info.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
@@ -137,13 +155,12 @@ actual fun DeviceInitializationConfig(
} }
} }
// SICHERHEITSSCHLÜSSEL
var passwordVisible by remember { mutableStateOf(false) } var passwordVisible by remember { mutableStateOf(false) }
MsTextField( MsTextField(
value = settings.sharedKey, value = settings.sharedKey,
onValueChange = { viewModel.updateSettings { s -> s.copy(sharedKey = it) } }, onValueChange = { viewModel.updateSettings { s -> s.copy(sharedKey = it) } },
label = "Sicherheitsschlüssel (Sync-Key)", label = "Sicherheitsschlüssel (Sync-Key)",
helpDescription = "Das 'Turnier-Passwort'. Muss auf allen Geräten gleich sein.", helpDescription = "Das Turnier-Passwort. Muss auf allen Geräten gleich sein.",
placeholder = "Mindestens 8 Zeichen", placeholder = "Mindestens 8 Zeichen",
isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey), isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey),
errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.", errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.",
@@ -157,7 +174,6 @@ actual fun DeviceInitializationConfig(
compact = true compact = true
) )
// CLIENT-VERBINDUNG-FEEDBACK
if (settings.networkRole == NetworkRole.CLIENT && !uiState.isLocked) { if (settings.networkRole == NetworkRole.CLIENT && !uiState.isLocked) {
val masterSelected = uiState.selectedMaster != null val masterSelected = uiState.selectedMaster != null
val canConnect = masterSelected && settings.sharedKey.isNotBlank() val canConnect = masterSelected && settings.sharedKey.isNotBlank()
@@ -170,13 +186,19 @@ actual fun DeviceInitializationConfig(
else -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.1f) else -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.1f)
}, },
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
border = BorderStroke(1.dp, when (uiState.connectionStatus) { border = BorderStroke(
1.dp, when (uiState.connectionStatus) {
ConnectionStatus.CONNECTED -> Color(0xFF4CAF50) ConnectionStatus.CONNECTED -> Color(0xFF4CAF50)
ConnectionStatus.FAILED -> Color(0xFFF44336) ConnectionStatus.FAILED -> Color(0xFFF44336)
else -> MaterialTheme.colorScheme.outlineVariant else -> MaterialTheme.colorScheme.outlineVariant
}) }
)
) {
Column(
Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
Column(Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
when (uiState.connectionStatus) { when (uiState.connectionStatus) {
ConnectionStatus.CONNECTING -> CircularProgressIndicator(Modifier.size(20.dp), strokeWidth = 2.dp) ConnectionStatus.CONNECTING -> CircularProgressIndicator(Modifier.size(20.dp), strokeWidth = 2.dp)
@@ -210,7 +232,6 @@ actual fun DeviceInitializationConfig(
} }
} }
// BACKUP & DRUCKER
MsFilePicker( MsFilePicker(
label = "Backup-Verzeichnis (Plan-USB)", label = "Backup-Verzeichnis (Plan-USB)",
selectedPath = settings.backupPath, selectedPath = settings.backupPath,
@@ -246,10 +267,13 @@ actual fun DeviceInitializationConfig(
) )
} }
// MASTER: ERWARTETE CLIENTS
if (settings.networkRole == NetworkRole.MASTER && !uiState.isLocked) { if (settings.networkRole == NetworkRole.MASTER && !uiState.isLocked) {
HorizontalDivider(Modifier.padding(vertical = 8.dp)) HorizontalDivider(Modifier.padding(vertical = 8.dp))
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("👥 Erwartete Clients", style = MaterialTheme.typography.titleSmall) Text("👥 Erwartete Clients", style = MaterialTheme.typography.titleSmall)
TextButton(onClick = { viewModel.addExpectedClient() }) { TextButton(onClick = { viewModel.addExpectedClient() }) {
Icon(Icons.Default.Add, null, Modifier.size(18.dp)) Icon(Icons.Default.Add, null, Modifier.size(18.dp))
@@ -295,7 +319,12 @@ actual fun DeviceInitializationConfig(
}, },
trailingContent = { trailingContent = {
IconButton(onClick = { viewModel.removeExpectedClient(index) }) { IconButton(onClick = { viewModel.removeExpectedClient(index) }) {
Icon(Icons.Default.Delete, null, tint = MaterialTheme.colorScheme.error, modifier = Modifier.size(20.dp)) Icon(
Icons.Default.Delete,
null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(20.dp)
)
} }
}, },
colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)), colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)),
@@ -307,4 +336,10 @@ actual fun DeviceInitializationConfig(
} }
} }
private data class InterfaceInfo(val id: String, val name: String, val address: String, val hardwareName: String, val isConnected: Boolean) private data class InterfaceInfo(
val id: String,
val name: String,
val address: String,
val hardwareName: String,
val isConnected: Boolean
)
@@ -2,9 +2,6 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
/**
* Dieses Modul kapselt die gesamte UI und Logik für das Ping-Feature.
*/
plugins { plugins {
alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeMultiplatform)
@@ -17,14 +14,9 @@ version = "1.0.0"
kotlin { kotlin {
jvm() jvm()
wasmJs { wasmJs {
binaries.library() binaries.library()
browser { browser { testTask { enabled = false } }
testTask {
enabled = false
}
}
} }
sourceSets { sourceSets {
@@ -33,12 +25,13 @@ kotlin {
implementation(projects.frontend.core.designSystem) implementation(projects.frontend.core.designSystem)
implementation(projects.frontend.core.sync) implementation(projects.frontend.core.sync)
implementation(projects.frontend.core.localDb) implementation(projects.frontend.core.localDb)
implementation(projects.frontend.core.auth) // Added auth module for AuthTokenManager implementation(projects.frontend.core.auth)
implementation(libs.sqldelight.coroutines) implementation(libs.sqldelight.coroutines)
implementation(projects.frontend.core.domain) implementation(projects.frontend.core.domain)
implementation(compose.foundation) // Explizite Compose-Abhängigkeiten zur Vermeidung von Gradle 10 Warnungen
implementation(compose.runtime) implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3) implementation(compose.material3)
implementation(compose.ui) implementation(compose.ui)
implementation(compose.components.resources) implementation(compose.components.resources)
@@ -49,7 +42,7 @@ kotlin {
implementation(libs.bundles.compose.common) implementation(libs.bundles.compose.common)
implementation(libs.koin.core) implementation(libs.koin.core)
implementation(libs.koin.compose) // Added koin.compose for koinInject implementation(libs.koin.compose)
} }
commonTest.dependencies { commonTest.dependencies {
@@ -72,6 +65,5 @@ kotlin {
wasmJsMain.dependencies { wasmJsMain.dependencies {
implementation(libs.kotlin.stdlib.wasm.js) implementation(libs.kotlin.stdlib.wasm.js)
} }
} }
} }
@@ -91,6 +91,7 @@ kotlin {
// Bundles // Bundles
implementation(libs.bundles.kmp.common) implementation(libs.bundles.kmp.common)
implementation(libs.bundles.compose.common) implementation(libs.bundles.compose.common)
implementation(libs.logback.classic)
} }
jvmTest.dependencies { jvmTest.dependencies {
@@ -178,6 +179,7 @@ compose.desktop {
// JVM-Argumente für die gepackte Anwendung // JVM-Argumente für die gepackte Anwendung
jvmArgs( jvmArgs(
"--enable-native-access=ALL-UNNAMED",
"-Xms128m", "-Xms128m",
"-Xmx512m", "-Xmx512m",
"-Dfile.encoding=UTF-8", "-Dfile.encoding=UTF-8",
@@ -8,7 +8,6 @@ import at.mocode.frontend.core.auth.di.authModule
import at.mocode.frontend.core.localdb.AppDatabase import at.mocode.frontend.core.localdb.AppDatabase
import at.mocode.frontend.core.localdb.DatabaseProvider import at.mocode.frontend.core.localdb.DatabaseProvider
import at.mocode.frontend.core.localdb.localDbModule import at.mocode.frontend.core.localdb.localDbModule
import at.mocode.frontend.core.network.NetworkConfig
import at.mocode.frontend.core.network.chat.KtorWebSocketServerService import at.mocode.frontend.core.network.chat.KtorWebSocketServerService
import at.mocode.frontend.core.network.discovery.NetworkDiscoveryService import at.mocode.frontend.core.network.discovery.NetworkDiscoveryService
import at.mocode.frontend.core.network.networkModule import at.mocode.frontend.core.network.networkModule
@@ -62,42 +61,32 @@ fun main() = application {
desktopModule, desktopModule,
) )
} }
println("[DesktopApp] KOIN initialisiert")
// Base URL Log für schnelle Fehlerdiagnose
println("[Network] baseUrl=${NetworkConfig.baseUrl}")
// Starte Netzwerk-Dienste für den POC // Datenbank EAGER initialisieren (JVM-safe via runBlocking)
val koin = GlobalContext.get() val koin = GlobalContext.get()
val dbProvider = koin.get<DatabaseProvider>()
val database = runBlocking { dbProvider.createDatabase() }
loadKoinModules(module {
single<AppDatabase> { database }
})
println("[DesktopApp] KOIN & DB initialisiert")
// Start POC Netzwerk-Dienste
try { try {
val wsServer = koin.get<KtorWebSocketServerService>() val wsServer = koin.get<KtorWebSocketServerService>()
wsServer.start() wsServer.start()
val discovery = koin.get<NetworkDiscoveryService>() val discovery = koin.get<NetworkDiscoveryService>()
discovery.startDiscovery() discovery.startDiscovery()
// Im Host-Modus würden wir hier registerService aufrufen.
// Für den POC registrieren wir den lokalen Host-Dienst immer mit dem WS-Port
try {
discovery.registerService(wsServer.getPort()) discovery.registerService(wsServer.getPort())
println("[DesktopApp] Discovery-Registrierung durchgeführt (Port ${wsServer.getPort()})")
} catch(e: Exception) { } catch(e: Exception) {
println("[DesktopApp] Discovery-Registrierung fehlgeschlagen: ${e.message}") println("[DesktopApp] Netzwerk-Dienste Fehler: %s".format(e.message))
}
} catch(e: Exception) {
println("[DesktopApp] POC-Dienste konnten nicht gestartet werden: ${e.message}")
} }
// Testdaten für Prototyp laden
at.mocode.frontend.shell.desktop.data.Store.seed() at.mocode.frontend.shell.desktop.data.Store.seed()
} catch (e: Exception) { } catch (e: Exception) {
println("[DesktopApp] Koin-Warnung: ${e.message}") println("[DesktopApp] Startup-Fehler: %s".format(e.message))
}
try {
val provider = GlobalContext.get().get<DatabaseProvider>()
val db = runBlocking { provider.createDatabase() }
loadKoinModules(module { single<AppDatabase> { db } })
println("[DesktopApp] Lokale DB bereit")
} catch (e: Exception) {
println("[DesktopApp] DB-Warnung: ${e.message}")
} }
Window( Window(
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<!-- JmDNS ist extrem gesprächig auf DEBUG/TRACE -->
<logger name="javax.jmdns" level="INFO"/>
<logger name="io.netty" level="INFO"/>
</configuration>
+14 -26
View File
@@ -4,31 +4,29 @@ android.nonTransitiveRClass=true
# Kotlin Configuration # Kotlin Configuration
kotlin.code.style=official kotlin.code.style=official
# Increased Kotlin Daemon Heap for JS Compilation # Increased Kotlin Daemon Heap for JS Compilation + JDK 25 Warning Suppression
kotlin.daemon.jvmargs=-Xmx8g -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g kotlin.daemon.jvmargs=-Xmx8g -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --enable-native-access=ALL-UNNAMED
kotlin.js.compiler.sourcemaps=false kotlin.js.compiler.sourcemaps=false
# Kotlin Compiler Optimizations (Phase 5) # Kotlin Compiler Optimizations
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.incremental.js=true kotlin.incremental.js=true
kotlin.caching.enabled=true kotlin.caching.enabled=true
kotlin.compiler.execution.strategy=in-process kotlin.compiler.execution.strategy=in-process
# kotlin.compiler.preciseCompilationResultsBackup=true
kotlin.stdlib.default.dependency=true kotlin.stdlib.default.dependency=true
# Gradle Configuration # Gradle Configuration
# Increased Gradle Daemon Heap # Optimized for JDK 25: Added --add-opens and --enable-native-access for compiler tools
org.gradle.jvmargs=-Xmx8g -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx6g" -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true org.gradle.jvmargs=-Xmx8g -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --enable-native-access=ALL-UNNAMED -Djdk.instrument.traceUsage=false
org.gradle.workers.max=8 org.gradle.workers.max=8
org.gradle.vfs.watch=true org.gradle.vfs.watch=true
# Configuration Cache optimieren - TEMPORÄR DEAKTIVIERT wegen JS-Test Serialisierungsproblemen # Configuration Cache (JS-Test workaround)
org.gradle.configuration-cache=false org.gradle.configuration-cache=false
org.gradle.configuration-cache.problems=warn org.gradle.configuration-cache.problems=warn
# Build Performance verbessern # Build Performance
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=true org.gradle.caching=true
@@ -46,7 +44,7 @@ org.jetbrains.kotlin.wasm.check.wasm.binary.format=false
kotlin.native.ignoreDisabledTargets=true kotlin.native.ignoreDisabledTargets=true
idea.project.settings.delegate.build.run.actions.to.gradle=true idea.project.settings.delegate.build.run.actions.to.gradle=true
# Enable NPM/Yarn lifecycle scripts for Kotlin/JS (required for sql.js & worker setup) # NPM/Yarn lifecycle
kotlin.js.yarn.ignoreScripts=false kotlin.js.yarn.ignoreScripts=false
org.jetbrains.kotlin.js.yarn.ignoreScripts=false org.jetbrains.kotlin.js.yarn.ignoreScripts=false
kotlin.js.npm.ignoreScripts=false kotlin.js.npm.ignoreScripts=false
@@ -56,31 +54,21 @@ org.jetbrains.kotlin.js.npm.ignoreScripts=false
org.gradle.logging.level=lifecycle org.gradle.logging.level=lifecycle
kotlin.build.report.single_file=false kotlin.build.report.single_file=false
# Compose Experimental Features # Compose Experimental
org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.wasm.enabled=true org.jetbrains.compose.experimental.wasm.enabled=true
# Java Toolchain: ensure Gradle auto-downloads a full JDK when needed # Java Toolchain
org.gradle.java.installations.auto-download=true org.gradle.java.installations.auto-download=true
org.gradle.java.installations.auto-detect=true org.gradle.java.installations.auto-detect=true
# Development Environment Support # Feature Toggles
dev.port.offset=0
# Set dev.port.offset=100 for second developer
# Set dev.port.offset=200 for the third developer
# ------------------------------------------------------------------
# Wasm/JS Feature Toggle
# ------------------------------------------------------------------
# Setze enableWasm=true, um die Web-App zu bauen oder Web-spezifische
# Module zu testen. Default=false spart massiv Zeit beim Desktop-Build.
enableWasm=true enableWasm=true
enableDesktop=true enableDesktop=true
dev.port.offset=0
# Dokka Gradle plugin V2 mode (with helpers for V1 compatibility) # Dokka V2
# See https://kotl.in/dokka-gradle-migration
# org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
# Workaround for Gradle 9 / KMP "Plugin loaded multiple times" error in Docker/CI # Gradle 9 Workaround
# This allows subprojects to re-declare plugins even if they are already on the classpath
kotlin.mpp.allowMultiplePluginDeclarations=true kotlin.mpp.allowMultiplePluginDeclarations=true