diff --git a/.agents/tasks/938-move-codegen-request-writer.md b/.agents/tasks/938-move-codegen-request-writer.md
new file mode 100644
index 0000000000..476ce91fc8
--- /dev/null
+++ b/.agents/tasks/938-move-codegen-request-writer.md
@@ -0,0 +1,46 @@
+---
+slug: 938-move-codegen-request-writer
+branch: claude/busy-dirac-wwflbm
+owner: claude
+status: in-progress
+started: 2026-06-10
+---
+
+## Goal
+
+`io.spine.code.proto.CodeGeneratorRequestWriter` is removed from `base`
+(it is protoc-plugin tooling, not runtime API), and the build is green.
+Closes [#938](https://github.com/SpineEventEngine/base-libraries/issues/938)
+together with the receiving change in `tool-base`.
+
+## Context
+
+- The class moves to the `tool-base` module of the ToolBase repository under
+ `io.spine.tools.code.proto` (same-named branch there).
+- The only consumers are the protoc-plugin entry points of the Compiler and
+ ProtoTap; they migrate by switching the import once both PRs are published.
+- `CodeGeneratorRequestParsingSpec.kt` and `CodeGeneratorRequestsJavaSpec.java`
+ stay: they test `io.spine.type` parsing APIs which remain in `base`, and the
+ Java spec still uses the `constructRequest` helper declared in the former.
+- Removing public API is a breaking change: the snapshot version advances to
+ the next multiple of 10.
+
+## Plan
+
+- [x] Remove `base/src/main/kotlin/io/spine/code/proto/CodeGeneratorRequestWriter.kt`.
+- [x] Remove `base/src/test/kotlin/io/spine/code/proto/CodeGeneratorRequestWriterSpec.kt`.
+- [x] Bump version `2.0.0-SNAPSHOT.404` -> `2.0.0-SNAPSHOT.410` (breaking).
+- [ ] `./gradlew build` green; commit regenerated dependency reports if any.
+ - Blocked in the sandbox: all Spine artifact repositories return 403 for
+ the buildscript dependency `io.spine.tools:protobuf-setup-plugins`, so
+ no Gradle build can run here at all. Verification is delegated to PR CI.
+ - Repo-wide greps confirm no remaining references to the removed class;
+ the surviving `CodeGeneratorRequest*` specs do not use it.
+- [x] Push and open a draft PR; merge after the tool-base PR.
+
+## Log
+
+- 2026-06-10 — drafted; executing autonomously per issue #938.
+- 2026-06-10 — removal committed and version bumped; sandbox cannot resolve
+ Spine snapshot artifacts (403 on all repos), so the build runs on PR CI
+ instead.
diff --git a/.github/workflows/build-on-ubuntu.yml b/.github/workflows/build-on-ubuntu.yml
index 03be0f5326..27d9302b9d 100644
--- a/.github/workflows/build-on-ubuntu.yml
+++ b/.github/workflows/build-on-ubuntu.yml
@@ -12,11 +12,12 @@ jobs:
with:
submodules: 'true'
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v5
with:
java-version: 17
distribution: zulu
- cache: gradle
+
+ - uses: gradle/actions/setup-gradle@v6
- name: Build project and run tests
shell: bash
diff --git a/.github/workflows/build-on-windows.yml b/.github/workflows/build-on-windows.yml
index de72407ede..992272ce4f 100644
--- a/.github/workflows/build-on-windows.yml
+++ b/.github/workflows/build-on-windows.yml
@@ -21,11 +21,12 @@ jobs:
submodules: recursive
fetch-depth: 0
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v5
with:
java-version: 17
distribution: zulu
- cache: gradle
+
+ - uses: gradle/actions/setup-gradle@v6
# See: https://github.com/al-cheb/configure-pagefile-action
- name: Configure Pagefile
@@ -33,8 +34,7 @@ jobs:
- name: Build project and run tests
shell: cmd
- # For the reason on `--no-daemon` see https://github.com/actions/cache/issues/454
- run: gradlew.bat build --stacktrace --no-daemon
+ run: gradlew.bat build --stacktrace
# See: https://github.com/marketplace/actions/junit-report-action
- name: Publish Test Report
diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml
index 3fa235be0b..6755ff910e 100644
--- a/.github/workflows/check-links.yml
+++ b/.github/workflows/check-links.yml
@@ -33,7 +33,7 @@ jobs:
cancel-in-progress: true
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
# Detect the Hugo site root (`docs/` or `site/`) by looking for a Hugo
# config file. Hugo config may live directly in the site root or in a
diff --git a/.github/workflows/ensure-reports-updated.yml b/.github/workflows/ensure-reports-updated.yml
index 315cd202b7..20deb3f695 100644
--- a/.github/workflows/ensure-reports-updated.yml
+++ b/.github/workflows/ensure-reports-updated.yml
@@ -1,19 +1,29 @@
# Ensures that the license report files were modified in this PR.
+#
+# The check runs only for pull requests targeting a default (`master`/`main`) or
+# a release-line (e.g. `2.x-jdk8-master`) branch. The report files embed the project
+# version, so they are refreshed by the branches which bump it. Pull requests
+# targeting auxiliary branches are not checked.
+#
+# The base branch is checked inside the job rather than via the `branches` filter:
+# a workflow skipped by branch filtering leaves its check in the `Pending` state,
+# blocking PRs which require it, while a job skipped via `if` reports `skipped`,
+# which satisfies required status checks.
name: License Reports
on:
pull_request:
- branches:
- - '**'
jobs:
check:
name: Ensure license reports are updated
runs-on: ubuntu-latest
+ # Default and release-line branches, e.g. `master`, `main`, `2.x-jdk8-master`.
+ if: endsWith(github.base_ref, 'master') || endsWith(github.base_ref, 'main')
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
# Configure the checkout of all branches so that it is possible to run the comparison.
fetch-depth: 0
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
index 50eb05eb15..fc0872b4c9 100644
--- a/.github/workflows/gradle-wrapper-validation.yml
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout latest code
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4
diff --git a/.github/workflows/increment-guard.yml b/.github/workflows/increment-guard.yml
index 38ce6f4d3e..f20b4bed6e 100644
--- a/.github/workflows/increment-guard.yml
+++ b/.github/workflows/increment-guard.yml
@@ -1,28 +1,39 @@
-# Ensures that the current lib version is not yet published but executing the Gradle
+# Ensures that the current lib version is not yet published by executing the Gradle
# `checkVersionIncrement` task.
+#
+# The check runs only for pull requests targeting a default (`master`/`main`) or
+# a release-line (e.g. `2.x-jdk8-master`) branch. It is the responsibility of a branch
+# which aims to merge into such a branch to bump the version. Auxiliary branches
+# do not deal with the versions in the release cycle and are not guarded.
+#
+# The base branch is checked inside the job rather than via the `branches` filter:
+# a workflow skipped by branch filtering leaves its check in the `Pending` state,
+# blocking PRs which require it, while a job skipped via `if` reports `skipped`,
+# which satisfies required status checks.
name: Version Guard
on:
- push:
- branches:
- - '**'
+ pull_request:
jobs:
check:
name: Check version increment
runs-on: ubuntu-latest
+ # Default and release-line branches, e.g. `master`, `main`, `2.x-jdk8-master`.
+ if: endsWith(github.base_ref, 'master') || endsWith(github.base_ref, 'main')
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
submodules: 'true'
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v5
with:
java-version: 17
distribution: zulu
- cache: gradle
+
+ - uses: gradle/actions/setup-gradle@v6
- name: Check version is not yet published
shell: bash
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index f7218c618e..df8f6cd01a 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -10,15 +10,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
submodules: 'true'
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@v5
with:
java-version: 17
distribution: zulu
- cache: gradle
+
+ - uses: gradle/actions/setup-gradle@v6
- name: Decrypt CloudRepo credentials
run: ./config/scripts/decrypt.sh "$CLOUDREPO_CREDENTIALS_KEY" ./.github/keys/cloudrepo.properties.gpg ./cloudrepo.properties
diff --git a/.github/workflows/remove-obsolete-artifacts-from-packages.yaml b/.github/workflows/remove-obsolete-artifacts-from-packages.yaml
index f706171007..62242c7bbb 100644
--- a/.github/workflows/remove-obsolete-artifacts-from-packages.yaml
+++ b/.github/workflows/remove-obsolete-artifacts-from-packages.yaml
@@ -39,7 +39,7 @@ jobs:
outputs:
package-names: ${{ steps.request-package-names.outputs.package-names }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
submodules: 'true'
diff --git a/.idea/misc.xml b/.idea/misc.xml
index c6c419e979..264f823015 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -21,39 +21,24 @@
-
-
-
\ No newline at end of file
+
+
diff --git a/base/src/main/java/io/spine/code/proto/FileName.java b/base/src/main/java/io/spine/code/proto/FileName.java
index 4f595f6d92..dbbb5d3325 100644
--- a/base/src/main/java/io/spine/code/proto/FileName.java
+++ b/base/src/main/java/io/spine/code/proto/FileName.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2022, TeamDev. All rights reserved.
+ * Copyright 2026, TeamDev. All rights reserved.
*
* 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
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
@@ -105,7 +105,7 @@ private String nameOnly() {
}
/**
- * Returns the file name with extension but without path.
+ * Returns the file name with an extension but without a path.
*/
public String nameWithExtension() {
var fullName = value();
diff --git a/base/src/main/kotlin/io/spine/code/proto/CodeGeneratorRequestWriter.kt b/base/src/main/kotlin/io/spine/code/proto/CodeGeneratorRequestWriter.kt
deleted file mode 100644
index afa339f294..0000000000
--- a/base/src/main/kotlin/io/spine/code/proto/CodeGeneratorRequestWriter.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2024, TeamDev. All rights reserved.
- *
- * 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
- *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package io.spine.code.proto
-
-import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest
-import io.spine.io.replaceExtension
-import io.spine.string.decodeBase64
-import io.spine.type.ExtensionRegistryHolder.extensionRegistry
-import io.spine.type.parse
-import io.spine.type.toJson
-import java.io.File
-import java.io.InputStream
-import java.nio.file.StandardOpenOption.CREATE
-import java.nio.file.StandardOpenOption.TRUNCATE_EXISTING
-import kotlin.io.path.writeBytes
-
-/**
- * Parses a [CodeGeneratorRequest] from given [input] and writes it into
- * files in [binary][writeBinary] and [JSON][writeJson] format.
- *
- * @param input The input stream containing binary version of the request.
- */
-public class CodeGeneratorRequestWriter(
- private val input: InputStream
-) {
- /**
- * Lazily evaluated [CodeGeneratorRequest] parsed from [input] using [extensionRegistry].
- */
- public val request: CodeGeneratorRequest by lazy {
- CodeGeneratorRequest::class.parse(input)
- }
-
- /**
- * The target file for writing the request in the binary form.
- *
- * The name of the request is passed as the [parameter][CodeGeneratorRequest.getParameter] of
- * the request as a Base64 encoded file path.
- */
- public val requestFile: File by lazy {
- File(request.parameter.decodeBase64())
- }
-
- /**
- * The path to the request file in JSON format.
- *
- * The file has the same name as [requestFile] and the extension of `".pb.json"`.
- */
- public val requestFileInJson: File by lazy {
- requestFile.replaceExtension("pb.json")
- }
-
- /**
- * Writes the request into the location specified in [requestFile].
- */
- public fun writeBinary() {
- ensureDirectory()
- requestFile.toPath().writeBytes(request.toByteArray(), CREATE, TRUNCATE_EXISTING)
- }
-
- /**
- * Writes the request in JSON format to the location specified in [requestFileInJson].
- */
- public fun writeJson() {
- val json = request.toJson()
- ensureDirectory()
- requestFileInJson.writeText(json)
- }
-
- private fun ensureDirectory() {
- val targetDir = requestFile.parentFile
- targetDir.mkdirs()
- }
-}
diff --git a/base/src/test/kotlin/io/spine/base/MessageFileSpec.kt b/base/src/test/kotlin/io/spine/base/MessageFileSpec.kt
new file mode 100644
index 0000000000..47a6f1d634
--- /dev/null
+++ b/base/src/test/kotlin/io/spine/base/MessageFileSpec.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2026, TeamDev. All rights reserved.
+ *
+ * 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
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.base
+
+import com.google.protobuf.Any
+import com.google.protobuf.DescriptorProtos.FileDescriptorProto
+import io.kotest.matchers.shouldBe
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+
+@DisplayName("`MessageFile` should")
+internal class MessageFileSpec {
+
+ @Test
+ fun `expose the suffix required for the corresponding kind of files`() {
+ MessageFile.COMMANDS.suffix() shouldBe "commands.proto"
+ MessageFile.EVENTS.suffix() shouldBe "events.proto"
+ MessageFile.REJECTIONS.suffix() shouldBe "rejections.proto"
+ }
+
+ @Nested internal inner class
+ `test a file descriptor` {
+
+ @Test
+ fun `accepting the file with matching suffix`() {
+ val file = FileDescriptorProto.newBuilder()
+ .setName("given_events.proto")
+ .build()
+ MessageFile.EVENTS.test(file) shouldBe true
+ }
+
+ @Test
+ fun `rejecting the file with non-matching suffix`() {
+ val file = Any.getDescriptor().file.toProto()
+ MessageFile.EVENTS.test(file) shouldBe false
+ }
+ }
+}
diff --git a/base/src/test/kotlin/io/spine/code/proto/CodeGeneratorRequestWriterSpec.kt b/base/src/test/kotlin/io/spine/code/proto/CodeGeneratorRequestWriterSpec.kt
deleted file mode 100644
index e85231b8d1..0000000000
--- a/base/src/test/kotlin/io/spine/code/proto/CodeGeneratorRequestWriterSpec.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2024, TeamDev. All rights reserved.
- *
- * 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
- *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package io.spine.code.proto
-
-import io.kotest.matchers.shouldBe
-import io.spine.io.replaceExtension
-import io.spine.string.toBase64Encoded
-import java.io.File
-import java.io.InputStream
-import java.nio.file.Path
-import kotlin.io.path.inputStream
-import kotlin.io.path.writeBytes
-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
-
-@DisplayName("`CodeGeneratorRequestWriter` should")
-internal class CodeGeneratorRequestWriterSpec {
-
- private lateinit var requestFile: File
- private lateinit var writer: CodeGeneratorRequestWriter
- private lateinit var input: InputStream
-
- @BeforeEach
- fun prepareInput(@TempDir dir: Path) {
- val inputFile = dir.resolve("input.stream")
- // Request the file in the directory which does not exist.
- requestFile = dir.resolve("nested/request.binbp").toFile()
- val request = constructRequest(requestFile.absolutePath.toBase64Encoded())
- inputFile.writeBytes(request.toByteArray())
- input = inputFile.inputStream()
- writer = CodeGeneratorRequestWriter(input)
- }
-
- @AfterEach
- fun closeInput() {
- input.close()
- }
-
- @Test
- fun `write binary version of the request`() {
- writer.writeBinary()
- requestFile.exists() shouldBe true
- }
-
- @Test
- fun `write JSON version of the request`() {
- writer.writeJson()
- requestFile.replaceExtension("pb.json").exists() shouldBe true
- }
-}
diff --git a/buildSrc/src/main/kotlin/config-tester.gradle.kts b/buildSrc/src/main/kotlin/config-tester.gradle.kts
index 7b64dac7d1..2463f738b2 100644
--- a/buildSrc/src/main/kotlin/config-tester.gradle.kts
+++ b/buildSrc/src/main/kotlin/config-tester.gradle.kts
@@ -42,10 +42,10 @@ val tempFolder = File("./tmp")
ConfigTester(config, tasks, tempFolder)
.addRepo(SpineRepos.baseTypes) // Builds `base-types` at `master`.
.addRepo(SpineRepos.base) // Builds `base` at `master`.
- .addRepo(SpineRepos.coreJava) // Builds `core-java` at `master`.
+ .addRepo(SpineRepos.coreJvm) // Builds `core-jvm` at `master`.
// This is how one builds a specific branch of some repository:
- // .addRepo(SpineRepos.coreJava, Branch("grpc-concurrency-fixes"))
+ // .addRepo(SpineRepos.coreJvm, Branch("grpc-concurrency-fixes"))
// Register the produced task under the selected name to invoke manually upon need.
.registerUnder("buildDependants")
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt
index 787b8cfb7e..f2f0507d72 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt
@@ -33,8 +33,8 @@ package io.spine.dependency.local
*/
@Suppress("ConstPropertyName", "unused")
object Base {
- const val version = "2.0.0-SNAPSHOT.400"
- const val versionForBuildScript = "2.0.0-SNAPSHOT.400"
+ const val version = "2.0.0-SNAPSHOT.404"
+ const val versionForBuildScript = "2.0.0-SNAPSHOT.404"
const val group = Spine.group
private const val prefix = "spine"
const val libModule = "$prefix-base"
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 c0c4238d92..6073038f35 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt
@@ -34,8 +34,8 @@ package io.spine.dependency.local
@Suppress("ConstPropertyName", "unused")
object ToolBase {
const val group = Spine.toolsGroup
- const val version = "2.0.0-SNAPSHOT.381"
- const val dogfoodingVersion = "2.0.0-SNAPSHOT.381"
+ const val version = "2.0.0-SNAPSHOT.399"
+ const val dogfoodingVersion = "2.0.0-SNAPSHOT.399"
const val lib = "$group:tool-base:$version"
const val classicCodegen = "$group:classic-codegen:$version"
diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt b/buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt
index c3bbfbe115..f95f23ea07 100644
--- a/buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt
+++ b/buildSrc/src/main/kotlin/io/spine/gradle/ConfigTester.kt
@@ -39,8 +39,8 @@ import org.gradle.api.tasks.TaskContainer
* A tool to execute the Gradle `build` task in selected Git repositories
* with the local version of [config] contents.
*
- * Checks out the content of selected repositories into the specified [tempFolder]. The folder
- * is created if it does not exist. By default, uses `./tmp` as a temp folder.
+ * Checks out the content of selected repositories into the specified [tempFolder].
+ * The folder is created if it does not exist. By default, uses `./tmp` as a temp folder.
*
* Replaces the `config` and `buildSrc` folders in the checked out repository by the local versions
* of code. If the repository-under-test already contains its own `buildSrc` or `config` folders,
@@ -356,7 +356,7 @@ object SpineRepos {
val base: URI = library("base")
val baseTypes: URI = library("base-types")
- val coreJava: URI = library("core-java")
+ val coreJvm: URI = library("core-jvm")
val web: URI = library("web")
private fun library(repo: String) = URI(libsOrg + repo)
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 1243b04522..195a514631 100644
--- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt
+++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt
@@ -40,7 +40,23 @@ import org.gradle.api.Project
class IncrementGuard : Plugin {
companion object {
+
const val taskName = "checkVersionIncrement"
+
+ /**
+ * Tells whether the version increment must be verified for the given
+ * GitHub Actions event and the base branch of the pull request.
+ *
+ * The version is guarded only for pull requests targeting a default or
+ * a release-line branch, i.e. a branch with the name ending with `master`
+ * or `main`. For example: `master`, `main`, `2.x-jdk8-master`, `2.x-jdk8-main`.
+ */
+ internal fun shouldCheckVersion(event: String?, baseBranch: String?): Boolean {
+ if (event != "pull_request" || baseBranch == null) {
+ return false
+ }
+ return baseBranch.endsWith("master") || baseBranch.endsWith("main")
+ }
}
/**
@@ -48,11 +64,15 @@ class IncrementGuard : Plugin {
*
* The task is created anyway, but it is enabled only if:
* 1. The project is built on GitHub CI, and
- * 2. The job is a pull request.
+ * 2. The job is a pull request targeting a default (`master` or `main`) or
+ * a release-line (e.g. `2.x-jdk8-master`) branch.
*
- * The task only runs on non-master branches on GitHub Actions.
- * This is done to prevent unexpected CI fails when re-building `master` multiple times,
- * creating git tags, and in other cases that go outside the "usual" development cycle.
+ * It is the responsibility of a branch which aims to merge into a default
+ * (or otherwise protected) branch to bump the version. Auxiliary branches do not
+ * deal with the versions in the release cycle, so pull requests targeting them,
+ * direct pushes, and tag builds do not run the check. This also prevents unexpected
+ * CI fails when re-building `master` multiple times, creating git tags, and in other
+ * cases that go outside the "usual" development cycle.
*/
override fun apply(target: Project) {
val tasks = target.tasks
@@ -64,7 +84,8 @@ class IncrementGuard : Plugin {
if (!shouldCheckVersion()) {
logger.info(
- "The build does not represent a GitHub Actions feature branch job, " +
+ "The build does not represent a GitHub Actions pull request job " +
+ "targeting a default or a release-line branch, " +
"the `checkVersionIncrement` task is disabled."
)
this.enabled = false
@@ -73,40 +94,19 @@ class IncrementGuard : Plugin {
}
/**
- * Returns `true` if the current build is a GitHub Actions build which represents a push
- * to a feature branch.
- *
- * Returns `false` if the associated reference is not a branch (e.g., a tag) or if it has
- * the name which ends with `master` or `main`.
+ * Returns `true` if the current build is a GitHub Actions build of a pull request
+ * targeting a default (`master` or `main`) or a release-line branch,
+ * such as `2.x-jdk8-master`.
*
- * For example, on the following branches the method would return `false`:
- *
- * 1. `master`.
- * 2. `main`.
- * 3. `2.x-jdk8-master`.
- * 4. `2.x-jdk8-main`.
+ * Returns `false` for all other builds, including direct pushes, tag builds,
+ * and pull requests targeting auxiliary branches.
*
* @see
* List of default environment variables provided for GitHub Actions builds
*/
private fun shouldCheckVersion(): Boolean {
val event = System.getenv("GITHUB_EVENT_NAME")
- val reference = System.getenv("GITHUB_REF")
- if (event != "push" || reference == null) {
- return false
- }
- val branch = branchName(reference)
- return when {
- branch == null -> false
- branch.endsWith("master") -> false
- branch.endsWith("main") -> false
- else -> true
- }
- }
-
- private fun branchName(gitHubRef: String): String? {
- val matches = Regex("refs/heads/(.+)").matchEntire(gitHubRef)
- val branch = matches?.let { it.groupValues[1] }
- return branch
+ val baseBranch = System.getenv("GITHUB_BASE_REF")
+ return shouldCheckVersion(event, baseBranch)
}
}
diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/KoverConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/KoverConfig.kt
index ed2bd0290d..e3bd5b98dd 100644
--- a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/KoverConfig.kt
+++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/KoverConfig.kt
@@ -276,7 +276,7 @@ class KoverConfig private constructor(
.flatMap { root ->
root.walk()
.filter { !it.isDirectory }
- .flatMap { it.fqnsRelativeTo(root).asSequence() }
+ .flatMap { it.classNamesIn(root).asSequence() }
}
.distinct()
.toList()
@@ -359,7 +359,7 @@ private fun KotlinSourceSet.isMainSourceSet(): Boolean =
*
* Returns an empty list if this file is not under [root].
*/
-private fun File.fqnsRelativeTo(root: File): List {
+internal fun File.classNamesIn(root: File): List {
if (!startsWith(root)) {
return emptyList()
}
diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/SiblingCoverage.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/SiblingCoverage.kt
new file mode 100644
index 0000000000..1c9d1f54da
--- /dev/null
+++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/SiblingCoverage.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2026, TeamDev. All rights reserved.
+ *
+ * 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
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.gradle.report.coverage
+
+import java.io.File
+import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.TaskCollection
+import org.gradle.api.tasks.testing.Test
+
+/**
+ * Credits the test coverage produced by the [contributor] module for the classes
+ * of this project to this project's own Kover report.
+ *
+ * Some modules' production classes are exercised only by the tests of a sibling
+ * module — for example, the language-neutral `psi` classes are tested through
+ * the Java-PSI fixtures that live in `psi-java`. Kover's per-module report sees
+ * only this module's own test execution data, so that cross-module coverage is
+ * otherwise missing from the per-module report (which is what Codecov consumes),
+ * even though the root aggregated report already accounts for it.
+ *
+ * This function adds the [contributor]'s JaCoCo execution data to this project's
+ * `total` report as additional binary reports. Only this project's classes are
+ * credited from them — coverage of unrelated classes in the same execution data
+ * is ignored, because a Kover report is scoped to the owning project's classes.
+ * The report tasks are wired to run after the contributor's JVM test tasks so the
+ * data is present when a report is generated.
+ *
+ * The contributor's JVM test tasks are discovered by type rather than by name, so
+ * the helper works regardless of the module convention: a `jvm-module` contributes
+ * through its `test` task, a `kmp-module` through `jvmTest`, and any additional
+ * JVM test tasks are picked up as well. Non-JVM Kotlin test tasks (`*Native`,
+ * `*Js`, …) are not of type [Test] and are correctly ignored — Kover instruments
+ * only JVM test tasks.
+ *
+ * Requires the Kover plugin to be applied to this project.
+ * A cross-project **task** dependency is used, not a project dependency,
+ * so it does not introduce a dependency cycle even when the [contributor]
+ * already depends on this project.
+ */
+fun Project.creditTestCoverageFrom(contributor: Project) {
+ val contributorTests = contributor.tasks.withType(Test::class.java)
+ extensions.configure(KoverProjectExtension::class.java) {
+ reports {
+ total {
+ additionalBinaryReports.addAll(contributor.execFilesOf(contributorTests))
+ }
+ }
+ }
+ tasks.matching { it.consumesCoverageBinaryReports() }.configureEach {
+ dependsOn(contributorTests)
+ }
+}
+
+/**
+ * Lazy `Provider` of the JaCoCo execution-data files produced by [testTasks]
+ * of this project.
+ *
+ * When the coverage engine is pinned to JaCoCo via `useJacoco(...)`, Kover writes
+ * one binary report per instrumented JVM test task at `build/`[BIN_REPORTS_DIR]
+ * `/.exec`, so the file name follows the task name. Resolved at
+ * task-graph time, after the contributor's test tasks have been registered.
+ */
+private fun Project.execFilesOf(testTasks: TaskCollection): Provider> {
+ val binReports = layout.buildDirectory.dir(BIN_REPORTS_DIR)
+ return provider {
+ testTasks.map { binReports.get().file("${it.name}.exec").asFile }
+ }
+}
+
+/**
+ * The directory under a module's `build/` where Kover writes the per-test-task
+ * binary execution-data files.
+ */
+private const val BIN_REPORTS_DIR: String = "kover/bin-reports"
+
+/**
+ * Tells whether this is a Kover task that reads the binary reports and therefore
+ * must run only after the [contributor's][creditTestCoverageFrom] test data exists.
+ *
+ * This matches both the report tasks (`koverXmlReport`, `koverHtmlReport`,
+ * `koverBinaryReport`) and the verification tasks (`koverVerify` and its
+ * cacheable companion `koverCachedVerify`) — the suffix test covers the
+ * `Cached*` variants Kover registers, which are the ones that actually consume
+ * the binary reports.
+ */
+private fun Task.consumesCoverageBinaryReports(): Boolean =
+ name.startsWith("kover") && (name.endsWith("Report") || name.endsWith("Verify"))
diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt
new file mode 100644
index 0000000000..5633c30d4d
--- /dev/null
+++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2026, TeamDev. All rights reserved.
+ *
+ * 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
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.gradle.publish
+
+import io.kotest.matchers.shouldBe
+import io.spine.gradle.publish.IncrementGuard.Companion.shouldCheckVersion
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+
+@DisplayName("`IncrementGuard` should")
+class IncrementGuardTest {
+
+ @Nested
+ inner class `require the version check` {
+
+ @Test
+ fun `for pull requests targeting default branches`() {
+ shouldCheckVersion("pull_request", "master") shouldBe true
+ shouldCheckVersion("pull_request", "main") shouldBe true
+ }
+
+ @Test
+ fun `for pull requests targeting release-line branches`() {
+ shouldCheckVersion("pull_request", "2.x-jdk8-master") shouldBe true
+ shouldCheckVersion("pull_request", "2.x-jdk8-main") shouldBe true
+ }
+ }
+
+ @Nested
+ inner class `not require the version check` {
+
+ @Test
+ fun `for pull requests targeting auxiliary branches`() {
+ shouldCheckVersion("pull_request", "epic-feature") shouldBe false
+ shouldCheckVersion("pull_request", "master-fixes") shouldBe false
+ }
+
+ @Test
+ fun `for push events`() {
+ shouldCheckVersion("push", "master") shouldBe false
+ shouldCheckVersion("push", null) shouldBe false
+ }
+
+ @Test
+ fun `for pull request events without a base branch`() {
+ shouldCheckVersion("pull_request", null) shouldBe false
+ }
+
+ @Test
+ fun `outside GitHub Actions`() {
+ shouldCheckVersion(null, null) shouldBe false
+ }
+ }
+}
diff --git a/config b/config
index a844dc7b77..234233e0fa 160000
--- a/config
+++ b/config
@@ -1 +1 @@
-Subproject commit a844dc7b77989dd0a227f22e5625444acef5c09a
+Subproject commit 234233e0fa407df296ff4742887723653c3dcc95
diff --git a/docs/dependencies/dependencies.md b/docs/dependencies/dependencies.md
index cb32f01cab..863514c5cb 100644
--- a/docs/dependencies/dependencies.md
+++ b/docs/dependencies/dependencies.md
@@ -1,6 +1,6 @@
-# Dependencies of `io.spine:spine-annotations:2.0.0-SNAPSHOT.404`
+# Dependencies of `io.spine:spine-annotations:2.0.0-SNAPSHOT.410`
## Runtime
1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.1.0.
@@ -760,14 +760,14 @@
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Mon Jun 08 18:37:28 WEST 2026** using
+This report was generated on **Wed Jun 10 21:05:52 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).
-# Dependencies of `io.spine:spine-base:2.0.0-SNAPSHOT.404`
+# Dependencies of `io.spine:spine-base:2.0.0-SNAPSHOT.410`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -1604,14 +1604,14 @@ This report was generated on **Mon Jun 08 18:37:28 WEST 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Mon Jun 08 18:37:29 WEST 2026** using
+This report was generated on **Wed Jun 10 21:05:52 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).
-# Dependencies of `io.spine:spine-environment:2.0.0-SNAPSHOT.404`
+# Dependencies of `io.spine:spine-environment:2.0.0-SNAPSHOT.410`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -2430,14 +2430,14 @@ This report was generated on **Mon Jun 08 18:37:29 WEST 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Mon Jun 08 18:37:28 WEST 2026** using
+This report was generated on **Wed Jun 10 21:05:52 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).
-# Dependencies of `io.spine:spine-format:2.0.0-SNAPSHOT.404`
+# Dependencies of `io.spine:spine-format:2.0.0-SNAPSHOT.410`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0.
@@ -3336,6 +3336,6 @@ This report was generated on **Mon Jun 08 18:37:28 WEST 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Mon Jun 08 18:37:28 WEST 2026** using
+This report was generated on **Wed Jun 10 21:05:52 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 c487c5f049..df9b10ba9b 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.spinebase-libraries
-2.0.0-SNAPSHOT.404
+2.0.0-SNAPSHOT.4102015
diff --git a/gradle.properties b/gradle.properties
index 49ad30c59b..3e4fd9de7d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,6 +6,10 @@ org.gradle.java.installations.auto-download=true
# Use parallel builds for better performance.
org.gradle.parallel=true
+
+# Reuse task outputs from the local build cache.
+# On CI, `gradle/actions/setup-gradle` persists `caches/build-cache-1` across runs,
+# so cold builds skip work whose inputs are unchanged.
#org.gradle.caching=true
# Dokka plugin eats more memory than usual. Therefore, all builds should have enough.
diff --git a/version.gradle.kts b/version.gradle.kts
index c5226ba68a..36a5dde535 100644
--- a/version.gradle.kts
+++ b/version.gradle.kts
@@ -1,5 +1,5 @@
/*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,4 +24,4 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-val versionToPublish: String by extra("2.0.0-SNAPSHOT.404")
+val versionToPublish: String by extra("2.0.0-SNAPSHOT.410")