From 66c4fab2a7c05b427691af9255891ced7802d46f Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 24 Jun 2026 19:38:50 +0100 Subject: [PATCH 1/5] Bump version -> `2.0.0-SNAPSHOT.402` --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index be50dc95..b82214e6 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -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.401") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.402") From 050d962b8acc21f4c827d1206d88bf49ec1abe92 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 24 Jun 2026 19:40:42 +0100 Subject: [PATCH 2/5] Update `config` --- .claude/settings.json | 4 + .github/workflows/build-on-ubuntu.yml | 63 ++++++++++++++-- .github/workflows/secret-scan.yml | 70 ++++++++++++++++++ .gitignore | 74 ++++++++++++++++++- .idea/kotlinc.xml | 8 +- .../io/spine/dependency/lib/GoogleApis.kt | 10 +-- .../io/spine/dependency/lib/GoogleCloud.kt | 21 +++--- .../io/spine/dependency/lib/PerfMark.kt | 34 +++++++++ .../io/spine/dependency/local/Compiler.kt | 4 +- .../io/spine/dependency/local/CoreJvm.kt | 2 +- .../spine/dependency/local/CoreJvmCompiler.kt | 4 +- .../io/spine/dependency/local/ToolBase.kt | 4 +- .../spine/dependency/test/Testcontainers.kt | 37 ++++++++++ config | 2 +- init-submodules | 12 +++ 15 files changed, 317 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/secret-scan.yml create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/lib/PerfMark.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/test/Testcontainers.kt diff --git a/.claude/settings.json b/.claude/settings.json index 08c47574..84a0e53d 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -67,6 +67,10 @@ { "matcher": "Bash", "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/secret-scan-gate.sh" + }, { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.agents/scripts/pre-pr-gate.sh" diff --git a/.github/workflows/build-on-ubuntu.yml b/.github/workflows/build-on-ubuntu.yml index 516f347e..01f6fd71 100644 --- a/.github/workflows/build-on-ubuntu.yml +++ b/.github/workflows/build-on-ubuntu.yml @@ -1,11 +1,27 @@ name: Ubuntu CI -on: push +# Triggers: +# * push to a default or release-line branch — those ending in `master` or +# `main`, the same set `increment-guard.yml` guards as PR bases (e.g. +# `master`, `2.x-jdk8-master`). These post-merge runs are the only source +# of base-branch coverage, since `Publish` runs `publish -x test` and +# uploads none; `target: auto` in `.codecov.yml` compares each pull request +# against its base baseline. +# * pull_request — gates a change on its merge result, not the branch tip. +on: + push: + branches: + - '**master' + - '**main' + pull_request: jobs: build: name: Build on Ubuntu runs-on: ubuntu-latest + concurrency: # Avoid canceling in-progress runs for the same branch. + group: ubuntu-ci-${{ github.ref }} + cancel-in-progress: false steps: - uses: actions/checkout@v6 @@ -19,13 +35,24 @@ jobs: - uses: gradle/actions/setup-gradle@v6 + # Mirrors the pagefile step in build-on-windows.yml. The Linux runner + # ships with effectively no swap, so a memory peak becomes an instant + # OOM kill; this gives the kernel somewhere to fall back to. + - name: Add swap space + uses: pierotofy/set-swap-space@v1.0 + with: + swap-size-gb: 8 + + - name: Build project, run tests + shell: bash + run: ./gradlew build --stacktrace + # `build` does not run Dokka — its tasks are gated to the publishing # graph — so `dokkaGenerate` is appended to surface documentation - # warnings on each push, before merge, instead of only in the post-merge - # `Publish` job. `failOnWarning` is enabled in the Dokka setup. - - name: Build project, run tests, and check documentation - shell: bash - run: ./gradlew build dokkaGenerate --stacktrace + # warnings before merge, instead of only in the post-merge `Publish` + # job. `failOnWarning` is enabled in the Dokka setup. + - name: Check documentation + run: ./gradlew dokkaGenerate --stacktrace # See: https://github.com/marketplace/actions/junit-report-action - name: Publish Test Report @@ -35,7 +62,31 @@ jobs: report_paths: '**/build/test-results/**/TEST-*.xml' require_tests: true # will fail workflow if test reports not found + # Probe whether the upload token is available without exposing its value + # to the build/test steps. Scoping `CODECOV_TOKEN` to this trivial step + # (and to the upload step's `with.token`) keeps PR-authored code in + # `./gradlew build` from ever seeing the secret. The `secrets` context is + # not available in a step `if:`, so the upload gates on this output. + - name: Detect Codecov token + id: codecov + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: | + if [ -n "$CODECOV_TOKEN" ]; then + echo "available=true" >> "$GITHUB_OUTPUT" + else + echo "available=false" >> "$GITHUB_OUTPUT" + fi + + # On `push` (master) always upload — these runs are the only source of + # the `target: auto` baseline, so an absent token there is a + # misconfiguration that should fail loudly rather than silently stop + # refreshing coverage. On `pull_request`, skip when the token is absent: + # forked and Dependabot PRs run without secrets, and `fail_ci_if_error` + # would otherwise redden a healthy PR (coverage gating is meaningless + # there anyway). - name: Upload code coverage report + if: steps.codecov.outputs.available == 'true' || github.event_name == 'push' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 00000000..6f0130e2 --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,70 @@ +name: Secret scan + +# Defense-in-depth behind the local `secret-scan` pre-commit hook and the +# `.gitignore` secret patterns: if a credential is committed despite those, this +# fails the pull request before it can merge. Distributed to every Spine repo by +# `./config/pull`. + +on: + pull_request: + push: + branches: + - master + - main + +permissions: + contents: read + +jobs: + gitleaks: + name: gitleaks + runs-on: ubuntu-latest + env: + # Pinned gitleaks version — bump through the usual dependency-update process. + GITLEAKS_VERSION: "8.21.2" + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + # Full history so a pull request's commit range can be scanned. + fetch-depth: 0 + + - name: Install gitleaks + # Run gitleaks as the runner user against the checkout it owns — no + # container, so no "dubious ownership" git error and no GitHub Action + # org-licence requirement. + run: | + curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xzf - gitleaks + ./gitleaks version + + - name: Scan + env: + EVENT: ${{ github.event_name }} + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + BEFORE: ${{ github.event.before }} + AFTER: ${{ github.sha }} + run: | + if [ "$EVENT" = pull_request ]; then + # Scan the PR's own commit RANGE: a secret added in one commit and + # deleted in a later commit of the same PR is still caught (a + # working-tree scan would miss it, yet merging keeps the secret-bearing + # commit reachable), while already-rotated secrets in older history + # outside base..head are not re-flagged. + ./gitleaks git --log-opts="$BASE..$HEAD" --redact --verbose --exit-code=1 . + else + # Push to a default branch: scan the pushed commit RANGE (before..after) + # so an add-then-remove batch is caught here too, not only on PRs — the + # leaked commit would otherwise stay reachable on the default branch. A + # branch's first push reports an all-zero `before` (no range); fall back + # to a working-tree scan then. + if [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ]; then + ./gitleaks git --log-opts="$BEFORE..$AFTER" --redact --verbose --exit-code=1 . + else + # Branch's first push (all-zero `before`): no range to diff against, so + # scan the whole history reachable from the pushed tip as the initial + # import — an add-then-remove within those commits is still caught. + ./gitleaks git --log-opts="$AFTER" --redact --verbose --exit-code=1 . + fi + fi diff --git a/.gitignore b/.gitignore index 38e5ad4b..dfb4774d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# >>> shared config (managed by ./config/pull -- do not edit inside this block) >>> # # Copyright 2025, TeamDev. All rights reserved. # @@ -103,14 +104,48 @@ gradle-app.setting # Spine internal directory for storing intermediate artifacts **/.spine/** -# Login details to Maven repository. -# Each workstation should have developer's login defined in this file. +# --------------------------------------------------------------------------- +# Secrets — NEVER commit these. +# +# Encrypted credentials live under `.github/keys/*.gpg` and ARE committed. +# `config/scripts/decrypt.sh` turns each into its PLAINTEXT twin at build / CI / +# publish time (e.g. `spine-dev-framework-ci.json.gpg` -> `spine-dev.json`). The +# decrypted twins below — and any private key or service-account file — must stay +# out of Git. The shared `secret-scan` pre-commit hook is the backstop if one ever +# slips past these patterns. +# --------------------------------------------------------------------------- + +# Maven repository login details; each workstation defines its own. credentials.tar credentials.properties cloudrepo.properties deploy_key_rsa gcs-auth-key.json +# Decrypted Google / GCP service-account keys (plaintext twins of *.gpg). +spine-dev.json +spine-dev-*.json +maven-publisher.json +firebase-sa.json +*-sa.json +*service-account*.json + +# Decrypted credential property files and portal / publisher secrets. +*.secret.properties + +# Private SSH keys (public keys are *.pub and remain committable). +*_rsa +*_dsa +*_ecdsa +*_ed25519 +id_rsa +id_dsa +id_ecdsa +id_ed25519 + +# ...but always keep the committed ENCRYPTED forms. +!*.gpg + # Log files *.log @@ -152,3 +187,38 @@ __pycache__/ docs/_preview/node_modules/ docs/_preview/public/ docs/_preview/resources/ +# <<< shared config <<< + +# >>> repo-local entries (preserved across ./config/pull) >>> +!.idea/misc.xml +!.idea/codeStyleSettings.xml +!.idea/codeStyles/ +!.idea/copyright/ +!**/src/**/build/** +!gradle-wrapper.jar +# Login details to Maven repository. +# Each workstation should have developer's login defined in this file. +# <<< repo-local entries <<< + +# >>> secret ignores re-asserted last (managed by ./config/pull -- do not edit) >>> +credentials.tar +credentials.properties +cloudrepo.properties +deploy_key_rsa +gcs-auth-key.json +spine-dev.json +spine-dev-*.json +maven-publisher.json +firebase-sa.json +*-sa.json +*service-account*.json +*.secret.properties +*_rsa +*_dsa +*_ecdsa +*_ed25519 +id_rsa +id_dsa +id_ecdsa +id_ed25519 +# <<< secret ignores <<< diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 5c4b3ef9..1b15ab44 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -3,8 +3,12 @@ + + - \ No newline at end of file + 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 7fd33403..3d5a6967 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleApis.kt @@ -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. @@ -36,13 +36,13 @@ object GoogleApis { const val client = "com.google.api-client:google-api-client:1.32.2" // https://github.com/googleapis/api-common-java - const val common = "com.google.api:api-common:2.1.1" + const val common = "com.google.api:api-common:2.64.0" // https://github.com/googleapis/java-common-protos - const val commonProtos = "com.google.api.grpc:proto-google-common-protos:2.7.0" + const val commonProtos = "com.google.api.grpc:proto-google-common-protos:2.72.0" // https://github.com/googleapis/gax-java - const val gax = "com.google.api:gax:2.7.1" + const val gax = "com.google.api:gax:2.80.0" // https://github.com/googleapis/java-iam const val protoAim = "com.google.api.grpc:proto-google-iam-v1:1.2.0" @@ -52,7 +52,7 @@ object GoogleApis { // https://github.com/googleapis/google-auth-library-java object AuthLibrary { - const val version = "1.3.0" + const val version = "1.47.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 b755168a..0aea8672 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/GoogleCloud.kt @@ -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. @@ -26,18 +26,21 @@ package io.spine.dependency.lib +/** + * https://github.com/googleapis/google-cloud-java + */ @Suppress("unused", "ConstPropertyName") object GoogleCloud { - // https://github.com/googleapis/java-core - const val core = "com.google.cloud:google-cloud-core:2.3.3" + // 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" - // https://github.com/googleapis/java-pubsub/tree/main/proto-google-cloud-pubsub-v1 - const val pubSubGrpcApi = "com.google.api.grpc:proto-google-cloud-pubsub-v1:1.97.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" - // https://github.com/googleapis/java-trace - const val trace = "com.google.cloud:google-cloud-trace:2.1.0" + // https://github.com/googleapis/google-cloud-java/tree/main/java-trace + const val trace = "com.google.cloud:google-cloud-trace:2.93.0" - // https://github.com/googleapis/java-datastore - const val datastore = "com.google.cloud:google-cloud-datastore:2.2.1" + // https://github.com/googleapis/google-cloud-java/tree/main/java-datastore + const val datastore = "com.google.cloud:google-cloud-datastore:2.31.2" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/lib/PerfMark.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/PerfMark.kt new file mode 100644 index 00000000..9a156c36 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/PerfMark.kt @@ -0,0 +1,34 @@ +/* + * 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.dependency.lib + +// https://github.com/perfmark/perfmark +@Suppress("unused", "ConstPropertyName") +object PerfMark { + private const val version = "0.27.0" + const val api = "io.perfmark:perfmark-api:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt index d118d7c6..caa002a5 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt @@ -72,7 +72,7 @@ object Compiler : Dependency() { * The version of the Compiler dependencies. */ override val version: String - private const val fallbackVersion = "2.0.0-SNAPSHOT.053" + private const val fallbackVersion = "2.0.0-SNAPSHOT.054" /** * The distinct version of the Compiler used by other build tools. @@ -81,7 +81,7 @@ object Compiler : Dependency() { * transitive dependencies, this is the version used to build the project itself. */ val dogfoodingVersion: String - private const val fallbackDfVersion = "2.0.0-SNAPSHOT.053" + private const val fallbackDfVersion = "2.0.0-SNAPSHOT.054" /** * The artifact for the Compiler Gradle plugin. 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 805fdb0c..54029eb0 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt @@ -39,7 +39,7 @@ typealias CoreJava = CoreJvm @Suppress("ConstPropertyName", "unused") object CoreJvm { const val group = Spine.group - const val version = "2.0.0-SNAPSHOT.376" + const val version = "2.0.0-SNAPSHOT.380" 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 1f91cf24..4754ad5b 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt @@ -46,12 +46,12 @@ object CoreJvmCompiler { /** * The version used in the build classpath. */ - const val dogfoodingVersion = "2.0.0-SNAPSHOT.077" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.079" /** * The version to be used for integration tests. */ - const val version = "2.0.0-SNAPSHOT.077" + const val version = "2.0.0-SNAPSHOT.079" /** * The ID of the Gradle plugin. 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 54c6ef90..d39bfb08 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.400" - const val dogfoodingVersion = "2.0.0-SNAPSHOT.400" + const val version = "2.0.0-SNAPSHOT.401" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.401" const val lib = "$group:tool-base:$version" const val classicCodegen = "$group:classic-codegen:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/Testcontainers.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/Testcontainers.kt new file mode 100644 index 00000000..bb3b22e3 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Testcontainers.kt @@ -0,0 +1,37 @@ +/* + * 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.dependency.test + +// https://github.com/testcontainers/testcontainers-java +@Suppress("unused", "ConstPropertyName") +object Testcontainers { + private const val version = "1.21.4" + private const val group = "org.testcontainers" + const val lib = "$group:testcontainers:$version" + const val junitJupiter = "$group:junit-jupiter:$version" + const val gcloud = "$group:gcloud:$version" +} diff --git a/config b/config index 89ddc759..0728520a 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 89ddc75919d75cc9ceee8822c2fdc93a24784e8b +Subproject commit 0728520aa2b6e418d9908ca226c2d331c8d94c83 diff --git a/init-submodules b/init-submodules index 0c12a281..2ae143d8 100755 --- a/init-submodules +++ b/init-submodules @@ -84,4 +84,16 @@ git submodule status 2>/dev/null | awk '$1 ~ /^-/ { print $2 }' | while read -r fi done +# Route Git hooks to the shared hooks directory so the secret-scan `pre-commit` +# hook is active even in a brand-new worktree, before `./config/pull` runs. The +# path floats with the `.agents/shared` submodule; until that submodule is +# initialized the hook simply does not fire (Git skips a missing hook). Set only +# when unset or already ours — never override a repo's own `core.hooksPath`. +desired_hooks=".agents/scripts/git-hooks" +current_hooks=$(git config --local --get core.hooksPath 2>/dev/null || true) +if [ -z "$current_hooks" ] || [ "$current_hooks" = "$desired_hooks" ]; then + git config --local core.hooksPath "$desired_hooks" \ + && echo "init-submodules: Git hooks routed to '$desired_hooks' (secret-scan pre-commit active)." +fi + exit 0 From e15ce45cb38958f0b1bc4ebee7c5856c2bcacba4 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 24 Jun 2026 19:40:51 +0100 Subject: [PATCH 3/5] Update the dependency reports --- docs/dependencies/dependencies.md | 56 +++++++++++++++---------------- docs/dependencies/pom.xml | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/dependencies/dependencies.md b/docs/dependencies/dependencies.md index d0ec489f..55e9b666 100644 --- a/docs/dependencies/dependencies.md +++ b/docs/dependencies/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.tools:classic-codegen:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:classic-codegen:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -828,14 +828,14 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:gradle-plugin-api:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:gradle-plugin-api:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -1734,14 +1734,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:gradle-plugin-api-test-fixtures:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:gradle-plugin-api-test-fixtures:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -2212,14 +2212,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:54 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:33 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:gradle-root-plugin:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:gradle-root-plugin:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -3070,14 +3070,14 @@ This report was generated on **Fri Jun 19 11:10:54 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:intellij-platform:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:intellij-platform:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -4151,14 +4151,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:intellij-platform-java:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:intellij-platform-java:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -5930,14 +5930,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:56 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:jvm-tool-plugins:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:jvm-tool-plugins:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -6780,14 +6780,14 @@ This report was generated on **Fri Jun 19 11:10:56 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:jvm-tools:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:jvm-tools:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.1.0. @@ -7547,14 +7547,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:plugin-base:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:plugin-base:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8405,14 +8405,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:plugin-testlib:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:plugin-testlib:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.auto.value. **Name** : auto-value-annotations. **Version** : 1.11.1. @@ -9367,14 +9367,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:protobuf-setup-plugins:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:protobuf-setup-plugins:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -10237,14 +10237,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:psi:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:psi:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -11345,14 +11345,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:psi-java:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:psi-java:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -13167,14 +13167,14 @@ This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:56 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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:tool-base:2.0.0-SNAPSHOT.401` +# Dependencies of `io.spine.tools:tool-base:2.0.0-SNAPSHOT.402` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -14054,6 +14054,6 @@ This report was generated on **Fri Jun 19 11:10:56 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Fri Jun 19 11:10:55 WEST 2026** using +This report was generated on **Wed Jun 24 19:40:34 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 43922c44..fb1223a2 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 tool-base -2.0.0-SNAPSHOT.401 +2.0.0-SNAPSHOT.402 2015 From c914abec052b6ced5fca6e834a23273d28c2820f Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 24 Jun 2026 20:41:19 +0100 Subject: [PATCH 4/5] Fix descriptor set generation when the project version changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `GenerateProtoTask` is a cacheable task whose key is the `.proto` sources and the compiler configuration only. The descriptor set file name embeds the project version, and the `desc.ref` file points at that name. After a version-only change the Proto sources are unchanged, so the build cache restored a descriptor set produced for the previous version while `desc.ref` referenced the new name — leaving Protobuf types unresolvable at runtime (`UnknownTypeException`). Declare the project version as an explicit, lazily-read input of the task so a version change invalidates the cached descriptor set and regenerates it, and its reference file, with matching names. Caching is preserved for all other changes. This mirrors the `inputs.property("projectVersion", ...)` approach already used in `LicenseReporter`. Add a regression test to `BuildCacheSpec` that bumps the version with the build cache on and asserts the descriptor set and its reference file are regenerated consistently. Fixes #183. Co-Authored-By: Claude Opus 4.8 --- .../gradle/plugin/DescriptorSetFilePlugin.kt | 33 +++++++++++++ .../protobuf/gradle/plugin/BuildCacheSpec.kt | 46 ++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/protobuf-setup-plugins/src/main/kotlin/io/spine/tools/protobuf/gradle/plugin/DescriptorSetFilePlugin.kt b/protobuf-setup-plugins/src/main/kotlin/io/spine/tools/protobuf/gradle/plugin/DescriptorSetFilePlugin.kt index 8731db6a..d34b7261 100644 --- a/protobuf-setup-plugins/src/main/kotlin/io/spine/tools/protobuf/gradle/plugin/DescriptorSetFilePlugin.kt +++ b/protobuf-setup-plugins/src/main/kotlin/io/spine/tools/protobuf/gradle/plugin/DescriptorSetFilePlugin.kt @@ -47,6 +47,11 @@ public class DescriptorSetFilePlugin : ProtobufSetupPlugin() { * The ID of this Gradle plugin. */ const val id = "io.spine.descriptor-set-file" + + /** + * The name of the [GenerateProtoTask] input property holding the project version. + */ + const val VERSION_PROPERTY = "projectVersion" } override fun setup(task: GenerateProtoTask) { @@ -77,6 +82,7 @@ public class DescriptorSetFilePlugin : ProtobufSetupPlugin() { DescriptorSetReferenceFile.create(descriptorsDir, descriptorSetFile) } task.declareReferenceFileOutput(descriptorsDir) + task.declareVersionInput() task.dependOnProcessResourcesTask() } @@ -102,6 +108,33 @@ private fun GenerateProtoTask.declareReferenceFileOutput(descriptorsDir: File) { .withPropertyName(REFERENCE_FILE_PROPERTY) } +/** + * Declares the project version as an explicit input of this task so that + * a version change invalidates the cached descriptor set. + * + * The descriptor set file name embeds the project version (see [descriptorSetFile]), + * and the [reference file][DescriptorSetReferenceFile] written in the `doLast` action + * points to that name. [GenerateProtoTask] is a cacheable task that keys its up-to-date + * check and build-cache entry on the `.proto` sources and the compiler configuration + * only — not on the project version or the names of the produced files. + * + * After a version-only change the Proto sources are unchanged, so the task is restored + * from the build cache, bringing back a descriptor set produced for the previous version + * while the reference file points to the new name. The mismatch leaves Protobuf types + * unresolvable at runtime, surfacing as an `UnknownTypeException`. + * + * Declaring the version as an input makes the build cache regenerate the descriptor set + * and its reference file when the version changes, while preserving caching for all other + * changes. The value is read lazily so that it reflects the version resolved at execution + * time, regardless of when `project.version` is assigned during configuration. + */ +private fun GenerateProtoTask.declareVersionInput() { + inputs.property( + DescriptorSetFilePlugin.VERSION_PROPERTY, + project.provider { project.version.toString() } + ) +} + /** * Make the `processResources` task depend on this `GenerateProtoTask`. */ diff --git a/protobuf-setup-plugins/src/test/kotlin/io/spine/tools/protobuf/gradle/plugin/BuildCacheSpec.kt b/protobuf-setup-plugins/src/test/kotlin/io/spine/tools/protobuf/gradle/plugin/BuildCacheSpec.kt index e54ca0e8..b3cdbac3 100644 --- a/protobuf-setup-plugins/src/test/kotlin/io/spine/tools/protobuf/gradle/plugin/BuildCacheSpec.kt +++ b/protobuf-setup-plugins/src/test/kotlin/io/spine/tools/protobuf/gradle/plugin/BuildCacheSpec.kt @@ -70,7 +70,10 @@ internal class BuildCacheSpec : ProtobufPluginTest() { private val descRef: File get() = File(descriptorsDir, DescriptorSetReferenceFile.NAME) private val expectedDescriptorName: String - get() = "${group}_${projectDir.name}_${version}.desc" + get() = descriptorName(version) + + private fun descriptorName(version: String): String = + "${group}_${projectDir.name}_${version}.desc" @Test fun `restore generated code and the reference file from the cache after 'clean'`() { @@ -111,9 +114,50 @@ internal class BuildCacheSpec : ProtobufPluginTest() { assertCodegenComplete(result) } + @Test + fun `regenerate the descriptor set after a version change with the cache on`() { + setupProject() + + // Seed the build cache at the initial version. + assertCodegenComplete(build(buildCacheArg)) + + // Bump the project version, leaving the Proto sources unchanged. + val newVersion = "1.0.1" + bumpVersionTo(newVersion) + + // Drop the build outputs. The local build cache lives outside `build/`, + // so it survives `clean` and could still serve the previous descriptor set. + runGradleBuild(projectDir, listOf("clean", buildCacheArg)) + + val result = build(buildCacheArg) + + // The version is a `generateProto` input, so the task re-executes instead of + // restoring a descriptor set produced for the previous version. + result.task(generateProto.path())?.outcome shouldBe SUCCESS + + // The descriptor set and its reference file are regenerated for the new version + // and stay consistent with each other. + val newDescriptorName = descriptorName(newVersion) + descRef.readText().trim() shouldBe newDescriptorName + File(descriptorsDir, newDescriptorName).exists() shouldBe true + File(projectDir, "build/resources/main/$newDescriptorName").exists() shouldBe true + File(projectDir, "build/resources/main/${DescriptorSetReferenceFile.NAME}") + .readText().trim() shouldBe newDescriptorName + } + private fun build(vararg options: String): BuildResult = runGradleBuild(projectDir, listOf("build") + options) + /** + * Bumps the project version in the build file, leaving the rest of the project intact. + */ + private fun bumpVersionTo(newVersion: String) { + val buildFile = Gradle.buildFile.under(projectDir) + val updated = buildFile.readText() + .replace("version = \"$version\"", "version = \"$newVersion\"") + buildFile.writeText(updated) + } + /** * Asserts that the [result] is successful and all the files produced by * the plugins are in place. From 502e3dfec90a7a74643be854caedf0370eae476b Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Wed, 24 Jun 2026 21:24:52 +0100 Subject: [PATCH 5/5] Key the descriptor-set cache on the file name, not just the version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback on PR #184: the descriptor set file name is derived from the full Maven coordinates (group, artifact ID, version, classifier), so keying the `GenerateProtoTask` cache on the project version alone left a group- or artifact-ID change able to restore a descriptor set produced under a different name — the same `desc.ref`/descriptor mismatch the fix set out to prevent. Key the task on the computed descriptor set file name instead, which already embeds every naming component. This still fixes the reported version-bump case (#183) and additionally covers group, artifact-ID, and classifier changes. The input is read from the same `descriptorSetFile` used for the output path, so the cache key and the produced name can never disagree. Add a regression test that changes only the project group (version unchanged) and asserts the descriptor set is regenerated rather than restored from the build cache. Co-Authored-By: Claude Opus 4.8 --- .../gradle/plugin/DescriptorSetFilePlugin.kt | 46 +++++++-------- .../protobuf/gradle/plugin/BuildCacheSpec.kt | 56 ++++++++++++++++--- 2 files changed, 70 insertions(+), 32 deletions(-) diff --git a/protobuf-setup-plugins/src/main/kotlin/io/spine/tools/protobuf/gradle/plugin/DescriptorSetFilePlugin.kt b/protobuf-setup-plugins/src/main/kotlin/io/spine/tools/protobuf/gradle/plugin/DescriptorSetFilePlugin.kt index d34b7261..b1b06bd2 100644 --- a/protobuf-setup-plugins/src/main/kotlin/io/spine/tools/protobuf/gradle/plugin/DescriptorSetFilePlugin.kt +++ b/protobuf-setup-plugins/src/main/kotlin/io/spine/tools/protobuf/gradle/plugin/DescriptorSetFilePlugin.kt @@ -49,9 +49,10 @@ public class DescriptorSetFilePlugin : ProtobufSetupPlugin() { const val id = "io.spine.descriptor-set-file" /** - * The name of the [GenerateProtoTask] input property holding the project version. + * The name of the [GenerateProtoTask] input property holding the descriptor set + * file name. */ - const val VERSION_PROPERTY = "projectVersion" + const val DESCRIPTOR_SET_NAME_PROPERTY = "descriptorSetName" } override fun setup(task: GenerateProtoTask) { @@ -82,7 +83,7 @@ public class DescriptorSetFilePlugin : ProtobufSetupPlugin() { DescriptorSetReferenceFile.create(descriptorsDir, descriptorSetFile) } task.declareReferenceFileOutput(descriptorsDir) - task.declareVersionInput() + task.declareDescriptorSetNameInput(descriptorSetFile) task.dependOnProcessResourcesTask() } @@ -109,30 +110,29 @@ private fun GenerateProtoTask.declareReferenceFileOutput(descriptorsDir: File) { } /** - * Declares the project version as an explicit input of this task so that - * a version change invalidates the cached descriptor set. + * Declares the descriptor set file name as an explicit input of this task so that + * a change to the name invalidates the cached descriptor set. * - * The descriptor set file name embeds the project version (see [descriptorSetFile]), - * and the [reference file][DescriptorSetReferenceFile] written in the `doLast` action - * points to that name. [GenerateProtoTask] is a cacheable task that keys its up-to-date - * check and build-cache entry on the `.proto` sources and the compiler configuration - * only — not on the project version or the names of the produced files. + * The descriptor set file name is derived from the project's Maven coordinates — group, + * artifact ID, version, and classifier (see [descriptorSetFile]) — and the + * [reference file][DescriptorSetReferenceFile] written in the `doLast` action points to + * that name. [GenerateProtoTask] is a cacheable task that keys its up-to-date check and + * build-cache entry on the `.proto` sources and the compiler configuration only — not on + * the names of the produced files. * - * After a version-only change the Proto sources are unchanged, so the task is restored - * from the build cache, bringing back a descriptor set produced for the previous version - * while the reference file points to the new name. The mismatch leaves Protobuf types - * unresolvable at runtime, surfacing as an `UnknownTypeException`. + * After a coordinate-only change — most commonly a version bump — the Proto sources are + * unchanged, so the task is restored from the build cache, bringing back a descriptor set + * produced under the previous name while the reference file points to the new name. The + * mismatch leaves Protobuf types unresolvable at runtime, surfacing as an + * `UnknownTypeException`. * - * Declaring the version as an input makes the build cache regenerate the descriptor set - * and its reference file when the version changes, while preserving caching for all other - * changes. The value is read lazily so that it reflects the version resolved at execution - * time, regardless of when `project.version` is assigned during configuration. + * Declaring the descriptor set file name as an input makes the build cache regenerate the + * descriptor set and its reference file whenever the name changes — covering version, + * group, artifact ID, and classifier changes alike — while preserving caching for all + * other changes. */ -private fun GenerateProtoTask.declareVersionInput() { - inputs.property( - DescriptorSetFilePlugin.VERSION_PROPERTY, - project.provider { project.version.toString() } - ) +private fun GenerateProtoTask.declareDescriptorSetNameInput(descriptorSetFile: File) { + inputs.property(DescriptorSetFilePlugin.DESCRIPTOR_SET_NAME_PROPERTY, descriptorSetFile.name) } /** diff --git a/protobuf-setup-plugins/src/test/kotlin/io/spine/tools/protobuf/gradle/plugin/BuildCacheSpec.kt b/protobuf-setup-plugins/src/test/kotlin/io/spine/tools/protobuf/gradle/plugin/BuildCacheSpec.kt index b3cdbac3..fb11593e 100644 --- a/protobuf-setup-plugins/src/test/kotlin/io/spine/tools/protobuf/gradle/plugin/BuildCacheSpec.kt +++ b/protobuf-setup-plugins/src/test/kotlin/io/spine/tools/protobuf/gradle/plugin/BuildCacheSpec.kt @@ -70,10 +70,12 @@ internal class BuildCacheSpec : ProtobufPluginTest() { private val descRef: File get() = File(descriptorsDir, DescriptorSetReferenceFile.NAME) private val expectedDescriptorName: String - get() = descriptorName(version) + get() = descriptorName() - private fun descriptorName(version: String): String = - "${group}_${projectDir.name}_${version}.desc" + private fun descriptorName( + group: String = this.group, + version: String = this.version + ): String = "${group}_${projectDir.name}_${version}.desc" @Test fun `restore generated code and the reference file from the cache after 'clean'`() { @@ -131,13 +133,14 @@ internal class BuildCacheSpec : ProtobufPluginTest() { val result = build(buildCacheArg) - // The version is a `generateProto` input, so the task re-executes instead of - // restoring a descriptor set produced for the previous version. + // The descriptor set file name (which embeds the version) is a `generateProto` + // input, so the task re-executes instead of restoring a descriptor set produced + // for the previous version. result.task(generateProto.path())?.outcome shouldBe SUCCESS // The descriptor set and its reference file are regenerated for the new version // and stay consistent with each other. - val newDescriptorName = descriptorName(newVersion) + val newDescriptorName = descriptorName(version = newVersion) descRef.readText().trim() shouldBe newDescriptorName File(descriptorsDir, newDescriptorName).exists() shouldBe true File(projectDir, "build/resources/main/$newDescriptorName").exists() shouldBe true @@ -145,6 +148,32 @@ internal class BuildCacheSpec : ProtobufPluginTest() { .readText().trim() shouldBe newDescriptorName } + @Test + fun `regenerate the descriptor set after a group change with the cache on`() { + setupProject() + + // Seed the build cache at the initial Maven coordinates. + assertCodegenComplete(build(buildCacheArg)) + + // Change the project group, leaving the version and the Proto sources unchanged. + // The version alone is therefore not enough to distinguish the two builds. + val newGroup = "cache.test.renamed" + changeGroupTo(newGroup) + + runGradleBuild(projectDir, listOf("clean", buildCacheArg)) + + val result = build(buildCacheArg) + + // The descriptor set file name embeds the group, so a coordinate change other than + // the version still re-runs the task instead of restoring a stale descriptor set. + result.task(generateProto.path())?.outcome shouldBe SUCCESS + + val newDescriptorName = descriptorName(group = newGroup) + descRef.readText().trim() shouldBe newDescriptorName + File(descriptorsDir, newDescriptorName).exists() shouldBe true + File(projectDir, "build/resources/main/$newDescriptorName").exists() shouldBe true + } + private fun build(vararg options: String): BuildResult = runGradleBuild(projectDir, listOf("build") + options) @@ -152,10 +181,19 @@ internal class BuildCacheSpec : ProtobufPluginTest() { * Bumps the project version in the build file, leaving the rest of the project intact. */ private fun bumpVersionTo(newVersion: String) { + replaceInBuildFile("version = \"$version\"", "version = \"$newVersion\"") + } + + /** + * Changes the project group in the build file, leaving the rest of the project intact. + */ + private fun changeGroupTo(newGroup: String) { + replaceInBuildFile("group = \"$group\"", "group = \"$newGroup\"") + } + + private fun replaceInBuildFile(old: String, new: String) { val buildFile = Gradle.buildFile.under(projectDir) - val updated = buildFile.readText() - .replace("version = \"$version\"", "version = \"$newVersion\"") - buildFile.writeText(updated) + buildFile.writeText(buildFile.readText().replace(old, new)) } /**