meldestelle/build.gradle.kts
Stefan Mogeritsch c613acb91d build: initialize JS target in root to fix isolated classpath issue
Explicitly added JS target with browser and Node.js configurations at the root level to ensure the NodeJsRootPlugin is loaded. Resolved the "IsolatedKotlinClasspathClassCastException" error in subprojects. Updated Yarn lock to reflect new dependencies.
2026-02-03 15:02:26 +01:00

399 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.tasks.KotlinCompile
import org.jlleitschuh.gradle.ktlint.KtlintExtension
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
import java.io.ByteArrayOutputStream
import java.util.zip.GZIPOutputStream
plugins {
// Version management plugin for dependency updates
alias(libs.plugins.benManesVersions)
// Kotlin plugins declared here with 'apply false' to centralize version management
// 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
// 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
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.spring.boot) apply false
alias(libs.plugins.spring.dependencyManagement) apply false
// Dokka plugin applied at root to create multi-module collector tasks
alias(libs.plugins.dokka)
// Static analysis (enabled at root and inherited by subprojects)
alias(libs.plugins.detekt)
alias(libs.plugins.ktlint)
}
// 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
// FIX: Explicitly initialize JS target at root to force NodeJsRootPlugin loading
// This prevents "IsolatedKotlinClasspathClassCastException" in subprojects
js {
browser()
nodejs()
}
}
// ##################################################################
// ### ALLPROJECTS CONFIGURATION ###
// ##################################################################
allprojects {
group = "at.mocode"
version = "1.0.0-SNAPSHOT"
// The 'repositories' block was removed from here.
// Repository configuration is now centralized in 'settings.gradle.kts'
// as per modern Gradle best practices. This resolves dependency resolution
// conflicts with platforms and Spring Boot 4+.
}
subprojects {
// FINALE KORREKTUR: Konsistente JVM-Target-Konfiguration für Java und Kotlin
// basierend auf der Tech-Stack-Referenz.
plugins.withType<org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper> {
extensions.configure<org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension> {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
}
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_25)
freeCompilerArgs.add("-Xannotation-default-target=param-property")
}
}
tasks.withType<Test>().configureEach {
useJUnitPlatform {
excludeTags("perf")
}
// Configure CDS in auto-mode to prevent bootstrap classpath warnings
jvmArgs("-Xshare:auto", "-Djdk.instrument.traceUsage=false")
// Increase test JVM memory with a stable configuration
minHeapSize = "512m"
maxHeapSize = "2g"
// Parallel test execution for better performance
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
// Removed byte-buddy-agent configuration to fix Gradle 9.0.0 deprecation warning
// The agent configuration was causing Task.project access at execution time
}
// ---------------------------------------------------------------------------
// Frontend/JS build noise reduction
// ---------------------------------------------------------------------------
// (B) Avoid noisy "will be copied ... overwriting" logs for Kotlin/JS *CompileSync tasks.
// The Kotlin JS plugin wires multiple resource sourcesets into the same destination.
// We keep the first occurrence and exclude duplicates.
tasks.withType<Copy>().configureEach {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
tasks.withType<Sync>().configureEach {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
// (A) Source map configuration is handled via `gradle.properties` (global Kotlin/JS settings)
// to avoid compiler-flag incompatibilities across toolchains.
// (B) JS test executable compilation/sync is currently very noisy (duplicate resource copying from jsMain + jsTest).
// We disable JS/WASM JS test executables in CI/build to keep output warning-free.
tasks.matching {
val n = it.name
n.contains("jsTest", ignoreCase = true) ||
n.contains("compileTestDevelopmentExecutableKotlinJs") ||
n.contains("compileTestDevelopmentExecutableKotlinWasmJs") ||
n.contains("TestDevelopmentExecutableCompileSync")
}.configureEach {
enabled = false
}
// Dedicated performance test task per JVM subproject
plugins.withId("java") {
val javaExt = extensions.getByType<JavaPluginExtension>()
// Ensure a full JDK toolchain with compiler is available (Gradle will auto-download if missing)
javaExt.toolchain.languageVersion.set(JavaLanguageVersion.of(25))
tasks.register<Test>("perfTest") {
description = "Runs tests tagged with 'perf'"
group = "verification"
// Use the regular test source set outputs
testClassesDirs = javaExt.sourceSets.getByName("test").output.classesDirs
classpath = javaExt.sourceSets.getByName("test").runtimeClasspath
useJUnitPlatform {
includeTags("perf")
}
shouldRunAfter("test")
// Keep the same JVM settings for consistency
jvmArgs("-Xshare:auto", "-Djdk.instrument.traceUsage=false")
maxHeapSize = "2g"
dependsOn("testClasses")
}
}
// Suppress Node.js deprecation warnings (e.g., DEP0040 punycode) during Kotlin/JS npm/yarn tasks
// Applies to all Exec-based tasks (covers Yarn/NPM invocations used by Kotlin JS plugin)
tasks.withType<Exec>().configureEach {
// Merge existing NODE_OPTIONS with --no-deprecation
val current = (environment["NODE_OPTIONS"] as String?) ?: System.getenv("NODE_OPTIONS")
val merged = if (current.isNullOrBlank()) "--no-deprecation" else "$current --no-deprecation"
environment("NODE_OPTIONS", merged)
// Also set the legacy switch to silence warnings entirely
environment("NODE_NO_WARNINGS", "1")
// Set a Chrome binary path to avoid snap permission issues
environment("CHROME_BIN", "/usr/bin/google-chrome-stable")
environment("CHROMIUM_BIN", "/usr/bin/chromium")
environment("PUPPETEER_EXECUTABLE_PATH", "/usr/bin/chromium")
}
// ------------------------------
// Detekt & Ktlint default setup
// ------------------------------
plugins.withId("io.gitlab.arturbosch.detekt") {
extensions.configure(DetektExtension::class.java) {
buildUponDefaultConfig = true
allRules = false
autoCorrect = false
config.setFrom(files(rootProject.file("config/detekt/detekt.yml")))
basePath = rootDir.absolutePath
}
tasks.withType<Detekt>().configureEach {
jvmTarget = "25"
reports {
xml.required.set(false)
txt.required.set(false)
sarif.required.set(false)
html.required.set(true)
}
}
}
plugins.withId("org.jlleitschuh.gradle.ktlint") {
extensions.configure(KtlintExtension::class.java) {
android.set(false)
outputToConsole.set(true)
ignoreFailures.set(false)
reporters {
reporter(ReporterType.CHECKSTYLE)
reporter(ReporterType.PLAIN)
}
}
}
}
// ------------------------------------------------------------------
// Bundle Size Budgets for Frontend Shells (Kotlin/JS)
// ------------------------------------------------------------------
// ✅ FIX: Klasse auf Top-Level verschieben
data class Budget(val rawBytes: Long, val gzipBytes: Long)
tasks.register("checkBundleBudget") {
group = "verification"
description = "Checks JS bundle sizes of frontend shells against configured budgets"
doLast {
val budgetsFile = file("config/bundles/budgets.json")
if (!budgetsFile.exists()) {
throw GradleException("Budgets file not found: ${budgetsFile.path}")
}
// Load budgets JSON as simple Map<String, Budget>
val text = budgetsFile.readText()
@Suppress("UNCHECKED_CAST")
val parsed =
JsonSlurper().parseText(text) as Map<String, Map<String, Any?>>
val budgets =
parsed.mapValues { (_, v) ->
val raw = (v["rawBytes"] as Number).toLong()
val gz = (v["gzipBytes"] as Number).toLong()
Budget(raw, gz)
}
fun gzipSize(bytes: ByteArray): Long {
val baos = ByteArrayOutputStream()
GZIPOutputStream(baos).use { it.write(bytes) }
return baos.toByteArray().size.toLong()
}
val errors = mutableListOf<String>()
val report = StringBuilder()
report.appendLine("Bundle Budget Report (per shell)")
// Consider modules under :frontend:shells: as shells
val shellPrefix = ":frontend:shells:"
val shells = rootProject.subprojects.filter { it.path.startsWith(shellPrefix) }
if (shells.isEmpty()) {
report.appendLine("No frontend shells found under $shellPrefix")
}
shells.forEach { shell ->
val key = shell.path.trimStart(':').replace(':', '/') // or use a colon form for budgets keys below
val colonKey = shell.path.trimStart(':').replace('/', ':').trim() // ensure ":a:b:c"
// Budgets are keyed by a Gradle path with colons but without leading colon in config for readability
val budgetKeyCandidates =
listOf(
// e.g., frontend:shells:meldestelle-portal
shell.path.removePrefix(":"),
colonKey.removePrefix(":"),
shell.name,
)
val budgetEntry = budgetKeyCandidates.firstNotNullOfOrNull { budgets[it] }
if (budgetEntry == null) {
report.appendLine("- ${shell.path}: No budget configured (skipping)")
return@forEach
}
// Locate distributions directory
val distDir = shell.layout.buildDirectory.dir("distributions").get().asFile
if (!distDir.exists()) {
report.appendLine("- ${shell.path}: distributions dir not found (expected build/distributions) did you build the JS bundle?")
return@forEach
}
// Collect JS files under distributions (avoid .map and .txt)
val jsFiles =
distDir.walkTopDown().filter { it.isFile && it.extension == "js" && !it.name.endsWith(".map") }.toList()
if (jsFiles.isEmpty()) {
report.appendLine("- ${shell.path}: no JS artifacts found in ${distDir.path}")
return@forEach
}
var totalRaw = 0L
var totalGzip = 0L
val topFiles = mutableListOf<Pair<String, Long>>()
jsFiles.forEach { f ->
val bytes = f.readBytes()
val raw = bytes.size.toLong()
val gz = gzipSize(bytes)
totalRaw += raw
totalGzip += gz
topFiles += f.name to raw
}
val top = topFiles.sortedByDescending { it.second }.take(5)
report.appendLine("- ${shell.path}:")
report.appendLine(" raw: $totalRaw bytes (budget ${budgetEntry.rawBytes})")
report.appendLine(" gzip: $totalGzip bytes (budget ${budgetEntry.gzipBytes})")
report.appendLine(" top files:")
top.forEach { (n, s) -> report.appendLine(" - $n: $s bytes") }
if (totalRaw > budgetEntry.rawBytes || totalGzip > budgetEntry.gzipBytes) {
errors += "${shell.path}: raw=$totalRaw/${budgetEntry.rawBytes}, gzip=$totalGzip/${budgetEntry.gzipBytes}"
}
}
val outDir = layout.buildDirectory.dir("reports/bundles").get().asFile
outDir.mkdirs()
file(outDir.resolve("bundle-budgets.txt")).writeText(report.toString())
if (errors.isNotEmpty()) {
val msg =
buildString {
appendLine("Bundle budget violations:")
errors.forEach { appendLine(" - $it") }
appendLine()
appendLine("See report: ${outDir.resolve("bundle-budgets.txt").path}")
}
throw GradleException(msg)
} else {
println(report.toString())
println("Bundle budgets OK. Report saved to ${outDir.resolve("bundle-budgets.txt").path}")
}
}
}
// Composite verification task including static analyzers if present
tasks.register("staticAnalysis") {
group = "verification"
description = "Run static analysis (detekt, ktlint) and architecture guards"
// Plugins provide these tasks; only 'depend on' if tasks exist
dependsOn(
tasks.matching { it.name == "detekt" },
tasks.matching { it.name == "ktlintCheck" },
// ARCHITECTURE-TESTS: Replaced old archGuards with the new test module
project(":platform:architecture-tests").tasks.named("test"),
)
}
// ##################################################################
// ### DOKKA (Multi-Module) ###
// ##################################################################
// Apply Dokka (V2) automatically to Kotlin subprojects
subprojects {
plugins.withId("org.jetbrains.kotlin.jvm") { apply(plugin = "org.jetbrains.dokka") }
plugins.withId("org.jetbrains.kotlin.multiplatform") { apply(plugin = "org.jetbrains.dokka") }
}
// Aggregate tasks to build multi-module docs in Markdown (GFM) and HTML
// Unified V2 aggregator: builds docs via `dokkaGenerate` in subprojects and aggregates outputs
val dokkaAll =
tasks.register("dokkaAll") {
group = "documentation"
description = "Builds Dokka (V2) for all modules and aggregates outputs under build/dokka/all"
// Trigger Dokka generation in all subprojects that have the Dokka plugin
dependsOn(
subprojects
.filter { it.plugins.hasPlugin("org.jetbrains.dokka") }
.map { "${it.path}:dokkaGenerate" },
)
doLast {
val dest = layout.buildDirectory.dir("dokka/all").get().asFile
if (dest.exists()) dest.deleteRecursively()
dest.mkdirs()
subprojects.filter { it.plugins.hasPlugin("org.jetbrains.dokka") }.forEach { p ->
// Dokka V2 writes into build/dokka; copy everything to keep format/plugins agnostic
val out = p.layout.buildDirectory.dir("dokka").get().asFile
if (out.exists()) {
out.copyRecursively(File(dest, p.path.trimStart(':').replace(':', '/')), overwrite = true)
}
}
println("[DOKKA] Aggregated Dokka V2 outputs into ${dest.absolutePath}")
}
}
// ##################################################################
// ### DOKU-AGGREGATOR ###
// ##################################################################
// Leichter Aggregator im Root-Projekt, ruft die eigentlichen Tasks im :docs Subprojekt auf
tasks.register("docs") {
description = "Aggregates documentation tasks from :docs"
group = "documentation"
dependsOn(":docs:generateAllDocs")
}
// Wrapper-Konfiguration
// Apply Node warning suppression on root project Exec tasks as well
// Ensures aggregated Kotlin/JS tasks created at root (e.g., kotlinNpmInstall) inherit the env
tasks.withType<Exec>().configureEach {
val current = (environment["NODE_OPTIONS"] as String?) ?: System.getenv("NODE_OPTIONS")
val merged = if (current.isNullOrBlank()) "--no-deprecation" else "$current --no-deprecation"
environment("NODE_OPTIONS", merged)
environment("NODE_NO_WARNINGS", "1")
// Set a Chrome binary path to avoid snap permission issues
environment("CHROME_BIN", "/usr/bin/google-chrome-stable")
environment("CHROMIUM_BIN", "/usr/bin/chromium")
environment("PUPPETEER_EXECUTABLE_PATH", "/usr/bin/chromium")
}
tasks.wrapper {
gradleVersion = "9.3.1"
distributionType = Wrapper.DistributionType.BIN
}