diff --git a/.claude/settings.json b/.claude/settings.json index 08c475740e..357650cf71 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -2,6 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-settings.json", "permissions": { "allow": [ + "Edit(version.gradle.kts)", "Bash(./gradlew:*)", "Bash(./config/gradlew:*)", "Bash(git status:*)", @@ -67,6 +68,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 516f347e14..b07bf0fb0a 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,8 +62,32 @@ 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 - uses: codecov/codecov-action@v4 + if: steps.codecov.outputs.available == 'true' || github.event_name == 'push' + uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/.github/workflows/increment-guard.yml b/.github/workflows/increment-guard.yml index f20b4bed6e..9a6333f0ff 100644 --- a/.github/workflows/increment-guard.yml +++ b/.github/workflows/increment-guard.yml @@ -1,5 +1,9 @@ -# Ensures that the current lib version is not yet published by executing the Gradle -# `checkVersionIncrement` task. +# Guards the project version by executing the Gradle `checkVersionIncrement` task, +# which verifies that the version is both (a) strictly greater than the base branch +# version in `version.gradle.kts` and (b) not already published. The result is +# published as the `Version Guard` commit status — the context required by branch +# protection and re-published by `revalidate-versions.yml` on the heads of other open +# PRs when the base branch advances (so a stale duplicate bump turns red before merge). # # The check runs only for pull requests targeting a default (`master`/`main`) or # a release-line (e.g. `2.x-jdk8-master`) branch. It is the responsibility of a branch @@ -15,19 +19,45 @@ name: Version Guard on: pull_request: + # Beyond the default activity types (`opened`, `synchronize`, `reopened`), two more are + # needed because they change what the guard must compare against without a new head SHA, + # which would otherwise leave a stale-green `Version Guard` status mergeable: + # * `ready_for_review` — a draft that went stale while in draft becomes ready; and + # * `edited` — the base branch is retargeted (e.g. a release line -> `master`), so the + # strict comparison must be recomputed against the new base. + types: [opened, synchronize, reopened, ready_for_review, edited] jobs: check: name: Check version increment runs-on: ubuntu-latest - # Default and release-line branches, e.g. `master`, `main`, `2.x-jdk8-master`. - if: endsWith(github.base_ref, 'master') || endsWith(github.base_ref, 'main') + # Default and release-line branches, e.g. `master`, `main`, `2.x-jdk8-master`. For an + # `edited` event, run only when the base actually changed (a retarget carries + # `changes.base.ref.from`); title/body edits carry no `changes.base` and are skipped, so + # the guard is not rebuilt needlessly. + if: >- + (endsWith(github.base_ref, 'master') || endsWith(github.base_ref, 'main')) + && (github.event.action != 'edited' || github.event.changes.base.ref.from != '') + + # `statuses: write` lets the job publish the `Version Guard` commit status that + # branch protection requires. + permissions: + contents: read + statuses: write steps: - uses: actions/checkout@v6 with: submodules: 'true' + # `checkVersionIncrement` reads `origin/:version.gradle.kts`. The pull request + # checkout does not include the base branch, so fetch its tip into the expected ref. + - name: Fetch the base branch + shell: bash + run: | + git fetch --no-tags --depth=1 \ + origin "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/${GITHUB_BASE_REF}" + - uses: actions/setup-java@v5 with: java-version: 17 @@ -35,6 +65,36 @@ jobs: - uses: gradle/actions/setup-gradle@v6 - - name: Check version is not yet published + - name: Check version increment + id: guard shell: bash + # `VERSION_GUARD` enables the strict base-branch comparison in `checkVersionIncrement`. + # Only this workflow fetches the base ref (the step above), so the comparison is gated + # to it: other CI builds pull the task in via `publishToMavenLocal` on a shallow + # checkout and must not attempt to read `origin/`. + env: + VERSION_GUARD: "true" run: ./gradlew checkVersionIncrement --stacktrace + + # Publish the verdict as the `Version Guard` commit status on the PR head. Posting it + # on every run (success or failure) is what lets a later re-bump clear a failure that + # `revalidate-versions.yml` set when the base branch advanced. + # + # Skipped for fork PRs: `GITHUB_TOKEN` is read-only for them, so the status cannot be + # posted (and a fork head SHA is not in this repo). The Spine agent workflow pushes PR + # branches to the same repository; fork contributions are handled by a maintainer, who + # owns the version bump. + - name: Report the Version Guard status + if: always() && github.event.pull_request.head.repo.fork == false + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + state=failure + if [ "${{ steps.guard.outcome }}" = "success" ]; then + state=success + fi + gh api -X POST "repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \ + -f state="${state}" \ + -f context="Version Guard" \ + -f description="Version increment check" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index df8f6cd01a..27c11c0f82 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -64,3 +64,18 @@ jobs: REPO_SLUG: ${{ github.repository }} # e.g. SpineEventEngine/core-jvm GOOGLE_APPLICATION_CREDENTIALS: ./maven-publisher.json NPM_TOKEN: ${{ secrets.NPM_SECRET }} + + # A failed publication on `master` is most often a version collision: a stale + # duplicate bump merged before `revalidate-versions.yml` could turn it red (the + # narrow auto-merge race). The artifact is safe — the registry rejects the + # overwrite — but the fix needs a human/agent, so make the failure loud and + # actionable instead of a quiet red run. + - name: Report a failed publication + if: failure() + shell: bash + run: | + echo "::error title=Publish failed::Publishing to Maven failed on the base branch. If this is a version collision, the version is already published (immutable). Bump 'version.gradle.kts' on the base branch (e.g. via a small PR) and re-run this workflow." + echo "Publish failed. If the cause is a version collision:" + echo " 1. Bump 'version.gradle.kts' on the base branch to the next free version." + echo " 2. Re-run this 'Publish' workflow." + echo "Stale duplicate bumps are normally caught before merge by 'revalidate-versions.yml'." diff --git a/.github/workflows/revalidate-versions.yml b/.github/workflows/revalidate-versions.yml new file mode 100644 index 0000000000..2d2ecb9c6d --- /dev/null +++ b/.github/workflows/revalidate-versions.yml @@ -0,0 +1,57 @@ +# Re-judges every other open pull request when the base branch advances. +# +# Publishing runs on every push to a release base branch, so once one pull request merges +# and bumps the version, any other open PR that bumped to the same (or a lower) value is +# now stale: its publish would collide. GitHub does not re-run a PR's checks when its base +# advances, so this workflow does it actively — for each other open PR whose +# `version.gradle.kts` version is `<=` the new base version, it posts a failing +# `Version Guard` commit status on the PR head, blocking the merge until the author +# re-bumps. The status self-clears: the re-bump push runs `increment-guard.yml`, which +# posts a fresh `success` on the new head. +# +# This narrows, but does not close, the race against auto-merge. A PR that is already +# mergeable can merge in the seconds before this fan-out marks it stale; that late merge +# produces a publish collision which the immutable Maven registry rejects (a loud, +# recoverable red Publish), never an overwrite. The deterministic guarantee is the +# registry's immutability, not this signal. + +name: Revalidate Versions + +on: + push: + # Matches the PR guard's `endsWith(base_ref, 'master'|'main')` and the same scope as + # `build-on-ubuntu.yml`. `**` (unlike `*`) also crosses `/`, so slash-named release + # lines such as `release/2.x-master` are covered. Keeping these definitions identical + # ensures every branch guarded on the PR side is also revalidated here. + branches: + - '**master' + - '**main' + +permissions: + contents: read + statuses: write + pull-requests: read + +concurrency: + # Only the newest tip of a given base matters; a later push to the same ref cancels an + # in-flight fan-out for it. Different bases run independently. + group: revalidate-versions-${{ github.ref }} + cancel-in-progress: true + +jobs: + revalidate: + name: Revalidate open PRs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: 'true' + + - name: Revalidate open PRs against the new base version + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + BASE_REF: ${{ github.ref_name }} + # Invoked via `bash` so it does not depend on the script's committed executable bit. + run: bash ./config/scripts/revalidate-versions.sh diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000000..6f0130e21d --- /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 38e5ad4b6d..dfb4774d4e 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/AGENTS.md b/AGENTS.md index 3d0ba5938a..8c5f6198d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,9 +19,15 @@ Shared skills, scripts, and guidelines come from the `.agents/shared` submodule `./config/pull` initializes and floats them automatically. But a fresh `git worktree` (and some shallow clones / cloud checkouts) start with NO submodules checked out, so those symlinks dangle and no skills are found. Bootstrap such a tree with -**`./init-submodules`** — a root script that materializes the missing submodules -(`config`, `.agents/shared`, …) at their pinned commits. It depends on no pre-existing -`config` submodule, so it works before `./config/pull` (which lives inside the `config` +**`./init-submodules`** — a root script that materializes the missing +*config-managed* submodules at their pinned commits: `config` itself, plus every +submodule that declares a tracked `branch` in `.gitmodules` (`.agents/shared`, and +any shared submodule added later) — the same rule `./config/pull` uses to decide +what it floats. Submodules the consumer owns (a Hugo theme, a vendored library, +doc-example submodules, …) declare no tracked branch and are left untouched, so the +automatic `SessionStart` run never tries to clone — or fail on credentials for — a +submodule this project does not manage. It depends on no pre-existing `config` +submodule, so it works before `./config/pull` (which lives inside the `config` submodule) can. Claude Code runs it automatically via a `SessionStart` hook; other agents and humans run it by hand, then `./config/pull` to float the shared submodules to their branch tips. diff --git a/base/src/main/kotlin/io/spine/io/Paths.kt b/base/src/main/kotlin/io/spine/io/Paths.kt index d58634096c..027e4525d2 100644 --- a/base/src/main/kotlin/io/spine/io/Paths.kt +++ b/base/src/main/kotlin/io/spine/io/Paths.kt @@ -112,10 +112,10 @@ public object Separator { * required when processing paths that originate from Windows while running on a non-Windows * machine. * - * It is intentionally `internal`: it backs [Path.toUnixPath] and [File.toUnixPath] and is not - * meant to be a general-purpose `String` API. + * It backs [Path.toUnixPath] and [File.toUnixPath], and is also available directly for + * normalizing string paths to the Unix form used in assertions. */ -internal fun String.toUnix(): String = if (contains(Separator.Windows)) { +public fun String.toUnix(): String = if (contains(Separator.Windows)) { replace(Separator.Windows, Separator.Unix) } else { this diff --git a/build.gradle.kts b/build.gradle.kts index 170a768375..6d0296e78a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -107,8 +107,7 @@ afterEvaluate { } } -@Suppress("unused") -val dokkaGeneratePublicationHtml by tasks.getting { +tasks.named("dokkaGeneratePublicationHtml") { dependsOn(tasks.jar) } diff --git a/buildSrc/src/main/kotlin/DependencyResolution.kt b/buildSrc/src/main/kotlin/DependencyResolution.kt index 124adb3f55..7d76bbba90 100644 --- a/buildSrc/src/main/kotlin/DependencyResolution.kt +++ b/buildSrc/src/main/kotlin/DependencyResolution.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. @@ -30,6 +30,7 @@ import io.spine.dependency.build.Dokka import io.spine.dependency.build.ErrorProne import io.spine.dependency.build.FindBugs import io.spine.dependency.build.JSpecify +import io.spine.dependency.isDokka import io.spine.dependency.lib.Asm import io.spine.dependency.lib.AutoCommon import io.spine.dependency.lib.AutoService @@ -73,6 +74,9 @@ fun doForceVersions(configurations: ConfigurationContainer) { */ fun NamedDomainObjectContainer.forceVersions() { all { + if (isDokka) { + return@all + } resolutionStrategy { failOnVersionConflict() cacheChangingModulesFor(0, "seconds") diff --git a/buildSrc/src/main/kotlin/dokka-setup.gradle.kts b/buildSrc/src/main/kotlin/dokka-setup.gradle.kts index d34615c3fe..a1915cf0c3 100644 --- a/buildSrc/src/main/kotlin/dokka-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-setup.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2025, TeamDev. All rights reserved. + * Copyright 2026, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,15 @@ tasks.withType().configureEach { } } +// The Dokka Javadoc format does not support Kotlin Multiplatform source sets, so its +// publication task fails for KMP modules ("No source set found for /jvmMain"). +// KMP modules publish HTML documentation, so skip the Javadoc publication for them. +plugins.withId("org.jetbrains.kotlin.multiplatform") { + tasks.matching { it.name == "dokkaGeneratePublicationJavadoc" }.configureEach { + enabled = false + } +} + afterEvaluate { dokka { configureForKotlin( diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/Dependency.kt b/buildSrc/src/main/kotlin/io/spine/dependency/Dependency.kt index 372651128e..0eb1956bda 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/Dependency.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/Dependency.kt @@ -111,6 +111,17 @@ abstract class DependencyWithBom : Dependency() { fun Configuration.diagSuffix(project: Project): String = "the configuration `$name` in the project: `${project.path}`." +/** + * Tells if this configuration belongs to Dokka's own generator/plugin classpath. + * + * Dokka resolves these `dokka*` configurations using dependency versions pinned by + * Dokka itself (for example, Jackson or Kotlin), which legitimately differ from the + * project's. Forcing the project's versions onto them breaks `dokkaGenerate`, so such + * configurations must be excluded from the project's version forcing. + */ +val Configuration.isDokka: Boolean + get() = name.startsWith("dokka") + private fun ResolutionStrategy.forceWithLogging( project: Project, configuration: Configuration, diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/boms/BomsPlugin.kt b/buildSrc/src/main/kotlin/io/spine/dependency/boms/BomsPlugin.kt index c5444a00d5..e60d6dea54 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/boms/BomsPlugin.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/boms/BomsPlugin.kt @@ -29,6 +29,7 @@ package io.spine.dependency.boms import io.gitlab.arturbosch.detekt.getSupportedKotlinVersion import io.spine.dependency.DependencyWithBom import io.spine.dependency.diagSuffix +import io.spine.dependency.isDokka import io.spine.dependency.kotlinx.Coroutines import io.spine.dependency.lib.Kotlin import io.spine.dependency.test.JUnit @@ -88,7 +89,7 @@ class BomsPlugin : Plugin { applyBoms(project, Boms.core + Boms.testing) } - matching { !supportsBom(it.name) }.all { + matching { !supportsBom(it.name) && !it.isDokka }.all { resolutionStrategy.eachDependency { if (requested.group == Kotlin.group) { val kotlinVersion = Kotlin.runtimeVersion @@ -170,7 +171,7 @@ private fun supportsBom(name: String) = private fun Project.forceArtifacts() = configurations.all { resolutionStrategy { - if (!isDetekt) { + if (!isDetekt && !isDokka) { val rs = this@resolutionStrategy val project = this@forceArtifacts val cfg = this@all 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 7fd33403dc..3d5a696704 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 b755168ada..0aea86727a 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/IntelliJ.kt b/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt index 7f9232b75f..cf2d6b02d8 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/lib/IntelliJ.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. @@ -31,8 +31,8 @@ package io.spine.dependency.lib /** * The components of the IntelliJ Platform. * - * Make sure to add the `intellijReleases` and `jetBrainsCacheRedirector` - * repositories to your project. See `kotlin/Repositories.kt` for details. + * Make sure to add the `intellijReleases` and `intellijDependencies` + * repositories to your project. See `io/spine/gradle/repo/Repositories.kt` for details. */ @Suppress("unused") object IntelliJ { 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 0000000000..9a156c36b8 --- /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/Base.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt index 0a95432524..433c1bd959 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt @@ -33,8 +33,8 @@ package io.spine.dependency.local */ @Suppress("ConstPropertyName", "unused") object Base { - const val version = "2.0.0-SNAPSHOT.413" - const val versionForBuildScript = "2.0.0-SNAPSHOT.413" + const val version = "2.0.0-SNAPSHOT.421" + const val versionForBuildScript = "2.0.0-SNAPSHOT.421" const val group = Spine.group private const val prefix = "spine" const val libModule = "$prefix-base" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt index d118d7c627..e3b4d0aa4d 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt @@ -27,6 +27,8 @@ package io.spine.dependency.local import io.spine.dependency.Dependency +import io.spine.dependency.local.Compiler.DF_VERSION_ENV +import io.spine.dependency.local.Compiler.VERSION_ENV /** * Dependencies on the Spine Compiler modules. @@ -72,7 +74,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.057" /** * The distinct version of the Compiler used by other build tools. @@ -81,7 +83,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.057" /** * 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 805fdb0c84..d4b8a18ccc 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.381" 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 1f91cf2490..c8d28e7f23 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.080" /** * The version to be used for integration tests. */ - const val version = "2.0.0-SNAPSHOT.077" + const val version = "2.0.0-SNAPSHOT.080" /** * The ID of the Gradle plugin. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt index 04d19ea52d..e9f477ac3c 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt @@ -33,7 +33,7 @@ package io.spine.dependency.local */ @Suppress("ConstPropertyName", "unused") object Logging { - const val version = "2.0.0-SNAPSHOT.417" + const val version = "2.0.0-SNAPSHOT.419" const val group = Spine.group const val loggingArtifact = "spine-logging" 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 54c6ef906d..cd11577fbe 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.402" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.402" const val lib = "$group:tool-base:$version" const val classicCodegen = "$group:classic-codegen:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/storage/H2.kt b/buildSrc/src/main/kotlin/io/spine/dependency/storage/H2.kt new file mode 100644 index 0000000000..5232e63fa5 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/storage/H2.kt @@ -0,0 +1,40 @@ +/* + * 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.storage + +/** + * The H2 Database Engine — a fast, in-memory/embedded SQL database used for exercising + * the JDBC storage in tests. + * + * @see H2 Database Engine at GitHub + * @see H2 Database Engine site + */ +@Suppress("unused", "ConstPropertyName") +object H2 { + private const val version = "2.4.240" + const val lib = "com.h2database:h2:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/storage/Hikari.kt b/buildSrc/src/main/kotlin/io/spine/dependency/storage/Hikari.kt new file mode 100644 index 0000000000..5ab609ef35 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/storage/Hikari.kt @@ -0,0 +1,40 @@ +/* + * 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.storage + +/** + * HikariCP — a fast, lightweight JDBC connection pool. + * + * The JDBC storage uses it to pool database connections. + * + * @see HikariCP at GitHub + */ +@Suppress("unused", "ConstPropertyName") +object Hikari { + private const val version = "7.1.0" + const val lib = "com.zaxxer:HikariCP:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/storage/HsqlDb.kt b/buildSrc/src/main/kotlin/io/spine/dependency/storage/HsqlDb.kt new file mode 100644 index 0000000000..3a6b509ca7 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/storage/HsqlDb.kt @@ -0,0 +1,41 @@ +/* + * 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.storage + +/** + * HyperSQL DataBase (HSQLDB) — a relational database engine written in Java, used in its + * in-memory mode for exercising the JDBC storage in tests. + * + * HSQLDB is hosted on SourceForge rather than GitHub. + * + * @see HyperSQL Database site + */ +@Suppress("unused", "ConstPropertyName") +object HsqlDb { + private const val version = "2.7.4" + const val lib = "org.hsqldb:hsqldb:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/storage/MySql.kt b/buildSrc/src/main/kotlin/io/spine/dependency/storage/MySql.kt new file mode 100644 index 0000000000..78bbfc3b1a --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/storage/MySql.kt @@ -0,0 +1,41 @@ +/* + * 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.storage + +/** + * MySQL Connector/J — the official JDBC driver for MySQL. + * + * Used by the MySQL-based storage tests. Note the modern `com.mysql:mysql-connector-j` + * coordinates, which superseded the legacy `mysql:mysql-connector-java` artifact. + * + * @see MySQL Connector/J at GitHub + */ +@Suppress("unused", "ConstPropertyName") +object MySql { + private const val version = "9.7.0" + const val connector = "com.mysql:mysql-connector-j:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/storage/PostgreSql.kt b/buildSrc/src/main/kotlin/io/spine/dependency/storage/PostgreSql.kt new file mode 100644 index 0000000000..d4083c65c9 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/storage/PostgreSql.kt @@ -0,0 +1,40 @@ +/* + * 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.storage + +/** + * The PostgreSQL JDBC driver (pgJDBC). + * + * Used by the PostgreSQL-based storage tests to connect to a real PostgreSQL server. + * + * @see PostgreSQL JDBC Driver at GitHub + */ +@Suppress("unused", "ConstPropertyName") +object PostgreSql { + private const val version = "42.7.11" + const val connector = "org.postgresql:postgresql:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/storage/QueryDsl.kt b/buildSrc/src/main/kotlin/io/spine/dependency/storage/QueryDsl.kt new file mode 100644 index 0000000000..e22cd9a90b --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/storage/QueryDsl.kt @@ -0,0 +1,45 @@ +/* + * 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.storage + +/** + * QueryDSL — a framework for constructing type-safe SQL-like queries in Java. + * + * The JDBC storage uses the SQL module to build database queries. + * + * @see QueryDSL at GitHub + */ +@Suppress("unused", "ConstPropertyName") +object QueryDsl { + private const val version = "5.1.0" + private const val group = "com.querydsl" + + /** + * The SQL module of QueryDSL. + */ + const val sql = "$group:querydsl-sql:$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 0000000000..85c91b5048 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/Testcontainers.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.dependency.test + +/** + * Testcontainers for Java — provides throwaway, lightweight instances of databases and other + * services running in Docker containers. + * + * The modules below are versioned and released together, so a single [version] applies to all + * of them. + * + * @see + * Testcontainers for Java at GitHub + */ +@Suppress("unused", "ConstPropertyName") +object Testcontainers { + private const val version = "1.21.4" + private const val group = "org.testcontainers" + + /** + * The core Testcontainers library. + */ + const val lib = "$group:testcontainers:$version" + + /** + * The JUnit 5 (Jupiter) integration. + */ + const val junitJupiter = "$group:junit-jupiter:$version" + + /** + * The Google Cloud (GCP) emulator container support. + */ + const val gcloud = "$group:gcloud:$version" + + /** + * The MySQL container support. + */ + const val mySql = "$group:mysql:$version" +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/VersionComparator.kt b/buildSrc/src/main/kotlin/io/spine/gradle/VersionComparator.kt new file mode 100644 index 0000000000..58e8aea215 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/VersionComparator.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle + +/** + * Compares dependency version strings by their meaning rather than lexicographically. + * + * Numeric segments are ordered as numbers, so `10.0.0` is newer than `9.2.0`, and + * `2.0.0-SNAPSHOT.100` is newer than `2.0.0-SNAPSHOT.99`. A plain `String` comparison + * would order both pairs the other way around. + * + * The rules follow Semantic Versioning where it applies: + * + * 1. A version consists of a release part and an optional qualifier, separated by + * the first `-`: for `2.0.0-SNAPSHOT.100` these are `2.0.0` and `SNAPSHOT.100`. + * 2. Both parts are compared segment by segment, as split by `.`, and also by `-` + * within a qualifier. Two numeric segments are compared as numbers, two textual + * ones as case-insensitive text, and a numeric segment is older than a textual one. + * 3. When one version runs out of segments, it is the older one: `1.0.1` is newer + * than `1.0`, and `1.0.0-RC.1` is newer than `1.0.0-RC`. + * 4. When the release parts are equal, a version without a qualifier is newer than + * a version with one: `2.0.0` is newer than `2.0.0-SNAPSHOT.100`. + * + * Unlike full Maven semantics, qualifiers carry no special meaning: `RC`, `SNAPSHOT`, + * and the like are ordered as plain text. This keeps the comparison simple and + * predictable for the report, where only the relative recency of the versions + * of the same artifact matters. + */ +internal object VersionComparator : Comparator { + + override fun compare(left: String, right: String): Int { + val (leftRelease, leftQualifier) = left.parse() + val (rightRelease, rightQualifier) = right.parse() + val byRelease = compareSegments(leftRelease, rightRelease) + if (byRelease != 0) { + return byRelease + } + return when { + leftQualifier == null && rightQualifier == null -> 0 + leftQualifier == null -> 1 + rightQualifier == null -> -1 + else -> compareSegments(leftQualifier, rightQualifier) + } + } + + /** + * Splits this version into the segments of its release part and the segments + * of its qualifier, the latter being `null` when the version has no qualifier. + */ + private fun String.parse(): Pair, List?> { + val release = substringBefore('-') + val qualifier = if ('-' in this) substringAfter('-') else null + return release.split('.') to qualifier?.split('.', '-') + } + + private fun compareSegments(left: List, right: List): Int { + for (index in 0 until maxOf(left.size, right.size)) { + val bySegment = compareSegment( + left.getOrElse(index) { "" }, + right.getOrElse(index) { "" } + ) + if (bySegment != 0) { + return bySegment + } + } + return 0 + } + + /** + * Compares single segments, ordering an absent (empty) segment below any present + * one, a numeric segment below a textual one, numbers by their value, and text + * case-insensitively. + * + * Keeping the empty, numeric, and textual segments in distinct buckets makes + * the order transitive: comparing a numeric pair as numbers, but a mixed pair + * as text, would order `2` < `10` < `1a` < `2`. + */ + private fun compareSegment(left: String, right: String): Int { + if (left.isEmpty() || right.isEmpty()) { + return left.length.compareTo(right.length) + } + val leftNumber = left.toLongOrNull() + val rightNumber = right.toLongOrNull() + return when { + leftNumber != null && rightNumber != null -> leftNumber.compareTo(rightNumber) + leftNumber != null -> -1 + rightNumber != null -> 1 + else -> left.compareTo(right, ignoreCase = true) + } + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/VersionGradleFile.kt b/buildSrc/src/main/kotlin/io/spine/gradle/VersionGradleFile.kt new file mode 100644 index 0000000000..7e86ea94ec --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/VersionGradleFile.kt @@ -0,0 +1,157 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle + +import java.io.File +import org.gradle.api.GradleException + +/** + * Reads a `version.gradle.kts` file and resolves the `extra` properties it declares. + * + * [contentUnder] and [contentInBase] read the file (from the working tree or a base branch); + * [keyForValue] and [valueForKey] resolve its `extra` properties. The following declaration + * shapes are handled (see the `bump-version` skill): + * + * 1. a literal: `val versionToPublish: String by extra("2.0.0-SNAPSHOT.182")`; + * 2. an alias to another `extra`: `val versionToPublish by extra(compilerVersion)` paired + * with `val compilerVersion: String by extra("2.0.0-SNAPSHOT.043")`; + * 3. an alias to a plain `val`: `val versionToPublish by extra(base)` paired with + * `val base = "2.0.0-SNAPSHOT.043"`. + * + * The publishing-version property is identified by [keyForValue] using the already-resolved + * project version as an oracle, so the specific property name (`versionToPublish`, + * `validationVersion`, `compilerVersion`, …) does not need to be hard-coded. + * + * Only a single alias hop is resolved; an alias to another alias falls through to `null`, + * which the caller treats as "publishing version not identified" and skips the check. + */ +internal object VersionGradleFile { + + /** + * The name of a `version.gradle.kts` file. + */ + const val NAME = "version.gradle.kts" + + private val literalExtra = + Regex("""val\s+(\w+)\s*(?::\s*String)?\s+by\s+extra\(\s*"([^"]+)"\s*\)""") + private val aliasExtra = + Regex("""val\s+(\w+)\s*(?::\s*String)?\s+by\s+extra\(\s*([A-Za-z_]\w*)\s*\)""") + private val plainAssignment = + Regex("""val\s+(\w+)\s*(?::\s*String)?\s*=\s*"([^"]+)"""") + + /** + * Resolves every named `extra` (and the plain `val`s an `extra` may alias) to its + * string value. + */ + private fun parse(content: String): Map { + val literals = literalExtra.findAll(content) + .associate { it.groupValues[1] to it.groupValues[2] } + val plains = plainAssignment.findAll(content) + .associate { it.groupValues[1] to it.groupValues[2] } + val resolved = literals.toMutableMap() + aliasExtra.findAll(content).forEach { match -> + val name = match.groupValues[1] + val source = match.groupValues[2] + if (name !in resolved) { + (literals[source] ?: plains[source])?.let { resolved[name] = it } + } + } + return resolved + } + + /** + * The name of the property whose resolved value equals [value], or `null` if none does. + */ + fun keyForValue(content: String, value: String): String? = + parse(content).entries.firstOrNull { it.value == value }?.key + + /** + * The resolved value of the property named [key], or `null` if it is absent. + */ + fun valueForKey(content: String, key: String): String? = parse(content)[key] + + /** + * Reads `version.gradle.kts` from [rootDir], or `null` when it is absent. + */ + fun contentUnder(rootDir: File): String? = + File(rootDir, NAME).takeIf { it.exists() }?.readText() + + /** + * Reads `version.gradle.kts` from the tip of the `origin/` remote-tracking branch. + * + * Returns `null` when the file does not exist at the base — a newly introduced version file. + * Throws a [GradleException] when the base ref cannot be resolved: the Version Guard workflow + * is responsible for fetching it, and failing closed surfaces that misconfiguration instead + * of silently passing the check. + */ + fun contentInBase(rootDir: File, baseRef: String): String? { + val result = gitShow(rootDir, "origin/$baseRef:$NAME") + if (result.exitCode == 0) { + return result.stdout + } + // `git show` reports a missing path with these phrasings; everything else + // (e.g. an unresolvable ref) is a configuration error we must not swallow. + val missingPath = result.stderr.contains("does not exist") || + result.stderr.contains("exists on disk, but not in") + if (missingPath) { + return null + } + throw GradleException( + "Unable to read `$NAME` from base `origin/$baseRef` " + + "(git exit code ${result.exitCode}): ${result.stderr.trim()}.\n" + + "Ensure the Version Guard workflow fetches the base branch before this check." + ) + } +} + +/** + * The outcome of a `git` invocation: its [exitCode] and the captured [stdout] and [stderr]. + * + * @property exitCode The process exit code; `0` on success. + * @property stdout The captured standard output stream. + * @property stderr The captured standard error stream. + */ +private data class GitResult(val exitCode: Int, val stdout: String, val stderr: String) + +private fun gitShow(rootDir: File, spec: String): GitResult { + // Redirect to files rather than reading the process pipes sequentially: draining + // stdout fully before stderr can deadlock if a stream fills its pipe buffer. + val outFile = File.createTempFile("git-show", ".out") + val errFile = File.createTempFile("git-show", ".err") + try { + val exitCode = ProcessBuilder("git", "show", spec) + .directory(rootDir) + .redirectOutput(outFile) + .redirectError(errFile) + .start() + .waitFor() + return GitResult(exitCode, outFile.readText(), errFile.readText()) + } finally { + outFile.delete() + errFile.delete() + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt index 92b7a38743..0e5099a77f 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.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. @@ -55,20 +55,26 @@ fun KotlinJvmProjectExtension.applyJvmToolchain(version: String) = */ @Suppress("unused") fun KotlinCommonCompilerOptions.setFreeCompilerArgs() { + val optIns = mutableListOf( + "kotlin.contracts.ExperimentalContracts", + "kotlin.ExperimentalUnsignedTypes", + "kotlin.ExperimentalStdlibApi", + "kotlin.experimental.ExperimentalTypeInference", + ) if (this is KotlinJvmCompilerOptions) { jvmDefault.set(JvmDefaultMode.NO_COMPATIBILITY) + // `kotlin.io.path` ships only in the JVM standard library, so for common + // and Native compilations this opt-in marker is unresolved and the compiler + // warns about it. Scope it to JVM compilations; multiplatform common and + // Native code cannot use the API anyway. + optIns.add("kotlin.io.path.ExperimentalPathApi") } freeCompilerArgs.addAll( listOf( "-Xskip-prerelease-check", "-Xexpect-actual-classes", "-Xcontext-parameters", - "-opt-in=" + - "kotlin.contracts.ExperimentalContracts," + - "kotlin.io.path.ExperimentalPathApi," + - "kotlin.ExperimentalUnsignedTypes," + - "kotlin.ExperimentalStdlibApi," + - "kotlin.experimental.ExperimentalTypeInference", + "-opt-in=" + optIns.joinToString(separator = ","), ) ) } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt index 4c215f14bd..16897ffd91 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/CheckVersionIncrement.kt @@ -26,12 +26,10 @@ package io.spine.gradle.publish -import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES -import com.fasterxml.jackson.dataformat.xml.XmlMapper +import io.spine.gradle.VersionComparator +import io.spine.gradle.VersionGradleFile import io.spine.gradle.repo.Repository -import java.io.FileNotFoundException import java.net.URI -import java.net.URL import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Project @@ -39,8 +37,20 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction /** - * A task that verifies that the current version of the library has not been published to the given - * Maven repository yet. + * A task that verifies the project version is fit to be published. + * + * Two independent checks run: + * + * 1. [checkIncrementedAgainstBase] — inside the dedicated `Version Guard` workflow, the + * project [version] must be strictly greater than the version declared by + * `version.gradle.kts` on the PR's base branch. This is deterministic and + * network-independent: it catches a behavior-changing PR that forgot to bump, and two + * parallel PRs that bumped to the same value, regardless of what is (or is not yet) + * published. + * 2. [checkNotPublished] — the [version] must not already exist in the target Maven + * repository, so a publication cannot overwrite an immutable artifact. + * + * The two checks are complementary; neither subsumes the other. */ open class CheckVersionIncrement : DefaultTask() { @@ -57,7 +67,111 @@ open class CheckVersionIncrement : DefaultTask() { val version: String = project.version as String @TaskAction - fun fetchAndCheck() { + fun checkVersion() { + checkIncrementedAgainstBase() + checkNotPublished() + } + + /** + * Verifies that the project [version] is strictly greater than the version declared by + * `version.gradle.kts` on the pull request's base branch. + * + * The comparison reads the base branch tip with `git show origin/:version.gradle.kts`, + * so it runs **only inside the dedicated `Version Guard` workflow** — the one context that + * fetches the base ref and signals it via the `VERSION_GUARD` environment variable (see + * [IncrementGuard.shouldCompareToBase]). Every other build skips it: a shallow CI checkout + * (e.g. the Ubuntu/Windows builds, which pull this task in via `publishToMavenLocal`) has + * no base ref to read, and local publishes are not pull requests. Those rely on + * [checkNotPublished] instead. + * + * Within the `Version Guard` workflow, failure modes are deliberately asymmetric: + * - base ref unresolvable — **fail closed** (a workflow misconfiguration must not pass + * silently); + * - `version.gradle.kts` absent on base — treated as a newly introduced file (**pass**); + * - the publishing-version property cannot be identified — **skip** with a warning, + * leaving [checkNotPublished] as the remaining guard, rather than blocking every PR in + * a repository whose `version.gradle.kts` uses an unrecognized shape. + */ + private fun checkIncrementedAgainstBase() { + val baseRef = System.getenv("GITHUB_BASE_REF") + if (!IncrementGuard.shouldCompareToBase(underVersionGuard(), baseRef)) { + logger.info( + "Skipping the base-branch increment comparison: it runs only inside the " + + "`Version Guard` workflow, which fetches the base branch. " + + "`checkNotPublished` remains the active guard here." + ) + return + } + val baseVersion = baseVersionToCompare( + checkNotNull(baseRef) { "`shouldCompareToBase` guarantees a non-blank base ref." } + ) + if (baseVersion != null && VersionComparator.compare(version, baseVersion) <= 0) { + throw GradleException( + """ + The project version `$version` is not greater than the base branch version + `$baseVersion` (base `$baseRef`). + + A pull request that merges into `$baseRef` must increment the version in + `${VersionGradleFile.NAME}`. Publishing runs on every push to the base branch, + so a non-incremented version would collide with the already-published artifact. + + Bump the version (e.g. run `/bump-version`) and push again. + + To disable this check, run Gradle with `-x $name`. + """.trimIndent() + ) + } + } + + /** + * Tells whether the build runs inside the dedicated `Version Guard` workflow. + * + * That workflow fetches the base branch before invoking this task and signals it by + * setting the `VERSION_GUARD` environment variable to `true`. The variable is the + * authoritative marker that `origin/` is present, so the base-branch comparison + * may run; see [IncrementGuard.shouldCompareToBase]. + */ + private fun underVersionGuard(): Boolean = + "true".equals(System.getenv("VERSION_GUARD")) + + /** + * Resolves the base-branch publishing version to compare [version] against, or `null` + * when the comparison does not apply. + * + * Returns `null` (skipping the check) when the publishing-version property cannot be + * identified in the working-tree `version.gradle.kts`, or when the base branch has no + * comparable value (the file is absent or newly introduced). Throws via + * [VersionGradleFile.contentInBase] when the base ref itself cannot be resolved. + */ + private fun baseVersionToCompare(baseRef: String): String? { + val headContent = VersionGradleFile.contentUnder(project.rootDir) + val key = headContent?.let { VersionGradleFile.keyForValue(it, version) } + if (key == null) { + logger.warn( + "Could not identify the publishing-version property matching `$version` in " + + "`${VersionGradleFile.NAME}`; skipping the base-branch increment check." + ) + return null + } + val baseContent = VersionGradleFile.contentInBase(project.rootDir, baseRef) + val baseVersion = baseContent?.let { VersionGradleFile.valueForKey(it, key) } + if (baseVersion == null) { + logger.info( + "No comparable `$key` in `${VersionGradleFile.NAME}` on base `$baseRef` " + + "(absent or newly introduced); skipping the base-branch increment check." + ) + } + return baseVersion + } + + /** + * Verifies that the current [version] has not been published to the target Maven + * repository yet. + * + * Both the `releases` and `snapshots` repositories are checked; artifacts in either + * may not be overwritten. + */ + private fun checkNotPublished() { val artifact = "${project.artifactPath()}/${MavenMetadata.FILE_NAME}" val snapshots = repository.target(snapshots = true) checkInRepo(snapshots, artifact) @@ -76,7 +190,7 @@ open class CheckVersionIncrement : DefaultTask() { """ The version `$version` is already published to the Maven repository `$repoUrl`. Try incrementing the library version. - All available versions are: ${versions?.joinToString(separator = ", ")}. + All available versions are: ${versions.joinToString(separator = ", ")}. To disable this check, run Gradle with `-x $name`. """.trimIndent() @@ -114,34 +228,3 @@ open class CheckVersionIncrement : DefaultTask() { return result } } - -private data class MavenMetadata(var versioning: Versioning = Versioning()) { - - companion object { - - const val FILE_NAME = "maven-metadata.xml" - - private val mapper = XmlMapper() - - init { - mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false) - } - - /** - * Fetches the metadata for the repository and parses the document. - * - *

