From 1f64e17d394a67adf7ff693705c98ffb2d3c81ee Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 15 Oct 2026 19:56:45 +0100 Subject: [PATCH 1/3] Update shared agent tooling and config (./config/pull) --- .claude/settings.json | 11 +- .github/workflows/publish.yml | 2 +- buildSrc/build.gradle.kts | 8 +- buildSrc/src/main/kotlin/DokkaExts.kt | 118 +++++- .../src/main/kotlin/dokka-setup.gradle.kts | 6 +- .../spine/dependency/build/AnimalSniffer.kt | 2 +- .../io/spine/dependency/build/CheckStyle.kt | 15 +- .../dependency/build/CheckerFramework.kt | 2 +- .../kotlin/io/spine/dependency/build/Ksp.kt | 2 +- .../dependency/build/PluginPublishPlugin.kt | 2 +- .../kotlin/io/spine/dependency/build/Pmd.kt | 2 +- .../io/spine/dependency/lib/ApacheHttp.kt | 2 +- .../kotlin/io/spine/dependency/lib/Asm.kt | 2 +- .../io/spine/dependency/lib/BouncyCastle.kt | 28 +- .../io/spine/dependency/lib/Caffeine.kt | 2 +- .../io/spine/dependency/lib/CommonsCodec.kt | 2 +- .../io/spine/dependency/lib/Firebase.kt | 2 +- .../io/spine/dependency/lib/GoogleApis.kt | 14 +- .../io/spine/dependency/lib/GoogleCloud.kt | 8 +- .../kotlin/io/spine/dependency/lib/Grpc.kt | 2 +- .../kotlin/io/spine/dependency/lib/Guava.kt | 2 +- .../io/spine/dependency/lib/IntelliJ.kt | 3 + .../io/spine/dependency/lib/JacksonV2.kt | 2 +- .../kotlin/io/spine/dependency/lib/JavaX.kt | 2 +- .../kotlin/io/spine/dependency/lib/Kotlin.kt | 4 +- .../io/spine/dependency/lib/KotlinPoet.kt | 2 +- .../kotlin/io/spine/dependency/lib/Netty.kt | 2 +- .../kotlin/io/spine/dependency/lib/Okio.kt | 2 +- .../dependency/lib/PalantirJavaFormat.kt | 2 +- .../io/spine/dependency/lib/Protobuf.kt | 2 +- .../kotlin/io/spine/dependency/lib/Slf4J.kt | 2 +- .../io/spine/dependency/local/CoreJvm.kt | 2 +- .../spine/dependency/local/CoreJvmCompiler.kt | 4 +- .../io/spine/dependency/local/Reflect.kt | 2 +- .../io/spine/dependency/local/TestLib.kt | 2 +- .../io/spine/dependency/local/ToolBase.kt | 4 +- .../kotlin/io/spine/dependency/storage/H2.kt | 2 +- .../io/spine/dependency/storage/PostgreSql.kt | 2 +- .../kotlin/io/spine/dependency/test/Kotest.kt | 2 +- .../dependency/test/KotlinCompileTesting.kt | 2 +- .../kotlin/io/spine/dependency/test/Kover.kt | 2 +- .../io/spine/dependency/test/TestKitTruth.kt | 11 +- .../gradle/checkstyle/CheckStyleConfig.kt | 7 + .../gradle/publish/CheckVersionIncrement.kt | 157 +++++--- .../io/spine/gradle/publish/IncrementGuard.kt | 56 ++- .../gradle/publish/PublicationHandler.kt | 57 ++- .../io/spine/gradle/publish/PublishingExts.kt | 12 + .../io/spine/gradle/repo/Repositories.kt | 19 +- .../kotlin/io/spine/gradle/repo/Repository.kt | 30 +- .../gradle/checkstyle/CheckStyleConfigSpec.kt | 44 +++ .../io/spine/gradle/docs/DokkaSetupIgTest.kt | 295 +++++++++++++++ .../publish/CheckVersionIncrementIgTest.kt | 179 +++++++++ .../gradle/publish/IncrementGuardIgTest.kt | 201 ++++++++++ .../gradle/publish/IncrementGuardTest.kt | 350 ++++++++++++++++++ .../publish/PublishingCredentialsIgTest.kt | 337 +++++++++++++++++ .../io/spine/gradle/repo/RepositoriesSpec.kt | 51 +++ config | 2 +- gradle.properties | 5 + gradle/wrapper/gradle-wrapper.jar | Bin 47505 -> 48749 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 52 ++- 61 files changed, 1998 insertions(+), 150 deletions(-) create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/checkstyle/CheckStyleConfigSpec.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/docs/DokkaSetupIgTest.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/publish/CheckVersionIncrementIgTest.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardIgTest.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/publish/PublishingCredentialsIgTest.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/repo/RepositoriesSpec.kt diff --git a/.claude/settings.json b/.claude/settings.json index 3c7da68..44a3ac2 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -14,6 +14,7 @@ "Bash(git switch:*)", "Bash(git checkout:*)", "Bash(git add:*)", + "Bash(git commit:*)", "Bash(git restore:*)", "Bash(git stash:*)", "Bash(git fetch:*)", @@ -48,7 +49,15 @@ "Bash(gh release create:*)" ], "ask": [ - "Bash(git commit:*)", + "Bash(git push --force*)", + "Bash(git push * --force*)", + "Bash(git push -f*)", + "Bash(git push * -f*)", + "Bash(git push -uf*)", + "Bash(git push * -uf*)", + "Bash(git push * +*)", + "Bash(git push --mirror*)", + "Bash(git push * --mirror*)", "Bash(git rebase:*)", "Bash(git merge:*)", "Bash(git cherry-pick:*)", diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 649df9a..3bad11f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ on: jobs: publish: name: Publish to Maven repositories - uses: SpineEventEngine/config/.github/workflows/publishing.yml@219fef6ddd42c1e236dddf04526fb2a64409de6a + uses: SpineEventEngine/config/.github/workflows/publishing.yml@a0cbbbfe66c12475224563439fb6deb3da71ba58 permissions: contents: read packages: write diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 94ca8cd..aeaa51d 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -77,7 +77,7 @@ val grGitVersion = "5.3.3" * This version may change from the [version of Kotlin][io.spine.dependency.lib.Kotlin.version] * used by the project. */ -val kotlinEmbeddedVersion = "2.4.10" +val kotlinEmbeddedVersion = "2.4.21" /** * The version of Guava used in `buildSrc`. @@ -85,7 +85,7 @@ val kotlinEmbeddedVersion = "2.4.10" * Always use the same version as the one specified in [io.spine.dependency.lib.Guava]. * Otherwise, when testing Gradle plugins, clashes may occur. */ -val guavaVersion = "33.7.1-jre" +val guavaVersion = "33.7.2-jre" /** * The version of ErrorProne Gradle plugin. @@ -132,7 +132,7 @@ val kotestJvmPluginVersion = "0.4.11" /** * @see [io.spine.dependency.test.Kover] */ -val koverVersion = "0.9.9" +val koverVersion = "0.9.11" /** * The version of the Shadow Plugin. @@ -175,7 +175,7 @@ val junitVersion = "6.1.3" * * @see [io.spine.dependency.test.Kotest] */ -val kotestVersion = "6.2.4" +val kotestVersion = "6.2.5" configurations.all { resolutionStrategy { diff --git a/buildSrc/src/main/kotlin/DokkaExts.kt b/buildSrc/src/main/kotlin/DokkaExts.kt index 2bb2cc2..d1483b6 100644 --- a/buildSrc/src/main/kotlin/DokkaExts.kt +++ b/buildSrc/src/main/kotlin/DokkaExts.kt @@ -14,6 +14,7 @@ import io.spine.dependency.local.DokkaTools import io.spine.gradle.SpineTaskGroup +import io.spine.gradle.github.pages.TaskName import io.spine.gradle.publish.getOrCreate import java.io.File import java.time.LocalDate @@ -21,14 +22,20 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.execution.TaskExecutionGraph +import org.gradle.api.publish.maven.tasks.PublishToMavenLocal +import org.gradle.api.publish.maven.tasks.PublishToMavenRepository import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.DependencyHandlerScope +import org.gradle.kotlin.dsl.property +import org.gradle.kotlin.dsl.withType import org.jetbrains.dokka.gradle.DokkaExtension import org.jetbrains.dokka.gradle.engine.parameters.DokkaSourceSetSpec import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier import org.jetbrains.dokka.gradle.engine.plugins.DokkaHtmlPluginParameters +import org.jetbrains.dokka.gradle.tasks.DokkaBaseTask /** * To exclude pieces of code annotated with `@Internal` from the documentation @@ -205,18 +212,127 @@ fun Project.htmlDocsJar(): TaskProvider = tasks.getOrCreate("htmlDocsJar") /** * Tells if this task belongs to the execution graph that contains - * the `publish` and `dokkaGenerate` tasks. + * the `publish` or `dokkaGenerate` tasks. * * This predicate could be useful for disabling publishing tasks * when doing, e.g., `publishToMavenLocal` for the purpose of the * integration tests that (of course) do not test the documentation * generation process and its results. + * + * The predicate reaches the task graph through [Task.getProject]. Called from an `onlyIf` + * spec or a task action, it does so at execution time, which Gradle deprecates and + * the configuration cache does not support. Also, the predicate holds whenever the graph + * contains a Dokka task, since Dokka 2.x names its tasks `dokkaGenerate…`. + * [skipDokkaWhenPublishingToMavenLocal] avoids both problems. */ +@Deprecated(message = "Please use `Project.skipDokkaWhenPublishingToMavenLocal()` instead.") fun Task.isInPublishingGraph(): Boolean = project.gradle.taskGraph.allTasks.any { it.name == "publish" || it.name.contains("dokkaGenerate") } +/** + * Configures the Dokka tasks of this project to be skipped in a build that publishes + * to Maven Local only. + * + * Such a build usually feeds integration tests, which need the published code, + * but not its documentation. So the Dokka tasks are skipped when the execution graph + * contains a [PublishToMavenLocal] task and no task that ships the documentation + * elsewhere: `publish`, a [PublishToMavenRepository] task, or `updateGitHubPages`. + * They still run if a task named on the command line selects a documentation task of + * the graph: one that has `dokka` in its name, ignoring case, or a documentation JAR, + * `javadocJar` or `htmlDocsJar`. The name may be the full one, e.g., `dokkaGenerate`, + * or a camel-case abbreviation, e.g., `dGPJ` for `dokkaGeneratePublicationJavadoc`. + * Without task names, a documentation request cannot be ruled out, so the tasks run too. + * This is the case of an included build, as Gradle does not pass it the tasks named + * on the command line. + * + * Any other build runs the Dokka tasks in its graph. When the tasks are skipped, + * the documentation JARs they feed, such as `javadocJar`, contain only a manifest, + * or the output of an earlier Dokka run. + * + * The `onlyIf` spec added to the tasks is evaluated at execution time, when it must not + * call [Task.getProject] to reach the task graph: Gradle deprecates that, and + * the configuration cache does not support it. So the graph is scanned at configuration + * time, once it is ready, and the spec reads the result from a property, which + * a configuration cache entry stores along with the tasks. + */ +fun Project.skipDokkaWhenPublishingToMavenLocal() { + // The project path is dropped, so that, e.g., `:dokka-extensions:build` + // is not taken for a Dokka request. + val requestedNames = gradle.startParameter.taskNames + .map { it.substringAfterLast(':') } + .filter { it.isNotEmpty() } + val docsNeeded = objects.property() + gradle.taskGraph.whenReady { + val docsRequested = requestedNames.isEmpty() || selectsDocsTask(requestedNames) + docsNeeded.set(docsRequested || !publishesToMavenLocalOnly()) + } + tasks.withType().configureEach { + onlyIf("documentation is needed beyond Maven Local") { docsNeeded.get() } + } +} + +/** + * The names of the lifecycle tasks that ship the documentation beyond Maven Local. + */ +private val docsShippingTaskNames = setOf("publish", TaskName.updateGitHubPages) + +/** + * Tells if this graph publishes to Maven Local and does not contain a task that ships + * the documentation elsewhere. + */ +private fun TaskExecutionGraph.publishesToMavenLocalOnly(): Boolean { + val tasks = allTasks + return tasks.any { it is PublishToMavenLocal } + && tasks.none { it is PublishToMavenRepository || it.name in docsShippingTaskNames } +} + +/** + * The names of the tasks that pack the documentation into JARs. + */ +private val docsJarTaskNames = setOf("javadocJar", "htmlDocsJar") + +/** + * Tells if any of the given names, taken from the command line, selects + * a documentation task of this graph. + * + * A documentation task is one that has `dokka` in its name, ignoring case, + * or a documentation JAR. The command line keeps a task name as typed, so the name + * may be a camel-case abbreviation that Gradle resolves, e.g., `dGPJ` + * for `dokkaGeneratePublicationJavadoc`. + */ +private fun TaskExecutionGraph.selectsDocsTask(names: List): Boolean { + val docsTaskNames = allTasks.map { it.name }.filter { + it.contains("dokka", ignoreCase = true) || it in docsJarTaskNames + } + return names.any { name -> + val pattern = abbreviationPattern(name) + docsTaskNames.any { pattern.containsMatchIn(it) } + } +} + +/** + * Creates the pattern for the task names that the given camel-case abbreviation selects. + * + * Follows the matching of Gradle: each word of the abbreviation starts a word of + * the task name, in the same order, and the task name may go on after the last one. + * Unlike Gradle, which prefers exact and case-sensitive matches, the pattern + * ignores case, so it may select more tasks, never fewer. + */ +private fun abbreviationPattern(abbreviation: String): Regex { + val words = abbreviation.split(wordStart).filter { it.isNotEmpty() } + val pattern = words.joinToString(separator = "[\\p{javaLowerCase}\\p{Digit}]*", prefix = "^") { + Regex.escape(it) + } + return Regex(pattern, RegexOption.IGNORE_CASE) +} + +/** + * Matches the position before an upper-case letter, where a word of a camel-case name starts. + */ +private val wordStart = Regex("(?=\\p{javaUpperCase})") + /** * Disables Dokka and Javadoc tasks in this `Project`. * diff --git a/buildSrc/src/main/kotlin/dokka-setup.gradle.kts b/buildSrc/src/main/kotlin/dokka-setup.gradle.kts index 9255d03..c66e9f2 100644 --- a/buildSrc/src/main/kotlin/dokka-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-setup.gradle.kts @@ -23,11 +23,7 @@ dependencies { useDokkaWithSpineExtensions() } -tasks.withType().configureEach { - onlyIf { - isInPublishingGraph() - } -} +skipDokkaWhenPublishingToMavenLocal() // The Dokka Javadoc format does not support Kotlin Multiplatform source sets, so its // publication task fails for KMP modules ("No source set found for /jvmMain"). diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/AnimalSniffer.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/AnimalSniffer.kt index 7abd1d6..4891a7d 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/AnimalSniffer.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/AnimalSniffer.kt @@ -17,6 +17,6 @@ package io.spine.dependency.build // https://www.mojohaus.org/animal-sniffer/animal-sniffer-maven-plugin/ @Suppress("unused", "ConstPropertyName") object AnimalSniffer { - private const val version = "1.27" + private const val version = "1.28" const val lib = "org.codehaus.mojo:animal-sniffer-annotations:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckStyle.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckStyle.kt index f2bb226..b493760 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckStyle.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckStyle.kt @@ -25,10 +25,19 @@ object CheckStyle { /** * The version to be used in the project. * - * `10.12.1` is the last version in `10.12.0`, which does not introduce - * capability conflict over `google-collections` with Guava. + * `12.3.1` is the last release that runs on Java 17, which our builds use + * to run Checkstyle. `13.0.0` and later require Java 21. + * + * `12.3.1` depends on Doxia, which Checkstyle uses only to generate its own + * website. Through Doxia, it brings two versions each of `commons-lang3` and + * `httpcore`. `forceVersions()` enables `failOnVersionConflict()` in the + * `checkstyle` configuration too, so Gradle would reject the graph. + * [CheckStyleConfig][io.spine.gradle.checkstyle.CheckStyleConfig] therefore + * excludes the `org.apache.maven.doxia` group from the `checkstyle` configuration. + * Releases from `13.11.0` on no longer depend on Doxia, so the exclusion can go + * once we run Checkstyle on Java 21. * * @see Checkstyle */ - const val version = "10.12.1" + const val version = "12.3.1" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckerFramework.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckerFramework.kt index b125eeb..85f2603 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckerFramework.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/CheckerFramework.kt @@ -17,7 +17,7 @@ package io.spine.dependency.build // https://checkerframework.org/ @Suppress("unused", "ConstPropertyName") object CheckerFramework { - private const val version = "4.2.2" + private const val version = "4.3.1" const val annotations = "org.checkerframework:checker-qual:$version" @Suppress("unused") val dataflow = listOf( diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt index 58d40cd..5f5f65c 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt @@ -23,7 +23,7 @@ import io.spine.dependency.Dependency */ @Suppress("unused") object Ksp : Dependency() { - override val version = "2.3.11" + override val version = "2.3.12" val dogfoodingVersion = version override val group = "com.google.devtools.ksp" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/PluginPublishPlugin.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/PluginPublishPlugin.kt index 0f8a48c..5ebe6a4 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/PluginPublishPlugin.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/PluginPublishPlugin.kt @@ -25,6 +25,6 @@ package io.spine.dependency.build */ @Suppress("ConstPropertyName") object PluginPublishPlugin { - const val version = "2.1.1" + const val version = "2.2.1" const val id = "com.gradle.plugin-publish" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt index 94a5fdc..0ca4a21 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/Pmd.kt @@ -19,5 +19,5 @@ package io.spine.dependency.build // https://github.com/pmd/pmd/releases @Suppress("unused", "ConstPropertyName") object Pmd { - const val version = "7.27.0" + const val version = "7.28.0" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/ApacheHttp.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/ApacheHttp.kt index 06ac48e..28b509e 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/ApacheHttp.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/ApacheHttp.kt @@ -18,5 +18,5 @@ package io.spine.dependency.lib object ApacheHttp { // https://hc.apache.org/downloads.cgi - const val core = "org.apache.httpcomponents:httpcore:4.4.14" + const val core = "org.apache.httpcomponents:httpcore:4.4.16" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Asm.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Asm.kt index fa21f8b..216e038 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Asm.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Asm.kt @@ -17,7 +17,7 @@ package io.spine.dependency.lib // https://asm.ow2.io/ @Suppress("unused", "ConstPropertyName") object Asm { - private const val version = "9.10.1" + private const val version = "9.11" const val group = "org.ow2.asm" const val lib = "$group:asm:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/BouncyCastle.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/BouncyCastle.kt index e65d153..88bea96 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/BouncyCastle.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/BouncyCastle.kt @@ -17,5 +17,31 @@ package io.spine.dependency.lib // https://www.bouncycastle.org/java.html @Suppress("unused", "ConstPropertyName") object BouncyCastle { - const val libPkcsJdk15 = "org.bouncycastle:bcpkix-jdk15on:1.68" + + /** + * The PKIX library, pinned to `bcpkix-jdk15on:1.70`, the last release of + * the `-jdk15on` artifacts. + * + * Newer releases are published as `bcpkix-jdk18on`. They carry the same + * `org.bouncycastle` classes, yet Gradle sees them as a different module + * and resolves no conflict between the two. + * + * The [IntelliJ Platform][IntelliJ.version] stays at `213.7172.53` for now. + * Its `ide-impl` module depends on the `-jdk15on` artifacts through two paths: + * + * - `marketplace-zip-signer:0.1.3` requires `bcpkix-jdk15on:1.64`. + * - `remote-dev-util` requires `bcpg-jdk15on:1.69`, which requires + * `bcprov-jdk15on:1.69`. + * + * Staying on `-jdk15on` lets Gradle resolve `bcpkix-jdk15on` and + * `bcprov-jdk15on` to `1.70`, the version of this library. With + * `bcpkix-jdk18on` instead, a project that also has the IntelliJ Platform + * on its classpath would get two copies of the BouncyCastle classes. + * + * The two paths also disagree on the version. That is why `tool-base` + * leaves BouncyCastle out of the POM of its IntelliJ Platform uber JAR. + * + * Revisit this pin when [IntelliJ.version] changes. + */ + const val libPkcsJdk15 = "org.bouncycastle:bcpkix-jdk15on:1.70" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Caffeine.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Caffeine.kt index 48e8d64..8e1d907 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Caffeine.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Caffeine.kt @@ -26,6 +26,6 @@ package io.spine.dependency.lib */ @Suppress("unused") object Caffeine { - private const val version = "3.2.4" + private const val version = "3.3.1" const val lib = "com.github.ben-manes.caffeine:caffeine:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCodec.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCodec.kt index 8bfe3b2..e2d45c2 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCodec.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/CommonsCodec.kt @@ -17,6 +17,6 @@ package io.spine.dependency.lib // https://commons.apache.org/proper/commons-codec/changes-report.html @Suppress("unused", "ConstPropertyName") object CommonsCodec { - private const val version = "1.22.1" + private const val version = "1.23.0" const val lib = "commons-codec:commons-codec:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Firebase.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Firebase.kt index e28a240..435861d 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Firebase.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Firebase.kt @@ -17,6 +17,6 @@ package io.spine.dependency.lib // https://firebase.google.com/docs/admin/setup#java @Suppress("unused", "ConstPropertyName") object Firebase { - private const val adminVersion = "9.10.0" + private const val adminVersion = "9.11.0" const val admin = "com.google.firebase:firebase-admin:$adminVersion" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt index 78d2a4b..8664637 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt @@ -21,26 +21,26 @@ package io.spine.dependency.lib object GoogleApis { // https://github.com/googleapis/google-api-java-client - const val client = "com.google.api-client:google-api-client:1.32.2" + const val client = "com.google.api-client:google-api-client:2.9.1" // https://github.com/googleapis/api-common-java - const val common = "com.google.api:api-common:2.64.0" + const val common = "com.google.api:api-common:2.70.0" // https://github.com/googleapis/java-common-protos - const val commonProtos = "com.google.api.grpc:proto-google-common-protos:2.72.0" + const val commonProtos = "com.google.api.grpc:proto-google-common-protos:2.78.0" // https://github.com/googleapis/gax-java - const val gax = "com.google.api:gax:2.80.0" + const val gax = "com.google.api:gax:2.87.0" // https://github.com/googleapis/java-iam - const val protoAim = "com.google.api.grpc:proto-google-iam-v1:1.2.0" + const val protoAim = "com.google.api.grpc:proto-google-iam-v1:1.73.0" // https://github.com/googleapis/google-oauth-java-client - const val oAuthClient = "com.google.oauth-client:google-oauth-client:1.32.1" + const val oAuthClient = "com.google.oauth-client:google-oauth-client:1.39.0" // https://github.com/googleapis/google-auth-library-java object AuthLibrary { - const val version = "1.51.0" + const val version = "1.54.0" const val credentials = "com.google.auth:google-auth-library-credentials:$version" const val oAuth2Http = "com.google.auth:google-auth-library-oauth2-http:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt index ff3de8e..4df086b 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt @@ -21,14 +21,14 @@ package io.spine.dependency.lib object GoogleCloud { // https://github.com/googleapis/google-cloud-java/tree/main/sdk-platform-java/java-core - const val core = "com.google.cloud:google-cloud-core:2.71.0" + const val core = "com.google.cloud:google-cloud-core:2.77.0" // https://github.com/googleapis/google-cloud-java/tree/main/java-pubsub/proto-google-cloud-pubsub-v1 - const val pubSubGrpcApi = "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.151.0" + const val pubSubGrpcApi = "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.157.0" // https://github.com/googleapis/google-cloud-java/tree/main/java-trace - const val trace = "com.google.cloud:google-cloud-trace:2.93.0" + const val trace = "com.google.cloud:google-cloud-trace:2.99.0" // https://github.com/googleapis/google-cloud-java/tree/main/java-datastore - const val datastore = "com.google.cloud:google-cloud-datastore:2.31.2" + const val datastore = "com.google.cloud:google-cloud-datastore:3.7.0" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Grpc.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Grpc.kt index bc201a3..7f89c29 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Grpc.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Grpc.kt @@ -20,7 +20,7 @@ import io.spine.dependency.DependencyWithBom @Suppress("unused") object Grpc : DependencyWithBom() { - override val version = "1.83.1" + override val version = "1.85.0" override val group = "io.grpc" override val bom = "$group:grpc-bom:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Guava.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Guava.kt index 3acff5b..8f9eb3b 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Guava.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Guava.kt @@ -25,7 +25,7 @@ package io.spine.dependency.lib */ @Suppress("unused", "ConstPropertyName") object Guava { - private const val version = "33.7.1-jre" + private const val version = "33.7.2-jre" const val group = "com.google.guava" const val lib = "$group:guava:$version" const val testLib = "$group:guava-testlib:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt index e8118ef..ea3ce8e 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt @@ -31,6 +31,9 @@ object IntelliJ { * This is the version used by Kotlin compiler `1.9.21`. * Advance this version with caution because it may break the setup of * IntelliJ platform standalone execution. + * + * This version also keeps [BouncyCastle.libPkcsJdk15] on the `-jdk15on` + * artifacts. Revisit that pin when changing this version. */ const val version = "213.7172.53" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JacksonV2.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JacksonV2.kt index d811d6d..f79deac 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JacksonV2.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JacksonV2.kt @@ -38,7 +38,7 @@ import io.spine.dependency.DependencyWithBom @Suppress("unused") object JacksonV2 : DependencyWithBom() { override val group = "com.fasterxml.jackson" - override val version = "2.22.2" + override val version = "2.22.3" // https://github.com/FasterXML/jackson-bom override val bom = "$group:jackson-bom:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaX.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaX.kt index 4c96a34..517b076 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaX.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/JavaX.kt @@ -21,5 +21,5 @@ object JavaX { const val annotationGroup = "javax.annotation" const val annotations = "$annotationGroup:javax.annotation-api:1.3.2" - const val servletApi = "javax.servlet:javax.servlet-api:3.1.0" + const val servletApi = "javax.servlet:javax.servlet-api:4.0.1" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt index 5f63d1f..4b49e3a 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt @@ -26,7 +26,7 @@ object Kotlin : DependencyWithBom() { * This is the version of Kotlin we use for writing code that does not depend on Gradle. */ @Suppress("MemberVisibilityCanBePrivate") // used directly from the outside. - const val runtimeVersion = "2.4.10" + const val runtimeVersion = "2.4.21" override val version = runtimeVersion override val group = "org.jetbrains.kotlin" @@ -43,7 +43,7 @@ object Kotlin : DependencyWithBom() { * [embedded into Gradle](https://docs.gradle.org/current/userguide/compatibility.html#kotlin), * which the Gradle version determines on its own. */ - const val embeddedVersion = "2.4.10" + const val embeddedVersion = "2.4.21" /** * The version of the JetBrains annotations library, which is a transitive diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinPoet.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinPoet.kt index 9cd9664..c7fcf35 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinPoet.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/KotlinPoet.kt @@ -17,7 +17,7 @@ package io.spine.dependency.lib // https://github.com/square/kotlinpoet @Suppress("unused", "ConstPropertyName") object KotlinPoet { - private const val version = "2.3.0" + private const val version = "2.4.0" const val lib = "com.squareup:kotlinpoet:$version" const val ksp = "com.squareup:kotlinpoet-ksp:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Netty.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Netty.kt index f494047..53f6b40 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Netty.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Netty.kt @@ -17,7 +17,7 @@ package io.spine.dependency.lib @Suppress("unused", "ConstPropertyName") object Netty { // https://github.com/netty/netty/tags - private const val version = "4.2.17.Final" + private const val version = "4.2.19.Final" const val common = "io.netty:netty-common:$version" const val buffer = "io.netty:netty-buffer:$version" const val transport = "io.netty:netty-transport:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Okio.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Okio.kt index 9bf98f7..1487b5d 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Okio.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Okio.kt @@ -21,6 +21,6 @@ package io.spine.dependency.lib */ @Suppress("unused", "ConstPropertyName") object Okio { - private const val version = "3.18.1" + private const val version = "3.18.2" const val lib = "com.squareup.okio:okio:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/PalantirJavaFormat.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/PalantirJavaFormat.kt index bb71e7a..12e4630 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/PalantirJavaFormat.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/PalantirJavaFormat.kt @@ -24,7 +24,7 @@ import io.spine.dependency.Dependency object PalantirJavaFormat : Dependency() { override val group = "com.palantir.javaformat" - override val version = "2.97.0" + override val version = "2.102.0" override val modules: List = listOf("$group:palantir-java-format") val lib = artifact(modules[0]) diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Protobuf.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Protobuf.kt index 5d83e2d..9478184 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Protobuf.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Protobuf.kt @@ -21,7 +21,7 @@ package io.spine.dependency.lib ) object Protobuf { const val group = "com.google.protobuf" - const val version = "4.36.0" + const val version = "4.36.2" /** * The Java library with Protobuf data types. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Slf4J.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Slf4J.kt index b329ff6..c3478a5 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/Slf4J.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/Slf4J.kt @@ -28,7 +28,7 @@ package io.spine.dependency.lib // https://search.maven.org/artifact/org.slf4j/slf4j-api @Suppress("unused", "ConstPropertyName") object Slf4J { - private const val version = "2.0.18" + private const val version = "2.0.20" const val lib = "org.slf4j:slf4j-api:$version" const val jdk14 = "org.slf4j:slf4j-jdk14:$version" const val reload4j = "org.slf4j:slf4j-reload4j:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt index c82cb3e..bef7362 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt @@ -27,7 +27,7 @@ typealias CoreJava = CoreJvm @Suppress("ConstPropertyName", "unused") object CoreJvm { const val group = Spine.group - const val version = "2.0.0-SNAPSHOT.561" + const val version = "2.0.0-SNAPSHOT.567" const val coreArtifact = "spine-core" const val clientArtifact = "spine-client" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt index 1df9ce5..5db1a81 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt @@ -34,12 +34,12 @@ object CoreJvmCompiler { /** * The version used in the build classpath. */ - const val dogfoodingVersion = "2.0.0-SNAPSHOT.094" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.097" /** * The version to be used for integration tests. */ - const val version = "2.0.0-SNAPSHOT.094" + const val version = "2.0.0-SNAPSHOT.097" /** * The ID of the Gradle plugin. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Reflect.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Reflect.kt index d45e1a2..ae8e17f 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Reflect.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Reflect.kt @@ -21,7 +21,7 @@ package io.spine.dependency.local */ @Suppress("ConstPropertyName") object Reflect { - const val version = "2.0.0-SNAPSHOT.200" + const val version = "2.0.0-SNAPSHOT.201" const val group = Spine.group const val artifact = "spine-reflect" const val lib = "$group:$artifact:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/TestLib.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/TestLib.kt index b07ddad..bf26e25 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/TestLib.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/TestLib.kt @@ -21,7 +21,7 @@ package io.spine.dependency.local */ @Suppress("ConstPropertyName") object TestLib { - const val version = "2.0.0-SNAPSHOT.213" + const val version = "2.0.0-SNAPSHOT.214" const val group = Spine.toolsGroup const val artifact = "base-testlib" const val lib = "$group:$artifact:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt index 9acd345..63ecfca 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt @@ -29,8 +29,8 @@ package io.spine.dependency.local @Suppress("ConstPropertyName", "unused") object ToolBase { const val group = Spine.toolsGroup - const val version = "2.0.0-SNAPSHOT.423" - const val dogfoodingVersion = "2.0.0-SNAPSHOT.423" + const val version = "2.0.0-SNAPSHOT.425" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.425" const val archive = "$group:archive:$version" const val code = "$group:code:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/storage/H2.kt b/buildSrc/src/main/kotlin/io/spine/dependency/storage/H2.kt index b970875..a7e81ff 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/storage/H2.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/storage/H2.kt @@ -23,6 +23,6 @@ package io.spine.dependency.storage */ @Suppress("unused", "ConstPropertyName") object H2 { - private const val version = "2.4.240" + private const val version = "2.5.252" const val lib = "com.h2database:h2:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/storage/PostgreSql.kt b/buildSrc/src/main/kotlin/io/spine/dependency/storage/PostgreSql.kt index fbfa0e9..9f47ac8 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/storage/PostgreSql.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/storage/PostgreSql.kt @@ -23,6 +23,6 @@ package io.spine.dependency.storage */ @Suppress("unused", "ConstPropertyName") object PostgreSql { - private const val version = "42.7.13" + private const val version = "42.7.14" const val connector = "org.postgresql:postgresql:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/Kotest.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/Kotest.kt index 0f1bf46..b0bb899 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/Kotest.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Kotest.kt @@ -23,7 +23,7 @@ package io.spine.dependency.test */ @Suppress("unused", "ConstPropertyName") object Kotest { - const val version = "6.2.4" + const val version = "6.2.5" const val group = "io.kotest" const val gradlePluginId = "io.kotest" const val assertions = "$group:kotest-assertions-core:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/KotlinCompileTesting.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/KotlinCompileTesting.kt index 4d54ce0..1976493 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/KotlinCompileTesting.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/KotlinCompileTesting.kt @@ -21,7 +21,7 @@ package io.spine.dependency.test */ @Suppress("unused", "ConstPropertyName") object KotlinCompileTesting { - private const val version = "0.13.0" + private const val version = "0.14.0" private const val group = "dev.zacsweers.kctfork" const val libCore = "$group:core:$version" const val libKsp = "$group:ksp:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/Kover.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/Kover.kt index cbaa2c8..9c984b4 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/Kover.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Kover.kt @@ -17,7 +17,7 @@ package io.spine.dependency.test // https://github.com/Kotlin/kotlinx-kover @Suppress("unused", "ConstPropertyName") object Kover { - const val version = "0.9.9" + const val version = "0.9.11" const val id = "org.jetbrains.kotlinx.kover" const val classpath = "org.jetbrains.kotlinx:kover-gradle-plugin:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/TestKitTruth.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/TestKitTruth.kt index 76b3f7a..69fabe7 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/test/TestKitTruth.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/TestKitTruth.kt @@ -19,11 +19,16 @@ package io.spine.dependency.test /** * Gradle TestKit extension for Google Truth. * - * @see TestKit source code + * @see TestKit source code * @see Usage description */ @Suppress("unused", "ConstPropertyName") object TestKitTruth { - private const val version = "1.20.0" - const val lib = "com.autonomousapps:testkit-truth:$version" + private const val version = "1.7.1" + + /** + * The artifact has been published as `gradle-testkit-truth` since `1.3`. + * Earlier versions, up to `1.1`, were published as `testkit-truth`. + */ + const val lib = "com.autonomousapps:gradle-testkit-truth:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt index 6ae2115..b823268 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt @@ -18,6 +18,7 @@ import io.spine.dependency.build.CheckStyle import org.gradle.api.Project import org.gradle.api.plugins.quality.CheckstyleExtension import org.gradle.api.plugins.quality.CheckstylePlugin +import org.gradle.kotlin.dsl.exclude import org.gradle.kotlin.dsl.the /** @@ -51,6 +52,12 @@ object CheckStyleConfig { configDirectory.set(configDir) } + // Checkstyle needs Doxia only to generate its own website, and Doxia's + // dependencies conflict with the versions we force. See `CheckStyle.version`. + project.configurations.named("checkstyle") { + exclude(group = "org.apache.maven.doxia") + } + project.afterEvaluate { // Disables checking the test sources and test fixtures. arrayOf( diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt index 6b4b98d..f43f019 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt @@ -20,9 +20,13 @@ import io.spine.gradle.repo.Repository import java.net.URI import org.gradle.api.DefaultTask import org.gradle.api.GradleException -import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault /** * A task that verifies the project version is fit to be published. @@ -36,11 +40,19 @@ import org.gradle.api.tasks.TaskAction * parallel PRs that bumped to the same value, regardless of what is (or is not yet) * published. * 2. [checkNotPublished] — the [version] must not already exist in the target Maven - * repository, so a publication cannot overwrite an immutable artifact. + * repository for any of the [artifacts][artifactPaths] the project publishes, so + * a publication cannot overwrite an immutable artifact. * * The two checks are complementary; neither subsumes the other. + * + * Neither the task action nor the `onlyIf` spec that gates it may access + * [project][org.gradle.api.Task.getProject]: Gradle deprecates that at execution time, and + * the configuration cache does not support it. Therefore, [version] is captured when the + * task is created, [IncrementGuard] sets [rootDir] and [artifactPaths] when it registers + * the task, and it sets [publishesToMavenLocal] once the task graph is ready. */ -open class CheckVersionIncrement : DefaultTask() { +@DisableCachingByDefault(because = "Queries a remote Maven repository and produces no outputs.") +abstract class CheckVersionIncrement : DefaultTask() { /** * The Maven repository in which to look for published artifacts. @@ -54,6 +66,44 @@ open class CheckVersionIncrement : DefaultTask() { @Input val version: String = project.version as String + /** + * The root directory of the build, which holds `version.gradle.kts`. + * + * Only its location matters — the task reads `version.gradle.kts` and runs `git` there. + * Hence, it is not an [input directory][org.gradle.api.tasks.InputDirectory], which + * would fingerprint the whole build tree. + */ + @get:Internal + abstract val rootDir: DirectoryProperty + + /** + * The paths to the artifacts of the project in a Maven repository, such as + * `io/spine/spine-base`, one per Maven publication of the project. + * + * Empty for a project that publishes nothing. + */ + @get:Input + abstract val artifactPaths: SetProperty + + /** + * Tells whether the build is going to publish this task's project to Maven Local. + * + * Integration tests in this and sibling projects consume freshly built artifacts + * from `~/.m2`. Publishing them under a version that already exists would let those + * tests pick up a stale artifact, so a local build verifies the version increment + * before any local publication of the project runs. + * + * Only this task's own project counts: a sibling module's local publish in the same + * invocation must not trigger this module's check (see + * [IncrementGuard.localPublishPlanned]). + * + * [IncrementGuard] scans the task graph once it is ready and sets this property, which + * the `onlyIf` spec it adds to the task then reads. The value affects only whether + * the task runs, not what it verifies, so it is not an [input][Input]. + */ + @get:Internal + abstract val publishesToMavenLocal: Property + @TaskAction fun checkVersion() { checkIncrementedAgainstBase() @@ -132,7 +182,8 @@ open class CheckVersionIncrement : DefaultTask() { * [VersionGradleFile.contentInBase] when the base ref itself cannot be resolved. */ private fun baseVersionToCompare(baseRef: String): String? { - val headContent = VersionGradleFile.contentUnder(project.rootDir) + val root = rootDir.get().asFile + val headContent = VersionGradleFile.contentUnder(root) val key = headContent?.let { VersionGradleFile.keyForValue(it, version) } if (key == null) { logger.warn( @@ -141,7 +192,7 @@ open class CheckVersionIncrement : DefaultTask() { ) return null } - val baseContent = VersionGradleFile.contentInBase(project.rootDir, baseRef) + val baseContent = VersionGradleFile.contentInBase(root, baseRef) val baseVersion = baseContent?.let { VersionGradleFile.valueForKey(it, key) } if (baseVersion == null) { logger.info( @@ -154,29 +205,76 @@ open class CheckVersionIncrement : DefaultTask() { /** * Verifies that the current [version] has not been published to the target Maven - * repository yet. + * repository yet, for any of the [artifactPaths]. * * Both the `releases` and `snapshots` repositories are checked; artifacts in either * may not be overwritten. */ private fun checkNotPublished() { - val artifact = "${project.artifactPath()}/${MavenMetadata.FILE_NAME}" - val snapshots = repository.target(snapshots = true) - checkInRepo(snapshots, artifact) + val paths = artifactPaths.get() + if (paths.isEmpty()) { + logger.info( + "The project has no Maven publications; " + + "skipping the check for an already published version." + ) + return + } + paths.forEach(::checkArtifactNotPublished) + } - if (!repository.hasOneTarget()) { - checkInRepo(repository.target(snapshots = false), artifact) + /** + * Verifies that the current [version] of the artifact at the given [path] has not been + * published to the target Maven repository yet. + * + * Each repository is checked as soon as its metadata is fetched, so a failure to reach + * one repository does not hide the version found in another one checked before it. + * + * When no repository has the metadata of the artifact, the version cannot be verified, + * and a warning is logged. This is expected before the first publication of the artifact. + * For an artifact published before, it means that [path] does not match the published one. + */ + private fun checkArtifactNotPublished(path: String) { + val artifact = "$path/${MavenMetadata.FILE_NAME}" + val repositories = targetRepositories() + var found = false + for (repoUrl in repositories) { + val metadata = fetch(repoUrl, artifact) ?: continue + found = true + checkNotListed(path = path, repoUrl = repoUrl, metadata = metadata) + } + if (!found) { + logger.warn( + "No `${MavenMetadata.FILE_NAME}` is found for `$path` in " + + "${repositories.joinToString { "`$it`" }}. Either the artifact has never " + + "been published, or the path does not match the published artifact. " + + "`$name` cannot tell whether the version `$version` is already published." + ) + } + } + + /** + * Returns the URLs of the target repositories: the `snapshots` one, and + * the `releases` one unless it is the same destination. + */ + private fun targetRepositories(): List { + val snapshots = repository.target(snapshots = true) + if (repository.hasOneTarget()) { + return listOf(snapshots) } + return listOf(snapshots, repository.target(snapshots = false)) } - private fun checkInRepo(repoUrl: String, artifact: String) { - val metadata = fetch(repoUrl, artifact) - val versions = metadata?.versioning?.versions - val versionExists = versions?.contains(version) ?: false - if (versionExists) { + /** + * Throws if the [metadata] of the artifact at the given [path], fetched from [repoUrl], + * lists the current [version]. + */ + private fun checkNotListed(path: String, repoUrl: String, metadata: MavenMetadata) { + val versions = metadata.versioning.versions + if (version in versions) { throw GradleException( """ - The version `$version` is already published to the Maven repository `$repoUrl`. + The version `$version` of `$path` is already published + to the Maven repository `$repoUrl`. Try incrementing the library version. All available versions are: ${versions.joinToString(separator = ", ")}. @@ -190,29 +288,4 @@ open class CheckVersionIncrement : DefaultTask() { val url = URI.create("$repository/$artifact").toURL() return MavenMetadata.fetchAndParse(url) } - - private fun Project.artifactPath(): String { - val group = this.group as String - val name = "${artifactPrefix()}${this.name}" - - val pathElements = ArrayList(group.split('.')) - pathElements.add(name) - val path = pathElements.joinToString(separator = "/") - return path - } - - /** - * Returns the artifact prefix used for the publishing of this project. - * - * All current Spine modules should be using `SpinePublishing`. - * Therefore, the corresponding extension should be present in the root project. - * However, just in case, we define the "standard" prefix here as well. - * - * This value MUST be the same as defined by the defaults in `SpinePublishing`. - */ - private fun Project.artifactPrefix(): String { - val ext = rootProject.extensions.findByType(SpinePublishing::class.java) - val result = ext?.artifactPrefix ?: SpinePublishing.DEFAULT_PREFIX - return result - } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt index 29b7dad..121cc20 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt @@ -21,6 +21,7 @@ import io.spine.gradle.SpineTaskGroup import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.tasks.PublishToMavenLocal /** @@ -143,8 +144,27 @@ class IncrementGuard : Plugin { group = SpineTaskGroup.name description = "Verifies that the project version was incremented before publishing" repository = CloudArtifactRegistry.repository + rootDir.set(target.rootDir) + // Resolved lazily: publications and their coordinates are configured + // after the task is created, some of them in `afterEvaluate`. + artifactPaths.set(target.provider { target.publishedArtifactPaths() }) onlyIf { - mustVerify(shouldCheckVersion(), Build.ci, it.publishesToMavenLocal()) + mustVerify( + ciPullRequest = shouldCheckVersion(), + onCi = Build.ci, + localPublish = publishesToMavenLocal.get() + ) + } + } + + // The `onlyIf` spec above runs at execution time, when it must not call `Task.project` + // to reach the task graph. So the graph is scanned for this project's Maven Local + // publishing at configuration time, once it is known. The result is kept in a task + // property, which a configuration cache entry stores along with the task. + target.gradle.taskGraph.whenReady { + val graph = this + checkVersion.configure { + publishesToMavenLocal.set(localPublishPlanned(graph.allTasks, target)) } } @@ -181,18 +201,28 @@ class IncrementGuard : Plugin { } /** - * Tells whether the current build is going to publish this task's project to - * Maven Local. + * Obtains the paths in a Maven repository to the artifacts this project publishes, + * such as `io/spine/spine-base`. + * + * The paths are taken from the coordinates of the Maven publications of the project, + * rather than computed from its name. So they cover the + * [tool artifact prefix][SpinePublishing.toolArtifactPrefix], an `artifactId` set by + * a module with [custom publishing][SpinePublishing.modulesWithCustomPublishing], and + * every publication of a module that has several, such as a Kotlin Multiplatform one. * - * Integration tests in this and sibling projects consume freshly built artifacts - * from `~/.m2`. Publishing them under a version that already exists would let those - * tests pick up a stale artifact, so the version increment must be verified before - * any local publication runs. + * Plugin markers are included. A marker is a publication of its own, which may be + * uploaded without the plugin it points to, e.g., by a publish that failed halfway. * - * Only this task's own project is considered: a sibling module's local publish in - * the same invocation must not trigger this module's check. The predicate is - * evaluated lazily as a task `onlyIf` spec, by which point the execution - * [task graph][org.gradle.api.execution.TaskExecutionGraph] is fully populated. + * The result is empty for a project that publishes nothing. + */ +private fun Project.publishedArtifactPaths(): Set = + mavenPublications() + .map { it.repositoryPath } + .toSet() + +/** + * The path to the artifact of this publication in a Maven repository, + * such as `io/spine/spine-base`. */ -private fun Task.publishesToMavenLocal(): Boolean = - IncrementGuard.localPublishPlanned(project.gradle.taskGraph.allTasks, project) +private val MavenPublication.repositoryPath: String + get() = "${groupId.replace('.', '/')}/$artifactId" diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt index 1884c54..1d4bb96 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationHandler.kt @@ -16,16 +16,22 @@ package io.spine.gradle.publish import DocumentationSettings import LicenseSettings +import io.spine.gradle.SpineTaskGroup import io.spine.gradle.artifactId import io.spine.gradle.isSnapshot import io.spine.gradle.repo.Repository import io.spine.gradle.report.pom.InceptionYear +import java.util.Locale +import java.util.concurrent.Callable import org.gradle.api.Project import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.api.invocation.BuildInvocationDetails import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.maven.tasks.PublishToMavenRepository import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.support.serviceOf +import org.gradle.kotlin.dsl.withType /** * The name of the Maven Publishing Gradle plugin. @@ -276,18 +282,55 @@ sealed class PublicationHandler( } /** - * Adds a Maven repository to the project specifying credentials, if they are - * [available][Repository.credentials] from the root project. + * Adds a Maven repository to the project. + * + * The credentials of the repository, if they are [available][Repository.credentials] + * from the root project, are supplied only when a task publishing to it is about + * to run (see [supplyCredentials]). */ private fun RepositoryHandler.register(project: Project, repository: Repository) { val isSnapshot = project.version.toString().isSnapshot() - val credentials = repository.credentials(project.rootProject) - maven { + val target = maven { name = repository.name(isSnapshot) url = project.uri(repository.target(isSnapshot)) - credentials { - username = credentials?.username - password = credentials?.password + } + project.supplyCredentials(target, repository) +} + +/** + * Registers a supplier task that sets the credentials of the [target] repository + * to those of the [source] one, and makes each task publishing to [target] + * depend on the supplier task. + * + * Obtaining the credentials may start an external process, e.g., `gcloud` + * for [CloudArtifactRegistry]. Gradle deprecates starting one at configuration time, + * and builds that do not publish should not obtain the credentials at all. + * An action of the publishing task cannot supply them, either: the task validates + * them before running any of its actions. Hence, the publishing task depends on + * the supplier task, which sets them on the repository rather than producing an output. + */ +private fun Project.supplyCredentials(target: MavenArtifactRepository, source: Repository) { + // Captured at configuration time: the action must not reach the project + // through its task, as Gradle deprecates calling `Task.project` at execution time. + val rootProject = rootProject + val repoName = target.name.replaceFirstChar { it.titlecase(Locale.ROOT) } + val supplier = tasks.register("supply${repoName}Credentials") { + group = SpineTaskGroup.name + description = "Obtains the credentials for publishing to the `${target.name}` repository" + notCompatibleWithConfigurationCache( + "Gradle does not support explicit repository credentials with the configuration cache." + ) + doLast { + val credentials = source.credentials(rootProject) + target.credentials { + username = credentials?.username + password = credentials?.password + } } } + tasks.withType().configureEach { + // The `maven-publish` plugin may assign the repository after this action runs, + // so the dependency is resolved later, when Gradle builds the task graph. + dependsOn(Callable { if (repository === target) supplier else null }) + } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt index 04df443..5a876d2 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublishingExts.kt @@ -203,7 +203,19 @@ private fun TaskContainer.registerCheckCredentialsTask( } } +/** + * Adds an action that fails this task if any of the [destinations] has no credentials. + * + * The action obtains the credentials with the [Project] of this task. The project is + * captured when this function runs, at configuration time, because Gradle deprecates + * calling [Task.getProject] at execution time. The configuration cache cannot store + * a project, though, so the task is not compatible with it. + */ private fun Task.doLastCredentialsCheck(destinations: Set) { + val project = project + notCompatibleWithConfigurationCache( + "Obtaining the credentials of a repository requires the `Project`." + ) doLast { if (logger.isDebugEnabled) { val isSnapshot = project.version.toString().isSnapshot() diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt index 74e0d3c..c552dd4 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt @@ -111,14 +111,21 @@ fun RepositoryHandler.standardToSpineSdk() { mavenCentral { excludeSpine() } gradlePluginPortal { excludeSpine() } - spineArtifacts() - + // + // Spine repositories. Gradle fetches a module's artifacts only from the repository + // that served its metadata. If that repository drops the artifacts after a build + // cached the metadata, later builds fail instead of falling through to the next one. + // + // Google Artifact Registry retains old versions, so it comes first. `CloudRepo` + // serves only earlier versions. GitHub Packages comes last: a scheduled workflow + // keeps only the newest snapshots of each package there. + // @Suppress("DEPRECATION") // Still use `CloudRepo` for earlier versions. val spineRepos = listOf( - Repos.spine, - Repos.spineSnapshots, Repos.artifactRegistry, - Repos.artifactRegistrySnapshots + Repos.artifactRegistrySnapshots, + Repos.spine, + Repos.spineSnapshots ) spineRepos @@ -130,6 +137,8 @@ fun RepositoryHandler.standardToSpineSdk() { } } + spineArtifacts() + // IntelliJ Platform repositories. `intellijReleases` serves the platform // artifacts (`com.jetbrains.intellij.*`); `intellijDependencies` serves the // repackaged third-party dependencies (`org.jetbrains.intellij.deps.*` and diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repository.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repository.kt index 4c39315..8e747f3 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repository.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repository.kt @@ -29,6 +29,10 @@ import org.gradle.api.Project * @param credentialsFile The path to the file that contains the credentials for the registry. * @param credentialValues The function to obtain an instance of [Credentials] from * a Gradle [Project], if [credentialsFile] is not specified. + * Publishing calls it only at execution time, so it may start an external process. + * It may be called several times for each published module, but never concurrently. + * It must not wait for the state of any project, e.g., resolve a configuration: + * the calls hold a lock that the tasks of all projects share. */ data class Repository( private val name: String, @@ -40,6 +44,11 @@ data class Repository( companion object { val regex = Regex("[A-Za-z0-9_\\-.]+") + + /** + * Serializes obtaining the credentials of all repositories (see [credentials]). + */ + private val credentialsLock = Any() } init { @@ -76,13 +85,22 @@ data class Repository( * If the credentials are represented by a `.properties` file, reads the file and parses * the credentials. The file must have properties `user.name` and `user.password`, which store * the username and the password for the Maven repository auth. + * + * Call this function from a task action rather than at configuration time: + * obtaining the credentials may start an external process, such as `gcloud`. + * + * The calls are serialized. Tasks of several projects may call it at once, and + * obtaining the credentials may write files that other calls share, e.g., + * the GitHub Packages token unpacked into the build directory of the given project. */ - fun credentials(project: Project): Credentials? = when { - credentialValues != null -> credentialValues.invoke(project) - credentialsFile != null -> credsFromFile(credentialsFile, project) - else -> throw IllegalArgumentException( - "Credentials file or a supplier function should be passed." - ) + fun credentials(project: Project): Credentials? = synchronized(credentialsLock) { + when { + credentialValues != null -> credentialValues.invoke(project) + credentialsFile != null -> credsFromFile(credentialsFile, project) + else -> throw IllegalArgumentException( + "Credentials file or a supplier function should be passed." + ) + } } private fun credsFromFile(fileName: String, project: Project): Credentials? { diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/checkstyle/CheckStyleConfigSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/checkstyle/CheckStyleConfigSpec.kt new file mode 100644 index 0000000..8b7669f --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/checkstyle/CheckStyleConfigSpec.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.checkstyle + +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.shouldBe +import io.spine.dependency.build.CheckStyle +import org.gradle.api.Project +import org.gradle.api.plugins.quality.CheckstyleExtension +import org.gradle.kotlin.dsl.the +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("`CheckStyleConfig` should") +internal class CheckStyleConfigSpec { + + private val project: Project = ProjectBuilder.builder().build().also { + CheckStyleConfig.applyTo(it) + } + + @Test + fun `use the declared Checkstyle version`() { + project.the().toolVersion shouldBe CheckStyle.version + } + + @Test + fun `exclude the whole Doxia group from the Checkstyle classpath`() { + val rules = project.configurations.getByName("checkstyle").excludeRules + rules.map { it.group to it.module } shouldContain ("org.apache.maven.doxia" to null) + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/docs/DokkaSetupIgTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/docs/DokkaSetupIgTest.kt new file mode 100644 index 0000000..6ec5eff --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/docs/DokkaSetupIgTest.kt @@ -0,0 +1,295 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.docs + +import io.kotest.assertions.withClue +import io.kotest.matchers.file.shouldExist +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import java.io.File +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome.SKIPPED +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * The path of the probe task that the build under test declares. + */ +private const val PROBE_PATH = ":dokka-sample:dokkaGeneratePublicationProbe" + +/** + * Verifies when the `dokka-setup` plugin lets Dokka tasks run in a real build + * that uses the configuration cache. + * + * Every case runs the build twice: the first run stores the configuration cache entry, + * and the second one reuses it. The task must reach the same outcome both times, so the + * decision whether to run cannot rely on configuration that a reused entry skips. + * + * Dokka's own tasks resolve the Dokka engine from Maven Central, which an offline + * build cannot reach. So the build declares a probe task that extends + * [DokkaBaseTask][org.jetbrains.dokka.gradle.tasks.DokkaBaseTask] and does nothing. + * The plugin gates it like any other Dokka task. The probe is named like + * `dokkaGeneratePublicationJavadoc`: the Dokka 2.x tasks that generate documentation + * have `dokkaGenerate` in their names, so a gate that relied on task names would never + * skip them. + * + * The build publishes a `javadocJar` that depends on the probe, just as the `javadocJar` + * that `spinePublishing` creates depends on `dokkaGeneratePublicationJavadoc`. The build + * publishes to a file-based repository and to its own Maven Local repository, so it never + * touches `~/.m2`. The `updateGitHubPages` task of the build stands in for the one that + * copies the Dokka output to GitHub Pages. + * + * The project with the probe is named `dokka-sample`, like the modules of `dokka-tools`, + * so that the tests cover `dokka` in the path of a requested task, which must not count + * as a Dokka request. + */ +@DisplayName("`dokka-setup` should, with the configuration cache,") +internal class DokkaSetupIgTest { + + @TempDir + lateinit var projectDir: File + + @BeforeEach + fun createBuild() { + writeBuild(projectDir, "documented-sample") + } + + @Test + fun `skip a Dokka task when the build publishes to Maven Local only`() { + runStoringThenReusing(":dokka-sample:publishToMavenLocal") { + it.probeOutcome shouldBe SKIPPED + // The javadoc JAR is still published, which is enough for integration tests. + mavenLocal.resolve("io/spine/sample/dokka-sample/1.0.0/dokka-sample-1.0.0-javadoc.jar") + .shouldExist() + } + } + + @Test + fun `run a Dokka task when the build publishes to a remote repository`() { + runStoringThenReusing(":dokka-sample:publish") { + it.probeOutcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task when the build publishes to both Maven Local and a remote repository`() { + runStoringThenReusing(":dokka-sample:publishToMavenLocal", ":dokka-sample:publish") { + it.probeOutcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task when a repository publication task runs along with Maven Local`() { + runStoringThenReusing( + ":dokka-sample:publishToMavenLocal", + ":dokka-sample:publishDocsPublicationToRemoteRepository" + ) { + it.probeOutcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task when the build updates GitHub Pages and publishes to Maven Local`() { + runStoringThenReusing( + ":dokka-sample:publishToMavenLocal", + ":dokka-sample:updateGitHubPages" + ) { + it.probeOutcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task when it is requested along with publishing to Maven Local`() { + runStoringThenReusing(":dokka-sample:publishToMavenLocal", PROBE_PATH) { + it.probeOutcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task requested by an abbreviation along with publishing to Maven Local`() { + // Gradle selects `dokkaGeneratePublicationProbe` by this camel-case abbreviation. + runStoringThenReusing(":dokka-sample:publishToMavenLocal", ":dokka-sample:dGPP") { + it.probeOutcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task when a docs JAR is requested along with publishing to Maven Local`() { + runStoringThenReusing(":dokka-sample:publishToMavenLocal", ":dokka-sample:javadocJar") { + it.probeOutcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task requested from an included build along with Maven Local`() { + // Gradle passes an included build no task names, so it cannot see the request. + val included = "included" + writeBuild(file(included), included) + file("settings.gradle.kts").appendText("\nincludeBuild(\"$included\")\n") + val probe = ":$included$PROBE_PATH" + runStoringThenReusing(":$included:dokka-sample:publishToMavenLocal", probe) { + it.task(probe)?.outcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task when the build does not publish`() { + runStoringThenReusing(":dokka-sample:javadocJar") { + it.probeOutcome shouldBe SUCCESS + } + } + + @Test + fun `run a Dokka task when the build generates documentation`() { + runStoringThenReusing(PROBE_PATH) { + it.probeOutcome shouldBe SUCCESS + } + } + + /** + * Writes a build named [name] into [dir], with the probe in its `dokka-sample` project. + */ + private fun writeBuild(dir: File, name: String) { + dir.mkdirs() + dir.resolve("settings.gradle.kts").writeText( + """ + rootProject.name = "$name" + include("dokka-sample") + """.trimIndent() + ) + // The standard library would be resolved from Maven Central otherwise, + // which an offline build cannot reach. + dir.resolve("gradle.properties").writeText("kotlin.stdlib.default.dependency=false\n") + dir.resolve("build.gradle.kts").writeText( + """ + buildscript { + dependencies { + classpath(files( + ${buildSrcClasspath()} + )) + } + } + """.trimIndent() + ) + dir.resolve("dokka-sample").mkdirs() + dir.resolve("dokka-sample/build.gradle.kts").writeText( + """ + import org.jetbrains.dokka.gradle.internal.InternalDokkaGradlePluginApi + import org.jetbrains.dokka.gradle.tasks.DokkaBaseTask + + plugins { + kotlin("jvm") + `maven-publish` + id("dokka-setup") + } + + @OptIn(InternalDokkaGradlePluginApi::class) + abstract class DocsProbe : DokkaBaseTask() { + + @TaskAction + fun probe() = Unit + } + + group = "io.spine.sample" + version = "1.0.0" + + val docsProbe = tasks.register("dokkaGeneratePublicationProbe") + + val javadocJar = tasks.register("javadocJar") { + archiveClassifier.set("javadoc") + from(layout.buildDirectory.dir("docs-probe")) + dependsOn(docsProbe) + } + + publishing { + publications.create("docs") { + artifact(javadocJar) + } + repositories.maven { + name = "remote" + url = uri(rootDir.resolve("remote-repo")) + } + } + + tasks.register("updateGitHubPages") { + dependsOn(docsProbe) + } + """.trimIndent() + ) + } + + private val BuildResult.probeOutcome + get() = task(PROBE_PATH)?.outcome + + /** + * The Maven Local repository of the build, which replaces `~/.m2/repository`. + */ + private val mavenLocal: File + get() = file("maven-local") + + /** + * Runs the given [tasks] twice with the configuration cache, and checks the result of + * each run with [verify]. + * + * The first run stores the cache entry, and the second one reuses it. + */ + private fun runStoringThenReusing(vararg tasks: String, verify: (BuildResult) -> Unit) { + val stored = runGradle(tasks) + withClue("The run that stores the configuration cache entry") { + stored.output shouldContain "Configuration cache entry stored" + verify(stored) + } + val reused = runGradle(tasks) + withClue("The run that reuses the configuration cache entry") { + reused.output shouldContain "Reusing configuration cache" + verify(reused) + } + } + + private fun runGradle(tasks: Array): BuildResult = + GradleRunner.create() + .withProjectDir(projectDir) + .withArguments( + *tasks, + "--configuration-cache", + "--offline", + "--stacktrace", + "-Dmaven.repo.local=${mavenLocal.absolutePath}" + ) + .build() + + private fun file(relativePath: String): File = projectDir.resolve(relativePath) + + /** + * Renders the classpath with the production classes of `buildSrc` as + * arguments of `files(...)`, for injection into the build script classpath + * of the generated build. + * + * The classpath comes from the `test` task in `buildSrc/build.gradle.kts`. + */ + private fun buildSrcClasspath(): String { + val classpath = requireNotNull(System.getProperty("buildSrc.classpath")) { + "The `buildSrc.classpath` system property is not set." + + " It is supplied by the `test` task in `buildSrc/build.gradle.kts`." + } + return classpath.split(File.pathSeparator).joinToString(",\n") { + " \"${File(it).invariantSeparatorsPath}\"" + } + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/CheckVersionIncrementIgTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/CheckVersionIncrementIgTest.kt new file mode 100644 index 0000000..2d440b0 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/CheckVersionIncrementIgTest.kt @@ -0,0 +1,179 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.publish + +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldNotContain +import java.io.File +import org.gradle.testkit.runner.GradleRunner +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * Runs `checkVersionIncrement` in a real build via Gradle TestKit. + * + * The build publishes a module under a custom `artifactId`, as `:compiler-plugins` of + * `core-jvm-compiler` does, and checks it against a Maven repository in a local directory. + * The environment of a pull request to `master` makes the task run. + * + * Unlike the tests built with `ProjectBuilder`, this one sees the output of the build, + * which carries the warnings of the task. + * + * The builds run without the [ciVariables] of the test JVM, like those of + * [IncrementGuardIgTest], which explains why a TestKit build must not see `CI`. + */ +@DisplayName("`checkVersionIncrement` should") +internal class CheckVersionIncrementIgTest { + + @TempDir + lateinit var projectDir: File + + @Test + fun `fail when the version of a publication is already published`() { + repoDir.writeMetadata(repository = "snapshots", path = artifactPath, version) + writeBuild() + + val output = runner().buildAndFail().output + + output shouldContain "The version `$version` of `$artifactPath` is already published" + } + + @Test + fun `pass without a warning when the version is not published yet`() { + repoDir.writeMetadata(repository = "snapshots", path = artifactPath, previousVersion) + writeBuild() + + val output = runner().build().output + + output shouldNotContain "No `${MavenMetadata.FILE_NAME}` is found" + } + + @Test + fun `warn when no repository has the metadata of a publication`() { + writeBuild() + + val output = runner().build().output + + output shouldContain "No `${MavenMetadata.FILE_NAME}` is found for `$artifactPath`" + output shouldContain + "cannot tell whether the version `$version` is already published" + } + + /** + * The directory holding the `snapshots` and `releases` repositories. + */ + private val repoDir: File + get() = projectDir.resolve("repo") + + private fun runner(): GradleRunner = + GradleRunner.create() + .withProjectDir(projectDir) + .withEnvironment(pullRequestEnvironment()) + .withArguments(IncrementGuard.taskName, "--stacktrace") + + /** + * Returns the environment of a build of a pull request to `master`, outside + * the `Version Guard` workflow, so that the task runs and skips the comparison + * with the base branch. + */ + private fun pullRequestEnvironment(): Map = + System.getenv() - ciVariables + mapOf( + "GITHUB_EVENT_NAME" to "pull_request", + "GITHUB_BASE_REF" to "master", + ) + + private fun writeBuild() { + val repoUrl = repoDir.toURI().toString().removeSuffix("/") + file("settings.gradle.kts").writeText( + """ + rootProject.name = "compiler-plugins" + """.trimIndent() + ) + file("build.gradle.kts").writeText( + """ + buildscript { + dependencies { + classpath(files( + ${buildSrcClasspath()} + )) + } + } + + import io.spine.gradle.publish.CheckVersionIncrement + import io.spine.gradle.publish.IncrementGuard + import io.spine.gradle.repo.Repository + + plugins { + `maven-publish` + } + + group = "$group" + version = "$version" + + apply() + + publishing { + publications { + create("fatJar") { + artifactId = "$artifactId" + } + } + } + + tasks.named(IncrementGuard.taskName) { + repository = Repository( + name = "local", + releases = "$repoUrl/releases", + snapshots = "$repoUrl/snapshots", + ) + } + """.trimIndent() + ) + } + + private fun file(relativePath: String): File = projectDir.resolve(relativePath) + + /** + * Renders the classpath with the production classes of `buildSrc` as + * arguments of `files(...)`, for injection into the build script classpath + * of the generated build. + * + * The classpath comes from the `test` task in `buildSrc/build.gradle.kts`. + */ + private fun buildSrcClasspath(): String { + val classpath = requireNotNull(System.getProperty("buildSrc.classpath")) { + "The `buildSrc.classpath` system property is not set." + + " It is supplied by the `test` task in `buildSrc/build.gradle.kts`." + } + return classpath.split(File.pathSeparator).joinToString(",\n") { + " \"${File(it).invariantSeparatorsPath}\"" + } + } + + private companion object { + + const val group = "io.spine.tools" + const val version = "2.0.0-SNAPSHOT.094" + const val previousVersion = "2.0.0-SNAPSHOT.093" + + /** + * The `artifactId` of the publication, unrelated to the name of the project. + */ + const val artifactId = "core-jvm-plugins" + + const val artifactPath = "io/spine/tools/$artifactId" + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardIgTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardIgTest.kt new file mode 100644 index 0000000..c4fea07 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardIgTest.kt @@ -0,0 +1,201 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.publish + +import io.kotest.assertions.withClue +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import java.io.File +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome.SKIPPED +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * Verifies when `checkVersionIncrement` runs in a real build that uses the + * configuration cache. + * + * Every case runs the build twice: the first run stores the configuration cache entry, + * and the second one reuses it. The task must reach the same outcome both times, so the + * decision whether to run cannot rely on configuration that a reused entry skips. + * + * The builds run without the `CI` and GitHub Actions variables of the test JVM, so the + * outcome is the same on a workstation and on CI. The Maven repository the task queries + * is a missing local directory, so the task finds no published versions and passes + * without network access. + * + * [io.spine.gradle.Build.ci] is read once per class loader, and a TestKit daemon may reuse + * the build script class loader across builds. Therefore, any TestKit build in this test + * run that reads `Build.ci` with `CI` set — e.g., by running `checkVersionIncrement` + * without removing `CI` from its environment — would leak that value into these builds. + */ +@DisplayName("`checkVersionIncrement` should, with the configuration cache,") +internal class IncrementGuardIgTest { + + @TempDir + lateinit var projectDir: File + + @BeforeEach + fun createBuild() { + file("settings.gradle.kts").writeText( + """ + rootProject.name = "guarded-sample" + include("lib", "app") + """.trimIndent() + ) + file("lib").mkdirs() + file("app").mkdirs() + file("build.gradle.kts").writeText( + """ + buildscript { + dependencies { + classpath(files( + ${buildSrcClasspath()} + )) + } + } + + import io.spine.gradle.publish.CheckVersionIncrement + import io.spine.gradle.publish.IncrementGuard + import io.spine.gradle.repo.Repository + + allprojects { + group = "io.spine.sample" + version = "1.0.0" + } + + subprojects { + apply(plugin = "maven-publish") + apply() + configure { + publications { + create("maven") + } + } + val unpublished = rootDir.resolve("repository").toURI().toString() + tasks.withType().configureEach { + repository = Repository( + name = "unpublished", + releases = unpublished, + snapshots = unpublished + ) + } + } + """.trimIndent() + ) + } + + @Test + fun `run when its project publishes to Maven Local`() { + runStoringThenReusing(":lib:publishToMavenLocal") { + it.task(":lib:checkVersionIncrement")?.outcome shouldBe SUCCESS + } + } + + @Test + fun `skip when only a sibling project publishes to Maven Local`() { + runStoringThenReusing(":lib:checkVersionIncrement", ":app:publishToMavenLocal") { + it.task(":lib:checkVersionIncrement")?.outcome shouldBe SKIPPED + it.task(":app:checkVersionIncrement")?.outcome shouldBe SUCCESS + } + } + + @Test + fun `skip on a local build that does not publish`() { + runStoringThenReusing(":lib:checkVersionIncrement") { + it.task(":lib:checkVersionIncrement")?.outcome shouldBe SKIPPED + } + } + + @Test + fun `run on a pull request to a protected branch`() { + val pullRequest = mapOf( + "GITHUB_EVENT_NAME" to "pull_request", + "GITHUB_BASE_REF" to "master" + ) + + runStoringThenReusing(":lib:checkVersionIncrement", env = pullRequest) { + it.task(":lib:checkVersionIncrement")?.outcome shouldBe SUCCESS + } + } + + /** + * Runs the given [tasks] twice with the configuration cache, and checks the result of + * each run with [verify]. + * + * The first run stores the cache entry, and the second one reuses it. The [env] + * variables are added to the environment of the test JVM, from which the [ciVariables] + * are removed. + */ + private fun runStoringThenReusing( + vararg tasks: String, + env: Map = emptyMap(), + verify: (BuildResult) -> Unit + ) { + val stored = runGradle(tasks, env) + withClue("The run that stores the configuration cache entry") { + stored.output shouldContain "Configuration cache entry stored" + verify(stored) + } + val reused = runGradle(tasks, env) + withClue("The run that reuses the configuration cache entry") { + reused.output shouldContain "Reusing configuration cache" + verify(reused) + } + } + + private fun runGradle(tasks: Array, env: Map): BuildResult { + val environment = System.getenv() - ciVariables + env + return GradleRunner.create() + .withProjectDir(projectDir) + .withEnvironment(environment) + .withArguments( + *tasks, + "--configuration-cache", + "-Dmaven.repo.local=${file("m2").absolutePath}", + "--stacktrace" + ) + .build() + } + + private fun file(relativePath: String): File = projectDir.resolve(relativePath) + + /** + * Renders the classpath with the production classes of `buildSrc` as + * arguments of `files(...)`, for injection into the build script classpath + * of the generated build. + * + * The classpath comes from the `test` task in `buildSrc/build.gradle.kts`. + */ + private fun buildSrcClasspath(): String { + val classpath = requireNotNull(System.getProperty("buildSrc.classpath")) { + "The `buildSrc.classpath` system property is not set." + + " It is supplied by the `test` task in `buildSrc/build.gradle.kts`." + } + return classpath.split(File.pathSeparator).joinToString(",\n") { + " \"${File(it).invariantSeparatorsPath}\"" + } + } +} + +/** + * The environment variables that select the CI behavior of [IncrementGuard] + * and [CheckVersionIncrement]. + */ +internal val ciVariables = setOf("CI", "GITHUB_EVENT_NAME", "GITHUB_BASE_REF", "VERSION_GUARD") diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt index bbb1b3d..9bcbc4e 100644 --- a/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt +++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt @@ -14,20 +14,31 @@ package io.spine.gradle.publish +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldNotContain import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain import io.spine.gradle.publish.IncrementGuard.Companion.localPublishPlanned import io.spine.gradle.publish.IncrementGuard.Companion.mustVerify import io.spine.gradle.publish.IncrementGuard.Companion.shouldCheckVersion import io.spine.gradle.publish.IncrementGuard.Companion.shouldCompareToBase +import io.spine.gradle.repo.Repository +import java.io.File +import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.tasks.PublishToMavenLocal +import org.gradle.api.publish.maven.tasks.PublishToMavenRepository +import org.gradle.kotlin.dsl.create import org.gradle.testfixtures.ProjectBuilder import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir @DisplayName("`IncrementGuard` should") class IncrementGuardTest { @@ -156,6 +167,17 @@ class IncrementGuardTest { localPublishPlanned(listOf(appPublish), lib) shouldBe false } + + @Test + fun `but not when the project publishes only to a remote repository`() { + val project = guardedProject() + val remotePublish = project.tasks.register( + "publishFooPublicationToCloudRepository", + PublishToMavenRepository::class.java + ).get() + + localPublishPlanned(listOf(remotePublish), project) shouldBe false + } } @Nested @@ -188,8 +210,266 @@ class IncrementGuardTest { check.dependencyNames() shouldNotContain IncrementGuard.taskName } } + + @Nested + inner class `configure 'checkVersionIncrement' with` { + + @Test + fun `the root directory of the build`() { + val root = ProjectBuilder.builder().build() + val sub = ProjectBuilder.builder().withParent(root).withName("sub").build() + sub.pluginManager.apply(IncrementGuard::class.java) + + sub.checkVersionTask().rootDir.get().asFile shouldBe root.rootDir + } + + @Test + fun `the artifact path of each Maven publication`() { + val project = publishingProject() + project.publication( + "kotlinMultiplatform", + groupId = "io.spine", + artifactId = "spine-logging" + ) + project.publication("jvm", groupId = "io.spine", artifactId = "spine-logging-jvm") + + project.checkVersionTask().artifactPaths.get() shouldBe + setOf("io/spine/spine-logging", "io/spine/spine-logging-jvm") + } + + /** + * The case of `:compiler-plugins` in `core-jvm-compiler`, which publishes + * `core-jvm-plugins`. A path computed from the module name does not exist, + * so the check would pass even for an already published version. + */ + @Test + fun `the artifact path of a publication with a custom artifact ID`() { + val project = publishingProject("compiler-plugins") + project.publication( + "fatJar", + groupId = "io.spine.tools", + artifactId = "core-jvm-plugins" + ) + + project.checkVersionTask().artifactPaths.get() shouldBe + setOf("io/spine/tools/core-jvm-plugins") + } + + @Test + fun `the artifact path under the tool artifact prefix`() { + val root = ProjectBuilder.builder().build() + root.extensions.create(SpinePublishing.extensionName, root).run { + toolArtifactPrefix = "core-jvm-" + modulesWithCustomPublishing = setOf("guarded-tool") + } + val tool = ProjectBuilder.builder().withParent(root).withName("guarded-tool").build() + tool.group = "io.spine.tools" + tool.pluginManager.apply("maven-publish") + tool.pluginManager.apply(IncrementGuard::class.java) + val task = tool.checkVersionTask() + tool.publications.create("mavenJava") + CustomPublicationHandler.serving(tool, emptySet()).apply() + + task.artifactPaths.get() shouldBe + setOf("io/spine/tools/core-jvm-guarded-tool") + } + + @Test + fun `the coordinates set after the task is created`() { + val project = publishingProject() + val task = project.checkVersionTask() + val publication = project.publication( + "mavenJava", + groupId = "io.spine", + artifactId = "base" + ) + publication.artifactId = "spine-base" + + task.artifactPaths.get() shouldBe setOf("io/spine/spine-base") + } + + /** + * A marker may be uploaded without the plugin it points to, + * so it must be checked on its own. + */ + @Test + fun `the artifact path of a plugin marker`() { + val project = publishingProject() + project.publication( + "pluginMaven", + groupId = "io.spine.tools", + artifactId = "core-jvm-gradle-plugin" + ) + project.publication( + "coreJvmPluginMarkerMaven", + groupId = "io.spine.core-jvm", + artifactId = "io.spine.core-jvm.gradle.plugin" + ) + + project.checkVersionTask().artifactPaths.get() shouldBe setOf( + "io/spine/tools/core-jvm-gradle-plugin", + "io/spine/core-jvm/io.spine.core-jvm.gradle.plugin" + ) + } + + @Test + fun `no artifact paths for a project that does not publish`() { + val project = ProjectBuilder.builder().build() + project.pluginManager.apply(IncrementGuard::class.java) + + project.checkVersionTask().artifactPaths.get().shouldBeEmpty() + } + } + + @Nested + inner class `make 'checkVersionIncrement' fail` { + + @TempDir + lateinit var repoDir: File + + /** + * Only the publication checked last has the version published, so + * the task must look past the artifacts that do not. + */ + @Test + fun `when the version of any publication is already published`() { + val project = publishingProject() + project.version = publishedVersion + project.publication( + "fatJar", + groupId = "io.spine.tools", + artifactId = "core-jvm-plugins" + ) + project.publication( + "mavenJava", + groupId = "io.spine.tools", + artifactId = "core-jvm-gradle-plugin" + ) + repoDir.writeMetadata( + repository = "snapshots", + path = "io/spine/tools/core-jvm-plugins", + "2.0.0-SNAPSHOT.093" + ) + repoDir.writeMetadata( + repository = "snapshots", + path = "io/spine/tools/core-jvm-gradle-plugin", + publishedVersion + ) + val task = project.checkVersionTask() + task.repository = repoDir.asRepository() + + val exception = shouldThrow { task.checkVersion() } + + exception.message shouldContain "already published" + exception.message shouldContain "io/spine/tools/core-jvm-gradle-plugin" + } + + @Test + fun `when the version is already published to the releases repository`() { + val project = publishingProject() + project.version = "2.0.0" + project.publication("mavenJava", groupId = "io.spine", artifactId = "spine-base") + repoDir.writeMetadata(repository = "releases", path = "io/spine/spine-base", "2.0.0") + val task = project.checkVersionTask() + task.repository = repoDir.asRepository() + + val exception = shouldThrow { task.checkVersion() } + + exception.message shouldContain "already published" + } + + /** + * The unreadable metadata in `releases` stands for an unreachable repository, + * such as a response with the 401 or 5xx code. + */ + @Test + fun `when the version is published to a repository checked before a failing one`() { + val project = publishingProject() + project.version = publishedVersion + project.publication("mavenJava", groupId = "io.spine", artifactId = "spine-base") + repoDir.writeMetadata( + repository = "snapshots", + path = "io/spine/spine-base", + publishedVersion + ) + repoDir.resolve("releases/io/spine/spine-base/${MavenMetadata.FILE_NAME}").run { + parentFile.mkdirs() + writeText("Not a Maven metadata document.") + } + val task = project.checkVersionTask() + task.repository = repoDir.asRepository() + + val exception = shouldThrow { task.checkVersion() } + + exception.message shouldContain "already published" + } + } + + @Nested + inner class `make 'checkVersionIncrement' pass` { + + @TempDir + lateinit var repoDir: File + + @Test + fun `when the version is not published yet`() { + val project = publishingProject() + project.version = "2.0.0-SNAPSHOT.095" + project.publication( + "fatJar", + groupId = "io.spine.tools", + artifactId = "core-jvm-plugins" + ) + repoDir.writeMetadata( + repository = "snapshots", + path = "io/spine/tools/core-jvm-plugins", + publishedVersion + ) + val task = project.checkVersionTask() + task.repository = repoDir.asRepository() + + shouldNotThrowAny { task.checkVersion() } + } + + /** + * The task logs a warning in this case: it cannot tell a new artifact from + * a path that does not match the published one. + * + * @see CheckVersionIncrementIgTest for the check of the warning + */ + @Test + fun `when the artifact has never been published`() { + val project = publishingProject() + project.version = publishedVersion + project.publication( + "mavenJava", + groupId = "io.spine.tools", + artifactId = "core-jvm-new-module" + ) + val task = project.checkVersionTask() + task.repository = repoDir.asRepository() + + shouldNotThrowAny { task.checkVersion() } + } + + @Test + fun `for a project that does not publish`() { + val project = ProjectBuilder.builder().build() + project.version = publishedVersion + project.pluginManager.apply(IncrementGuard::class.java) + val task = project.checkVersionTask() + task.repository = repoDir.asRepository() + + shouldNotThrowAny { task.checkVersion() } + } + } } +/** + * The version listed as published by the metadata written with [writeMetadata]. + */ +private const val publishedVersion = "2.0.0-SNAPSHOT.094" + /** * Creates a project with the `base` plugin (for the `check` task), the * `maven-publish` plugin (for [PublishToMavenLocal] tasks), and [IncrementGuard] @@ -203,6 +483,76 @@ private fun guardedProject(): Project { return project } +/** + * Creates a project with the given [name], which has the `maven-publish` plugin and + * [IncrementGuard] applied. + */ +private fun publishingProject(name: String = "guarded"): Project { + val project = ProjectBuilder.builder().withName(name).build() + project.pluginManager.apply("maven-publish") + project.pluginManager.apply(IncrementGuard::class.java) + return project +} + +/** + * Creates a Maven publication with the given [name] and coordinates in this project. + */ +private fun Project.publication( + name: String, + groupId: String, + artifactId: String +): MavenPublication = publications.create(name) { + this.groupId = groupId + this.artifactId = artifactId +} + +/** + * Obtains the [CheckVersionIncrement] task that [IncrementGuard] registered in this project. + * + * The task captures the project version when it is created, so the version must be + * set before this call. + */ +private fun Project.checkVersionTask(): CheckVersionIncrement = + tasks.named(IncrementGuard.taskName, CheckVersionIncrement::class.java).get() + +/** + * Writes `maven-metadata.xml` listing the given [versions] of the artifact at [path] + * into the [repository] subdirectory of this directory. + * + * The [repository] is either `snapshots` or `releases`, as in [asRepository]. + */ +internal fun File.writeMetadata(repository: String, path: String, vararg versions: String) { + val (groupPath, artifactId) = path.split('/').let { it.dropLast(1) to it.last() } + val versionElements = versions.joinToString(separator = "") { "$it" } + val file = resolve("$repository/$path/${MavenMetadata.FILE_NAME}") + file.parentFile.mkdirs() + file.writeText( + """ + + + ${groupPath.joinToString(separator = ".")} + $artifactId + + $versionElements + + + """.trimIndent() + ) +} + +/** + * Represents this directory as a Maven repository with separate destinations + * for snapshots and releases, stored in the subdirectories of the same names. + */ +private fun File.asRepository(): Repository { + val url = toURI().toString().removeSuffix("/") + return Repository( + name = "local", + releases = "$url/releases", + snapshots = "$url/snapshots", + ) +} + /** * Obtains the names of the tasks this task directly depends on. */ diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/PublishingCredentialsIgTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/PublishingCredentialsIgTest.kt new file mode 100644 index 0000000..bfb5e70 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/PublishingCredentialsIgTest.kt @@ -0,0 +1,337 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.publish + +import com.sun.net.httpserver.HttpExchange +import com.sun.net.httpserver.HttpServer +import io.kotest.assertions.withClue +import io.kotest.matchers.file.shouldExist +import io.kotest.matchers.file.shouldNotExist +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldNotContain +import java.io.File +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.URI +import java.util.Base64 +import java.util.concurrent.CopyOnWriteArrayList +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * Verifies when the credentials of a publishing destination are obtained, + * against a real build run via Gradle TestKit. + * + * Obtaining the credentials may start an external process — `gcloud` for + * [CloudArtifactRegistry]. Gradle deprecates starting one at configuration time, + * so the credentials must be obtained only by a build that publishes, and only + * when it is about to. + * + * Each [Destination] of the fixture obtains its own credentials with a function + * that records each call in a file, and points at its own [ProbeRepository], + * an HTTP server that accepts uploads only with credentials. + */ +@DisplayName("Credentials of a publishing destination should") +internal class PublishingCredentialsIgTest { + + @TempDir + lateinit var projectDir: File + + private val destinations = listOf( + Destination(name = "Probe", username = "publisher", password = "secret"), + Destination(name = "Other", username = "deployer", password = "token"), + ) + + @BeforeEach + fun createBuild() { + file("settings.gradle.kts").writeText( + """ + rootProject.name = "credentials-sample" + include("lib") + """.trimIndent() + ) + file("build.gradle.kts").writeText( + """ + buildscript { + dependencies { + classpath(files( + ${buildSrcClasspath()} + )) + } + } + + import io.spine.gradle.publish.spinePublishing + import io.spine.gradle.repo.Credentials + import io.spine.gradle.repo.Repository + + allprojects { + group = "io.spine.sample" + version = "1.0.0" + } + + subprojects { + apply(plugin = "java-library") + apply(plugin = "maven-publish") + + // `javadocJar` of `spinePublishing` is wired to Dokka, which + // this fixture does not apply: the credentials are under test. + tasks.register("dokkaGeneratePublicationJavadoc") + + // The probe repositories are served over plain HTTP. + the().repositories + .withType() + .configureEach { isAllowInsecureProtocol = true } + } + + spinePublishing { + modules = setOf("lib") + destinations = setOf( + ${destinations.joinToString(",\n") { it.declaration() }} + ) + } + """.trimIndent() + ) + file("lib/src/main/java/io/spine/sample/Sample.java").apply { + parentFile.mkdirs() + writeText("package io.spine.sample;\n\npublic class Sample {}\n") + } + } + + @AfterEach + fun stopRepositories() { + destinations.forEach { it.repository.close() } + } + + @Test + fun `not be obtained by a build that does not publish`() { + val stored = runGradle("help", "--configuration-cache") + withClue("The run that stores the configuration cache entry") { + stored.output shouldContain "Configuration cache entry stored" + } + val reused = runGradle("help", "--configuration-cache") + withClue("The run that reuses the configuration cache entry") { + reused.output shouldContain "Reusing configuration cache" + } + + file(requestLog).shouldNotExist() + } + + @Test + fun `be sent with each upload to its own repository`() { + runGradle(publishTask) + + shouldSendOwnCredentials() + } + + /** + * Gradle does not support explicit repository credentials with the configuration + * cache, so a build that publishes with them falls back to running without it + * instead of failing. + */ + @Test + fun `be sent with each upload when the configuration cache is requested`() { + runGradle(publishTask, "--configuration-cache") + + shouldSendOwnCredentials() + } + + /** + * Gradle deprecates calling `Task.project` at execution time, which is + * when the check obtains the credentials. + */ + @Test + fun `be checked by 'checkCredentials' without reaching the project through its task`() { + val result = runGradle(checkTask, "--warning-mode", "all") + + result.output shouldNotContain "Invocation of Task.project at execution time" + file(requestLog).shouldExist() + } + + /** + * Only the last destination lacks credentials, so a check that stops + * at the first one cannot pass. + */ + @Test + fun `be required by 'checkCredentials' for every destination`() { + val lacking = destinations.last().name + val result = gradle(checkTask, "-P$withoutCredentials=$lacking").buildAndFail() + + result.output shouldContain "No valid credentials for repository `$lacking`" + } + + /** + * Asserts that each repository accepted uploads only with the credentials + * of its own destination. + * + * Checking the log of credential requests here shows that the fixture writes it, + * so the `help` case, which expects no log, cannot pass vacuously. + */ + private fun shouldSendOwnCredentials() { + file(requestLog).shouldExist() + destinations.forEach { + withClue(it.name) { + it.repository.authorizations.toSet() shouldBe setOf(it.authorization) + } + } + } + + private fun runGradle(vararg arguments: String): BuildResult = gradle(*arguments).build() + + private fun gradle(vararg arguments: String): GradleRunner = + GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(*arguments, "--stacktrace") + + private fun file(relativePath: String): File = projectDir.resolve(relativePath) + + /** + * Renders the classpath with the production classes of `buildSrc` as + * arguments of `files(...)`, for injection into the build script classpath + * of the generated build. + * + * The classpath comes from the `test` task in `buildSrc/build.gradle.kts`. + */ + private fun buildSrcClasspath(): String { + val classpath = requireNotNull(System.getProperty("buildSrc.classpath")) { + "The `buildSrc.classpath` system property is not set." + + " It is supplied by the `test` task in `buildSrc/build.gradle.kts`." + } + return classpath.split(File.pathSeparator).joinToString(",\n") { + " \"${File(it).invariantSeparatorsPath}\"" + } + } + + /** + * A publishing destination of the fixture, served by its own [repository]. + */ + private class Destination(val name: String, val username: String, val password: String) { + + val repository = ProbeRepository() + + /** + * The `Authorization` header that carries the credentials of this destination. + */ + val authorization: String = + "Basic " + Base64.getEncoder().encodeToString("$username:$password".toByteArray()) + + /** + * Declares this destination in the build script of the fixture. + * + * The lines are indented like those of [buildSrcClasspath], so that + * the build script keeps its indentation once it is trimmed. + */ + fun declaration(): String = + """ + Repository( + name = "$name", + releases = "${repository.url}", + snapshots = "${repository.url}", + credentialValues = { project -> + project.rootDir.resolve("$requestLog").appendText("$name\n") + if (project.findProperty("$withoutCredentials") == "$name") null + else Credentials("$username", "$password") + } + ) + """.trimIndent().prependIndent(" ") + } + + private companion object { + + /** + * The file to which the credentials functions of the fixture append + * a line each time they are called. + */ + const val requestLog = "credential-requests.log" + + /** + * The project property that names the destination whose credentials function + * finds no credentials. + */ + const val withoutCredentials = "withoutCredentials" + + /** + * Publishes to every destination and checks their credentials. + */ + const val publishTask = ":lib:publish" + + /** + * Checks that every destination has credentials. + */ + const val checkTask = ":lib:checkCredentials" + } +} + +/** + * A Maven repository that accepts an upload only with credentials. + * It is served over HTTP from the loopback interface. + * + * Gradle does not send the credentials until challenged, so the repository answers + * an upload without them with `401`, asking for Basic authentication. + * It has no artifacts to serve, so it answers any other request with `404`. + */ +private class ProbeRepository : AutoCloseable { + + private val server: HttpServer = + HttpServer.create(InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0) + + private val accepted = CopyOnWriteArrayList() + + /** + * The `Authorization` headers of the accepted uploads. + */ + val authorizations: List + get() = accepted.toList() + + /** + * The URL of this repository. + * + * The loopback address may be an IPv6 one, which [URI] encloses in square brackets. + */ + val url: String + get() { + val address = server.address + return URI("http", null, address.hostString, address.port, "/maven", null, null) + .toString() + } + + init { + server.createContext("/") { exchange -> exchange.use { respond(it) } } + server.start() + } + + private fun respond(exchange: HttpExchange) { + exchange.requestBody.readBytes() + val authorization = exchange.requestHeaders.getFirst("Authorization") + when { + exchange.requestMethod != "PUT" -> exchange.sendResponseHeaders(404, -1) + authorization == null -> { + exchange.responseHeaders.add("WWW-Authenticate", "Basic realm=\"probe\"") + exchange.sendResponseHeaders(401, -1) + } + else -> { + accepted.add(authorization) + exchange.sendResponseHeaders(201, -1) + } + } + } + + override fun close() = server.stop(0) +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/repo/RepositoriesSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/repo/RepositoriesSpec.kt new file mode 100644 index 0000000..df120ff --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/repo/RepositoriesSpec.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.repo + +import io.kotest.matchers.collections.shouldContainExactly +import io.spine.gradle.publish.CloudRepo +import io.spine.gradle.publish.PublishingRepos +import java.net.URI +import org.gradle.api.artifacts.repositories.MavenArtifactRepository +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("`standardToSpineSdk()` should") +internal class RepositoriesSpec { + + @Test + fun `query Spine repositories from Artifact Registry to GitHub Packages`() { + val project = ProjectBuilder.builder().build() + project.repositories.standardToSpineSdk() + + val registry = PublishingRepos.cloudArtifactRegistry + @Suppress("DEPRECATION") // Still queried for earlier versions. + val cloudRepo = CloudRepo.published + val spineRepos = listOf( + registry.target(snapshots = false), + registry.target(snapshots = true), + cloudRepo.target(snapshots = false), + cloudRepo.target(snapshots = true), + "https://maven.pkg.github.com/SpineEventEngine/*" + ).map { URI(it) } + + val queried = project.repositories + .filterIsInstance() + .map { it.url } + .filter { it in spineRepos } + queried shouldContainExactly spineRepos + } +} diff --git a/config b/config index 219fef6..a0cbbbf 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 219fef6ddd42c1e236dddf04526fb2a64409de6a +Subproject commit a0cbbbfe66c12475224563439fb6deb3da71ba58 diff --git a/gradle.properties b/gradle.properties index 7c2bb5f..78a5789 100644 --- a/gradle.properties +++ b/gradle.properties @@ -43,3 +43,8 @@ kotlin.tests.individualTaskReports=true # For details please see: # https://kotlinlang.org/docs/dokka-migration.html#enable-migration-helpers org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers + +# Let the IDE fetch the project models in parallel during a Gradle sync. +# Since Gradle 9.4, `org.gradle.parallel` above covers only the build execution. +# This affects only the models built for an IDE, not the tasks of a build. +org.gradle.tooling.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index eddabd2eef8d94a5437d6168ff9c87a78ff725b3..9fa7f288283f81eec4952784c271abd7e0938dee 100644 GIT binary patch delta 40003 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 delta 38903 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ad7845b..2e7bb9f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/gradlew.bat b/gradlew.bat index a51ec4f..3185a43 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,33 @@ @rem Set local scope for the variables, and ensure extensions are enabled setlocal EnableExtensions +@rem Catch executions from older scripts and ensure they exit cleanly. +@rem This can be removed once we can be reasonably confident that few people +@rem will be migrating directly to this new wrapper. +goto afterSafetyNet +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +goto exitWithErrorLevel +:afterSafetyNet + set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused @@ -45,13 +72,14 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. "%COMSPEC%" /c exit 1 +goto exitWithErrorLevel :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -59,13 +87,14 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. "%COMSPEC%" /c exit 1 +goto exitWithErrorLevel :execute @rem Setup the command line @@ -75,8 +104,9 @@ echo location of your Java installation. 1>&2 @rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command -endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel +@rem This label must not be changed. We rely on old scripts being able to jump to this point. :exitWithErrorLevel @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts "%COMSPEC%" /c exit %ERRORLEVEL% From 39f3d3d79763f0cf514f51e53e9253c9fe309c8b Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 15 Oct 2026 19:56:47 +0100 Subject: [PATCH 2/3] Bump version -> `2.0.0-SNAPSHOT.202` --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index 0d1e631..bd66ba4 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -12,4 +12,4 @@ * and limitations under the License. */ -extra.set("versionToPublish", "2.0.0-SNAPSHOT.201") +extra.set("versionToPublish", "2.0.0-SNAPSHOT.202") From 23d55db7a207c0df47ae5abf584d7cd2a89e75e8 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Thu, 15 Oct 2026 20:10:08 +0100 Subject: [PATCH 3/3] Update dependency reports --- docs/dependencies/dependencies.md | 145 ++++++++++++++---------------- docs/dependencies/pom.xml | 38 ++++---- 2 files changed, 86 insertions(+), 97 deletions(-) diff --git a/docs/dependencies/dependencies.md b/docs/dependencies/dependencies.md index 817192b..a8ff08d 100644 --- a/docs/dependencies/dependencies.md +++ b/docs/dependencies/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-reflect:2.0.0-SNAPSHOT.201` +# Dependencies of `io.spine:spine-reflect:2.0.0-SNAPSHOT.202` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -21,7 +21,7 @@ * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.guava. **Name** : guava. **Version** : 33.7.1-jre. +1. **Group** : com.google.guava. **Name** : guava. **Version** : 33.7.2-jre. * **Project URL:** [https://github.com/google/guava](https://github.com/google/guava) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -33,17 +33,17 @@ * **Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.36.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -52,15 +52,15 @@ * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-bom. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-bom. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -167,11 +167,11 @@ 1. **Group** : com.google.errorprone. **Name** : error_prone_type_annotations. **Version** : 2.42.0. * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.flogger. **Name** : flogger. **Version** : 0.7.4. +1. **Group** : com.google.flogger. **Name** : flogger. **Version** : 0.9. * **Project URL:** [https://github.com/google/flogger](https://github.com/google/flogger) * **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.flogger. **Name** : flogger-system-backend. **Version** : 0.7.4. +1. **Group** : com.google.flogger. **Name** : flogger-system-backend. **Version** : 0.9. * **Project URL:** [https://github.com/google/flogger](https://github.com/google/flogger) * **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -183,12 +183,12 @@ * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.guava. **Name** : guava. **Version** : 33.7.1-jre. +1. **Group** : com.google.guava. **Name** : guava. **Version** : 33.7.2-jre. * **Project URL:** [https://github.com/google/guava](https://github.com/google/guava) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.guava. **Name** : guava-testlib. **Version** : 33.7.1-jre. +1. **Group** : com.google.guava. **Name** : guava-testlib. **Version** : 33.7.2-jre. * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group** : com.google.guava. **Name** : listenablefuture. **Version** : 9999.0-empty-to-avoid-conflict-with-guava. @@ -198,17 +198,17 @@ * **Project URL:** [https://github.com/google/j2objc/](https://github.com/google/j2objc/) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.36.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-java. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.0. +1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -225,21 +225,16 @@ 1. **Group** : com.google.truth.extensions. **Name** : truth-proto-extension. **Version** : 1.4.5. * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 10.12.1. +1. **Group** : com.puppycrawl.tools. **Name** : checkstyle. **Version** : 12.3.1. * **Project URL:** [https://checkstyle.org/](https://checkstyle.org/) - * **License:** [LGPL-2.1+](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) + * **License:** [LGPL-2.1-or-later](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) 1. **Group** : com.soywiz.korlibs.korte. **Name** : korte-jvm. **Version** : 4.0.10. * **Project URL:** [https://github.com/korlibs/korge-next](https://github.com/korlibs/korge-next) * **License:** [MIT](https://raw.githubusercontent.com/korlibs/korge-next/master/korge/LICENSE.txt) -1. **Group** : commons-beanutils. **Name** : commons-beanutils. **Version** : 1.9.4. - * **Project URL:** [https://commons.apache.org/proper/commons-beanutils/](https://commons.apache.org/proper/commons-beanutils/) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : commons-codec. **Name** : commons-codec. **Version** : 1.22.1. - * **Project URL:** [https://commons.apache.org/proper/commons-codec/](https://commons.apache.org/proper/commons-codec/) +1. **Group** : commons-beanutils. **Name** : commons-beanutils. **Version** : 1.11.0. + * **Project URL:** [https://commons.apache.org/proper/commons-beanutils](https://commons.apache.org/proper/commons-beanutils) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -256,7 +251,7 @@ * **Project URL:** [https://github.com/drewhamilton/Poko](https://github.com/drewhamilton/Poko) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.7.4. +1. **Group** : info.picocli. **Name** : picocli. **Version** : 4.7.7. * **Project URL:** [https://picocli.info](https://picocli.info) * **License:** [The Apache Software License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -373,27 +368,27 @@ * **Project URL:** [https://detekt.dev](https://detekt.dev) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : io.kotest. **Name** : kotest-assertions-core. **Version** : 6.2.4. +1. **Group** : io.kotest. **Name** : kotest-assertions-core. **Version** : 6.2.5. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-assertions-core-jvm. **Version** : 6.2.4. +1. **Group** : io.kotest. **Name** : kotest-assertions-core-jvm. **Version** : 6.2.5. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-assertions-shared. **Version** : 6.2.4. +1. **Group** : io.kotest. **Name** : kotest-assertions-shared. **Version** : 6.2.5. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-assertions-shared-jvm. **Version** : 6.2.4. +1. **Group** : io.kotest. **Name** : kotest-assertions-shared-jvm. **Version** : 6.2.5. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-common. **Version** : 6.2.4. +1. **Group** : io.kotest. **Name** : kotest-common. **Version** : 6.2.5. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.kotest. **Name** : kotest-common-jvm. **Version** : 6.2.4. +1. **Group** : io.kotest. **Name** : kotest-common-jvm. **Version** : 6.2.5. * **Project URL:** [https://github.com/kotest/kotest](https://github.com/kotest/kotest) * **License:** [Apache-2.0](https://opensource.org/licenses/Apache-2.0) @@ -409,24 +404,19 @@ * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) -1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 12.2. +1. **Group** : net.sf.saxon. **Name** : Saxon-HE. **Version** : 12.9. * **Project URL:** [http://www.saxonica.com/](http://www.saxonica.com/) * **License:** [Mozilla Public License Version 2.0](http://www.mozilla.org/MPL/2.0/) -1. **Group** : net.sourceforge.pmd. **Name** : pmd-ant. **Version** : 7.27.0. +1. **Group** : net.sourceforge.pmd. **Name** : pmd-ant. **Version** : 7.28.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) -1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 7.27.0. +1. **Group** : net.sourceforge.pmd. **Name** : pmd-core. **Version** : 7.28.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) -1. **Group** : net.sourceforge.pmd. **Name** : pmd-java. **Version** : 7.27.0. +1. **Group** : net.sourceforge.pmd. **Name** : pmd-java. **Version** : 7.28.0. * **License:** [BSD-style](http://pmd.sourceforge.net/license.html) -1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.11.1. - * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) - * **License:** [https://www.antlr.org/license.html](https://www.antlr.org/license.html) - * **License:** [BSD-3-Clause](https://www.antlr.org/license.html) - 1. **Group** : org.antlr. **Name** : antlr4-runtime. **Version** : 4.13.2. * **Project URL:** [https://www.antlr.org/](https://www.antlr.org/) * **License:** [https://www.antlr.org/license.html](https://www.antlr.org/license.html) @@ -437,25 +427,11 @@ * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.httpcomponents.client5. **Name** : httpclient5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5-h2. **Version** : 5.1.3. - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : org.apiguardian. **Name** : apiguardian-api. **Version** : 1.1.2. * **Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.checkerframework. **Name** : checker-compat-qual. **Version** : 2.5.3. - * **Project URL:** [https://checkerframework.org](https://checkerframework.org) - * **License:** [GNU General Public License, version 2 (GPL2), with the classpath exception](http://www.gnu.org/software/classpath/license.html) - * **License:** [The MIT License](http://opensource.org/licenses/MIT) - -1. **Group** : org.checkerframework. **Name** : checker-qual. **Version** : 4.2.2. +1. **Group** : org.checkerframework. **Name** : checker-qual. **Version** : 4.3.1. * **Project URL:** [https://checkerframework.org/](https://checkerframework.org/) * **License:** MIT * **License:** [The MIT License](https://opensource.org/licenses/MIT) @@ -556,19 +532,23 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-api. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-bom. **Version** : 2.4.21. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-api. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-compat. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-compat. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-cri-impl. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-cri-impl. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-impl. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-build-tools-impl. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -576,15 +556,15 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-embeddable. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-embeddable. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-runner. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-compiler-runner. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-client. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-client. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -592,11 +572,11 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-embeddable. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-daemon-embeddable. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-klib-commonizer-embeddable. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-klib-commonizer-embeddable. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -608,27 +588,31 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-reflect. **Version** : 2.4.21. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-script-runtime. **Version** : 2.0.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-script-runtime. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-script-runtime. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-common. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-common. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-compiler-embeddable. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-compiler-embeddable. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-compiler-impl-embeddable. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-compiler-impl-embeddable. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-jvm. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-scripting-jvm. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -644,6 +628,10 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib. **Version** : 2.4.21. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-common. **Version** : 2.0.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -668,7 +656,7 @@ * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-tooling-core. **Version** : 2.4.10. +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-tooling-core. **Version** : 2.4.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -787,19 +775,19 @@ * **License:** The Apache License, Version 2.0 * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.ow2.asm. **Name** : asm. **Version** : 9.10.1. +1. **Group** : org.ow2.asm. **Name** : asm. **Version** : 9.11. * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) * **License:** [BSD-3-Clause](https://asm.ow2.io/LICENSE.txt) * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.ow2.asm. **Name** : asm-commons. **Version** : 9.10.1. +1. **Group** : org.ow2.asm. **Name** : asm-commons. **Version** : 9.11. * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) * **License:** [BSD-3-Clause](https://asm.ow2.io/LICENSE.txt) * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.ow2.asm. **Name** : asm-tree. **Version** : 9.10.1. +1. **Group** : org.ow2.asm. **Name** : asm-tree. **Version** : 9.11. * **Project URL:** [http://asm.ow2.io/](http://asm.ow2.io/) * **License:** [BSD-3-Clause](https://asm.ow2.io/LICENSE.txt) * **License:** [BSD-3-Clause](https://asm.ow2.io/license.html) @@ -823,15 +811,16 @@ * **Project URL:** [http://www.slf4j.org](http://www.slf4j.org) * **License:** [MIT License](http://www.opensource.org/licenses/mit-license.php) +1. **Group** : org.slf4j. **Name** : slf4j-simple. **Version** : 2.0.17. + * **Project URL:** [http://www.slf4j.org](http://www.slf4j.org) + * **License:** [https://opensource.org/license/mit](https://opensource.org/license/mit) + * **License:** [MIT](https://opensource.org/license/mit) + 1. **Group** : org.snakeyaml. **Name** : snakeyaml-engine. **Version** : 2.7. * **Project URL:** [https://bitbucket.org/snakeyaml/snakeyaml-engine](https://bitbucket.org/snakeyaml/snakeyaml-engine) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 5.1.2. - * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) - * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : org.xmlresolver. **Name** : xmlresolver. **Version** : 5.3.3. * **Project URL:** [https://github.com/xmlresolver/xmlresolver](https://github.com/xmlresolver/xmlresolver) * **License:** [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0) @@ -839,6 +828,6 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Oct 07 18:48:43 WEST 2026** using +This report was generated on **Thu Oct 15 20:10:06 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/docs/dependencies/pom.xml b/docs/dependencies/pom.xml index a820239..a889f6d 100644 --- a/docs/dependencies/pom.xml +++ b/docs/dependencies/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine reflect -2.0.0-SNAPSHOT.201 +2.0.0-SNAPSHOT.202 2015 @@ -26,43 +26,43 @@ all modules and does not describe the project structure per-subproject. com.google.guava guava - 33.7.1-jre + 33.7.2-jre compile com.google.protobuf protobuf-java - 4.36.0 + 4.36.2 compile com.google.protobuf protobuf-java-util - 4.36.0 + 4.36.2 compile com.google.protobuf protobuf-kotlin - 4.36.0 + 4.36.2 compile org.jetbrains.kotlin kotlin-bom - 2.4.10 + 2.4.21 compile org.jetbrains.kotlin kotlin-reflect - 2.4.10 + 2.4.21 compile org.jetbrains.kotlin kotlin-stdlib - 2.4.10 + 2.4.21 compile @@ -92,25 +92,25 @@ all modules and does not describe the project structure per-subproject. org.checkerframework checker-qual - 4.2.2 + 4.3.1 provided com.google.guava guava-testlib - 33.7.1-jre + 33.7.2-jre test io.kotest kotest-assertions-core - 6.2.4 + 6.2.5 test io.spine.tools base-testlib - 2.0.0-SNAPSHOT.213 + 2.0.0-SNAPSHOT.214 test @@ -151,7 +151,7 @@ all modules and does not describe the project structure per-subproject. com.puppycrawl.tools checkstyle - 10.12.1 + 12.3.1 io.gitlab.arturbosch.detekt @@ -166,12 +166,12 @@ all modules and does not describe the project structure per-subproject. net.sourceforge.pmd pmd-ant - 7.27.0 + 7.28.0 net.sourceforge.pmd pmd-java - 7.27.0 + 7.28.0 org.jacoco @@ -216,22 +216,22 @@ all modules and does not describe the project structure per-subproject. org.jetbrains.kotlin kotlin-build-tools-compat - 2.4.10 + 2.4.21 org.jetbrains.kotlin kotlin-build-tools-impl - 2.4.10 + 2.4.21 org.jetbrains.kotlin kotlin-klib-commonizer-embeddable - 2.4.10 + 2.4.21 org.jetbrains.kotlin kotlin-scripting-compiler-embeddable - 2.4.10 + 2.4.21