diff --git a/.agents/tasks/disable-protobuf-version-check.md b/.agents/tasks/disable-protobuf-version-check.md new file mode 100644 index 00000000000..f97a1215f2c --- /dev/null +++ b/.agents/tasks/disable-protobuf-version-check.md @@ -0,0 +1,105 @@ +--- +slug: disable-protobuf-version-check +branch: disable-protobuf-version-check +owner: claude +status: in-review +started: 2026-10-19 +--- + +## Goal + +Users can turn off the Protobuf runtime/gencode version check in the process +of the Compiler with a DSL call instead of exporting an environment variable: + +```kotlin +spine { + compiler { + disableProtobufVersionCheck.set(true) + } +} +``` + +The property controls `TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK` in the environment +of every `LaunchSpineCompiler` fork: `true` sets it to `true` (check off), `false` +removes it (check on, even if the build exports it), not set (default) inherits it +from the build. A default that cleared the variable would silently break builds +relying on the exported variable, including this repo's functional tests +(`RunnerTuning.tuneRunner()`), hence three states rather than `false` by default. + +## Context + +- `protobuf-java` 4.x validates the gencode version against the runtime in + `RuntimeVersion.validateProtobufGencodeVersion()` and skips the check only + when `System.getenv("TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK")` is `"true"`. + The Compiler fork is where a user classpath (plugins built with another + `protoc`) meets the Compiler's Protobuf runtime, so it is the process where + users hit the check. +- Today the only ways are exporting the variable for the Gradle daemon, or + `tasks.withType { environment(...) }`. The new property + is a convenience shortcut for the latter. +- Gradle 9.8.1 (this branch's wrapper; also checked 9.7.1) + `DefaultProcessForkOptions.getActualEnvironment()` converts values + with `String.valueOf()` — no `Provider` unpacking, so the setting cannot be + passed to `environment` lazily (`JavaExec.getEnvironment()` is marked + `@ToBeReplacedByLazyProperty`). +- A probe showed the launch tasks are not realized while the plugins are applied, + but in the `afterEvaluate` hooks, after the build script body. So `applyDefaults()` + reads the setting at configuration time; the extension property is + `finalizeValueOnRead()`, so a change after that (early task realization, a late + `afterEvaluate`) fails loudly instead of being ignored. +- `Extension` is the only implementation of `CompilerSettings` across Spine repos + (`core-jvm-compiler` only calls it), so adding an interface member is safe. +- Out of scope: the Gradle daemon and the `protoc` plugin processes. Their + environment cannot be changed from the build script; users still export the + variable for those. +- After #90 (`align-protobuf-runtime`), the fork runs on the highest Protobuf runtime + requested by the CLI and by `spineCompiler`, so a newer plugin runtime no longer + fails. The setting remains for builds that force an older runtime (e.g. via `force`). +- End-to-end: the `protobuf-runtime-test` fixture from #90 removes the variable in + the fork. `DisableProtobufVersionCheckSpec` reuses it, forcing the CLI runtime + below the plugin gencode (`4.37.0-RC1`) so the check really fails. + +## Plan + +- [x] `CompilerSettings` (`gradle-api`): add + `public val disableProtobufVersionCheck: Property` with KDoc. +- [x] `Extension` (`gradle-plugin`): implement it, not set by default, + `finalizeValueOnRead()`. +- [x] `LaunchSpineCompiler`: no copy of the setting on the task (review by + the user). `applyDefaults()` calls + `applyProtobufVersionCheckSetting(ext.disableProtobufVersionCheck.orNull)`: + `true` sets the variable, `false` removes it, `null` leaves the environment + intact. Not a task input: the check does not change the outputs. +- [x] Tests (`gradle-plugin/src/test`): + - [x] `ExtensionSpec` — not set by default. + - [x] `LaunchTaskEnvironmentSpec` — the extension's value reaches the task; + a change after the task is configured is rejected; `null` keeps + a planted inherited value; `true` sets the variable; `false` removes + a planted `true`. +- [x] Functional test `DisableProtobufVersionCheckSpec` — `false` keeps the check + even when the build sets the variable (fails with + `ProtobufRuntimeVersionException`); `true` lets the Compiler run. +- [x] Docs: `gradle-plugin/README.md` configuration table + a short section on + when to use it and its scope. +- [x] Version bump `.077` → `.079` (`.078` is taken by + `origin/align-protobuf-runtime`), `./gradlew build`, dependency reports, + `dokkaGenerate`. +- [x] Commits: `` Bump version -> `2.0.0-SNAPSHOT.079` ``, the feature, and + `Update dependency reports`. + +## Log + +- 2026-10-19 — drafted in worktree `.claude/worktrees/disable-protobuf-version-check` + branched from `origin/master` @ `977762d8f9`. +- 2026-10-19 — implemented; `./gradlew build dokkaGenerate` green; reviews by + `kotlin-engineer` (APPROVE), `spine-code-review` and `review-docs` + (APPROVE WITH CHANGES) applied: planted-value test, `protoc` plugins wording, + `applyDefaults` KDoc exception note. +- 2026-10-19 — the worktree's pinned `.agents/shared` `version-bumped.sh` cannot + parse `extra.set(...)` in `version.gradle.kts` (exit 2); the floated copy in + the main checkout handles it. Fixed upstream; resolves on the next `config` pull. +- 2026-10-19 — PR #95 opened; merged `origin/master` after #90 landed (conflicts only + in `version.gradle.kts` and reports; kept `.079`); CodeMatters headers applied. +- 2026-10-19 — per review by the user, `false` now removes the variable; the default + became "not set" to keep the exported-variable workaround working. Added the + end-to-end spec. diff --git a/docs/dependencies/dependencies.md b/docs/dependencies/dependencies.md index f4125572ea6..d1748189ae3 100644 --- a/docs/dependencies/dependencies.md +++ b/docs/dependencies/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.tools:compiler-api:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-api:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -1864,14 +1864,14 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:54 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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.tools:compiler-api-tests:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-api-tests:2.0.0-SNAPSHOT.079` ## Runtime ## Compile, tests, and tooling @@ -2582,14 +2582,14 @@ This report was generated on **Mon Oct 19 19:23:54 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:30 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:18 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.tools:compiler-backend:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-backend:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -4457,14 +4457,14 @@ This report was generated on **Mon Oct 19 19:23:30 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:56 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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.tools:compiler-cli:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-cli:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -6540,14 +6540,14 @@ This report was generated on **Mon Oct 19 19:23:56 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:56 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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.tools:compiler-gradle-api:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-gradle-api:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -8301,14 +8301,14 @@ This report was generated on **Mon Oct 19 19:23:56 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:55 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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.tools:compiler-gradle-plugin:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-gradle-plugin:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -10102,14 +10102,14 @@ This report was generated on **Mon Oct 19 19:23:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:56 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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.tools:compiler-jvm:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-jvm:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -12043,14 +12043,14 @@ This report was generated on **Mon Oct 19 19:23:56 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:32 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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.tools:compiler-params:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-params:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -13906,14 +13906,14 @@ This report was generated on **Mon Oct 19 19:23:32 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:55 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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.tools:compiler-protoc-plugin:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-protoc-plugin:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -14820,14 +14820,14 @@ This report was generated on **Mon Oct 19 19:23:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:32 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:18 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.tools:compiler-test-env:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-test-env:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -16671,14 +16671,14 @@ This report was generated on **Mon Oct 19 19:23:32 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:31 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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.tools:compiler-testlib:2.0.0-SNAPSHOT.078` +# Dependencies of `io.spine.tools:compiler-testlib:2.0.0-SNAPSHOT.079` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -18647,6 +18647,6 @@ This report was generated on **Mon Oct 19 19:23:31 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Oct 19 19:23:56 WEST 2026** using +This report was generated on **Mon Oct 19 20:40:19 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 6886a09c4cc..ded1c2122a5 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.tools compiler -2.0.0-SNAPSHOT.078 +2.0.0-SNAPSHOT.079 2015 diff --git a/gradle-api/src/main/kotlin/io/spine/tools/compiler/gradle/api/CompilerSettings.kt b/gradle-api/src/main/kotlin/io/spine/tools/compiler/gradle/api/CompilerSettings.kt index 90e87df18ea..84bc530cea0 100644 --- a/gradle-api/src/main/kotlin/io/spine/tools/compiler/gradle/api/CompilerSettings.kt +++ b/gradle-api/src/main/kotlin/io/spine/tools/compiler/gradle/api/CompilerSettings.kt @@ -1,32 +1,21 @@ /* - * Copyright 2026, TeamDev. All rights reserved. + * 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 + * 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. + * 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.tools.compiler.gradle.api import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property /** * Configures the code generation process performed by the Compiler. @@ -64,4 +53,49 @@ public interface CompilerSettings { * By default, points at the `$projectDir/generated/` directory. */ public val outputBaseDir: DirectoryProperty + + /** + * Tells whether to disable the Protobuf version check in the Compiler process. + * + * The Protobuf runtime checks that the code generated by `protoc` is compatible + * with the runtime — in particular, that it is not newer than the runtime — and + * fails otherwise. The Compiler runs on the highest Protobuf runtime requested by + * the Compiler itself and by its plugins, which avoids such a failure in most cases. + * The check may still fail, for example, when the build forces a Protobuf runtime + * older than the one the plugins were built with. + * + * The check is turned off by the `TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK` + * environment variable set to `true`. This property controls the variable in + * the environment of the Compiler process: + * + * - `true` sets the variable to `true`, turning the check off; + * - `false` removes the variable, so the check is performed even if the variable + * is set in the environment of the build; + * - when the property is not set, which is the default, the Compiler process + * inherits the variable from the environment of the build, if any. + * + * This is a shortcut for setting the variable in the environment of + * the `LaunchSpineCompiler` tasks directly. + * + * The tasks read the property when they are configured, which happens after + * the build script is evaluated. Set the property in the `compiler { }` block: + * once read, it cannot be changed. + * + * The setting affects neither the Gradle daemon nor the `protoc` plugins. + * If the check fails there, set the variable in the environment of the build instead. + * + * Use this setting only as a temporary workaround until the Protobuf versions + * are aligned. + * + * Example (`build.gradle.kts`): + * + * ```kotlin + * spine { + * compiler { + * disableProtobufVersionCheck.set(true) + * } + * } + * ``` + */ + public val disableProtobufVersionCheck: Property } diff --git a/gradle-plugin/README.md b/gradle-plugin/README.md index 256627ea976..c35de8e61b8 100644 --- a/gradle-plugin/README.md +++ b/gradle-plugin/README.md @@ -51,11 +51,12 @@ The Compiler runs the plugins you specify via the `compiler { }` extension, nest Here is the complete list of configuration options: -| Name | Format | Description | Default value | -|-----------------|------------------|-------------------------------------------------------------------------------------------|----------------------------------------| -| `plugins` | Java class names | Class names of `io.spine.tools.compiler.plugin.Plugin` implementations the Compiler runs. | None | -| `subDirs` | Directory names | Subdirectories under the generated source set that the Compiler processes. | `java`, `kotlin`, `grpc`, `js`, `dart` | -| `outputBaseDir` | Directory | Base directory where the files generated by the Compiler are placed. | `$projectDir/generated/` | +| Name | Format | Description | Default value | +|-------------------------------|------------------|-------------------------------------------------------------------------------------------|----------------------------------------| +| `plugins` | Java class names | Class names of `io.spine.tools.compiler.plugin.Plugin` implementations the Compiler runs. | None | +| `subDirs` | Directory names | Subdirectories under the generated source set that the Compiler processes. | `java`, `kotlin`, `grpc`, `js`, `dart` | +| `outputBaseDir` | Directory | Base directory where the files generated by the Compiler are placed. | `$projectDir/generated/` | +| `disableProtobufVersionCheck` | Boolean | Whether to disable the [Protobuf version check](#protobuf-version-check) in the Compiler. | Not set | A complete configuration may look as follows: ```kotlin @@ -79,6 +80,39 @@ dependencies { } ``` +### Protobuf version check + +The Protobuf runtime checks that the code generated by `protoc` is compatible with the runtime — +in particular, that it is not newer than the runtime — and fails otherwise. The Compiler runs on +the highest Protobuf runtime requested by the Compiler itself and by its plugins, which avoids +such a failure in most cases. The check may still fail, for example, when the build forces +a Protobuf runtime older than the one the plugins were built with. + +As a temporary workaround, until the Protobuf versions are aligned, the check may be turned off +for the Compiler process: + +```kotlin +spine { + compiler { + disableProtobufVersionCheck.set(true) + } +} +``` + +This sets the `TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK` environment variable to `true` for +all the [`LaunchSpineCompiler`](#source-sets) tasks of the project. + +Setting the property to `false` removes the variable from the environment of these tasks, +so the check is performed even if the variable is set for the build. When the property is +not set, which is the default, the tasks inherit the variable from the environment of the build. + +The setting affects neither the Gradle daemon nor the `protoc` plugins. +If the check fails there, set the variable in the environment of the build instead: + +```bash +TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK=true ./gradlew build +``` + ## Source sets For each source set in the project, the plugin generates a distinct task launching the Compiler. diff --git a/gradle-plugin/src/functionalTest/kotlin/io/spine/tools/compiler/gradle/plugin/DisableProtobufVersionCheckSpec.kt b/gradle-plugin/src/functionalTest/kotlin/io/spine/tools/compiler/gradle/plugin/DisableProtobufVersionCheckSpec.kt new file mode 100644 index 00000000000..793b7c7e452 --- /dev/null +++ b/gradle-plugin/src/functionalTest/kotlin/io/spine/tools/compiler/gradle/plugin/DisableProtobufVersionCheckSpec.kt @@ -0,0 +1,127 @@ +/* + * 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.tools.compiler.gradle.plugin + +import io.kotest.assertions.withClue +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.spine.testing.SlowTest +import io.spine.tools.code.SourceSetName +import io.spine.tools.compiler.gradle.api.CompilerTaskName +import io.spine.tools.compiler.gradle.api.Names.COMPILER_RAW_ARTIFACT +import io.spine.tools.compiler.gradle.api.Names.GRADLE_PLUGIN_ID +import io.spine.tools.gradle.task.TaskName +import io.spine.tools.gradle.testing.GradleProject +import io.spine.tools.gradle.testing.get +import java.io.File +import org.gradle.api.logging.LogLevel +import org.gradle.testkit.runner.TaskOutcome.FAILED +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 that `compiler { disableProtobufVersionCheck }` controls the Protobuf + * gencode/runtime version check in the JVM forked by [LaunchSpineCompiler]. + * + * The tests use the `protobuf-runtime-test` fixture of [ProtobufRuntimeSpec]. The plugin + * of the fixture is built with a Protobuf release newer than the runtime of the CLI. + * Each test forces the runtime of the CLI configuration, which comes first on the classpath + * of the forked JVM, to stay at the CLI's own version. So the gencode of the plugin finds + * a runtime older than itself, as in a build that forces an older Protobuf runtime. + * + * Unlike [ProtobufRuntimeSpec], the tests ask the fixture to keep the variable of the build + * in the forked JVM, so that only the setting under test changes it. + */ +@SlowTest +@DisplayName("`disableProtobufVersionCheck` should") +internal class DisableProtobufVersionCheckSpec { + + private val launchSpineCompiler: TaskName = CompilerTaskName(SourceSetName.main) + + private lateinit var projectDir: File + + @BeforeEach + fun prepareDir(@TempDir projectDir: File) { + this.projectDir = projectDir + withClue("Pick a Protobuf release newer than `$CLI_PROTOBUF` for the test.") { + isNewer(NEWER_PROTOBUF, than = CLI_PROTOBUF) shouldBe true + } + } + + @Test + fun `keep the check when set to 'false', even if the build disables it`() { + // `tuneRunner()` sets the variable for the whole build, as a user may do. + val project = createProject(disableCheck = false) + + val result = project.executeAndFail(launchSpineCompiler) + + result[launchSpineCompiler] shouldBe FAILED + result.output shouldContain "ProtobufRuntimeVersionException" + } + + @Test + fun `turn the check off when set to 'true'`() { + val project = createProject(disableCheck = true) + // Drop the variable set by `tuneRunner()`, so that only the setting turns the check off. + project.runner.withEnvironment(emptyMap()) + + val result = project.executeTask(launchSpineCompiler) + + result[launchSpineCompiler] shouldBe SUCCESS + } + + /** + * Creates the fixture project with the plugin built on [NEWER_PROTOBUF]. + * + * Appends to the build script of the project the code that forces the runtime of + * the CLI configuration to [CLI_PROTOBUF], and sets the property under test. + * A `force` wins over the constraints the Compiler Gradle plugin derives from + * the user classpath, so the newer runtime of the plugin stays behind the CLI's one + * on the classpath of the forked JVM. + * + * @param disableCheck The value to set to the `disableProtobufVersionCheck` property. + */ + private fun createProject(disableCheck: Boolean): GradleProject { + val project = GradleProject.setupAt(projectDir) + .fromResources("protobuf-runtime-test") + .withSharedTestKitDirectory() + .replace("@COMPILER_PLUGIN_ID@", GRADLE_PLUGIN_ID) + .replace("@COMPILER_VERSION@", Plugin.version) + .replace("@PLUGIN_PROTOBUF@", NEWER_PROTOBUF) + .withLoggingLevel(LogLevel.INFO) + .copyBuildSrc() + .create() + project.tuneRunner() + projectDir.resolve("gradle.properties") + .appendText("\nkeepVersionCheckVariable=true\n") + val caseSetup = """ + configurations.named("$COMPILER_RAW_ARTIFACT") { + resolutionStrategy.force("com.google.protobuf:protobuf-java:$CLI_PROTOBUF") + } + + spine { + compiler { + disableProtobufVersionCheck.set($disableCheck) + } + } + """ + projectDir.resolve("build.gradle.kts") + .appendText("\n" + caseSetup.trimIndent() + "\n") + return project + } +} diff --git a/gradle-plugin/src/functionalTest/kotlin/io/spine/tools/compiler/gradle/plugin/ProtobufRuntimeSpec.kt b/gradle-plugin/src/functionalTest/kotlin/io/spine/tools/compiler/gradle/plugin/ProtobufRuntimeSpec.kt index 4c23f1c709b..463989b042a 100644 --- a/gradle-plugin/src/functionalTest/kotlin/io/spine/tools/compiler/gradle/plugin/ProtobufRuntimeSpec.kt +++ b/gradle-plugin/src/functionalTest/kotlin/io/spine/tools/compiler/gradle/plugin/ProtobufRuntimeSpec.kt @@ -123,7 +123,7 @@ internal class ProtobufRuntimeSpec { * the build after the build moves to the latest stable release. Replace it once * the build reaches that minor version. */ -private const val NEWER_PROTOBUF = "4.37.0-RC1" +internal const val NEWER_PROTOBUF = "4.37.0-RC1" /** * A Protobuf release older than the one used by this build. @@ -136,7 +136,7 @@ private const val OLDER_PROTOBUF = "4.33.0" * It equals the version the CLI declares: both come from `Protobuf.libs`, which * the CLI declares in its POM, and the build forces in every configuration. */ -private val CLI_PROTOBUF = +internal val CLI_PROTOBUF = "${RuntimeVersion.MAJOR}.${RuntimeVersion.MINOR}.${RuntimeVersion.PATCH}" /** @@ -148,7 +148,7 @@ private val PROTOBUF_JAVA_JAR = Regex("""protobuf-java-\d.*\.jar""") * Tells whether the [version] is newer than the [than] version, comparing only * the major, minor, and patch numbers. A qualifier such as `-RC1` is ignored. */ -private fun isNewer(version: String, than: String): Boolean { +internal fun isNewer(version: String, than: String): Boolean { fun numbers(v: String) = v.substringBefore('-').split('.').map(String::toInt) val (mine, theirs) = numbers(version).zip(numbers(than)) .firstOrNull { (a, b) -> a != b } diff --git a/gradle-plugin/src/functionalTest/resources/protobuf-runtime-test/build.gradle.kts b/gradle-plugin/src/functionalTest/resources/protobuf-runtime-test/build.gradle.kts index 93338e91bd6..6e4c2f7baba 100644 --- a/gradle-plugin/src/functionalTest/resources/protobuf-runtime-test/build.gradle.kts +++ b/gradle-plugin/src/functionalTest/resources/protobuf-runtime-test/build.gradle.kts @@ -55,11 +55,16 @@ spine { } } +// Set by a test that controls the version check variable via the `compiler { }` extension. +val keepVersionCheckVariable = providers.gradleProperty("keepVersionCheckVariable").isPresent + tasks.withType().configureEach { // The functional tests turn off the Protobuf gencode/runtime version check // for the whole build. Turn it back on in the Compiler JVM, where the gencode // of the plugin must not find a runtime older than itself. - environment.remove("TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK") + if (!keepVersionCheckVariable) { + environment.remove("TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK") + } // Record the classpath of the Compiler JVM, letting the functional test // check which Protobuf runtime comes first. val classpathFile = layout.projectDirectory.file("compiler-classpath.txt") diff --git a/gradle-plugin/src/main/kotlin/io/spine/tools/compiler/gradle/plugin/Extension.kt b/gradle-plugin/src/main/kotlin/io/spine/tools/compiler/gradle/plugin/Extension.kt index e9323cc3eba..eee22aa16c2 100644 --- a/gradle-plugin/src/main/kotlin/io/spine/tools/compiler/gradle/plugin/Extension.kt +++ b/gradle-plugin/src/main/kotlin/io/spine/tools/compiler/gradle/plugin/Extension.kt @@ -1,27 +1,15 @@ /* - * Copyright 2025, TeamDev. All rights reserved. + * 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 + * 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. + * 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.tools.compiler.gradle.plugin @@ -37,9 +25,11 @@ import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.plugins.ExtensionContainer import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.SourceSet import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.property /** * The `compiler { }` Gradle project extension. @@ -108,6 +98,20 @@ public class Extension(private val project: Project): CompilerSettings { ) } + /** + * Allows disabling the Protobuf version check in the Compiler process. + * + * Not set by default, so the Compiler process inherits the setting from + * the environment of the build. + * + * The value is finalized when the Compiler launch tasks read it as they are + * configured, so that a later change fails instead of being silently ignored. + * + * @see CompilerSettings.disableProtobufVersionCheck + */ + public override val disableProtobufVersionCheck: Property = + factory.property().apply { finalizeValueOnRead() } + /** * Obtains the source directories for the given source set. */ diff --git a/gradle-plugin/src/main/kotlin/io/spine/tools/compiler/gradle/plugin/LaunchSpineCompiler.kt b/gradle-plugin/src/main/kotlin/io/spine/tools/compiler/gradle/plugin/LaunchSpineCompiler.kt index ea3b3640d5c..6724b033a42 100644 --- a/gradle-plugin/src/main/kotlin/io/spine/tools/compiler/gradle/plugin/LaunchSpineCompiler.kt +++ b/gradle-plugin/src/main/kotlin/io/spine/tools/compiler/gradle/plugin/LaunchSpineCompiler.kt @@ -1,32 +1,21 @@ /* - * Copyright 2026, TeamDev. All rights reserved. + * 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 + * 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. + * 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.tools.compiler.gradle.plugin import com.google.protobuf.gradle.GenerateProtoTask +import io.spine.annotation.VisibleForTesting import io.spine.tools.code.SourceSetName import io.spine.tools.compiler.Constants.CLI_APP_CLASS import io.spine.tools.compiler.ast.toAbsoluteFile @@ -276,6 +265,7 @@ internal fun LaunchSpineCompiler.applyDefaults(sourceSet: SourceSet) { val project = project val ext = project.compilerSettings plugins = ext.plugins + applyProtobufVersionCheckSetting(ext.disableProtobufVersionCheck.orNull) compilerConfiguration = project.compilerRawArtifact userClasspathConfiguration = project.userClasspath classpath(compilerConfiguration) @@ -412,6 +402,35 @@ internal fun LaunchSpineCompiler.hasRequestFile(sourceSet: SourceSet): Boolean { return file.exists() } +/** + * Applies the given setting of the Protobuf version check to + * the [PROTOBUF_VERSION_CHECK_ENV] environment variable of the Compiler process. + * + * Sets the variable to `true` if [disableCheck] is `true`, and removes the variable + * if it is `false`. If the setting is `null`, leaves the environment intact, so that + * the variable set in the environment of the build still reaches the Compiler. + * + * @param disableCheck The value of `CompilerSettings.disableProtobufVersionCheck`, + * or `null` if it is not set. + */ +@VisibleForTesting +internal fun LaunchSpineCompiler.applyProtobufVersionCheckSetting(disableCheck: Boolean?) { + // `else` rather than `null` keeps Kotlin from adding an unreachable exhaustiveness branch. + when (disableCheck) { + true -> environment(PROTOBUF_VERSION_CHECK_ENV, "true") + false -> environment.remove(PROTOBUF_VERSION_CHECK_ENV) + else -> {} // Not set: inherit the variable from the environment of the build. + } +} + +/** + * The name of the environment variable which turns off the check of the code + * generated by `protoc` against the version of the Protobuf runtime. + * + * The Protobuf runtime skips the check if the variable is set to `true`. + */ +private const val PROTOBUF_VERSION_CHECK_ENV = "TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK" + private val Project.compilerRawArtifact: Configuration get() = configurations.getByName(COMPILER_RAW_ARTIFACT) diff --git a/gradle-plugin/src/test/kotlin/io/spine/tools/compiler/gradle/plugin/ExtensionSpec.kt b/gradle-plugin/src/test/kotlin/io/spine/tools/compiler/gradle/plugin/ExtensionSpec.kt index 6e815d7a484..f8a5b56d208 100644 --- a/gradle-plugin/src/test/kotlin/io/spine/tools/compiler/gradle/plugin/ExtensionSpec.kt +++ b/gradle-plugin/src/test/kotlin/io/spine/tools/compiler/gradle/plugin/ExtensionSpec.kt @@ -1,27 +1,15 @@ /* - * Copyright 2025, TeamDev. All rights reserved. + * 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 + * 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. + * 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.tools.compiler.gradle.plugin @@ -47,7 +35,7 @@ class ExtensionSpec { private lateinit var project: Project private lateinit var extension: Extension - + @BeforeEach fun prepareProject(@TempDir projectDir: File) { project = ProjectBuilder.builder() @@ -87,6 +75,11 @@ class ExtensionSpec { targetDirs[0].toPath() shouldBe mainDir / expected[0] targetDirs[1].toPath() shouldBe mainDir / expected[1] } + + @Test + fun `leave the Protobuf version check to the build environment by default`() { + extension.disableProtobufVersionCheck.isPresent shouldBe false + } } private fun Directory.toPath() = asFile.toPath() diff --git a/gradle-plugin/src/test/kotlin/io/spine/tools/compiler/gradle/plugin/LaunchTaskEnvironmentSpec.kt b/gradle-plugin/src/test/kotlin/io/spine/tools/compiler/gradle/plugin/LaunchTaskEnvironmentSpec.kt new file mode 100644 index 00000000000..5f39b635fc3 --- /dev/null +++ b/gradle-plugin/src/test/kotlin/io/spine/tools/compiler/gradle/plugin/LaunchTaskEnvironmentSpec.kt @@ -0,0 +1,124 @@ +/* + * 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.tools.compiler.gradle.plugin + +import com.google.protobuf.gradle.ProtobufPlugin +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.maps.shouldNotContainKey +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.spine.tools.code.SourceSetName +import io.spine.tools.compiler.gradle.api.CompilerTaskName +import java.io.File +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.testfixtures.ProjectBuilder +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 how [LaunchSpineCompiler] controls the variable disabling the Protobuf + * version check in the environment of the Compiler process. + * + * The end-to-end counterpart, with a real version check failing in the Compiler + * process, is `DisableProtobufVersionCheckSpec` in the `functionalTest` suite. + */ +@DisplayName("`LaunchSpineCompiler` should") +internal class LaunchTaskEnvironmentSpec { + + private lateinit var project: Project + + @BeforeEach + fun prepareProject(@TempDir projectDir: File) { + project = ProjectBuilder.builder() + .withProjectDir(projectDir) + .build() + with(project) { + apply(plugin = "java") + apply() + apply() + } + } + + /** + * Obtains the launch task for the `main` source set, configuring it on first access. + */ + private fun launchTask(): LaunchSpineCompiler { + val taskName = CompilerTaskName(SourceSetName.main).value() + return project.tasks.getByName(taskName).shouldBeInstanceOf() + } + + @Test + fun `apply the setting of the extension when configured`() { + project.compilerSettings.disableProtobufVersionCheck.set(true) + + launchTask().environment[ENV_VARIABLE] shouldBe "true" + } + + @Test + fun `reject a change of the setting after the task is configured`() { + launchTask() + + shouldThrow { + project.compilerSettings.disableProtobufVersionCheck.set(true) + } + } + + @Test + fun `leave the inherited environment intact when the setting is not set`() { + val task = launchTask() + // A value other than `true` tells an untouched variable from an overwritten one, + // regardless of the environment of the machine running the test. + val inherited = "inherited-from-build" + task.environment(ENV_VARIABLE, inherited) + + task.applyProtobufVersionCheckSetting(null) + + task.environment[ENV_VARIABLE] shouldBe inherited + } + + @Test + fun `disable the Protobuf version check when the setting is 'true'`() { + val task = launchTask() + + task.applyProtobufVersionCheckSetting(true) + + task.environment[ENV_VARIABLE] shouldBe "true" + } + + @Test + fun `keep the Protobuf version check when the setting is 'false'`() { + val task = launchTask() + // As if the variable were set in the environment of the build. + task.environment(ENV_VARIABLE, "true") + + task.applyProtobufVersionCheckSetting(false) + + task.environment shouldNotContainKey ENV_VARIABLE + } + + private companion object { + + /** + * The name of the variable checked by the Protobuf runtime. + * + * The name is repeated here rather than referenced to lock the exact value + * expected by the Protobuf runtime. + */ + const val ENV_VARIABLE = "TEMPORARILY_DISABLE_PROTOBUF_VERSION_CHECK" + } +} diff --git a/version.gradle.kts b/version.gradle.kts index 2b0bb331748..0393886bf8a 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -18,7 +18,7 @@ * This version is also used by integration test projects. * E.g. see `tests/consumer/build.gradle.kts`. */ -private val compilerVersion = "2.0.0-SNAPSHOT.078" +private val compilerVersion = "2.0.0-SNAPSHOT.079" extra.set("compilerVersion", compilerVersion) /**