From 9711ce8a744612ba8acc8fbf32fa7302ffeb11ba Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Tue, 24 May 2022 12:44:44 +0200 Subject: [PATCH 01/54] chore(gradle): remove xmlpull and document update issues --- GUIDELINES.md | 14 +++++++++++++- sdk/build.gradle.kts | 5 ++--- sdk/src/test/config/KotestConfig.kt | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/GUIDELINES.md b/GUIDELINES.md index cffcda96e..5a0a0bb76 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -2,6 +2,18 @@ This document captures development standards and architecture decisions of this project as a point of reference. +## Gradle + +We build everything with gradle, nesting projects if needed. +Currently we have some pending updates, due to the following incompatibilities: + +- The new johnrengelman.shadow plugin version needs Gradle 7 +- The current Dokka version can neither handle Gradle 7 nor Kotlin 1.5.30+ nor Java beyond version 8 + +The bottleneck is updating dokka, +which is a longer process tracked in +https://github.com/software-challenge/backend/pull/404 + ## Testing Unsere Unittests nutzen das [Kotest-Framework](https://kotest.io) @@ -58,7 +70,7 @@ annotate the serialized fields with a concrete type instead. Ideally these fields should then be private with generically typed getters as to not expose the implementation details internally. -## Cloning +## Object Cloning Relevant discussion: https://github.com/software-challenge/backend/pull/148 diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts index f43fafdc5..1751413d4 100644 --- a/sdk/build.gradle.kts +++ b/sdk/build.gradle.kts @@ -17,7 +17,7 @@ configurations { } artifacts { - val kt = tasks["compileTestConfigKotlin"] + val kt = tasks.getByName("compileTestConfigKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class) add("testConfig", kt.outputs.files.first()) { builtBy(kt) } @@ -26,13 +26,12 @@ configurations.archives.get().artifacts.removeIf { it.name == "testConfig" } dependencies { api(kotlin("stdlib")) - api("com.thoughtworks.xstream", "xstream", "1.4.17") // New security config for 1.4.20 + api("com.thoughtworks.xstream", "xstream", "1.4.19") // New security config for 1.4.20 api("jargs", "jargs", "1.0") api("org.slf4j", "slf4j-api", "2.0.9") implementation("org.hamcrest", "hamcrest-core", "2.2") implementation("net.sf.kxml", "kxml2", "2.3.0") - implementation("xmlpull", "xmlpull", "1.1.3.1") "testConfigApi"("io.kotest", "kotest-assertions-core") "testConfigApi"("io.kotest", "kotest-runner-junit5-jvm", "5.0.3") diff --git a/sdk/src/test/config/KotestConfig.kt b/sdk/src/test/config/KotestConfig.kt index 2b8338148..cf4c5ec4e 100644 --- a/sdk/src/test/config/KotestConfig.kt +++ b/sdk/src/test/config/KotestConfig.kt @@ -5,6 +5,7 @@ import io.kotest.core.spec.IsolationMode import io.kotest.core.test.TestCaseOrder import kotlin.time.Duration.Companion.seconds +@OptIn(ExperimentalTime::class) object KotestConfig: AbstractProjectConfig() { override val parallelism = Runtime.getRuntime().availableProcessors() From ad02e058bd8d999105f5aee0b91d99e0cb546098 Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Thu, 6 May 2021 14:33:37 +0200 Subject: [PATCH 02/54] chore(gradle): update to gradle 7 Migrate maven to maven-publish --- gradle/build.gradle.kts | 34 +++++++++++++++--------- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/gradle/build.gradle.kts b/gradle/build.gradle.kts index e749767de..5f3e95ecd 100644 --- a/gradle/build.gradle.kts +++ b/gradle/build.gradle.kts @@ -7,11 +7,11 @@ import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicBoolean plugins { - maven kotlin("jvm") version "1.6.21" id("org.jetbrains.dokka") version "0.10.1" id("scripts-task") id("idea") + `maven-publish` id("com.github.ben-manes.versions") version "0.42.0" // only upgrade with Gradle 7: https://github.com/ben-manes/gradle-versions-plugin/issues/778 id("se.patrikerdes.use-latest-versions") version "0.2.18" @@ -285,8 +285,21 @@ allprojects { } if (this.name in documentedProjects) { - apply(plugin = "maven") + apply(plugin = "maven-publish") apply(plugin = "org.jetbrains.dokka") + publishing { + publications { + create(name) { + println(components.joinToString()) + from(components["java"]) + version = rootProject.version.toString() + } + } + } + java { + withSourcesJar() + withJavadocJar() + } tasks { val doc by creating(DokkaTask::class) { group = "documentation" @@ -300,17 +313,14 @@ allprojects { archiveBaseName.set(jar.get().archiveBaseName) archiveClassifier.set("javadoc") } - val sourcesJar by creating(Jar::class) { - group = "build" - archiveBaseName.set(jar.get().archiveBaseName) - archiveClassifier.set("sources") - from(sourceSets.main.get().allSource) - } - install { - dependsOn(docJar, sourcesJar) - } + //val sourcesJar by creating(Jar::class) { + // group = "build" + // archiveBaseName.set(jar.get().archiveBaseName) + // archiveClassifier.set("sources") + // from(sourceSets.main.get().allSource) + //} artifacts { - archives(sourcesJar.archiveFile) { classifier = "sources" } + //archives(sourcesJar.archiveFile) { classifier = "sources" } archives(docJar.archiveFile) { classifier = "javadoc" } } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c51cbf173..3c4101c3e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 1f83f6eae68c5def81e18d01a0b72cec4dd83d2c Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Wed, 24 Sep 2025 18:17:49 +0200 Subject: [PATCH 03/54] build: some fixes --- gradle/build.gradle.kts | 5 +++++ gradle/wrapper/gradle-wrapper.properties | 2 ++ plugin2025/src/test/kotlin/sc/plugin2025/MoveTest.kt | 2 +- sdk/build.gradle.kts | 2 +- sdk/src/test/config/KotestConfig.kt | 1 - 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/gradle/build.gradle.kts b/gradle/build.gradle.kts index 5f3e95ecd..623f2d72a 100644 --- a/gradle/build.gradle.kts +++ b/gradle/build.gradle.kts @@ -306,6 +306,11 @@ allprojects { dependsOn(classes) outputDirectory = buildDir.resolve("doc").toString() outputFormat = "javadoc" + configuration { + reportUndocumented = false + moduleName = "Software-Challenge ${project.name} $version" + jdkVersion = 8 + } } val docJar by creating(Jar::class) { group = "build" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3c4101c3e..03e41b61c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip +# 7.6.6 +# 8.14.3 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/plugin2025/src/test/kotlin/sc/plugin2025/MoveTest.kt b/plugin2025/src/test/kotlin/sc/plugin2025/MoveTest.kt index 7a613e802..7e07c67f0 100644 --- a/plugin2025/src/test/kotlin/sc/plugin2025/MoveTest.kt +++ b/plugin2025/src/test/kotlin/sc/plugin2025/MoveTest.kt @@ -208,7 +208,7 @@ class MoveTest: WordSpec({ state.getSensibleMoves() shouldBe listOf(FallBack, ExchangeCarrots(10)) } - "produce concicse XML" { + "produce concise XML" { ExchangeCarrots(-10) shouldSerializeTo "" } } diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts index 1751413d4..8c388feb4 100644 --- a/sdk/build.gradle.kts +++ b/sdk/build.gradle.kts @@ -26,7 +26,7 @@ configurations.archives.get().artifacts.removeIf { it.name == "testConfig" } dependencies { api(kotlin("stdlib")) - api("com.thoughtworks.xstream", "xstream", "1.4.19") // New security config for 1.4.20 + api("com.thoughtworks.xstream", "xstream", "1.4.17") // New security config, then 1.4.20 api("jargs", "jargs", "1.0") api("org.slf4j", "slf4j-api", "2.0.9") diff --git a/sdk/src/test/config/KotestConfig.kt b/sdk/src/test/config/KotestConfig.kt index cf4c5ec4e..2b8338148 100644 --- a/sdk/src/test/config/KotestConfig.kt +++ b/sdk/src/test/config/KotestConfig.kt @@ -5,7 +5,6 @@ import io.kotest.core.spec.IsolationMode import io.kotest.core.test.TestCaseOrder import kotlin.time.Duration.Companion.seconds -@OptIn(ExperimentalTime::class) object KotestConfig: AbstractProjectConfig() { override val parallelism = Runtime.getRuntime().availableProcessors() From 29351f4d818eff8b7e4661d8200a65b30df8f7aa Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Wed, 4 Feb 2026 14:49:54 +0300 Subject: [PATCH 04/54] build(gradle): update gradle, kotlin, dokka --- GUIDELINES.md | 13 +-- gradle/build.gradle.kts | 135 +++++++++++++---------- gradle/custom-tasks/build.gradle.kts | 4 +- gradle/wrapper/gradle-wrapper.properties | 4 +- player/build.gradle.kts | 50 +++++---- player/configuration/build.gradle.kts | 6 +- sdk/build.gradle.kts | 2 +- server/build.gradle.kts | 39 ++++--- 8 files changed, 141 insertions(+), 112 deletions(-) diff --git a/GUIDELINES.md b/GUIDELINES.md index 5a0a0bb76..c6987c0e0 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -4,15 +4,11 @@ This document captures development standards and architecture decisions of this ## Gradle -We build everything with gradle, nesting projects if needed. -Currently we have some pending updates, due to the following incompatibilities: +We build everything with Gradle, nesting projects if needed. +Current toolchain: Gradle 9.3, Java 25 toolchain (bytecode target 8), Kotlin 2.3, Dokka 2.1. -- The new johnrengelman.shadow plugin version needs Gradle 7 -- The current Dokka version can neither handle Gradle 7 nor Kotlin 1.5.30+ nor Java beyond version 8 - -The bottleneck is updating dokka, -which is a longer process tracked in -https://github.com/software-challenge/backend/pull/404 +Dokka v2 generates Javadoc per project (Javadoc is still alpha and does not support multi-project aggregation), +so we generate per-module docs and collect them during bundling. ## Testing @@ -121,4 +117,3 @@ and is then wrapped in a [RoomPacket](sdk/src/server-api/sc/protocol/room/RoomPa The package contains a few standard messages, but most will be implemented in the corresponding plugin. - diff --git a/gradle/build.gradle.kts b/gradle/build.gradle.kts index 623f2d72a..442495dfa 100644 --- a/gradle/build.gradle.kts +++ b/gradle/build.gradle.kts @@ -1,20 +1,24 @@ import org.gradle.kotlin.dsl.support.unzipTo -import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import sc.gradle.ScriptsTask +import org.gradle.api.GradleException import java.nio.file.Files +import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicBoolean plugins { - kotlin("jvm") version "1.6.21" - id("org.jetbrains.dokka") version "0.10.1" + kotlin("jvm") version "2.3.0" + id("org.jetbrains.dokka") version "2.1.0" + id("org.jetbrains.dokka-javadoc") version "2.1.0" id("scripts-task") id("idea") `maven-publish` - id("com.github.ben-manes.versions") version "0.42.0" // only upgrade with Gradle 7: https://github.com/ben-manes/gradle-versions-plugin/issues/778 - id("se.patrikerdes.use-latest-versions") version "0.2.18" + id("com.github.ben-manes.versions") version "0.53.0" + id("se.patrikerdes.use-latest-versions") version "0.2.19" } idea { @@ -31,42 +35,44 @@ version = versionObject.toString() + property("socha.version.suffix").toString() val year by extra { "20${versionObject.major}" } val game by extra { "${gameName}_$year" } -val bundleDir by extra { buildDir.resolve("bundle") } +val bundleDir by extra { layout.buildDirectory.dir("bundle").get().asFile } val bundledPlayer by extra { "randomplayer-$gameName-$version.jar" } -val testingDir by extra { buildDir.resolve("tests") } +val testingDir by extra { layout.buildDirectory.dir("tests").get().asFile } val documentedProjects = listOf("sdk", "plugin$year") val isBeta by extra { versionObject.minor == 0 } val enableTestClient by extra { arrayOf("check", "testTestClient").any { gradle.startParameter.taskNames.contains(it) } || !isBeta } val enableIntegrationTesting = !project.hasProperty("nointegration") && (!isBeta || enableTestClient) +val javaToolchainVersion = 25 val javaTargetVersion = JavaVersion.VERSION_1_8 -val javaVersion = JavaVersion.current() -println("Current version: $version (unstable: $isBeta) Game: $game (Kotlin ${kotlinExtension.coreLibrariesVersion}, Java $javaVersion)") -if (javaVersion != javaTargetVersion) - System.err.println("Java version $javaTargetVersion is recommended - expect issues with generating documentation (consider '-x doc' if you don't care)") +val kotlinJvmTarget = JvmTarget.fromTarget(javaTargetVersion.toString()) +val javaRuntimeVersion = JavaVersion.current() +println("Current version: $version (unstable: $isBeta) Game: $game (Kotlin ${kotlinExtension.coreLibrariesVersion}, Java runtime $javaRuntimeVersion, toolchain $javaToolchainVersion, target $javaTargetVersion)") +if (!javaRuntimeVersion.isCompatibleWith(JavaVersion.VERSION_17)) + System.err.println("Gradle 9+ requires Java 17+ to run. Toolchain is set to $javaToolchainVersion; install it or enable toolchain auto-download.") val doAfterEvaluate = ArrayList<(Project) -> Unit>() tasks { - val startServer by creating { + val startServer by registering { dependsOn(":server:run") group = "application" } - val doc by creating(DokkaTask::class) { - dependsOn(documentedProjects.map { ":$it:classes" }) + val doc by registering(Copy::class) { + dependsOn(documentedProjects.map { ":$it:doc" }) group = "documentation" - outputDirectory = bundleDir.resolve("doc").toString() - outputFormat = "javadoc" - subProjects = documentedProjects - configuration { - reportUndocumented = false - moduleName = "Software-Challenge API $version" - jdkVersion = 8 + description = "Collects Javadoc output for documented projects into ${bundleDir.relativeTo(projectDir)}/doc" + into(bundleDir.resolve("doc")) + from(project(":sdk").layout.buildDirectory.dir("doc")) { + into("sdk") + } + from(project(":plugin$year").layout.buildDirectory.dir("doc")) { + into("plugin-$gameName") } } - val bundle by creating { + val bundle by registering { dependsOn(doc) dependOnSubprojects() group = "distribution" @@ -74,12 +80,12 @@ tasks { outputs.dir(bundleDir) } - val release by creating { + val release by registering { dependsOn(clean, check) group = "distribution" description = "Prepares a new Release by bumping the version and pushing a commit tagged with the new version" doLast { - var newVersion = version + var newVersion = version.toString() fun String.editVersion(version: String, new: Int) = if (startsWith("socha.version.$version")) "socha.version.$version=${new.toString().padStart(2, '0')}" @@ -105,10 +111,19 @@ tasks { println("Version: $newVersion") println("Beschreibung: $desc") - exec { commandLine("git", "add", "gradle.properties", "CHANGELOG.md") } - exec { commandLine("git", "commit", "-m", "release: v$newVersion") } - exec { commandLine("git", "tag", newVersion, "-m", desc) } - exec { commandLine("git", "push", "--follow-tags") } + fun runGit(vararg args: String) { + val process = ProcessBuilder(listOf("git") + args) + .inheritIO() + .start() + val exitCode = process.waitFor() + if (exitCode != 0) { + throw GradleException("git ${args.joinToString(" ")} failed with exit code $exitCode") + } + } + runGit("add", "gradle.properties", "CHANGELOG.md") + runGit("commit", "-m", "release: v$newVersion") + runGit("tag", newVersion, "-m", desc) + runGit("push", "--follow-tags") } } @@ -125,7 +140,7 @@ tasks { // TODO create a global constant which can be shared with testclient & co - maybe a resource? val maxGameLength = 150L // 2m30s - val testGame by creating { + val testGame by registering { dependsOn(":server:makeRunnable", ":player:bundleShadow") group = "verification" doFirst { @@ -204,7 +219,7 @@ tasks { } } - val testTestClient by creating { + val testTestClient by registering { dependsOn(":server:bundle") group = "verification" shouldRunAfter(testGame) @@ -232,7 +247,7 @@ tasks { } } - val integrationTest by creating { + val integrationTest by registering { dependsOn(testGame, ":player:playerTest") if (enableTestClient) dependsOn(testTestClient) @@ -255,6 +270,15 @@ subprojects { apply(plugin = "com.github.ben-manes.versions") apply(plugin = "se.patrikerdes.use-latest-versions") + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(javaToolchainVersion)) + } + } + kotlin { + jvmToolchain(javaToolchainVersion) + } + dependencies { testImplementation(project(":sdk", "testConfig")) } @@ -269,10 +293,10 @@ subprojects { this.targetCompatibility = javaTargetVersion.toString() } - withType { - kotlinOptions { - this.jvmTarget = javaTargetVersion.toString() - this.freeCompilerArgs = listOf("-Xjvm-default=all") + withType().configureEach { + compilerOptions { + jvmTarget.set(kotlinJvmTarget) + freeCompilerArgs.add("-Xjvm-default=all") } } } @@ -287,6 +311,7 @@ allprojects { if (this.name in documentedProjects) { apply(plugin = "maven-publish") apply(plugin = "org.jetbrains.dokka") + apply(plugin = "org.jetbrains.dokka-javadoc") publishing { publications { create(name) { @@ -300,33 +325,25 @@ allprojects { withSourcesJar() withJavadocJar() } + dokka { + dokkaPublications.named("javadoc") { + moduleName.set("Software-Challenge ${project.name} $version") + moduleVersion.set(version.toString()) + outputDirectory.set(layout.buildDirectory.dir("doc")) + } + dokkaSourceSets.configureEach { + reportUndocumented.set(false) + jdkVersion.set(javaTargetVersion.majorVersion.toInt()) + } + } tasks { - val doc by creating(DokkaTask::class) { + val doc by registering { group = "documentation" - dependsOn(classes) - outputDirectory = buildDir.resolve("doc").toString() - outputFormat = "javadoc" - configuration { - reportUndocumented = false - moduleName = "Software-Challenge ${project.name} $version" - jdkVersion = 8 - } - } - val docJar by creating(Jar::class) { - group = "build" - from(doc) - archiveBaseName.set(jar.get().archiveBaseName) - archiveClassifier.set("javadoc") + dependsOn("dokkaGeneratePublicationJavadoc") } - //val sourcesJar by creating(Jar::class) { - // group = "build" - // archiveBaseName.set(jar.get().archiveBaseName) - // archiveClassifier.set("sources") - // from(sourceSets.main.get().allSource) - //} - artifacts { - //archives(sourcesJar.archiveFile) { classifier = "sources" } - archives(docJar.archiveFile) { classifier = "javadoc" } + named("javadocJar") { + dependsOn("dokkaGeneratePublicationJavadoc") + from(layout.buildDirectory.dir("doc")) } } } diff --git a/gradle/custom-tasks/build.gradle.kts b/gradle/custom-tasks/build.gradle.kts index 0b1ea6b67..74c8160bb 100644 --- a/gradle/custom-tasks/build.gradle.kts +++ b/gradle/custom-tasks/build.gradle.kts @@ -1,12 +1,12 @@ plugins { - kotlin("jvm") version "1.6.21" + kotlin("jvm") version "2.3.0" `java-gradle-plugin` } sourceSets.main.get().java.setSrcDirs(listOf("src")) repositories { - jcenter() + mavenCentral() } dependencies { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 03e41b61c..5dc98dbcf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip -# 7.6.6 -# 8.14.3 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/player/build.gradle.kts b/player/build.gradle.kts index 25cb59f4e..e45c64139 100644 --- a/player/build.gradle.kts +++ b/player/build.gradle.kts @@ -1,9 +1,10 @@ +import org.gradle.api.GradleException import org.gradle.internal.os.OperatingSystem import java.time.Duration plugins { application - id("com.github.johnrengelman.shadow") version "6.1.0" // Update to v8 with Gradle update + id("com.gradleup.shadow") version "9.3.1" } val game: String by project @@ -18,7 +19,7 @@ sourceSets.main { } application { - mainClassName = "sc.player.util.Starter" + mainClass.set("sc.player.util.Starter") } dependencies { @@ -35,21 +36,21 @@ tasks { archiveFileName.set("defaultplayer.jar") } - val copyDocs by creating(Copy::class) { + val copyDocs by registering(Copy::class) { dependsOn(":sdk:doc", ":plugin$year:doc") - into(buildDir.resolve("zip")) + into(layout.buildDirectory.dir("zip")) with(copySpec { - from(project(":plugin$year").buildDir.resolve("doc")) + from(project(":plugin$year").layout.buildDirectory.dir("doc")) into("doc/plugin-$gameName") }, copySpec { - from(project(":sdk").buildDir.resolve("doc")) + from(project(":sdk").layout.buildDirectory.dir("doc")) into("doc/sdk") }) } - val prepareZip by creating(Copy::class) { + val prepareZip by registering(Copy::class) { group = "distribution" - into(buildDir.resolve("zip")) + into(layout.buildDirectory.dir("zip")) with(copySpec { from("configuration") filter { @@ -70,8 +71,8 @@ tasks { .replace("TwoPlayerGameState", "GameState") } }, copySpec { - from(configurations.default, arrayOf("sdk", "plugin$year") - .map { project(":$it").getTasksByName("sourcesJar", false) }) + from(configurations.default) + from(arrayOf("sdk", "plugin$year").map { project(":$it").tasks.named("sourcesJar") }) into("lib") }) } @@ -83,13 +84,13 @@ tasks { val bundleDir: File by project val bundledPlayer: String by project - val bundleShadow by creating(Copy::class) { + val bundleShadow by registering(Copy::class) { group = "distribution" from(shadowJar) into(bundleDir) rename { bundledPlayer } } - val bundle by creating(Zip::class) { + val bundle by registering(Zip::class) { group = "distribution" dependsOn(bundleShadow) from(prepareZip, copyDocs) @@ -98,7 +99,7 @@ tasks { } /** Build a player that times out. */ - val playerTest by creating(Exec::class) { + val playerTest by registering(Exec::class) { group = "verification" dependsOn(prepareZip) val execDir = testingDir.resolve("player") @@ -126,9 +127,18 @@ tasks { args("shadowJar", "--quiet", "--offline") doLast { - exec { - commandLine("java", "-jar", execDir.resolve("${game}_client.jar"), "--verify") - standardOutput = org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM + val process = ProcessBuilder( + "java", + "-jar", + execDir.resolve("${game}_client.jar").absolutePath, + "--verify" + ) + .redirectOutput(ProcessBuilder.Redirect.DISCARD) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + val exitCode = process.waitFor() + if (exitCode != 0) { + throw GradleException("playerTest verification failed with exit code $exitCode") } println("Successfully built the shipped player package!") } @@ -136,11 +146,13 @@ tasks { /** Run a player that hits the soft-timeout. */ // TODO incorporate into a proper test - val runTimeout by creating(Exec::class) { + val runTimeout by registering(Exec::class) { group = "verification" dependsOn(playerTest) - workingDir(playerTest.workingDir) - commandLine("java", "-jar", workingDir.resolve("${game}_client.jar")) + doFirst { + workingDir = playerTest.get().workingDir + } + commandLine("java", "-jar", file("${game}_client.jar").absolutePath) } } diff --git a/player/configuration/build.gradle.kts b/player/configuration/build.gradle.kts index 6d87ff89f..9d5b8da26 100644 --- a/player/configuration/build.gradle.kts +++ b/player/configuration/build.gradle.kts @@ -1,18 +1,18 @@ plugins { java application - id("com.github.johnrengelman.shadow") version "6.1.0" + id("com.gradleup.shadow") version "9.3.1" } sourceSets.main.get().java.srcDir("src/main") sourceSets.main.get().resources.srcDir("src/resources") application { - mainClassName = "sc.player.util.Starter" + mainClass.set("sc.player.util.Starter") } repositories { - jcenter() + mavenCentral() maven("https://maven.wso2.org/nexus/content/groups/wso2-public/") maven("https://jitpack.io") } diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts index 8c388feb4..81525cc17 100644 --- a/sdk/build.gradle.kts +++ b/sdk/build.gradle.kts @@ -22,7 +22,7 @@ artifacts { builtBy(kt) } } -configurations.archives.get().artifacts.removeIf { it.name == "testConfig" } +configurations.findByName("archives")?.artifacts?.removeIf { it.name == "testConfig" } dependencies { api(kotlin("stdlib")) diff --git a/server/build.gradle.kts b/server/build.gradle.kts index e1104131a..87ad2ebaa 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -1,3 +1,4 @@ +import org.gradle.api.GradleException import sc.gradle.ScriptsTask plugins { @@ -28,15 +29,15 @@ tasks { systemProperty("junit.jupiter.execution.timeout.default", "10 s") // legacy junit tests } - val runnableDir = buildDir.resolve("runnable") + val runnableDir = layout.buildDirectory.dir("runnable").get().asFile - val createStartScripts by creating(ScriptsTask::class) { + val createStartScripts by registering(ScriptsTask::class) { destinationDir = runnableDir fileName = "start" content = "java -Dfile.encoding=UTF-8 -Dlogback.configurationFile=logback.xml -jar server.jar" } - val copyConfig by creating(Copy::class) { + val copyConfig by registering(Copy::class) { group = "distribution" from("configuration/logback-release.xml", "configuration/server.properties.example") into(runnableDir) @@ -44,14 +45,14 @@ tasks { rename("server.properties.example", "server.properties") } - val makeRunnable by creating(Copy::class) { + val makeRunnable by registering(Copy::class) { group = "distribution" dependsOn(jar, copyConfig, createStartScripts) from(configurations.default) into(runnableDir.resolve("lib")) } - val bundle by creating(Zip::class) { + val bundle by registering(Zip::class) { group = "distribution" dependsOn(":test-client:jar", ":player:shadowJar", makeRunnable) destinationDirectory.set(bundleDir) @@ -59,27 +60,33 @@ tasks { from(runnableDir) doFirst { if(project.property("enableTestClient") !in arrayOf(null, false)) - from(project(":test-client").getTasksByName("copyLogbackConfig", false)) - from(project(":player").getTasksByName("shadowJar", false)) + from(project(":test-client").tasks.named("copyLogbackConfig")) + from(project(":player").tasks.named("shadowJar")) val versionFile = runnableDir.resolve("version") try { - exec { - commandLine("git", "describe", "--long", "--tags") - standardOutput = versionFile.outputStream() + val describe = ProcessBuilder("git", "describe", "--long", "--tags") + .redirectOutput(versionFile) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + if (describe.waitFor() != 0) { + throw GradleException("git describe failed") } } catch(e: Exception) { println("Issue with git describe for version detection, falling back to rev-parse: $e") println(versionFile.readText()) - exec { - commandLine("git", "rev-parse", "HEAD") - standardOutput = versionFile.outputStream() + val revParse = ProcessBuilder("git", "rev-parse", "HEAD") + .redirectOutput(versionFile) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + if (revParse.waitFor() != 0) { + throw GradleException("git rev-parse failed") } } } } - val startProduction by creating(JavaExec::class) { + val startProduction by registering(JavaExec::class) { group = "application" dependsOn(makeRunnable) classpath = jar.get().outputs.files @@ -87,10 +94,10 @@ tasks { "-XX:MaxGCPauseMillis=100", "-XX:GCPauseIntervalMillis=2050", "-XX:+ScavengeBeforeFullGC") } - val dockerImage by creating(Exec::class) { + val dockerImage by registering(Exec::class) { group = "application" dependsOn(makeRunnable) - workingDir = buildDir + workingDir = layout.buildDirectory.get().asFile doFirst { val tag = Runtime.getRuntime().exec(arrayOf("git", "rev-parse", "--short", "--verify", "HEAD")) .inputStream.reader().readText().trim() From 367bc9c1cb5d2ad83f0123eb984ca7d1defa1ab8 Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Wed, 4 Feb 2026 20:41:07 +0300 Subject: [PATCH 05/54] docs: suppress companion objects in javadoc --- .old-plugins/blokus_2020/Board.kt | 1 + .old-plugins/blokus_2020/GameState.kt | 1 + .../piranhas_2019/shared/sc/plugin2019/FieldState.kt | 1 + gradle/build.gradle.kts | 2 ++ plugin/src/main/kotlin/sc/plugin2023/Board.kt | 1 + plugin/src/main/kotlin/sc/plugin2023/Move.kt | 1 + plugin/src/main/kotlin/sc/plugin2023/util/GamePlugin.kt | 1 + plugin/src/main/kotlin/sc/plugin2024/Board.kt | 1 + plugin/src/main/kotlin/sc/plugin2024/Segment.kt | 1 + plugin/src/main/kotlin/sc/plugin2024/util/GamePlugin.kt | 1 + plugin2025/src/main/kotlin/sc/plugin2025/Board.kt | 1 + plugin2025/src/main/kotlin/sc/plugin2025/Field.kt | 1 + plugin2025/src/main/kotlin/sc/plugin2025/util/GamePlugin.kt | 1 + plugin2026/src/main/kotlin/sc/plugin2026/Board.kt | 5 +++-- plugin2026/src/main/kotlin/sc/plugin2026/FieldState.kt | 1 + plugin2026/src/main/kotlin/sc/plugin2026/util/GamePlugin.kt | 1 + sdk/src/main/server-api/sc/api/plugins/Coordinates.kt | 1 + sdk/src/main/server-api/sc/api/plugins/CubeCoordinates.kt | 1 + sdk/src/main/server-api/sc/api/plugins/Direction.kt | 2 ++ sdk/src/main/server-api/sc/api/plugins/IGamePlugin.kt | 1 + sdk/src/main/server-api/sc/framework/ReplayLoader.kt | 1 + sdk/src/main/server-api/sc/networking/XStreamProvider.kt | 1 + server/src/main/java/sc/server/network/ClientManager.kt | 1 + server/src/test/java/sc/server/helpers/ConverterTest.kt | 1 + server/src/test/java/sc/server/plugins/TestPlugin.kt | 1 + 25 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.old-plugins/blokus_2020/Board.kt b/.old-plugins/blokus_2020/Board.kt index ba4ae40fb..220a0907e 100644 --- a/.old-plugins/blokus_2020/Board.kt +++ b/.old-plugins/blokus_2020/Board.kt @@ -106,6 +106,7 @@ data class Board( fun getFieldsOwnedBy(owner: PlayerColor): List = fields.filter { it.owner == owner } + /** @suppress */ companion object { private const val SHIFT = (Constants.BOARD_SIZE - 1) / 2 diff --git a/.old-plugins/blokus_2020/GameState.kt b/.old-plugins/blokus_2020/GameState.kt index 8e265eae3..419b2f3fe 100644 --- a/.old-plugins/blokus_2020/GameState.kt +++ b/.old-plugins/blokus_2020/GameState.kt @@ -108,6 +108,7 @@ class GameState @JvmOverloads constructor( return result } + /** @suppress */ companion object { fun parsePiecesString(s: String, p: PlayerColor): ArrayList { val l = ArrayList() diff --git a/.old-plugins/piranhas_2019/shared/sc/plugin2019/FieldState.kt b/.old-plugins/piranhas_2019/shared/sc/plugin2019/FieldState.kt index 742e03a81..2bae40e71 100644 --- a/.old-plugins/piranhas_2019/shared/sc/plugin2019/FieldState.kt +++ b/.old-plugins/piranhas_2019/shared/sc/plugin2019/FieldState.kt @@ -22,6 +22,7 @@ enum class FieldState { else -> ' ' } + /** @suppress */ companion object { @JvmStatic fun from(color: PlayerColor): FieldState { diff --git a/gradle/build.gradle.kts b/gradle/build.gradle.kts index 442495dfa..7080fa22d 100644 --- a/gradle/build.gradle.kts +++ b/gradle/build.gradle.kts @@ -330,9 +330,11 @@ allprojects { moduleName.set("Software-Challenge ${project.name} $version") moduleVersion.set(version.toString()) outputDirectory.set(layout.buildDirectory.dir("doc")) + suppressInheritedMembers.set(false) } dokkaSourceSets.configureEach { reportUndocumented.set(false) + suppressGeneratedFiles.set(true) jdkVersion.set(javaTargetVersion.majorVersion.toInt()) } } diff --git a/plugin/src/main/kotlin/sc/plugin2023/Board.kt b/plugin/src/main/kotlin/sc/plugin2023/Board.kt index 2e3f81b8e..103c6ad2b 100644 --- a/plugin/src/main/kotlin/sc/plugin2023/Board.kt +++ b/plugin/src/main/kotlin/sc/plugin2023/Board.kt @@ -66,6 +66,7 @@ class Board( override fun clone(): Board = Board(this) + /** @suppress */ companion object { /** Generiert ein neues Spielfeld mit zufällig auf dem Spielbrett verteilten Fischen. */ private fun generateFields(seed: Int = Random.nextInt()): MutableTwoDBoard { diff --git a/plugin/src/main/kotlin/sc/plugin2023/Move.kt b/plugin/src/main/kotlin/sc/plugin2023/Move.kt index 80a03faf6..58cd26a98 100644 --- a/plugin/src/main/kotlin/sc/plugin2023/Move.kt +++ b/plugin/src/main/kotlin/sc/plugin2023/Move.kt @@ -25,6 +25,7 @@ data class Move( override fun toString(): String = from?.let { "Schlittern $from zu $to" } ?: "Setze Pinguin auf $to" + /** @suppress */ companion object { @JvmStatic fun run(start: Coordinates, delta: Vector): Move = diff --git a/plugin/src/main/kotlin/sc/plugin2023/util/GamePlugin.kt b/plugin/src/main/kotlin/sc/plugin2023/util/GamePlugin.kt index 8e51b4807..c5bca4711 100644 --- a/plugin/src/main/kotlin/sc/plugin2023/util/GamePlugin.kt +++ b/plugin/src/main/kotlin/sc/plugin2023/util/GamePlugin.kt @@ -12,6 +12,7 @@ import sc.shared.ScoreFragment import sc.shared.WinReason class GamePlugin: IGamePlugin { + /** @suppress */ companion object { const val PLUGIN_ID = "swc_2023_penguins" val scoreDefinition: ScoreDefinition = diff --git a/plugin/src/main/kotlin/sc/plugin2024/Board.kt b/plugin/src/main/kotlin/sc/plugin2024/Board.kt index 8b4f3579f..b8d62ef54 100644 --- a/plugin/src/main/kotlin/sc/plugin2024/Board.kt +++ b/plugin/src/main/kotlin/sc/plugin2024/Board.kt @@ -22,6 +22,7 @@ data class Board( @XStreamAsAttribute var nextDirection: CubeDirection = segments.lastOrNull()?.direction ?: CubeDirection.RIGHT, ): IBoard { + /** @suppress */ companion object { val logger: Logger = LoggerFactory.getLogger(this::class.java) } diff --git a/plugin/src/main/kotlin/sc/plugin2024/Segment.kt b/plugin/src/main/kotlin/sc/plugin2024/Segment.kt index 98180cf1c..23e7c282b 100644 --- a/plugin/src/main/kotlin/sc/plugin2024/Segment.kt +++ b/plugin/src/main/kotlin/sc/plugin2024/Segment.kt @@ -90,6 +90,7 @@ data class Segment( return result } + /** @suppress */ companion object { fun inDirection(previousCenter: CubeCoordinates, direction: CubeDirection, fields: SegmentFields) = Segment(direction, previousCenter + direction.vector * MQConstants.SEGMENT_FIELDS_WIDTH, fields) diff --git a/plugin/src/main/kotlin/sc/plugin2024/util/GamePlugin.kt b/plugin/src/main/kotlin/sc/plugin2024/util/GamePlugin.kt index 771bfe48f..9ac09872a 100644 --- a/plugin/src/main/kotlin/sc/plugin2024/util/GamePlugin.kt +++ b/plugin/src/main/kotlin/sc/plugin2024/util/GamePlugin.kt @@ -19,6 +19,7 @@ enum class MQWinReason(override val message: String, override val isRegular: Boo } class GamePlugin: IGamePlugin { + /** @suppress */ companion object { const val PLUGIN_ID = "swc_2024_mississippi_queen" val scoreDefinition: ScoreDefinition = diff --git a/plugin2025/src/main/kotlin/sc/plugin2025/Board.kt b/plugin2025/src/main/kotlin/sc/plugin2025/Board.kt index 339fd7c12..e4579e8d1 100644 --- a/plugin2025/src/main/kotlin/sc/plugin2025/Board.kt +++ b/plugin2025/src/main/kotlin/sc/plugin2025/Board.kt @@ -45,6 +45,7 @@ class Board( override fun hashCode(): Int = track.contentHashCode() + /** @suppress */ companion object { private fun shuffledFields(vararg fields: Field) = fields.asList().shuffled() diff --git a/plugin2025/src/main/kotlin/sc/plugin2025/Field.kt b/plugin2025/src/main/kotlin/sc/plugin2025/Field.kt index 4215b5659..a2a70019a 100644 --- a/plugin2025/src/main/kotlin/sc/plugin2025/Field.kt +++ b/plugin2025/src/main/kotlin/sc/plugin2025/Field.kt @@ -27,6 +27,7 @@ enum class Field(val short: String, val unicode: String = short) { /** Das Startfeld */ START("0", "▶"); + /** @suppress */ companion object { val POSITION_3 = CARROTS val POSITION_4 = MARKET diff --git a/plugin2025/src/main/kotlin/sc/plugin2025/util/GamePlugin.kt b/plugin2025/src/main/kotlin/sc/plugin2025/util/GamePlugin.kt index 8bc2d0965..6f7ea8690 100644 --- a/plugin2025/src/main/kotlin/sc/plugin2025/util/GamePlugin.kt +++ b/plugin2025/src/main/kotlin/sc/plugin2025/util/GamePlugin.kt @@ -17,6 +17,7 @@ enum class HuIWinReason(override val message: String, override val isRegular: Bo } class GamePlugin: IGamePlugin { + /** @suppress */ companion object { const val PLUGIN_ID = "swc_2025_hase_und_igel" val scoreDefinition: ScoreDefinition = diff --git a/plugin2026/src/main/kotlin/sc/plugin2026/Board.kt b/plugin2026/src/main/kotlin/sc/plugin2026/Board.kt index 4a4ac162d..0b01979ed 100644 --- a/plugin2026/src/main/kotlin/sc/plugin2026/Board.kt +++ b/plugin2026/src/main/kotlin/sc/plugin2026/Board.kt @@ -7,8 +7,6 @@ import sc.framework.deepCopy import sc.plugin2026.util.PiranhaConstants import kotlin.random.Random -val line = "-".repeat(PiranhaConstants.BOARD_LENGTH * 2 + 2) - /** Spielbrett für Piranhas mit [PiranhaConstants.BOARD_LENGTH]² Feldern. */ @XStreamAlias(value = "board") class Board( @@ -16,6 +14,8 @@ class Board( override val gameField: MutableTwoDBoard = randomFields() ): RectangularBoard(), IBoard { + private val line = "-".repeat(PiranhaConstants.BOARD_LENGTH * 2 + 2) + override fun toString() = "Board " + gameField.withIndex().joinToString(" ", "[", "]") { row -> row.value.withIndex().joinToString(", ", prefix = "[", postfix = "]") { @@ -47,6 +47,7 @@ class Board( filterValues { field -> field.team == team } .mapValues { (_, field) -> field.size } + /** @suppress */ companion object { /** Erstellt ein zufälliges Spielbrett. */ fun randomFields( diff --git a/plugin2026/src/main/kotlin/sc/plugin2026/FieldState.kt b/plugin2026/src/main/kotlin/sc/plugin2026/FieldState.kt index 02547d1a1..7fdc23cd7 100644 --- a/plugin2026/src/main/kotlin/sc/plugin2026/FieldState.kt +++ b/plugin2026/src/main/kotlin/sc/plugin2026/FieldState.kt @@ -42,6 +42,7 @@ enum class FieldState(val size: Int): IField, DeepCloneable { else -> team?.letter.toString() + size.toString() } + /** @suppress */ companion object { fun from(team: Team, size: Int): FieldState = when(team) { diff --git a/plugin2026/src/main/kotlin/sc/plugin2026/util/GamePlugin.kt b/plugin2026/src/main/kotlin/sc/plugin2026/util/GamePlugin.kt index 2b9e315d4..e21f67dd8 100644 --- a/plugin2026/src/main/kotlin/sc/plugin2026/util/GamePlugin.kt +++ b/plugin2026/src/main/kotlin/sc/plugin2026/util/GamePlugin.kt @@ -16,6 +16,7 @@ enum class PiranhasWinReason(override val message: String, override val isRegula } class GamePlugin: IGamePlugin { + /** @suppress */ companion object { const val PLUGIN_ID = "swc_2026_piranhas" val scoreDefinition: ScoreDefinition = diff --git a/sdk/src/main/server-api/sc/api/plugins/Coordinates.kt b/sdk/src/main/server-api/sc/api/plugins/Coordinates.kt index 9a23a2c60..3c8397532 100644 --- a/sdk/src/main/server-api/sc/api/plugins/Coordinates.kt +++ b/sdk/src/main/server-api/sc/api/plugins/Coordinates.kt @@ -33,6 +33,7 @@ data class Coordinates( val neighbors: Collection get() = Direction.cardinals.map { this + it } + /** @suppress */ companion object { /** Der Ursprung des Koordinatensystems (0, 0). */ val ORIGIN = Coordinates(0, 0) diff --git a/sdk/src/main/server-api/sc/api/plugins/CubeCoordinates.kt b/sdk/src/main/server-api/sc/api/plugins/CubeCoordinates.kt index da73be101..2352e44ad 100644 --- a/sdk/src/main/server-api/sc/api/plugins/CubeCoordinates.kt +++ b/sdk/src/main/server-api/sc/api/plugins/CubeCoordinates.kt @@ -80,6 +80,7 @@ data class CubeCoordinates override fun hashCode(): Int = q * 998 + r + /** @suppress */ companion object { /** Der Ursprung des Koordinatensystems (0, 0). */ val ORIGIN = CubeCoordinates(0, 0) diff --git a/sdk/src/main/server-api/sc/api/plugins/Direction.kt b/sdk/src/main/server-api/sc/api/plugins/Direction.kt index 06802b2fa..c7873223c 100644 --- a/sdk/src/main/server-api/sc/api/plugins/Direction.kt +++ b/sdk/src/main/server-api/sc/api/plugins/Direction.kt @@ -16,6 +16,7 @@ enum class Direction(val vector: Vector): IVector by vector { LEFT(Vector(-1, 0)), UP_LEFT(Vector(-1, 1)); + /** @suppress */ companion object { val diagonals = arrayOf(UP_RIGHT, DOWN_RIGHT, DOWN_LEFT, UP_LEFT) val cardinals = arrayOf(UP, RIGHT, DOWN, LEFT) @@ -50,6 +51,7 @@ enum class HexDirection(val vector: Vector): IVector by vector { fun rotatedBy(turns: Int): HexDirection = values().let { it[(ordinal + turns).mod(it.size)] } + /** @suppress */ companion object { fun random(): HexDirection = values()[Random.nextInt(values().size)] } diff --git a/sdk/src/main/server-api/sc/api/plugins/IGamePlugin.kt b/sdk/src/main/server-api/sc/api/plugins/IGamePlugin.kt index c1f290369..b942f3ada 100644 --- a/sdk/src/main/server-api/sc/api/plugins/IGamePlugin.kt +++ b/sdk/src/main/server-api/sc/api/plugins/IGamePlugin.kt @@ -31,6 +31,7 @@ interface IGamePlugin { /** @return ein neues Spiel mit dem gegebenen GameState. */ fun createGameFromState(state: IGameState): IGameInstance + /** @suppress */ companion object { @JvmStatic fun loadPlugins(): Iterator> = diff --git a/sdk/src/main/server-api/sc/framework/ReplayLoader.kt b/sdk/src/main/server-api/sc/framework/ReplayLoader.kt index 101a69822..8a0d29138 100644 --- a/sdk/src/main/server-api/sc/framework/ReplayLoader.kt +++ b/sdk/src/main/server-api/sc/framework/ReplayLoader.kt @@ -60,6 +60,7 @@ class ReplayLoader(inputStream: InputStream) { loadHistory { it.turn >= turn }.first.last().takeIf { it.turn >= turn } ?: throw NoSuchElementException("No GameState of turn $turn") + /** @suppress */ companion object { private val logger = LoggerFactory.getLogger(this::class.java) diff --git a/sdk/src/main/server-api/sc/networking/XStreamProvider.kt b/sdk/src/main/server-api/sc/networking/XStreamProvider.kt index a71775a22..59570edcd 100644 --- a/sdk/src/main/server-api/sc/networking/XStreamProvider.kt +++ b/sdk/src/main/server-api/sc/networking/XStreamProvider.kt @@ -6,6 +6,7 @@ import sc.protocol.LobbyProtocol import java.util.ServiceLoader interface XStreamProvider { + /** @suppress */ companion object { /* * Using the KXml2 parser because the default (Xpp3) and StAX can't parse some special characters in attribute values: diff --git a/server/src/main/java/sc/server/network/ClientManager.kt b/server/src/main/java/sc/server/network/ClientManager.kt index 830123325..3274e7ec2 100644 --- a/server/src/main/java/sc/server/network/ClientManager.kt +++ b/server/src/main/java/sc/server/network/ClientManager.kt @@ -90,6 +90,7 @@ class ClientManager(private val requestHandler: IClientRequestListener) : Runnab clients.remove(source) } + /** @suppress */ companion object { private val logger = LoggerFactory.getLogger(ClientManager::class.java) } diff --git a/server/src/test/java/sc/server/helpers/ConverterTest.kt b/server/src/test/java/sc/server/helpers/ConverterTest.kt index 2a987d84e..8a15ec057 100644 --- a/server/src/test/java/sc/server/helpers/ConverterTest.kt +++ b/server/src/test/java/sc/server/helpers/ConverterTest.kt @@ -18,6 +18,7 @@ class ConverterTest { return !(field == "secret" && hacker == viewer) } + /** @suppress */ companion object { val hacker = Any() val goodFriend = Any() diff --git a/server/src/test/java/sc/server/plugins/TestPlugin.kt b/server/src/test/java/sc/server/plugins/TestPlugin.kt index 7eb6a87d6..e983c18d2 100644 --- a/server/src/test/java/sc/server/plugins/TestPlugin.kt +++ b/server/src/test/java/sc/server/plugins/TestPlugin.kt @@ -6,6 +6,7 @@ import sc.api.plugins.IGameState import sc.shared.ScoreDefinition class TestPlugin: IGamePlugin { + /** @suppress */ companion object { const val TEST_PLUGIN_UUID = "012345-norris" } From 976ae1a05039d84e458eaafd4de105ceef05e452 Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Wed, 4 Feb 2026 21:08:01 +0300 Subject: [PATCH 06/54] docs(gradle): proper module naming --- gradle/build.gradle.kts | 29 ++++++++++++++++++++++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/gradle/build.gradle.kts b/gradle/build.gradle.kts index 7080fa22d..a49523bfb 100644 --- a/gradle/build.gradle.kts +++ b/gradle/build.gradle.kts @@ -260,6 +260,31 @@ tasks { if (enableIntegrationTesting) dependsOn(integrationTest) } + + val verifyDocs by registering { + group = "documentation" + description = "Verifies generated docs omit Companion classes." + dependsOn("doc") + doLast { + val forbidden = Regex("\\bCompanion\\b") + val offending = mutableListOf() + documentedProjects.map { project(":$it") }.forEach { target -> + val docDir = target.layout.buildDirectory.dir("doc").get().asFile + if (!docDir.exists()) return@forEach + docDir.walkTopDown() + .filter { it.isFile && (it.extension == "html" || it.extension == "js" || it.name == "package-list" || it.name == "element-list") } + .forEach { file -> + val text = runCatching { file.readText() }.getOrNull() ?: return@forEach + if (forbidden.containsMatchIn(text)) { + offending.add(file.relativeTo(rootProject.projectDir).path) + } + } + } + if (offending.isNotEmpty()) { + throw GradleException("Companion entries found in docs: ${offending.joinToString(", ")}") + } + } + } } // == Cross-project configuration == @@ -327,8 +352,8 @@ allprojects { } dokka { dokkaPublications.named("javadoc") { - moduleName.set("Software-Challenge ${project.name} $version") - moduleVersion.set(version.toString()) + moduleName.set("Software-Challenge ${project.name} \"$gameName\"") + moduleVersion.set(rootProject.version.toString()) outputDirectory.set(layout.buildDirectory.dir("doc")) suppressInheritedMembers.set(false) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5dc98dbcf..93d666c48 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From b521dbb49feaa64f3f506edb38a4e0903ef131fa Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Thu, 5 Feb 2026 19:02:32 +0300 Subject: [PATCH 07/54] docs(gradle): also generate dokka html --- gradle/build.gradle.kts | 95 +++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/gradle/build.gradle.kts b/gradle/build.gradle.kts index a49523bfb..9a2839ff2 100644 --- a/gradle/build.gradle.kts +++ b/gradle/build.gradle.kts @@ -2,6 +2,7 @@ import org.gradle.kotlin.dsl.support.unzipTo import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier import sc.gradle.ScriptsTask import org.gradle.api.GradleException import java.nio.file.Files @@ -71,6 +72,19 @@ tasks { into("plugin-$gameName") } } + + val docHtml by registering(Copy::class) { + dependsOn(documentedProjects.map { ":$it:docHtml" }) + group = "documentation" + description = "Collects Dokka HTML output for documented projects into ${bundleDir.relativeTo(projectDir)}/doc-html" + into(bundleDir.resolve("doc-html")) + from(project(":sdk").layout.buildDirectory.dir("doc-html")) { + into("sdk") + } + from(project(":plugin$year").layout.buildDirectory.dir("doc-html")) { + into("plugin-$gameName") + } + } val bundle by registering { dependsOn(doc) @@ -264,21 +278,26 @@ tasks { val verifyDocs by registering { group = "documentation" description = "Verifies generated docs omit Companion classes." - dependsOn("doc") + dependsOn("doc", "docHtml") doLast { val forbidden = Regex("\\bCompanion\\b") val offending = mutableListOf() documentedProjects.map { project(":$it") }.forEach { target -> - val docDir = target.layout.buildDirectory.dir("doc").get().asFile - if (!docDir.exists()) return@forEach - docDir.walkTopDown() - .filter { it.isFile && (it.extension == "html" || it.extension == "js" || it.name == "package-list" || it.name == "element-list") } - .forEach { file -> - val text = runCatching { file.readText() }.getOrNull() ?: return@forEach - if (forbidden.containsMatchIn(text)) { - offending.add(file.relativeTo(rootProject.projectDir).path) + val docDirs = listOf( + target.layout.buildDirectory.dir("doc").get().asFile, + target.layout.buildDirectory.dir("doc-html").get().asFile, + ) + docDirs.forEach { docDir -> + if (!docDir.exists()) return@forEach + docDir.walkTopDown() + .filter { it.isFile && (it.extension == "html" || it.extension == "js" || it.name == "package-list" || it.name == "element-list") } + .forEach { file -> + val text = runCatching { file.readText() }.getOrNull() ?: return@forEach + if (forbidden.containsMatchIn(text)) { + offending.add(file.relativeTo(rootProject.projectDir).path) + } } - } + } } if (offending.isNotEmpty()) { throw GradleException("Companion entries found in docs: ${offending.joinToString(", ")}") @@ -357,19 +376,71 @@ allprojects { outputDirectory.set(layout.buildDirectory.dir("doc")) suppressInheritedMembers.set(false) } + dokkaPublications.named("html") { + moduleName.set("Software-Challenge ${project.name} \"$gameName\"") + moduleVersion.set(rootProject.version.toString()) + outputDirectory.set(layout.buildDirectory.dir("doc-html")) + suppressInheritedMembers.set(false) + } dokkaSourceSets.configureEach { reportUndocumented.set(false) suppressGeneratedFiles.set(true) + documentedVisibilities.set(setOf(VisibilityModifier.Public)) jdkVersion.set(javaTargetVersion.majorVersion.toInt()) } } tasks { - val doc by registering { + val inlineHtmlNav by registering { group = "documentation" + description = "Inlines navigation.html into Dokka HTML to avoid empty sidebars." + dependsOn("dokkaGeneratePublicationHtml") + doLast { + val docDir = layout.buildDirectory.dir("doc-html").get().asFile + val navFile = docDir.resolve("navigation.html") + if (!navFile.exists()) return@doLast + val navHtml = navFile.readText() + val marker = "
" + docDir.walkTopDown() + .filter { it.isFile && it.extension == "html" } + .forEach { file -> + val original = file.readText() + if (!original.contains(marker)) return@forEach + val updated = original.replace(marker, "
$navHtml
") + if (updated != original) { + file.writeText(updated) + } + } + } + } + val sanitizeJavadoc by registering { + group = "documentation" + description = "Removes private field rows from Dokka Javadoc HTML output." dependsOn("dokkaGeneratePublicationJavadoc") + doLast { + val docDir = layout.buildDirectory.dir("doc").get().asFile + if (!docDir.exists()) return@doLast + val rowRegex = Regex("(?s)]*>\\s*private.*?.*?") + docDir.walkTopDown() + .filter { it.isFile && it.extension == "html" } + .forEach { file -> + val original = file.readText() + val sanitized = original.replace(rowRegex, "") + if (sanitized != original) { + file.writeText(sanitized) + } + } + } + } + val doc by registering { + group = "documentation" + dependsOn(sanitizeJavadoc) + } + val docHtml by registering { + group = "documentation" + dependsOn(inlineHtmlNav) } named("javadocJar") { - dependsOn("dokkaGeneratePublicationJavadoc") + dependsOn(sanitizeJavadoc) from(layout.buildDirectory.dir("doc")) } } From 12b010237d971e5f32c702d47f0ae7e3633fc750 Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Thu, 5 Feb 2026 19:08:05 +0300 Subject: [PATCH 08/54] docs(plugin26): add and translate user documentation --- .../main/kotlin/sc/plugin2024/GameState.kt | 12 ++++----- .../main/kotlin/sc/plugin2025/GameState.kt | 14 +++++------ .../src/main/kotlin/sc/plugin2026/Board.kt | 9 ++++++- .../main/kotlin/sc/plugin2026/GameState.kt | 25 +++++++++++++------ .../sc/plugin2026/util/GameRuleLogic.kt | 8 +++--- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/plugin/src/main/kotlin/sc/plugin2024/GameState.kt b/plugin/src/main/kotlin/sc/plugin2024/GameState.kt index 63449538b..28df7de09 100644 --- a/plugin/src/main/kotlin/sc/plugin2024/GameState.kt +++ b/plugin/src/main/kotlin/sc/plugin2024/GameState.kt @@ -19,14 +19,14 @@ import sc.shared.WinCondition import kotlin.math.absoluteValue /** - * The GameState class represents the current state of the game. + * Die Klasse GameState repräsentiert den aktuellen Zustand des Spiels. * - * It holds all the information about the current round, which is used - * to calculate the next move. + * Sie enthält alle Informationen über die aktuelle Runde, + * die zur Berechnung des nächsten Zuges verwendet werden. * - * @property board The current game board. - * @property turn The number of turns already made in the game. - * @property lastMove The last move made in the game. + * @property board Das aktuelle Spielfeld. + * @property turn Die Anzahl der bereits gespielten Züge. + * @property lastMove Der zuletzt gespielte Zug. */ @XStreamAlias(value = "state") data class GameState @JvmOverloads constructor( diff --git a/plugin2025/src/main/kotlin/sc/plugin2025/GameState.kt b/plugin2025/src/main/kotlin/sc/plugin2025/GameState.kt index b262c6875..916f4d165 100644 --- a/plugin2025/src/main/kotlin/sc/plugin2025/GameState.kt +++ b/plugin2025/src/main/kotlin/sc/plugin2025/GameState.kt @@ -17,14 +17,14 @@ import kotlin.math.pow import kotlin.math.sqrt /** - * The GameState class represents the current state of the game. + * Die Klasse GameState repräsentiert den aktuellen Zustand des Spiels. * - * It holds all the information about the current round, - * to provide all information needed to make the next move. + * Sie enthält alle Informationen über die aktuelle Runde, + * um alle benötigten Daten für den nächsten Zug bereitzustellen. * - * @property board The current game board. - * @property turn The number of turns already made in the game. - * @property lastMove The last move made in the game. + * @property board Das aktuelle Spielfeld. + * @property turn Die Anzahl der bereits gespielten Züge. + * @property lastMove Der zuletzt gespielte Zug. */ @XStreamAlias(value = "state") data class GameState @JvmOverloads constructor( @@ -326,4 +326,4 @@ data class GameState @JvmOverloads constructor( fun succeedsState(other: GameState) = other.turn + 1 == turn || other.turn + 2 == turn -} \ No newline at end of file +} diff --git a/plugin2026/src/main/kotlin/sc/plugin2026/Board.kt b/plugin2026/src/main/kotlin/sc/plugin2026/Board.kt index 0b01979ed..943cd7b54 100644 --- a/plugin2026/src/main/kotlin/sc/plugin2026/Board.kt +++ b/plugin2026/src/main/kotlin/sc/plugin2026/Board.kt @@ -16,6 +16,8 @@ class Board( private val line = "-".repeat(PiranhaConstants.BOARD_LENGTH * 2 + 2) + /** Gibt eine kompakte String-Darstellung des Spielfelds mit Koordinaten + * und einer zweibuchstabigen Darstellung der Feldzustände zurück. */ override fun toString() = "Board " + gameField.withIndex().joinToString(" ", "[", "]") { row -> row.value.withIndex().joinToString(", ", prefix = "[", postfix = "]") { @@ -23,6 +25,7 @@ class Board( } } + /** Gibt eine visuell formatierte Darstellung des Spielfelds mit ASCII-Rahmen zurück. */ fun prettyString(): String { val map = StringBuilder(line) gameField.forEach { row -> @@ -35,21 +38,25 @@ class Board( return map.toString() } + /** Erstellt eine tiefe Kopie dieses [Board] durch Klonen der zugrunde liegenden Felder. */ override fun clone(): Board { //println("Cloning with ${gameField::class.java}: $this") return Board(gameField.deepCopy()) } + /** Gibt das [Team] eines Fisches an [pos] zurück, + * oder `null` falls sich kein Fisch auf dem Feld befindet. */ fun getTeam(pos: Coordinates): Team? = this[pos].team + /** Gibt eine Zuordnung aller von [team] belegten Felder zu deren Fischgrößen zurück. */ fun fieldsForTeam(team: ITeam): Map = filterValues { field -> field.team == team } .mapValues { (_, field) -> field.size } /** @suppress */ companion object { - /** Erstellt ein zufälliges Spielbrett. */ + /** Erstellt ein zufälliges Spielbrett. */ fun randomFields( obstacleCount: Int = PiranhaConstants.NUM_OBSTACLES, random: Random = Random.Default, diff --git a/plugin2026/src/main/kotlin/sc/plugin2026/GameState.kt b/plugin2026/src/main/kotlin/sc/plugin2026/GameState.kt index a14a05509..019d2af34 100644 --- a/plugin2026/src/main/kotlin/sc/plugin2026/GameState.kt +++ b/plugin2026/src/main/kotlin/sc/plugin2026/GameState.kt @@ -15,14 +15,17 @@ import sc.shared.WinCondition import sc.shared.WinReasonTie /** - * The GameState class represents the current state of the game. + * Die Klasse GameState repräsentiert den aktuellen Zustand des Spiels. * - * It holds all the information about the current round, - * to provide all information needed to make the next move. + * Sie enthält alle Informationen über die aktuelle Runde, + * um alle benötigten Daten für den nächsten Zug bereitzustellen. * - * @property board The current game board. - * @property turn The number of turns already made in the game. - * @property lastMove The last move made in the game. + * @param turn Die Anzahl der bereits gespielten Züge. + * @param lastMove Der zuletzt gespielte Zug. + * @param board Das aktuelle Spielfeld. + * @property board Das aktuelle Spielfeld. + * @property turn Die Anzahl der bereits gespielten Züge. + * @property lastMove Der zuletzt gespielte Zug. */ @XStreamAlias(value = "state") data class GameState @JvmOverloads constructor( @@ -34,14 +37,17 @@ data class GameState @JvmOverloads constructor( override val board: Board = Board(), ): TwoPlayerGameState(Team.ONE) { + /** Berechnet die Punktwerte für das angegebene [team]. */ override fun getPointsForTeam(team: ITeam): IntArray = intArrayOf(GameRuleLogic.greatestSwarmSize(board, team)) // TODO test if one player is unable to move he loses e.g. in corner + /** Gibt an, ob das Spiel beendet ist. */ override val isOver: Boolean get() = (Team.values().any { GameRuleLogic.isSwarmConnected(board, it) } && turn.mod(2) == 0) || turn / 2 >= PiranhaConstants.ROUND_LIMIT + /** Liefert die aktuelle Gewinnbedingung oder null, falls das Spiel noch nicht entschieden ist. */ override val winCondition: WinCondition? get() = if(Team.values().any { team -> GameRuleLogic.isSwarmConnected(board, team) }) { @@ -52,6 +58,7 @@ data class GameState @JvmOverloads constructor( null } + /** Führt einen gültigen [move] direkt aus und aktualisiert Spielfeld sowie Zähler. */ override fun performMoveDirectly(move: Move) { if(board.getTeam(move.from) != currentTeam) { throw InvalidMoveException(PiranhaMoveMistake.WRONG_START, move) @@ -64,6 +71,7 @@ data class GameState @JvmOverloads constructor( lastMove = move } + /** Gibt alle sinnvollen Züge für den aktuellen Spieler auf dem aktuellen [board] zurück. */ override fun getSensibleMoves(): List { val piranhas = board.filterValues { field -> field.team == currentTeam } val moves = ArrayList(piranhas.size * 2) @@ -73,16 +81,19 @@ data class GameState @JvmOverloads constructor( return moves } + /** Iteriert über alle sinnvollen Züge des aktuellen Spielers. */ override fun moveIterator(): Iterator = getSensibleMoves().iterator() + /** Erstellt eine tiefe Kopie dieses Spielzustands inklusive geklontem [board]. */ override fun clone(): GameState = copy(board = board.clone()) + /** Ermittelt zusammenfassende Statistiken für das angegebene [team]. */ override fun teamStats(team: ITeam): List = listOf( Stat("Anzahl Fische", board.fieldsForTeam(team).size), Stat("Größter Schwarm", GameRuleLogic.greatestSwarmSize(board, team)) ) -} \ No newline at end of file +} diff --git a/plugin2026/src/main/kotlin/sc/plugin2026/util/GameRuleLogic.kt b/plugin2026/src/main/kotlin/sc/plugin2026/util/GameRuleLogic.kt index 89dd737a3..33db48421 100644 --- a/plugin2026/src/main/kotlin/sc/plugin2026/util/GameRuleLogic.kt +++ b/plugin2026/src/main/kotlin/sc/plugin2026/util/GameRuleLogic.kt @@ -92,7 +92,7 @@ object GameRuleLogic { .map { direction -> Move(pos, direction)} .filter { move -> checkMove(board, move) == null } - /** @return the [Coordinates] from [parentSet] which are neighbors of [pos] */ + /** @return die [Coordinates] aus [parentSet], die Nachbarn von [pos] sind */ private fun selectNeighbors(pos: Coordinates, parentSet: Collection): Collection { val returnSet = ArrayList(8) for(i in -1..1) { @@ -112,8 +112,8 @@ object GameRuleLogic { return returnSet } - /** Called with a single fish in [swarm] and the [looseFishes] left, - * recursively calling with neighbors added to [swarm] to find a whole swarm. */ + /** Startet mit einem einzelnen Fisch in [swarm] und den verbleibenden [looseFishes], + * ruft sich rekursiv mit hinzugefügten Nachbarn auf, um den gesamten Schwarm zu finden. */ private fun getSwarm(looseFishes: Collection, swarm: List): List { val swarmNeighbors = swarm.flatMap { selectNeighbors(it, looseFishes) } @@ -125,7 +125,7 @@ object GameRuleLogic { return swarm } - /** Finds the most weighty swarm among a set of positions with weights. */ + /** Findet den schwersten Schwarm innerhalb einer Menge gewichteter Positionen. */ @JvmStatic fun greatestSwarm(fieldsToCheck: Map): Map? { // Make a copy, so there will be no conflict with direct calls. From 6f6421f965e237f2d1809d40aec0025d9ea9877a Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Thu, 5 Feb 2026 19:13:43 +0300 Subject: [PATCH 09/54] build(gradle): simplify custom dokka publication config --- gradle/build.gradle.kts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/gradle/build.gradle.kts b/gradle/build.gradle.kts index 9a2839ff2..cc0ade293 100644 --- a/gradle/build.gradle.kts +++ b/gradle/build.gradle.kts @@ -359,7 +359,6 @@ allprojects { publishing { publications { create(name) { - println(components.joinToString()) from(components["java"]) version = rootProject.version.toString() } @@ -370,16 +369,9 @@ allprojects { withJavadocJar() } dokka { - dokkaPublications.named("javadoc") { + dokkaPublications.configureEach { moduleName.set("Software-Challenge ${project.name} \"$gameName\"") moduleVersion.set(rootProject.version.toString()) - outputDirectory.set(layout.buildDirectory.dir("doc")) - suppressInheritedMembers.set(false) - } - dokkaPublications.named("html") { - moduleName.set("Software-Challenge ${project.name} \"$gameName\"") - moduleVersion.set(rootProject.version.toString()) - outputDirectory.set(layout.buildDirectory.dir("doc-html")) suppressInheritedMembers.set(false) } dokkaSourceSets.configureEach { From 1582ab355ca67e3c7536baeb5c8467ef86bb064d Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Thu, 5 Feb 2026 21:06:40 +0300 Subject: [PATCH 10/54] docs(gradle): fix dokka sidebar links and standardize output directories --- gradle/build.gradle.kts | 82 +++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/gradle/build.gradle.kts b/gradle/build.gradle.kts index cc0ade293..fea90f80e 100644 --- a/gradle/build.gradle.kts +++ b/gradle/build.gradle.kts @@ -1,4 +1,5 @@ import org.gradle.kotlin.dsl.support.unzipTo +import org.jetbrains.dokka.gradle.tasks.DokkaGeneratePublicationTask import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -65,10 +66,12 @@ tasks { group = "documentation" description = "Collects Javadoc output for documented projects into ${bundleDir.relativeTo(projectDir)}/doc" into(bundleDir.resolve("doc")) - from(project(":sdk").layout.buildDirectory.dir("doc")) { + val sdkJavadoc = project(":sdk").tasks.named("dokkaGeneratePublicationJavadoc") + val pluginJavadoc = project(":plugin$year").tasks.named("dokkaGeneratePublicationJavadoc") + from(sdkJavadoc.flatMap { it.outputDirectory }) { into("sdk") } - from(project(":plugin$year").layout.buildDirectory.dir("doc")) { + from(pluginJavadoc.flatMap { it.outputDirectory }) { into("plugin-$gameName") } } @@ -78,10 +81,12 @@ tasks { group = "documentation" description = "Collects Dokka HTML output for documented projects into ${bundleDir.relativeTo(projectDir)}/doc-html" into(bundleDir.resolve("doc-html")) - from(project(":sdk").layout.buildDirectory.dir("doc-html")) { + val sdkHtml = project(":sdk").tasks.named("dokkaGeneratePublicationHtml") + val pluginHtml = project(":plugin$year").tasks.named("dokkaGeneratePublicationHtml") + from(sdkHtml.flatMap { it.outputDirectory }) { into("sdk") } - from(project(":plugin$year").layout.buildDirectory.dir("doc-html")) { + from(pluginHtml.flatMap { it.outputDirectory }) { into("plugin-$gameName") } } @@ -283,10 +288,17 @@ tasks { val forbidden = Regex("\\bCompanion\\b") val offending = mutableListOf() documentedProjects.map { project(":$it") }.forEach { target -> - val docDirs = listOf( - target.layout.buildDirectory.dir("doc").get().asFile, - target.layout.buildDirectory.dir("doc-html").get().asFile, - ) + val javadocDir = target.tasks.named("dokkaGeneratePublicationJavadoc") + .get() + .outputDirectory + .get() + .asFile + val htmlDir = target.tasks.named("dokkaGeneratePublicationHtml") + .get() + .outputDirectory + .get() + .asFile + val docDirs = listOf(javadocDir, htmlDir) docDirs.forEach { docDir -> if (!docDir.exists()) return@forEach docDir.walkTopDown() @@ -382,41 +394,73 @@ allprojects { } } tasks { + val javadocTask = named("dokkaGeneratePublicationJavadoc") + val htmlTask = named("dokkaGeneratePublicationHtml") + val inlineHtmlNav by registering { group = "documentation" description = "Inlines navigation.html into Dokka HTML to avoid empty sidebars." - dependsOn("dokkaGeneratePublicationHtml") + dependsOn(htmlTask) doLast { - val docDir = layout.buildDirectory.dir("doc-html").get().asFile + val docDir = htmlTask.get().outputDirectory.get().asFile val navFile = docDir.resolve("navigation.html") if (!navFile.exists()) return@doLast val navHtml = navFile.readText() val marker = "
" + val linkFixScript = """ + + """.trimIndent() docDir.walkTopDown() .filter { it.isFile && it.extension == "html" } .forEach { file -> - val original = file.readText() - if (!original.contains(marker)) return@forEach - val updated = original.replace(marker, "
$navHtml
") - if (updated != original) { - file.writeText(updated) + var updated = file.readText() + if (updated.contains(marker)) { + updated = updated.replace(marker, "
$navHtml
") + } + if (!updated.contains("dokka-inline-nav-fix")) { + val taggedScript = linkFixScript.replace("