If the document could not be found, assumes that the module was never - * released and thus has no metadata. - */ - fun fetchAndParse(url: URL): MavenMetadata? { - return try { - val metadata = mapper.readValue(url, MavenMetadata::class.java) - metadata - } catch (_: FileNotFoundException) { - null - } - } - } -} - -private data class Versioning(var versions: List = listOf()) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt index 24d6d0e2d6..ddda57e793 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/IncrementGuard.kt @@ -28,14 +28,21 @@ package io.spine.gradle.publish +import io.spine.gradle.Build import io.spine.gradle.SpineTaskGroup import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.publish.maven.tasks.PublishToMavenLocal /** - * Gradle plugin that adds a [CheckVersionIncrement] task. + * Gradle plugin that adds a [CheckVersionIncrement] task verifying that the + * project version was incremented before its artifacts are published. * - * The task is called `checkVersionIncrement` inserted before the `check` task. + * The task — named `checkVersionIncrement` — is run directly by the `Version Guard` + * CI workflow and before any `publishToMavenLocal` task. It is deliberately kept out + * of the `check` lifecycle, and actually executes only when the verification is + * meaningful; see [apply]. */ class IncrementGuard : Plugin { @@ -57,40 +64,114 @@ class IncrementGuard : Plugin { } return baseBranch.endsWith("master") || baseBranch.endsWith("main") } + + /** + * Tells whether the [CheckVersionIncrement] action must actually run for + * the current build. + * + * The increment is verified in two situations: + * 1. [ciPullRequest] — a CI pull request that must check the version + * (see [shouldCheckVersion]); or + * 2. a local build (not [onCi]) that is going to publish to Maven Local + * ([localPublish]). + * + * CI pushes and tag builds that publish to Maven Local — e.g. to feed + * integration tests — deliberately skip the check, so that re-publishing + * an already released version does not fail them. + */ + internal fun mustVerify( + ciPullRequest: Boolean, + onCi: Boolean, + localPublish: Boolean, + ): Boolean = ciPullRequest || (!onCi && localPublish) + + /** + * Tells whether [CheckVersionIncrement] should compare the project version against + * the base branch. + * + * The comparison reads `origin/:version.gradle.kts`, so it needs the base + * branch to have been fetched. Only the dedicated `Version Guard` workflow fetches it + * and sets the `VERSION_GUARD` environment variable, which [CheckVersionIncrement] + * passes here as the [underVersionGuard] flag; the workflow runs for pull requests, so + * a non-blank [baseRef] is required as well. + * + * Every other CI build (e.g. the Ubuntu and Windows builds) pulls the check into the + * task graph through `publishToMavenLocal` but runs a shallow checkout without the + * base ref. Such builds report `underVersionGuard = false`, so the comparison is + * skipped and `checkNotPublished` stays the guard — otherwise the comparison would + * fail closed on `origin/` and break every pull request. + */ + internal fun shouldCompareToBase(underVersionGuard: Boolean, baseRef: String?): Boolean = + underVersionGuard && !baseRef.isNullOrBlank() + + /** + * Tells whether [tasks] contains a Maven Local publishing task that + * belongs to the given [project]. + * + * The scan is limited to [project]'s own publications so that, in a + * multi-project build, a sibling module's `publishToMavenLocal` does not + * trigger this module's check — which would verify an unrelated version. + */ + internal fun localPublishPlanned(tasks: Iterable, project: Project): Boolean = + tasks.any { it is PublishToMavenLocal && it.project == project } } /** - * Adds the [CheckVersionIncrement] task to the project. + * Adds the [CheckVersionIncrement] task to the [target] project and makes every + * `publishToMavenLocal` task depend on it, so that a local publish — used by + * integration tests that consume artifacts from `~/.m2` — cannot overwrite an + * already published version. + * + * The CI pull-request increment check is driven separately by the `Version Guard` + * workflow (`increment-guard.yml`), which invokes `checkVersionIncrement` by name after + * fetching the base branch and setting `VERSION_GUARD` — the signal that gates + * [CheckVersionIncrement]'s strict base-branch comparison (see [shouldCompareToBase]). + * The task is intentionally not wired into the `check` lifecycle: the version check + * belongs to the publishing path, not to generic `check` runs. A build that still pulls + * the task in through `publishToMavenLocal` (e.g. the Ubuntu/Windows CI builds, which + * publish locally to feed integration tests) stays green, because the base comparison — + * which reads `origin/` and would otherwise fail closed on a shallow checkout — is + * skipped outside the `Version Guard` workflow. * - * The task is created anyway, but it is enabled only if: - * 1. The project is built on GitHub CI, and - * 2. The job is a pull request targeting a default (`master` or `main`) or - * a release-line (e.g. `2.x-jdk8-master`) branch. + * The task is always created and wired, but its action runs only when: + * 1. the build is a GitHub Actions pull request targeting a default + * (`master` or `main`) or a release-line (e.g. `2.x-jdk8-master`) branch; or + * 2. the build runs locally (outside CI) and is going to publish artifacts + * to Maven Local. * * It is the responsibility of a branch that aims to merge into a default * (or otherwise protected) branch to bump the version. Auxiliary branches do not * deal with the versions in the release cycle, so pull requests targeting them, - * direct pushes, and tag builds do not run the check. This also prevents unexpected - * CI fails when re-building `master` multiple times, creating git tags, and in other - * cases that go outside the "usual" development cycle. + * direct pushes, and tag builds do not run the check. In particular, the + * Maven Local guard is restricted to local builds: re-building `master`, + * creating git tags, and other CI jobs that publish locally (e.g. to feed + * integration tests) keep succeeding even though their version is already + * published. Ordinary local builds that do not publish stay free from the + * network-bound version check as well. */ override fun apply(target: Project) { val tasks = target.tasks - tasks.register(taskName, CheckVersionIncrement::class.java) { + val checkVersion = tasks.register(taskName, CheckVersionIncrement::class.java) { group = SpineTaskGroup.name description = "Verifies that the project version was incremented before publishing" repository = CloudArtifactRegistry.repository - tasks.getByName("check").dependsOn(this) - - if (!shouldCheckVersion()) { - logger.info( - "The build does not represent a GitHub Actions pull request job " + - "targeting a default or a release-line branch, " + - "the `checkVersionIncrement` task is disabled." - ) - this.enabled = false + onlyIf { + mustVerify(shouldCheckVersion(), Build.ci, it.publishesToMavenLocal()) } } + + // The CI pull-request increment check is run by the `Version Guard` workflow, + // which calls `checkVersionIncrement` directly after fetching the base branch. + // It is intentionally not a dependency of `check`: that would run it in every + // `./gradlew build` (e.g. the Ubuntu/Windows CI builds), where `origin/` + // is not fetched and the fail-closed base comparison would break the build. + + // Verify before publishing to Maven Local: integration tests in this and + // sibling projects consume the freshly published artifacts from `~/.m2`, so a + // non-incremented version would let them pick up a stale artifact. + tasks.withType(PublishToMavenLocal::class.java).configureEach { + dependsOn(checkVersion) + } } /** @@ -110,3 +191,20 @@ class IncrementGuard : Plugin { return shouldCheckVersion(event, baseBranch) } } + +/** + * Tells whether the current build is going to publish this task's project to + * Maven Local. + * + * Integration tests in this and sibling projects consume freshly built artifacts + * from `~/.m2`. Publishing them under a version that already exists would let those + * tests pick up a stale artifact, so the version increment must be verified before + * any local publication runs. + * + * Only this task's own project is considered: a sibling module's local publish in + * the same invocation must not trigger this module's check. The predicate is + * evaluated lazily as a task `onlyIf` spec, by which point the execution + * [task graph][org.gradle.api.execution.TaskExecutionGraph] is fully populated. + */ +private fun Task.publishesToMavenLocal(): Boolean = + IncrementGuard.localPublishPlanned(project.gradle.taskGraph.allTasks, project) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/MavenMetadata.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/MavenMetadata.kt new file mode 100644 index 0000000000..3d4906d293 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/MavenMetadata.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle.publish + +import com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES +import com.fasterxml.jackson.dataformat.xml.XmlMapper +import java.io.FileNotFoundException +import java.net.URL + +/** + * A minimal model of a Maven `maven-metadata.xml` document, exposing the published + * versions of an artifact. + * + * Instances are produced by [XmlMapper] from a registry response; only the `` + * element is mapped, and unknown elements are ignored. + * + * @property versioning The `` element holding the list of published versions. + * It is `var` with a default value purely to support deserialization: `buildSrc` uses a + * plain [XmlMapper] without the Kotlin module, so Jackson instantiates this class through + * the synthesized no-arg constructor and then assigns the property through its setter. The + * mutability is required by Jackson, not used by our own code; a `val` would silently + * leave the version list empty. + */ +internal data class MavenMetadata(var versioning: Versioning = Versioning()) { + + companion object { + + const val FILE_NAME = "maven-metadata.xml" + + private val mapper = XmlMapper() + + init { + mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false) + } + + /** + * Fetches the metadata for the repository and parses the document. + * + * If the document could not be found, assumes that the module was never + * released and thus has no metadata. + */ + fun fetchAndParse(url: URL): MavenMetadata? { + return try { + val metadata = mapper.readValue(url, MavenMetadata::class.java) + metadata + } catch (_: FileNotFoundException) { + null + } + } + } +} + +/** + * The `` element of a `maven-metadata.xml` document, listing the published versions. + * + * @property versions The published version strings. It is `var` for the same reason as + * [MavenMetadata.versioning]: Jackson assigns it through the setter during deserialization + * (`buildSrc` has no Kotlin module), so a `val` would leave it empty. + */ +internal data class Versioning(var versions: List = listOf()) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt index 800f2a38f8..8877dc615e 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt @@ -99,11 +99,6 @@ val RepositoryHandler.intellijReleases: MavenArtifactRepository includeIntelliJPlatformOnly() } -val RepositoryHandler.jetBrainsCacheRedirector: MavenArtifactRepository - get() = maven("https://cache-redirector.jetbrains.com/intellij-dependencies") { - includeIntelliJPlatformOnly() - } - val RepositoryHandler.intellijDependencies: MavenArtifactRepository get() = maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") { includeIntelliJPlatformOnly() @@ -118,7 +113,7 @@ fun RepositoryHandler.standardToSpineSdk() { // the first repository that can serve an artifact, so keeping these ahead of // the special-purpose ones means coordinates shared with them (such as // `org.jetbrains:annotations`) resolve here and never reach a less reliable - // mirror like `cache-redirector.jetbrains.com`. + // JetBrains mirror. // // `io.spine.*` modules are served only by the Spine repositories below, so // they are excluded here. Otherwise Gradle would query Central / the Plugin @@ -147,11 +142,13 @@ fun RepositoryHandler.standardToSpineSdk() { } } - // IntelliJ Platform repositories. Each is restricted to the IntelliJ - // coordinates it serves (see `includeIntelliJPlatformOnly`), so a transient - // 5xx from one of them cannot break the resolution of unrelated artifacts. + // IntelliJ Platform repositories. `intellijReleases` serves the platform + // artifacts (`com.jetbrains.intellij.*`); `intellijDependencies` serves the + // repackaged third-party dependencies (`org.jetbrains.intellij.deps.*` and + // JetBrains-internal builds). Each is restricted to the coordinates it serves + // (see `includeIntelliJPlatformOnly`), so a transient 5xx from one of them + // cannot break the resolution of unrelated artifacts. intellijReleases - jetBrainsCacheRedirector intellijDependencies maven { @@ -219,12 +216,11 @@ private fun ArtifactRepository.excludeSpine() { * Restricts a JetBrains/IntelliJ Platform repository to the coordinates it * actually serves. * - * These hosts — `cache-redirector.jetbrains.com` in particular — periodically - * answer with HTTP 5xx. Once Gradle sees such an error, it disables the - * repository for the rest of the build and fails the resolution instead of - * falling back to another repository. Without this filter the redirector is - * queried for every artifact, so a single 502 on an unrelated POM (such as - * `com.fasterxml.jackson:jackson-parent`) breaks the whole build. + * These hosts periodically answer with HTTP 5xx. Once Gradle sees such an error, + * it disables the repository for the rest of the build and fails the resolution + * instead of falling back to another repository. Without this filter such a + * repository is queried for every artifact, so a single 502 on an unrelated POM + * (such as `com.fasterxml.jackson:jackson-parent`) would break the whole build. */ private fun MavenArtifactRepository.includeIntelliJPlatformOnly() { content { diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt index db2bf761ae..64e46ef75b 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt @@ -27,6 +27,7 @@ package io.spine.gradle.report.pom import groovy.xml.MarkupBuilder +import io.spine.gradle.VersionComparator import java.io.Writer import java.util.* import kotlin.reflect.full.isSubclassOf diff --git a/buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts b/buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts deleted file mode 100644 index 7334ef97f0..0000000000 --- a/buildSrc/src/main/kotlin/jacoco-kmm-jvm.gradle.kts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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. - */ - -import java.io.File -import org.gradle.kotlin.dsl.getValue -import org.gradle.kotlin.dsl.getting -import org.gradle.kotlin.dsl.jacoco -import org.gradle.testing.jacoco.tasks.JacocoReport - -// DEPRECATED: this script plugin distributes vanilla JaCoCo. -// New code should apply `kmp-module`, which configures Kover via -// `useJacoco(version = Jacoco.version)` and writes JaCoCo-format XML at -// `build/reports/kover/report.xml`. (Same task and path as Kotlin-JVM — -// `kmp-module` configures only Kover's `total` report, so no -// `koverXmlReport` task is generated.) The `raise-coverage` skill -// migrates existing consumers automatically. Kept so older consumer repos -// continue to build; will be removed in a future release. -// See: .agents/skills/raise-coverage/references/migrate-to-kover.md - -plugins { - jacoco -} - -logger.warn( - "'jacoco-kmm-jvm' is deprecated; use 'kmp-module' which applies Kover. " + - "See .agents/skills/raise-coverage/references/migrate-to-kover.md." -) - -/** - * Configures [JacocoReport] task to run in a Kotlin KMM project for `commonMain` and `jvmMain` - * source sets. - * - * This script plugin must be applied using the following construct at the end of - * a `build.gradle.kts` file of a module: - * - * ```kotlin - * apply(plugin="jacoco-kmm-jvm") - * ``` - * Please do not apply this script plugin in the `plugins {}` block because `jacocoTestReport` - * task is not yet available at this stage. - */ -@Suppress("unused") -private val about = "" - -/** - * Configure the Jacoco task with custom input a KMM project - * to which this convention plugin is applied. - */ -@Suppress("unused") -val jacocoTestReport: JacocoReport by tasks.getting(JacocoReport::class) { - val buildDir = project.layout.buildDirectory.get().asFile.absolutePath - val classFiles = File("${buildDir}/classes/kotlin/jvm/") - .walkBottomUp() - .toSet() - classDirectories.setFrom(classFiles) - - val coverageSourceDirs = arrayOf( - "src/commonMain", - "src/jvmMain" - ) - sourceDirectories.setFrom(files(coverageSourceDirs)) - - executionData.setFrom(files("${buildDir}/jacoco/jvmTest.exec")) -} diff --git a/buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts b/buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts deleted file mode 100644 index 185c9cdfdd..0000000000 --- a/buildSrc/src/main/kotlin/jacoco-kotlin-jvm.gradle.kts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - */ - -import io.spine.gradle.buildDirectory - -// DEPRECATED: this script plugin distributes vanilla JaCoCo. -// New code should apply `jvm-module`, which configures Kover via -// `useJacoco(version = Jacoco.version)` and writes JaCoCo-format XML at -// `build/reports/kover/report.xml`. The `raise-coverage` skill migrates -// existing consumers automatically. Kept so older consumer repos continue to -// build; will be removed in a future release. -// See: .agents/skills/raise-coverage/references/migrate-to-kover.md - -plugins { - jacoco -} - -logger.warn( - "'jacoco-kotlin-jvm' is deprecated; use 'jvm-module' which applies Kover. " + - "See .agents/skills/raise-coverage/references/migrate-to-kover.md." -) - -/** - * Configures [JacocoReport] task to run in a Kotlin Multiplatform project for - * `commonMain` and `jvmMain` source sets. - * - * This script plugin must be applied using the following construct at the end of - * a `build.gradle.kts` file of a module: - * - * ```kotlin - * apply(plugin="jacoco-kotlin-jvm") - * ``` - * Please do not apply this script plugin in the `plugins {}` block because `jacocoTestReport` - * task is not yet available at this stage. - */ -@Suppress("unused") -private val about = "" - -/** - * Configure Jacoco task with custom input from this Kotlin Multiplatform project. - */ -@Suppress("unused") -val jacocoTestReport: JacocoReport by tasks.getting(JacocoReport::class) { - - val classFiles = File("$buildDirectory/classes/kotlin/jvm/") - .walkBottomUp() - .toSet() - classDirectories.setFrom(classFiles) - - val coverageSourceDirs = arrayOf( - "src/commonMain", - "src/jvmMain" - ) - sourceDirectories.setFrom(files(coverageSourceDirs)) - - executionData.setFrom(files("$buildDirectory/jacoco/jvmTest.exec")) -} diff --git a/buildSrc/src/main/kotlin/jvm-module.gradle.kts b/buildSrc/src/main/kotlin/jvm-module.gradle.kts index a7b3113092..8e777c6aca 100644 --- a/buildSrc/src/main/kotlin/jvm-module.gradle.kts +++ b/buildSrc/src/main/kotlin/jvm-module.gradle.kts @@ -29,6 +29,7 @@ import io.spine.dependency.build.CheckerFramework import io.spine.dependency.build.Dokka import io.spine.dependency.build.ErrorProne import io.spine.dependency.build.JSpecify +import io.spine.dependency.isDokka import io.spine.dependency.lib.Guava import io.spine.dependency.lib.Jackson import io.spine.dependency.lib.Kotlin @@ -132,6 +133,9 @@ fun Module.forceConfigurations() { forceVersions() excludeProtobufLite() all { + if (isDokka) { + return@all + } resolutionStrategy { val cfg = this@all val rs = this@resolutionStrategy diff --git a/buildSrc/src/main/kotlin/kmp-module.gradle.kts b/buildSrc/src/main/kotlin/kmp-module.gradle.kts index a2e6d82e58..636a84d22f 100644 --- a/buildSrc/src/main/kotlin/kmp-module.gradle.kts +++ b/buildSrc/src/main/kotlin/kmp-module.gradle.kts @@ -25,6 +25,7 @@ */ import io.spine.dependency.boms.BomsPlugin +import io.spine.dependency.isDokka import io.spine.dependency.lib.Jackson import io.spine.dependency.lib.Kotlin import io.spine.dependency.local.Reflect @@ -83,6 +84,9 @@ fun Project.forceConfigurations() { with(configurations) { forceVersions() all { + if (isDokka) { + return@all + } resolutionStrategy { val cfg = this@all val rs = this@resolutionStrategy diff --git a/buildSrc/src/main/kotlin/module.gradle.kts b/buildSrc/src/main/kotlin/module.gradle.kts index e0280fba4b..bc077f0644 100644 --- a/buildSrc/src/main/kotlin/module.gradle.kts +++ b/buildSrc/src/main/kotlin/module.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2025, TeamDev. All rights reserved. + * Copyright 2026, TeamDev. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,7 +141,7 @@ fun Module.forceConfigurations() { fun Module.setTaskDependencies(generatedDir: String) { tasks { - val cleanGenerated by registering(Delete::class) { + val cleanGenerated = register("cleanGenerated") { delete(generatedDir) } clean.configure { diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/VersionComparatorSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/VersionComparatorSpec.kt new file mode 100644 index 0000000000..c643645baa --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/VersionComparatorSpec.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle + +import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.ints.shouldBeLessThan +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("`VersionComparator` should") +internal class VersionComparatorSpec { + + /** + * Asserts that [newer] compares above [older], checking both directions. + */ + private fun assertNewer(newer: String, older: String) { + VersionComparator.compare(newer, older) shouldBeGreaterThan 0 + VersionComparator.compare(older, newer) shouldBeLessThan 0 + } + + @Test + fun `compare numeric segments as numbers`() { + assertNewer("10.0.0", "9.2.0") + assertNewer("2.10.0", "2.9.1") + assertNewer("1.0.10", "1.0.9") + } + + @Test + fun `compare numeric qualifier segments as numbers`() { + assertNewer("2.0.0-SNAPSHOT.100", "2.0.0-SNAPSHOT.99") + assertNewer("2.0.0-SNAPSHOT.100", "2.0.0-SNAPSHOT.070") + } + + @Test + fun `treat a release as newer than its pre-release`() { + assertNewer("2.0.0", "2.0.0-SNAPSHOT.100") + assertNewer("1.0.0", "1.0.0-RC.2") + } + + @Test + fun `treat a longer version as newer when the common segments are equal`() { + assertNewer("1.0.1", "1.0") + assertNewer("1.0.0-RC.1", "1.0.0-RC") + } + + @Test + fun `ignore the case of textual segments`() { + assertNewer("1.0.0-snapshot.10", "1.0.0-SNAPSHOT.2") + VersionComparator.compare("1.0.0-RC", "1.0.0-rc") shouldBe 0 + } + + @Test + fun `order a numeric segment before a textual one`() { + assertNewer("1.0.0-alpha", "1.0.0-1") + } + + @Test + fun `treat equal versions as equal`() { + VersionComparator.compare("2.0.0-SNAPSHOT.070", "2.0.0-SNAPSHOT.070") shouldBe 0 + VersionComparator.compare("31.1-jre", "31.1-jre") shouldBe 0 + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/VersionGradleFileSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/VersionGradleFileSpec.kt new file mode 100644 index 0000000000..e76febe211 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/VersionGradleFileSpec.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("`VersionGradleFile` should read the publishing version") +internal class VersionGradleFileSpec { + + @Test + fun `declared as a literal`() { + val content = """ + val versionToPublish: String by extra("2.0.0-SNAPSHOT.182") + """.trimIndent() + + VersionGradleFile.keyForValue(content, "2.0.0-SNAPSHOT.182") shouldBe "versionToPublish" + VersionGradleFile.valueForKey(content, "versionToPublish") shouldBe "2.0.0-SNAPSHOT.182" + } + + @Test + fun `declared as an alias to another 'extra'`() { + val content = """ + val compilerVersion: String by extra("2.0.0-SNAPSHOT.043") + val versionToPublish by extra(compilerVersion) + """.trimIndent() + + VersionGradleFile.valueForKey(content, "versionToPublish") shouldBe "2.0.0-SNAPSHOT.043" + VersionGradleFile.valueForKey(content, "compilerVersion") shouldBe "2.0.0-SNAPSHOT.043" + } + + @Test + fun `declared as an alias to a plain 'val'`() { + val content = """ + val base = "2.0.0-SNAPSHOT.043" + val versionToPublish by extra(base) + """.trimIndent() + + VersionGradleFile.valueForKey(content, "versionToPublish") shouldBe "2.0.0-SNAPSHOT.043" + } + + @Test + fun `identified by the resolved project version, not a hard-coded name`() { + val content = """ + val kotlinVersion: String by extra("2.1.0") + val versionToPublish: String by extra("2.0.0-SNAPSHOT.182") + """.trimIndent() + + VersionGradleFile.keyForValue(content, "2.0.0-SNAPSHOT.182") shouldBe "versionToPublish" + } + + @Test + fun `absent when no property matches`() { + val content = """ + val versionToPublish: String by extra("2.0.0-SNAPSHOT.182") + """.trimIndent() + + VersionGradleFile.keyForValue(content, "9.9.9") shouldBe null + VersionGradleFile.valueForKey(content, "missing") shouldBe null + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt index 5633c30d4d..7b317c94e9 100644 --- a/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt +++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt @@ -26,8 +26,17 @@ package io.spine.gradle.publish +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldNotContain import io.kotest.matchers.shouldBe +import io.spine.gradle.publish.IncrementGuard.Companion.localPublishPlanned +import io.spine.gradle.publish.IncrementGuard.Companion.mustVerify import io.spine.gradle.publish.IncrementGuard.Companion.shouldCheckVersion +import io.spine.gradle.publish.IncrementGuard.Companion.shouldCompareToBase +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.publish.maven.tasks.PublishToMavenLocal +import org.gradle.testfixtures.ProjectBuilder import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -76,4 +85,138 @@ class IncrementGuardTest { shouldCheckVersion(null, null) shouldBe false } } + + @Nested + inner class `actually run the check` { + + @Test + fun `on a CI pull request to a protected branch`() { + mustVerify(ciPullRequest = true, onCi = true, localPublish = false) shouldBe true + } + + @Test + fun `on a local build that publishes to Maven Local`() { + mustVerify(ciPullRequest = false, onCi = false, localPublish = true) shouldBe true + } + } + + @Nested + inner class `skip the check` { + + @Test + fun `on a local build that does not publish`() { + mustVerify(ciPullRequest = false, onCi = false, localPublish = false) shouldBe false + } + + @Test + fun `on a CI build that publishes to Maven Local outside a protected-branch PR`() { + // E.g. a push to `master` or a tag build running integration tests: the + // version is already published, so re-verifying it would fail the build. + mustVerify(ciPullRequest = false, onCi = true, localPublish = true) shouldBe false + } + } + + @Nested + inner class `compare against the base branch` { + + @Test + fun `inside the Version Guard workflow with a base branch`() { + shouldCompareToBase(underVersionGuard = true, baseRef = "master") shouldBe true + shouldCompareToBase(underVersionGuard = true, baseRef = "2.x-jdk8-master") shouldBe true + } + } + + @Nested + inner class `not compare against the base branch` { + + @Test + fun `outside the Version Guard workflow`() { + // The Ubuntu/Windows CI builds pull the task in via `publishToMavenLocal`, + // but they never fetch the base ref, so `VERSION_GUARD` is unset. + shouldCompareToBase(underVersionGuard = false, baseRef = "master") shouldBe false + } + + @Test + fun `when no base branch is present`() { + shouldCompareToBase(underVersionGuard = true, baseRef = null) shouldBe false + shouldCompareToBase(underVersionGuard = true, baseRef = "") shouldBe false + } + } + + @Nested + inner class `detect a Maven Local publish` { + + @Test + fun `for the task's own project`() { + val project = guardedProject() + val publish = project.tasks + .register("publishFooPublicationToMavenLocal", PublishToMavenLocal::class.java) + .get() + + localPublishPlanned(listOf(publish), project) shouldBe true + } + + @Test + fun `but not when only a sibling project publishes`() { + val root = ProjectBuilder.builder().build() + val lib = ProjectBuilder.builder().withParent(root).withName("lib").build() + val app = ProjectBuilder.builder().withParent(root).withName("app").build() + app.pluginManager.apply("maven-publish") + val appPublish = app.tasks + .register("publishFooPublicationToMavenLocal", PublishToMavenLocal::class.java) + .get() + + localPublishPlanned(listOf(appPublish), lib) shouldBe false + } + } + + @Nested + inner class `make 'checkVersionIncrement' a dependency of` { + + @Test + fun `every Maven Local publishing task`() { + val project = guardedProject() + val localPublish = project.tasks.register( + "publishFooPublicationToMavenLocal", + PublishToMavenLocal::class.java + ).get() + + localPublish.dependencyNames() shouldContain IncrementGuard.taskName + } + } + + @Nested + inner class `keep 'checkVersionIncrement' out of` { + + @Test + fun `the 'check' lifecycle task`() { + // The CI check runs via the `Version Guard` workflow, which fetches the + // base branch first. Wiring it into `check` would run it in every + // `./gradlew build`, where `origin/` is absent and the fail-closed + // base comparison would break the build. + val project = guardedProject() + val check = project.tasks.getByName("check") + + check.dependencyNames() shouldNotContain IncrementGuard.taskName + } + } } + +/** + * Creates a project with the `base` plugin (for the `check` task), the + * `maven-publish` plugin (for [PublishToMavenLocal] tasks), and [IncrementGuard] + * applied. + */ +private fun guardedProject(): Project { + val project = ProjectBuilder.builder().build() + project.pluginManager.apply("base") + project.pluginManager.apply("maven-publish") + project.pluginManager.apply(IncrementGuard::class.java) + return project +} + +/** + * Obtains the names of the tasks this task directly depends on. + */ +private fun Task.dependencyNames(): Set = + taskDependencies.getDependencies(this).map { it.name }.toSet() diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/MavenMetadataSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/MavenMetadataSpec.kt new file mode 100644 index 0000000000..39f8a510fe --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/MavenMetadataSpec.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2026, TeamDev. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package io.spine.gradle.publish + +import com.fasterxml.jackson.dataformat.xml.XmlMapper +import io.kotest.matchers.collections.shouldContainExactly +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("`MavenMetadata` should") +internal class MavenMetadataSpec { + + /** + * Round-trips through the same [XmlMapper] used in production, asserting the version list + * survives. This guards the `var` properties of [MavenMetadata] and [Versioning]: a `val` + * (or `internal`-mangled setter) would leave the list empty after deserialization, silently + * disabling the "already published" check. + */ + @Test + fun `survive a Jackson round-trip, keeping its versions`() { + val versions = listOf("2.0.0-SNAPSHOT.79", "2.0.0-SNAPSHOT.80", "2.0.0-SNAPSHOT.81") + val mapper = XmlMapper() + + val xml = mapper.writeValueAsString(MavenMetadata(Versioning(versions))) + val parsed = mapper.readValue(xml, MavenMetadata::class.java) + + parsed.versioning.versions shouldContainExactly versions + } +} diff --git a/config b/config index d93220ab6d..3f13608b3b 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit d93220ab6d1e3bc97c333894596bac8b9bc9e898 +Subproject commit 3f13608b3bb1a8bc736fbb748b95ec3dc11843a2 diff --git a/docs/dependencies/dependencies.md b/docs/dependencies/dependencies.md index fd604e743e..91bc369b57 100644 --- a/docs/dependencies/dependencies.md +++ b/docs/dependencies/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-annotations:2.0.0-SNAPSHOT.421` +# Dependencies of `io.spine:spine-annotations:2.0.0-SNAPSHOT.422` ## Runtime 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.1.0. @@ -90,6 +90,10 @@ * **Project URL:** [https://github.com/google/gson](https://github.com/google/gson) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.code.gson. **Name** : gson. **Version** : 2.8.9. + * **Project URL:** [https://github.com/google/gson/gson](https://github.com/google/gson/gson) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.errorprone. **Name** : error_prone_annotation. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_annotation](https://errorprone.info/error_prone_annotation) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -98,6 +102,10 @@ * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.errorprone. **Name** : error_prone_annotations. **Version** : 2.47.0. + * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) + * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.errorprone. **Name** : error_prone_check_api. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_check_api](https://errorprone.info/error_prone_check_api) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -425,6 +433,10 @@ * **Project URL:** [https://jcommander.org](https://jcommander.org) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 23.0.0. + * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.1.0. * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -637,11 +649,11 @@ * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -760,14 +772,14 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using +This report was generated on **Mon Jun 29 20:03:32 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-base:2.0.0-SNAPSHOT.421` +# Dependencies of `io.spine:spine-base:2.0.0-SNAPSHOT.422` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -839,7 +851,7 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0. +1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. * **Project URL:** [https://github.com/FasterXML/jackson-bom](https://github.com/FasterXML/jackson-bom) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -905,6 +917,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/google/gson](https://github.com/google/gson) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.code.gson. **Name** : gson. **Version** : 2.8.9. + * **Project URL:** [https://github.com/google/gson/gson](https://github.com/google/gson/gson) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-api. **Version** : 2.3.9. * **Project URL:** [https://goo.gle/ksp](https://goo.gle/ksp) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -917,6 +933,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.errorprone. **Name** : error_prone_annotations. **Version** : 2.47.0. + * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) + * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.errorprone. **Name** : error_prone_check_api. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_check_api](https://errorprone.info/error_prone_check_api) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1212,13 +1232,13 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using 1. **Group** : org.apache.httpcomponents.core5. **Name** : httpcore5-h2. **Version** : 5.1.3. * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.logging.log4j. **Name** : log4j-api. **Version** : 2.20.0. - * **Project URL:** [https://www.apache.org/](https://www.apache.org/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.apache.logging.log4j. **Name** : log4j-api. **Version** : 2.26.0. + * **Project URL:** [https://logging.apache.org/log4j/2.x/](https://logging.apache.org/log4j/2.x/) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.apache.logging.log4j. **Name** : log4j-core. **Version** : 2.20.0. - * **Project URL:** [https://www.apache.org/](https://www.apache.org/) - * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.apache.logging.log4j. **Name** : log4j-core. **Version** : 2.26.0. + * **Project URL:** [https://logging.apache.org/log4j/2.x/](https://logging.apache.org/log4j/2.x/) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group** : org.apiguardian. **Name** : apiguardian-api. **Version** : 1.1.2. * **Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) @@ -1269,6 +1289,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://jcommander.org](https://jcommander.org) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 23.0.0. + * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.1.0. * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1425,6 +1449,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 1.8.20. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk7. **Version** : 2.0.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1433,6 +1461,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 1.8.20. + * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) + * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains.kotlin. **Name** : kotlin-stdlib-jdk8. **Version** : 2.0.21. * **Project URL:** [https://kotlinlang.org/](https://kotlinlang.org/) * **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1457,10 +1489,18 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-bom. **Version** : 1.7.3. + * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core. **Version** : 1.7.3. + * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core-jvm. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1469,6 +1509,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-core-jvm. **Version** : 1.7.3. + * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-coroutines-jdk8. **Version** : 1.10.2. * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1481,11 +1525,11 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1604,14 +1648,14 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using +This report was generated on **Mon Jun 29 20:03:32 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-environment:2.0.0-SNAPSHOT.421` +# Dependencies of `io.spine:spine-environment:2.0.0-SNAPSHOT.422` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -1681,11 +1725,11 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1760,6 +1804,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/google/gson](https://github.com/google/gson) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.code.gson. **Name** : gson. **Version** : 2.8.9. + * **Project URL:** [https://github.com/google/gson/gson](https://github.com/google/gson/gson) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.errorprone. **Name** : error_prone_annotation. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_annotation](https://errorprone.info/error_prone_annotation) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -1768,6 +1816,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.errorprone. **Name** : error_prone_annotations. **Version** : 2.47.0. + * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) + * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.errorprone. **Name** : error_prone_check_api. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_check_api](https://errorprone.info/error_prone_check_api) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -2095,6 +2147,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://jcommander.org](https://jcommander.org) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 23.0.0. + * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.1.0. * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -2307,11 +2363,11 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -2430,14 +2486,14 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using +This report was generated on **Mon Jun 29 20:03:32 WEST 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-format:2.0.0-SNAPSHOT.421` +# Dependencies of `io.spine:spine-format:2.0.0-SNAPSHOT.422` ## Runtime 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.0. @@ -2662,6 +2718,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/google/gson](https://github.com/google/gson) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.code.gson. **Name** : gson. **Version** : 2.8.9. + * **Project URL:** [https://github.com/google/gson/gson](https://github.com/google/gson/gson) + * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.errorprone. **Name** : error_prone_annotation. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_annotation](https://errorprone.info/error_prone_annotation) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -2670,6 +2730,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : com.google.errorprone. **Name** : error_prone_annotations. **Version** : 2.47.0. + * **Project URL:** [https://errorprone.info/error_prone_annotations](https://errorprone.info/error_prone_annotations) + * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : com.google.errorprone. **Name** : error_prone_check_api. **Version** : 2.36.0. * **Project URL:** [https://errorprone.info/error_prone_check_api](https://errorprone.info/error_prone_check_api) * **License:** [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -2997,6 +3061,10 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://jcommander.org](https://jcommander.org) * **License:** [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 23.0.0. + * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) + * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) + 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.1.0. * **Project URL:** [https://github.com/JetBrains/java-annotations](https://github.com/JetBrains/java-annotations) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -3209,11 +3277,11 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using * **Project URL:** [https://github.com/Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.7.1. +1. **Group** : org.jetbrains.kotlinx. **Name** : kotlinx-datetime-jvm. **Version** : 0.8.0. * **Project URL:** [https://github.com/Kotlin/kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) * **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -3336,6 +3404,6 @@ This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Tue Jun 23 00:11:07 WEST 2026** using +This report was generated on **Mon Jun 29 20:03:32 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 8179771bf9..47208a1141 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 base-libraries -2.0.0-SNAPSHOT.421 +2.0.0-SNAPSHOT.422 2015 @@ -81,7 +81,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-logging - 2.0.0-SNAPSHOT.417 + 2.0.0-SNAPSHOT.419 compile @@ -164,7 +164,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-logging-smoke-test - 2.0.0-SNAPSHOT.417 + 2.0.0-SNAPSHOT.419 test @@ -176,7 +176,7 @@ all modules and does not describe the project structure per-subproject. io.spine.tools logging-testlib - 2.0.0-SNAPSHOT.417 + 2.0.0-SNAPSHOT.419 test diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df6a6ad763..a9db11550c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/gradlew b/gradlew index b9bb139f79..249efbb032 100755 --- a/gradlew +++ b/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: diff --git a/gradlew.bat b/gradlew.bat index 24c62d56f2..a51ec4f588 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -19,7 +19,7 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## @@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2 -@rem Execute Gradle +@rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel diff --git a/init-submodules b/init-submodules index d2914ac6b9..2ae143d8ea 100755 --- a/init-submodules +++ b/init-submodules @@ -2,8 +2,8 @@ ################################################################################ # -# Materialize the submodules a fresh working tree is missing, so agent assets -# resolve. +# Materialize the *config-managed* submodules a fresh working tree is missing, so +# agent assets resolve. # # `git worktree add` — and some shallow CI / cloud checkouts — populate only the # superproject's own tracked files; registered submodules are left UNinitialized. @@ -17,12 +17,28 @@ # (distributed by `config`), so `git worktree add` always checks it out — it can # therefore bring `config` itself into existence. # -# It initializes ONLY submodules that are not yet checked out (those -# `git submodule status` marks with a leading `-`), at the commit the branch -# pins. Submodules already present are left exactly as they are, so a tree that -# floated `config` / `.agents/shared` to a branch tip via `./config/pull` is -# never silently rewound to the pin. That makes the script idempotent and safe to -# run on every session start. +# It initializes ONLY submodules that are BOTH: +# +# * not yet checked out — those `git submodule status` marks with a leading `-`, +# at the commit the branch pins; and +# +# * config-managed — `config` itself (the bootstrap target `pull` lives inside, +# which carries no tracked `branch` in a consumer's `.gitmodules`), plus every +# submodule that declares a tracked `branch` in `.gitmodules`. This is exactly +# the rule `./config/pull` uses to decide what it floats, so the two scripts +# can never disagree about what is shared. +# +# Consumer-owned submodules (a Hugo theme, a vendored library, documentation +# examples, ...) declare no tracked branch and are deliberately left untouched. +# Because a `SessionStart` hook runs this script automatically on every session, +# initializing them would mean trying to clone — or failing on credentials for — +# a submodule this project does not manage, on every single start. They are +# skipped (noted on stderr). +# +# Submodules already present are left exactly as they are, so a tree that floated +# `config` / `.agents/shared` to a branch tip via `./config/pull` is never +# silently rewound to the pin. That makes the script idempotent and safe to run on +# every session start. # # It does NOT float submodules to their branch tips — run `./config/pull` # afterwards for that. Unlike `pull`, it depends on no pre-existing `config` @@ -39,14 +55,45 @@ cd "$root" || exit 0 # Nothing to do in a repo without submodules. [ -f .gitmodules ] || exit 0 +# The set of config-managed submodule paths: `config` itself (handled specially — +# it carries no tracked branch, exactly as in `./config/pull`), plus every +# submodule declaring a tracked `branch` in `.gitmodules`. Mirrors `pull`'s rule. +config_managed_paths() { + printf '%s\n' 'config' + git config -f .gitmodules --get-regexp '^submodule\..*\.branch$' 2>/dev/null \ + | while read -r key _branch; do + name=${key#submodule.}; name=${name%.branch} + git config -f .gitmodules --get "submodule.$name.path" 2>/dev/null + done +} + +managed=$(config_managed_paths | sort -u) + # `git submodule status` prefixes each uninitialized submodule with `-`; an -# initialized one starts with a space (at the pinned commit) or `+` (ahead of -# it). Act only on the `-` lines, taking the path from the second field. +# initialized one starts with a space (at the pinned commit) or `+` (ahead of it). +# Act only on the `-` lines, taking the path from the second field, and only when +# that path is config-managed. git submodule status 2>/dev/null | awk '$1 ~ /^-/ { print $2 }' | while read -r path; do [ -n "$path" ] || continue - echo "init-submodules: initializing '$path'" - git submodule update --init --recursive -- "$path" \ - || echo "init-submodules: WARNING — could not initialize '$path' (offline?)." >&2 + if printf '%s\n' "$managed" | grep -qxF -- "$path"; then + echo "init-submodules: initializing '$path'" + git submodule update --init --recursive -- "$path" \ + || echo "init-submodules: WARNING — could not initialize '$path' (offline?)." >&2 + else + echo "init-submodules: skipping consumer-owned '$path' (not config-managed)." >&2 + 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 diff --git a/version.gradle.kts b/version.gradle.kts index 3254c54509..42a0dc25b4 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.421") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.422")