diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 6755ff910e..cf02c5d64f 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -8,16 +8,23 @@ on: - 'docs/**' - 'site/**' - 'lychee.toml' + - '.lycheeignore' - '.github/workflows/check-links.yml' workflow_dispatch: env: - HUGO_VERSION: 0.161.1 + HUGO_VERSION: 0.169.0 LYCHEE_RELEASE: "lychee-x86_64-unknown-linux-gnu.tar.gz" LYCHEE_VERSION_TAG: "lychee-v0.24.2" # SHA256 of the above tarball, pinned at download time. Update alongside # LYCHEE_VERSION_TAG whenever the binary is upgraded. LYCHEE_SHA256: "1f4e0ef7f6554a6ed33dd7ac144fb2e1bbed98598e7af973042fc5cd43951c9a" + # Pinned to the release the Spine sites were checked with. Newer releases + # add Sass deprecations, which nobody has checked against those sites yet. + DART_SASS_VERSION: 1.99.0 + # SHA256 of `dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz`, from the GitHub release. + # Update alongside DART_SASS_VERSION. + DART_SASS_SHA256: "f0a32ab99bc0a1eb62ea7140a056a1bca82d534390b7c3dba4f2016eb0077753" # Force Hugo to write its module cache where the cache step actually # restores from. Hugo's default on Linux is `~/.cache/hugo_cache` # (or `$TMPDIR/hugo_cache_$USER`), neither of which matches the @@ -38,6 +45,11 @@ jobs: # Detect the Hugo site root (`docs/` or `site/`) by looking for a Hugo # config file. Hugo config may live directly in the site root or in a # `config/` or `config/_default/` subdirectory (both layouts are valid). + # A repo may also keep its only Hugo config in the `_preview/` sub-tree + # (e.g. `docs/_preview/hugo.toml`), so the same paths count there too. + # Hugo finds such a config only when it runs in `$dir/_preview`, so it + # needs `$dir/_preview/package-lock.json`: the `$dir` fallback below + # is for site-root configs only. # Outputs `present=true|false` and `work_dir` (the directory where # `npm ci` / `hugo` commands should run — either `$dir/_preview` for # repos that use a separate preview sub-tree, or `$dir` for repos whose @@ -51,19 +63,22 @@ jobs: for dir in docs site; do for cfg in hugo.toml hugo.yaml \ config/hugo.toml config/hugo.yaml \ - config/_default/hugo.toml config/_default/hugo.yaml; do + config/_default/hugo.toml config/_default/hugo.yaml \ + _preview/hugo.toml _preview/hugo.yaml \ + _preview/config/hugo.toml _preview/config/hugo.yaml \ + _preview/config/_default/hugo.toml _preview/config/_default/hugo.yaml; do if [ -f "$dir/$cfg" ]; then if [ -f "$dir/_preview/package-lock.json" ]; then echo "work_dir=$dir/_preview" >> "$GITHUB_OUTPUT" echo "present=true" >> "$GITHUB_OUTPUT" echo "::notice::Hugo site found under $dir/ (work_dir: $dir/_preview)" - elif [ -f "$dir/package-lock.json" ]; then + elif [ -f "$dir/package-lock.json" ] && [[ $cfg != _preview/* ]]; then echo "work_dir=$dir" >> "$GITHUB_OUTPUT" echo "present=true" >> "$GITHUB_OUTPUT" echo "::notice::Hugo site found under $dir/ (work_dir: $dir)" else echo "present=false" >> "$GITHUB_OUTPUT" - echo "::notice::Hugo config found in $dir/ but no package-lock.json found — skipping link check." + echo "::notice::Found $dir/$cfg but no package-lock.json to build it with — skipping link check." fi exit 0 fi @@ -79,13 +94,38 @@ jobs: hugo-version: ${{ env.HUGO_VERSION }} extended: true - # `actions/setup-node@v4` ships with built-in npm caching that hashes + # The Spine Hugo theme compiles its SCSS with Dart Sass, because Hugo + # deprecated LibSass in v0.153.0 and will remove it. Hugo does not + # bundle Dart Sass: it runs the `sass` binary it finds on `PATH`, and + # a site on that theme fails to build without one. The release + # tarball is standalone, so no Node or Dart setup is needed. The + # checksum guards the download, as it does for Lychee below. Sites + # still on LibSass ignore the binary. + - name: Install Dart Sass + if: steps.docs.outputs.present == 'true' + run: | + archive="dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + curl -fsSLO "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/${archive}" + echo "${DART_SASS_SHA256} ${archive}" | sha256sum --check --strict + tar -xzf "${archive}" -C "${RUNNER_TEMP}" + rm "${archive}" + echo "${RUNNER_TEMP}/dart-sass" >> "${GITHUB_PATH}" + + # Hugo finds Dart Sass on `PATH`. `hugo env` lists it when it does. + # `GITHUB_PATH` only takes effect from the next step on, so this check + # cannot live in the install step above. It fails here, rather than + # mid-build, if the install did not put `sass` where Hugo looks. + - name: Check Dart Sass + if: steps.docs.outputs.present == 'true' + run: hugo env | grep 'github.com/sass/dart-sass' + + # `actions/setup-node` ships with built-in npm caching that hashes # the lockfile and restores `~/.npm`. We use that instead of a - # standalone `actions/cache@v4` block so there is only one source of + # standalone `actions/cache` block so there is only one source of # truth for the cache key (no drift between two layers). - name: Setup Node if: steps.docs.outputs.present == 'true' - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: '26' cache: 'npm' @@ -97,7 +137,7 @@ jobs: # invalidates the cache deterministically regardless of site root. - name: Cache Hugo Modules if: steps.docs.outputs.present == 'true' - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: /tmp/hugo_cache key: ${{ runner.os }}-hugomod-${{ hashFiles('docs/**/go.sum', 'site/**/go.sum') }} @@ -121,7 +161,7 @@ jobs: # `max_cache_age` expires. - name: Cache Lychee results if: steps.docs.outputs.present == 'true' - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: .lycheecache key: cache-lychee-${{ runner.os }}-${{ hashFiles('lychee.toml') }} @@ -136,7 +176,7 @@ jobs: - name: Cache Lychee executable if: steps.docs.outputs.present == 'true' id: cache-lychee - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: lychee key: ${{ runner.os }}-${{ env.LYCHEE_VERSION_TAG }}-${{ env.LYCHEE_RELEASE }} @@ -156,7 +196,7 @@ jobs: # string when the file is absent, so this guard runs the download iff # neither the exact key nor any restore-key restored the binary. - name: Download Lychee executable - uses: robinraju/release-downloader@v1.7 + uses: robinraju/release-downloader@v1.13 if: steps.docs.outputs.present == 'true' && hashFiles('lychee/lychee') == '' with: repository: "lycheeverse/lychee" @@ -208,9 +248,32 @@ jobs: exit 1 fi + # Lychee prints broken links to this step's log only; the run page + # shows just "Process completed with exit code 2". `tee` keeps the + # report in the log and saves a copy, which the next step adds to the + # run summary. (`--output` would not do: it moves the report from the + # log to the file.) `pipefail` keeps Lychee's exit code. Without it, + # the step would take `tee`'s 0 and pass despite broken links. - name: Check links if: steps.docs.outputs.present == 'true' run: | + set -o pipefail ./lychee/lychee --config lychee.toml --timeout 60 \ --base-url http://localhost:1313/ \ - '${{ steps.docs.outputs.work_dir }}/public/**/*.html' + '${{ steps.docs.outputs.work_dir }}/public/**/*.html' \ + | tee lychee-report.txt + + # Runs after a failed `Check links` too, which is when the report + # matters most. `hashFiles` skips this step when Lychee never ran. + # `!cancelled()` needs the `${{ }}` wrapper: a bare leading `!` is + # a YAML tag. + - name: Publish link report + if: ${{ !cancelled() && hashFiles('lychee-report.txt') != '' }} + run: | + { + echo '## Link check' + echo + echo '```text' + cat lychee-report.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/increment-guard.yml b/.github/workflows/increment-guard.yml index 9a6333f0ff..56f3008854 100644 --- a/.github/workflows/increment-guard.yml +++ b/.github/workflows/increment-guard.yml @@ -1,9 +1,13 @@ # 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). +# published as the `Version bump` commit status, which `revalidate-versions.yml` also +# re-publishes on the heads of other open PRs when the base branch advances (so a stale +# duplicate bump turns red before merge). +# +# Branch protection must require the `Version bump` status, not the job's own +# `Check version increment` check run: only the status carries the revalidation verdict, +# and a job skipped by its `if` reports a passing check run. # # 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 @@ -21,7 +25,7 @@ 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: + # which would otherwise leave a stale-green `Version bump` 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. @@ -39,7 +43,7 @@ jobs: (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 + # `statuses: write` lets the job publish the `Version bump` commit status that # branch protection requires. permissions: contents: read @@ -76,7 +80,7 @@ jobs: VERSION_GUARD: "true" run: ./gradlew checkVersionIncrement --stacktrace - # Publish the verdict as the `Version Guard` commit status on the PR head. Posting it + # Publish the verdict as the `Version bump` 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. # @@ -84,7 +88,7 @@ jobs: # 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 + - name: Report the Version bump status if: always() && github.event.pull_request.head.repo.fork == false shell: bash env: @@ -96,5 +100,5 @@ jobs: fi gh api -X POST "repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \ -f state="${state}" \ - -f context="Version Guard" \ + -f context="Version bump" \ -f description="Version increment check" diff --git a/.github/workflows/publish-api-docs.yml b/.github/workflows/publish-api-docs.yml new file mode 100644 index 0000000000..1f6f17a18f --- /dev/null +++ b/.github/workflows/publish-api-docs.yml @@ -0,0 +1,100 @@ +name: Publish API docs + +# Publishes the API documentation of the published modules to the `gh-pages` +# branch, which GitHub Pages serves under `spine.io`. +# +# The job runs apart from `publish.yml`, and neither waits for the other: a +# failure here leaves the Maven publication intact, and the other way round. +# `:publishAllApiDocs` is registered in the root project by `spinePublishing { }`, +# so it exists in every repository that publishes, and does nothing where no +# module documents itself. It runs the `publishApiDocs` task of each published +# module. +# +# Only release versions are published. For a snapshot version, which most merges +# produce, the task of each module only says that it skips the documentation, +# and the run ends green without generating or pushing any. +# +# A failed run can be re-run as it is: each module commits its documentation +# even when nothing changed, and retries a failed push after rebasing onto +# `gh-pages`. +# +# Each module clones only the tip of `gh-pages` and checks out only its own most +# recent documentation and the folder of its version, so that a run does not +# fetch the documentation of every version of every module. +# +# This job does not ask whether `publish.yml` could publish the version, as that +# would couple the two again. When `publish.yml` stops because the version is +# already taken, which the version checks of pull requests normally prevent, +# this job still copies the documentation of the commit over that of the version. +# +# Unlike `publish.yml`, this file is not a caller of a reusable workflow hosted +# by `config`. That split exists to make `config` the signer of the attestation +# of the published artifacts, and the documentation is not attested. + +on: + push: + branches: [master] + +jobs: + publish-api-docs: + name: Publish API docs + runs-on: ubuntu-latest + + # One run at a time per repository, since every run pushes to `gh-pages`. + # `queue: max` keeps up to 100 runs waiting, instead of letting a newer run + # cancel the waiting one: each run also adds the documentation of its own + # version, which no later run would publish. The waiting runs may start in + # another order than their commits, so a run replaces the most recent + # documentation of a module only if no newer version is published already. + concurrency: + group: api-docs-publishing-${{ github.repository }} + queue: max + + # The push goes over SSH with the deploy key, not with `GITHUB_TOKEN`, which + # only reads the dependencies published to GitHub Packages. + permissions: + contents: read + packages: read + + steps: + - uses: actions/checkout@v6 + with: + submodules: 'true' + + - uses: actions/setup-java@v5 + with: + java-version: 17 + distribution: zulu + + # The cache stays on, unlike in `publishing.yml`, which turns it off to keep + # its attestation trustworthy: nothing here is attested. A run on `master` + # reads only the caches written by the workflows that run on `master`. + - uses: gradle/actions/setup-gradle@v6 + + # The same step as in `build-on-ubuntu.yml`. For a release version, the build + # runs Dokka for every published module, in parallel, and the Linux runner + # ships with effectively no swap, so a memory peak would be an instant OOM + # kill. It goes before the deploy key is decrypted, so that the action never + # runs with the key on disk. + - name: Add swap space + uses: pierotofy/set-swap-space@v1.0 + with: + swap-size-gb: 8 + + - name: Decrypt Git SSH credentials + run: ./config/scripts/decrypt.sh "$GIT_CREDENTIALS_KEY" ./.github/keys/deploy_key_rsa.gpg ./deploy_key_rsa + env: + GIT_CREDENTIALS_KEY: ${{ secrets.GIT_CREDENTIALS_KEY }} + + # The SSH agent does not accept a key that others can read. The build runs + # `register-ssh-key.sh` to hand the key to the agent. + - name: Set file system permissions + run: chmod 400 ./deploy_key_rsa && chmod +x ./config/scripts/register-ssh-key.sh + + - name: Publish API docs + run: ./gradlew :publishAllApiDocs --stacktrace + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FORMAL_GIT_HUB_PAGES_AUTHOR: developers@spine.io + # https://docs.github.com/en/actions/reference/environment-variables + REPO_SLUG: ${{ github.repository }} # e.g. SpineEventEngine/core-jvm diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8dc7c913bb..aacfe8e243 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,7 +30,7 @@ on: jobs: publish: name: Publish to Maven repositories - uses: SpineEventEngine/config/.github/workflows/publishing.yml@ad3dff0890c7716cbba3fe2ce17bf4fe5905a318 + uses: SpineEventEngine/config/.github/workflows/publishing.yml@3a0d13a6bc72f1d3b873a6c5f300aa9bf26ffe0a permissions: contents: read packages: write diff --git a/.github/workflows/revalidate-versions.yml b/.github/workflows/revalidate-versions.yml index 2d2ecb9c6d..59f81803cc 100644 --- a/.github/workflows/revalidate-versions.yml +++ b/.github/workflows/revalidate-versions.yml @@ -5,7 +5,7 @@ # 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 +# `Version bump` 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. # @@ -40,14 +40,14 @@ concurrency: jobs: revalidate: - name: Revalidate open PRs + name: Mark stale version bumps runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: 'true' - - name: Revalidate open PRs against the new base version + - name: Mark open PRs whose version is not above the new base shell: bash env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/buildSrc/src/main/kotlin/BuildExtensions.kt b/buildSrc/src/main/kotlin/BuildExtensions.kt index a69dd1a044..cedacd2588 100644 --- a/buildSrc/src/main/kotlin/BuildExtensions.kt +++ b/buildSrc/src/main/kotlin/BuildExtensions.kt @@ -27,7 +27,6 @@ import io.spine.dependency.test.Kotest import io.spine.dependency.test.Kover import io.spine.gradle.repo.standardToSpineSdk import org.gradle.api.Project -import org.gradle.api.Task import org.gradle.api.artifacts.ModuleDependency import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.tasks.JavaExec @@ -168,7 +167,8 @@ fun Project.configureTaskDependencies() { * Creates a dependency between the Gradle task of *this* name * onto the task with `taskName`. * - * If either of the tasks does not exist in the enclosing `Project`, + * Neither task is created by this method. A task registered later is matched too. + * If either of the tasks never exists in the enclosing `Project`, * this method does nothing. * * This extension is kept local to the `configureTaskDependencies` extension @@ -176,9 +176,8 @@ fun Project.configureTaskDependencies() { */ fun String.dependOn(taskName: String) { val whoDepends = this - val dependOntoTask: Task? = tasks.findByName(taskName) - dependOntoTask?.let { - tasks.findByName(whoDepends)?.dependsOn(it) + tasks.named { it == whoDepends }.configureEach { + dependsOn(tasks.named { it == taskName }) } } @@ -267,19 +266,20 @@ fun JavaExec.remoteDebug(enabled: Boolean = true) { * * The port number is [5566][BuildSettings.REMOTE_DEBUG_PORT]. * + * The task is configured when Gradle creates it, so calling this function + * does not create the task. + * * @param enabled If `true` the task will be suspended. - * @throws IllegalStateException if the task with the given name is not found, or, - * if the task is not of [JavaExec] type. + * @throws IllegalStateException if the task with the given name is not found. + * @throws org.gradle.api.InvalidUserDataException if the task is not of [JavaExec] type. */ fun Project.setRemoteDebug(taskName: String, enabled: Boolean = true) { - val task = tasks.findByName(taskName) - check(task != null) { + check(taskName in tasks.names) { "Could not find a task named `$taskName` in the project `$name`." } - check(task is JavaExec) { - "The task `$taskName` is not of type `JavaExec`." + tasks.named(taskName, JavaExec::class.java).configure { + remoteDebug(enabled) } - task.remoteDebug(enabled) } /** diff --git a/buildSrc/src/main/kotlin/DokkaExts.kt b/buildSrc/src/main/kotlin/DokkaExts.kt index 5aeba8d5f0..849fea6cb2 100644 --- a/buildSrc/src/main/kotlin/DokkaExts.kt +++ b/buildSrc/src/main/kotlin/DokkaExts.kt @@ -25,6 +25,7 @@ import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.execution.TaskExecutionGraph import org.gradle.api.publish.maven.tasks.PublishToMavenLocal import org.gradle.api.publish.maven.tasks.PublishToMavenRepository +import org.gradle.api.tasks.TaskCollection import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.Jar @@ -183,14 +184,22 @@ fun DokkaExtension.configureForJava(project: Project, sourceLinkRemoteUrl: Strin } /** - * Finds the `dokkaGenerateHtml` Gradle task. + * Finds the `dokkaGeneratePublicationHtml` Gradle task without creating it. + * + * The returned collection is live: it holds the task once Dokka registers it, + * and stays empty in a project without Dokka. */ -fun TaskContainer.dokkaHtmlTask(): Task? = this.findByName("dokkaGeneratePublicationHtml") +fun TaskContainer.dokkaHtmlTask(): TaskCollection = + named { it == "dokkaGeneratePublicationHtml" } /** - * Finds the `dokkaGeneratePublicationJavadoc` Gradle task. + * Finds the `dokkaGeneratePublicationJavadoc` Gradle task without creating it. + * + * The returned collection is live: it holds the task once Dokka registers it, + * and stays empty in a project without Dokka. */ -fun TaskContainer.dokkaJavadocTask(): Task? = this.findByName("dokkaGeneratePublicationJavadoc") +fun TaskContainer.dokkaJavadocTask(): TaskCollection = + named { it == "dokkaGeneratePublicationJavadoc" } /** * Locates or creates the `htmlDocsJar` task in this [Project]. @@ -205,9 +214,7 @@ fun Project.htmlDocsJar(): TaskProvider = tasks.getOrCreate("htmlDocsJar") archiveClassifier.set("html-docs") from(files(dokkaHtmlOutput())) - tasks.dokkaHtmlTask()?.let { dokkaTask -> - this@getOrCreate.dependsOn(dokkaTask) - } + this@getOrCreate.dependsOn(tasks.dokkaHtmlTask()) } /** @@ -238,7 +245,7 @@ fun Task.isInPublishingGraph(): Boolean = * Such a build usually feeds integration tests, which need the published code, * but not its documentation. So the Dokka tasks are skipped when the execution graph * contains a [PublishToMavenLocal] task and no task that ships the documentation - * elsewhere: `publish`, a [PublishToMavenRepository] task, or `updateGitHubPages`. + * elsewhere: `publish`, a [PublishToMavenRepository] task, or `publishApiDocs`. * They still run if a task named on the command line selects a documentation task of * the graph: one that has `dokka` in its name, ignoring case, or a documentation JAR, * `javadocJar` or `htmlDocsJar`. The name may be the full one, e.g., `dokkaGenerate`, @@ -276,7 +283,7 @@ fun Project.skipDokkaWhenPublishingToMavenLocal() { /** * The names of the lifecycle tasks that ship the documentation beyond Maven Local. */ -private val docsShippingTaskNames = setOf("publish", TaskName.updateGitHubPages) +private val docsShippingTaskNames = setOf("publish", TaskName.publishApiDocs) /** * Tells if this graph publishes to Maven Local and does not contain a task that ships diff --git a/buildSrc/src/main/kotlin/dokka-setup.gradle.kts b/buildSrc/src/main/kotlin/dokka-setup.gradle.kts index c66e9f2f4d..43eb56fa75 100644 --- a/buildSrc/src/main/kotlin/dokka-setup.gradle.kts +++ b/buildSrc/src/main/kotlin/dokka-setup.gradle.kts @@ -41,10 +41,7 @@ afterEvaluate { DocumentationSettings.SourceLink.url(project) ) } - val kspKotlin = tasks.findByName("kspKotlin") - kspKotlin?.let { - tasks.withType().configureEach { - dependsOn(kspKotlin) - } + tasks.withType().configureEach { + dependsOn(tasks.named { it == "kspKotlin" }) } } 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 baaf5c7053..e58ab1415e 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt @@ -25,7 +25,7 @@ package io.spine.dependency.local ) object Base { const val version = "2.0.0-SNAPSHOT.462" - const val versionForBuildScript = "2.0.0-SNAPSHOT.461" + const val versionForBuildScript = "2.0.0-SNAPSHOT.462" 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/BaseTypes.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt index d92259dc5a..5f08d0bb3e 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt @@ -23,7 +23,7 @@ package io.spine.dependency.local "ConstPropertyName" /* We use a custom convention for artifact properties. */, ) object BaseTypes { - const val version = "2.0.0-SNAPSHOT.226" + const val version = "2.0.0-SNAPSHOT.227" const val group = Spine.group const val artifact = "spine-base-types" const val lib = "$group:$artifact:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt index 1f6ebe1c62..91864be355 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt @@ -23,7 +23,7 @@ package io.spine.dependency.local "ConstPropertyName" /* We use a custom convention for artifact properties. */, ) object Change { - const val version = "2.0.0-SNAPSHOT.208" + const val version = "2.0.0-SNAPSHOT.209" const val group = Spine.group const val artifact = "spine-change" const val lib = "$group:$artifact:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt index 66a3e8c889..b75c0f1ef9 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt @@ -69,7 +69,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.069" + private const val fallbackDfVersion = "2.0.0-SNAPSHOT.081" /** * 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 35246a7793..94f3d9d682 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt @@ -30,7 +30,7 @@ typealias CoreJava = CoreJvm ) object CoreJvm { const val group = Spine.group - const val version = "2.0.0-SNAPSHOT.571" + const val version = "2.0.0-SNAPSHOT.577" 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 3465c08c02..8fc131eaa8 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt @@ -34,12 +34,12 @@ object CoreJvmCompiler { /** * The version used in the build classpath. */ - const val dogfoodingVersion = "2.0.0-SNAPSHOT.098" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.099" /** * The version to be used for integration tests. */ - const val version = "2.0.0-SNAPSHOT.098" + const val version = "2.0.0-SNAPSHOT.099" /** * The ID of the Gradle plugin. diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/DokkaTools.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/DokkaTools.kt index 37bfae4ed7..4591fac501 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/DokkaTools.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/DokkaTools.kt @@ -25,7 +25,7 @@ package io.spine.dependency.local ) object DokkaTools { const val group = Spine.toolsGroup - const val version = "2.0.0-SNAPSHOT.11" + const val version = "2.0.0-SNAPSHOT.12" /** * The artifact dropped its `spine-` prefix in `2.0.0-SNAPSHOT.9`, to 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 96fbbb284c..b84652a194 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Logging.kt @@ -42,7 +42,22 @@ object Logging { // Transitive dependencies. // Make `public` and use them to force a version in a particular repository, if needed. internal const val julBackend = "$group:spine-logging-jul-backend:$version" + + /** + * The former `middleware` module of Spine Logging. + * + * In `2.0.0-SNAPSHOT.270`, its code moved into the `logging` module, which is + * published as [lib]. The last published version of this artifact is + * `2.0.0-SNAPSHOT.263`, so these coordinates do not resolve at [version]. + * + * @deprecated Use [lib] instead. + */ + @Deprecated( + "The module was merged into `spine-logging` and is no longer published.", + ReplaceWith("lib"), + ) const val middleware = "$group:spine-logging-middleware:$version" + internal const val platformGenerator = "$group:spine-logging-platform-generator:$version" internal const val jvmDefaultPlatform = "$group:spine-logging-jvm-default-platform:$version" } diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt index 3e8e39938d..d9221c3d7b 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt @@ -28,7 +28,7 @@ import io.spine.dependency.Dependency ) object Time : Dependency() { override val group = Spine.group - override val version = "2.0.0-SNAPSHOT.251" + override val version = "2.0.0-SNAPSHOT.253" private const val infix = "spine-time" fun lib(version: String): String = "$group:$infix:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt index bb675bca2d..7bd22c472a 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt @@ -33,7 +33,7 @@ package io.spine.dependency.local object ToolBase { const val group = Spine.toolsGroup const val version = "2.0.0-SNAPSHOT.428" - const val dogfoodingVersion = "2.0.0-SNAPSHOT.426" + const val dogfoodingVersion = "2.0.0-SNAPSHOT.428" const val archive = "$group:archive:$version" const val code = "$group:code:$version" @@ -56,11 +56,28 @@ object ToolBase { const val rootGradlePlugins = "$group:root-gradle-plugins:$version" const val gradlePluginApi = "$group:gradle-plugin-api:$version" + + /** + * Stub Gradle plugins for the tests of the `gradle-plugin-api` module. + * + * The `gradle-plugin-api-test-fixtures` module that provides them is internal + * to `tool-base`, which never publishes it to a remote repository. Only + * `publishToMavenLocal` installs it, so these coordinates resolve only from + * a local `~/.m2` copy. + * + * @deprecated There is no replacement. Remove the usage. + */ + @Deprecated( + "The artifact is internal to `tool-base` and is not published remotely. Remove the usage." + ) const val gradlePluginApiTestFixtures = "$group:gradle-plugin-api-test-fixtures:$version" const val jvmTools = "$group:jvm-tools:$version" - const val jvmToolPluginDogfooding = "$group:jvm-tool-plugins-all:$dogfoodingVersion" - const val jvmToolPlugins = "$group:jvm-tool-plugins-all:$version" + + // The plain plugin JAR. `jvm-tool-plugins-all` is no longer published + // as of `2.0.0-SNAPSHOT.440`. See https://github.com/SpineEventEngine/tool-base/pull/201 + const val jvmToolPluginDogfooding = "$group:jvm-tool-plugins:$dogfoodingVersion" + const val jvmToolPlugins = "$group:jvm-tool-plugins:$version" const val protobufSetupPlugins = "$group:protobuf-setup-plugins:$version" diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt index 155cbbc602..1be9aff47a 100644 --- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt +++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt @@ -27,7 +27,7 @@ object Validation { /** * The version of the Validation library artifacts. */ - const val version = "2.0.0-SNAPSHOT.464" + const val version = "2.0.0-SNAPSHOT.469" const val group = Spine.toolsGroup private const val prefix = "validation" diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/Cli.kt b/buildSrc/src/main/kotlin/io/spine/gradle/Cli.kt index 0e43f3f71f..36ba14ebe3 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/Cli.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/Cli.kt @@ -20,7 +20,6 @@ import java.io.File import java.io.InputStream import java.io.StringWriter import java.lang.ProcessBuilder.Redirect.PIPE -import java.util.* /** * Executes a process from Gradle code. @@ -32,20 +31,34 @@ class Cli(private val workingFolder: File) { /** * Executes the given terminal command and retrieves the command output. * - * [Executes][Runtime.exec] the given `String` array as a CLI command. + * Runs the given command, the program followed by its arguments, in + * the [workingFolder]. * * If the execution is successful, returns the command output. * Throws an [IllegalStateException] otherwise. * * @param command the command to execute. - * @return the command line output. + * @return the standard output as the command wrote it, line breaks included, + * decoded as UTF-8. * @throws IllegalStateException if the execution fails. */ - fun execute(vararg command: String): String { + fun execute(vararg command: String): String = execute(command.asList()) + + /** + * Executes the given terminal command and retrieves the command output. + * + * Works like the `vararg` overload, for a command assembled as a list. + * + * @param command the command to execute, the program followed by its arguments. + * @return the standard output as the command wrote it, line breaks included, + * decoded as UTF-8. + * @throws IllegalStateException if the execution fails. + */ + fun execute(command: List): String { val outWriter = StringWriter() val errWriter = StringWriter() - val process = ProcessBuilder(*command).apply { + val process = ProcessBuilder(command).apply { directory(workingFolder) redirectOutput(PIPE) redirectError(PIPE) @@ -66,22 +79,22 @@ class Cli(private val workingFolder: File) { val commandLine = command.joinToString(" ") val nl = System.lineSeparator() val errorMsg = "Command `$commandLine` finished with exit code $exitCode:" + - "$nl$errWriter" + - "$nl$outWriter." + "$nl${errWriter.toString().trimEnd()}" + + "$nl${outWriter.toString().trimEnd()}." throw IllegalStateException(errorMsg) } } } /** - * Starts a background thread that reads all lines from this [InputStream] and - * appends them to [dest], returning the thread so the caller can [join][Thread.join] - * it once the process has exited, ensuring the buffer holds the complete output. + * Starts a background thread that reads all of this [InputStream] and appends it + * to [dest], returning the thread so the caller can [join][Thread.join] it once + * the process has exited, ensuring the buffer holds the complete output. + * + * The text is appended as the process wrote it, line breaks included, so that + * a multi-line output can be split into lines. */ private fun InputStream.pourTo(dest: StringWriter): Thread = Thread { - val sc = Scanner(this) - while (sc.hasNextLine()) { - dest.append(sc.nextLine()) - } + bufferedReader().use { dest.append(it.readText()) } }.also { it.start() } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt index 41cac88758..5c16bd5661 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/ProjectExtensions.kt @@ -59,7 +59,11 @@ fun Project.applyPlugin(cls: Class>) { * * The task must be present. Also, a caller is responsible for using the proper value of * the generic parameter `T`. + * + * Gradle creates the task if it is only registered so far, even in a build that never + * runs it. Prefer `tasks.named(name)`, which finds the task without creating it. */ +@Deprecated(message = "Please use `tasks.named(name)`, which does not create the task.") @Suppress("UNCHECKED_CAST") /* See the method docs. */ fun Project.getTask(name: String): T { val task = this.tasks.findByName(name) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/TaskName.kt b/buildSrc/src/main/kotlin/io/spine/gradle/TaskName.kt index 0a595b3b40..763b9832b2 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/TaskName.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/TaskName.kt @@ -17,6 +17,7 @@ package io.spine.gradle import kotlin.reflect.KClass import org.gradle.api.Task import org.gradle.api.tasks.TaskContainer +import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.register @@ -45,3 +46,22 @@ internal fun TaskContainer.named(name: TaskName) = named(name.valu */ internal fun TaskContainer.register(name: TaskName, init: T.() -> Unit) = register(name.value, name.clazz, init) + +/** + * Returns the task named [name] in this container, registering it with [init] + * if it is not there yet. + * + * Unlike [register][TaskContainer.register], this does not fail when the task is + * already registered, so it suits code that may reach the same task more than once. + * [init] applies only when this call registers the task; for an existing task it is + * ignored, so a setup needed in both cases goes to [TaskProvider.configure]. + */ +internal fun TaskContainer.getOrRegister( + name: String, + init: Task.() -> Unit = {} +): TaskProvider = + if (name in names) { + named(name) + } else { + register(name, init) + } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt index b8232683fa..79351fbf16 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/checkstyle/CheckStyleConfig.kt @@ -64,7 +64,7 @@ object CheckStyleConfig { "checkstyleTest", "checkstyleTestFixtures" ).forEach { - task -> tasks.findByName(task)?.enabled = false + task -> tasks.named { it == task }.configureEach { enabled = false } } } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/git/Repository.kt b/buildSrc/src/main/kotlin/io/spine/gradle/git/Repository.kt index 1b7e09abf9..6630f985f2 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/git/Repository.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/git/Repository.kt @@ -61,14 +61,33 @@ class Repository private constructor( repoExecute("git", "clone", sshUrl, ".") } + /** + * Clones the tip of the [branch], or of the default branch if it is `null`, + * into the [temporary folder][location]. + * + * The blobs are fetched only when the working tree needs them, and the working + * tree holds only the top-level files until [checkOutOnly] replaces them with + * the selected paths. + */ + private fun cloneSparsely(branch: String?) { + val options = listOf("--depth", "1", "--filter=blob:none", "--sparse", "--single-branch") + val branchOption = branch?.let { listOf("--branch", it) } ?: emptyList() + repoExecute(listOf("git", "clone") + options + branchOption + listOf("--", sshUrl, ".")) + } + /** * Executes a command in the [location]. */ - private fun repoExecute(vararg command: String): String { - val cmd = command.toList().joinToString(" ") + private fun repoExecute(vararg command: String): String = repoExecute(command.asList()) + + /** + * Executes a command, assembled as a list, in the [location]. + */ + private fun repoExecute(command: List): String { + val cmd = command.joinToString(" ") val msg = "[Repo (${project.path})] Executing command: `$cmd`." System.err.println(msg) - return Cli(location.toFile()).execute(*command) + return Cli(location.toFile()).execute(command) } /** @@ -125,12 +144,59 @@ class Repository private constructor( * match a namespaced branch such as `feature/$name`. Relies on `git ls-remote` * returning an empty output with a zero exit code when the branch is absent, * so the check does not raise an exception. + * + * @param name the name of the branch. + * @param remote the name of the remote or its URL; `origin` by default. */ - private fun remoteHasBranch(name: String): Boolean { - val output = repoExecute("git", "ls-remote", "--heads", "origin", "refs/heads/$name") + private fun remoteHasBranch(name: String, remote: String = "origin"): Boolean { + val output = repoExecute("git", "ls-remote", "--heads", remote, "refs/heads/$name") return output.isNotBlank() } + /** + * Checks out only the paths of the current branch that match the given [patterns], + * leaving the rest of the branch out of the working tree. + * + * The call replaces the paths checked out before, including the top-level files + * of a [sparse clone][Factory.cloneSparsely], so it has to be given every path + * to keep at once. + * + * The patterns follow the `.gitignore` syntax, so a folder can be checked out + * without some of its subfolders. The paths left out stay in the branch: committing + * [all changes][commitAllChanges] does not delete them. + * + * Leaving out some subfolders of a folder takes the non-cone mode of + * `git sparse-checkout`, which Git documents as deprecated, though Git 2.54 still + * supports it without a warning. The mode needs Git 2.35 or newer. + */ + fun checkOutOnly(patterns: List) { + repoExecute(listOf("git", "sparse-checkout", "set", "--no-cone") + patterns) + } + + /** + * Obtains the names of the folders in the given [folder] of the current commit, + * whether they are checked out or not. + * + * Reads the tree of the current commit, so the changes in the working tree that + * are not committed yet are not reflected. The files in the [folder] are skipped. + * + * @param folder the path of the folder relative to the repository root. + * @return the names of the folders in [folder], or an empty list if there is + * no such folder. + */ + fun listFolders(folder: String): List { + require(folder.isNotBlank()) { "The folder to list cannot be blank." } + // Each entry is ` \t`, the entries are separated + // by `NUL`, and the paths are not quoted. + return repoExecute("git", "ls-tree", "-z", "HEAD", "${folder.trimEnd('/')}/") + .split('\u0000') + .filter { it.isNotBlank() } + .mapNotNull { entry -> + val (info, path) = entry.split('\t', limit = 2) + path.substringAfterLast('/').takeIf { info.split(' ')[1] == "tree" } + } + } + /** * Creates the [branch] as an orphan branch seeded with [initialFiles] and * pushes it to the remote. @@ -173,7 +239,6 @@ class Repository private constructor( // If the branch now exists on the remote, another module won the // creation race and we adopt its branch; otherwise the failure is // genuine and is rethrown. - repoExecute("git", "fetch", "origin") if (!remoteHasBranch(branch)) { throw e } @@ -184,8 +249,14 @@ class Repository private constructor( /** * Discards the local orphan branch in favour of the same-named branch that * already exists on the remote, keeping the local branch in sync with it. + * + * The branch is added to the branches fetched from the remote first: a single-branch + * clone, which a shallow one is too, fetches no other branch, and Git does not + * accept a branch it does not fetch as the upstream of a local one. */ private fun adoptRemoteBranch(branch: String) { + repoExecute("git", "remote", "set-branches", "--add", "origin", branch) + repoExecute("git", "fetch", "origin", "+refs/heads/$branch:refs/remotes/origin/$branch") repoExecute("git", "reset", "--hard", "origin/$branch") repoExecute("git", "branch", "--set-upstream-to=origin/$branch", branch) } @@ -274,6 +345,53 @@ class Repository private constructor( return repo } + + /** + * Clones the tip of the [branch] of the repository with the provided SSH URL + * in a temporary folder, with only its top-level files checked out. + * + * Suits a branch with a large tip of which a change touches only a small part, + * such as the branch with the documentation of every published version. + * Further blobs are fetched only for the paths that [checkOutOnly] selects. + * The commits are made and [pushed][push] as in a full clone. + * + * A [branch] that does not exist yet is created and seeded with [initialFiles], + * which are top-level files. + * + * Configures the username and the email of the Git user. + * See [configureUser] documentation for more information. + * + * @throws IllegalArgumentException if SSH URL is a blank string. + */ + fun cloneSparsely( + project: Project, + sshUrl: String, + user: UserInfo, + branch: String, + initialFiles: Map = emptyMap(), + ): Repository { + require(sshUrl.isNotBlank()) { "SSH URL cannot be an empty string." } + + val repo = Repository(project, sshUrl = sshUrl, user = user, currentBranch = branch) + var cloned = false + try { + // Ask the remote first: an existing branch is cloned alone, while + // a missing one needs the default branch to start from. + val branchExists = repo.remoteHasBranch(branch, remote = sshUrl) + repo.cloneSparsely(branch.takeIf { branchExists }) + repo.configureUser(user) + if (!branchExists) { + repo.createOrphanBranch(branch, initialFiles) + } + cloned = true + return repo + } finally { + // The caller cannot close a repository it never gets. + if (!cloned) { + repo.close() + } + } + } } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/AuthorEmail.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/AuthorEmail.kt index 39d16ac8fd..cec12afdc9 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/AuthorEmail.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/AuthorEmail.kt @@ -15,7 +15,8 @@ package io.spine.gradle.github.pages /** - * An author of updates to GitHub pages. + * The email of the author of the commits that publish the API documentation + * to GitHub Pages. */ class AuthorEmail(val value: String) { diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/DocsPublication.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/DocsPublication.kt new file mode 100644 index 0000000000..11f37da3b0 --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/DocsPublication.kt @@ -0,0 +1,254 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.github.pages + +import io.spine.gradle.VersionComparator +import io.spine.gradle.git.Repository +import java.io.File +import java.nio.file.Path +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.gradle.api.logging.Logger + +/** + * Publishes the API documentation of the given [project] to GitHub Pages. + * + * The documentation in each format is copied into a clone of the `gh-pages` branch + * and committed there, and the commits are pushed. + * + * The clone has only the most recent documentation of the [project] and the folder of + * its version checked out, without the folders of the versions published earlier. + * The branch holds the documentation of every version of every module, which is too + * much to check out for a change to a single module. + */ +internal fun Task.publishToGitHubPages(project: Project) { + val plugin = project.plugins.getPlugin(PublishApiDocs::class.java) + + SshKey(plugin.rootFolder, logger).register() + + val repository = Repository.forPublishingDocumentation(project) + + val javadoc = JavadocPublication(project, plugin.javadocOutputFolder, repository, logger) + val html = HtmlPublication(project, plugin.htmlOutputFolder, repository, logger) + + repository.use { + repository.checkOutOnly(javadoc.checkoutPatterns() + html.checkoutPatterns()) + javadoc.publish() + html.publish() + repository.push() + } +} + +/** + * Publishes the documentation of a [project] in one format to the clone of + * the [repository] serving GitHub Pages. + * + * The documentation goes to the folder of its version, and also replaces the most + * recent documentation of the project, unless a newer version is already published. + */ +private abstract class DocsPublication( + private val project: Project, + private val docsSourceFolder: Path, + private val repository: Repository, + private val logger: Logger +) { + + /** + * The folder under the repository's root(`/`) for storing documentation. + * + * The value should not contain any leading or trailing file separators. + * + * The absolute path to the project's documentation is made by appending its + * name to the end, making `/docsDestinationFolder/project.name`. + */ + protected abstract val docsDestinationFolder: String + + /** + * The name of the format of the documentation to publish. + * + * This name will appear in logs as part of a message. + */ + protected abstract val formatName: String + + /** + * The path of the folder with the documentation of the [project] in this format, + * relative to the repository root. + */ + private val folder: String + get() = "$docsDestinationFolder/${project.name}" + + private val mostRecentFolder by lazy { + File("${repository.location}/$folder") + } + + /** + * The folder holding the documentation of each published version in a subfolder + * named after the version. + */ + private val versionsFolder by lazy { + File(mostRecentFolder, VERSIONS_FOLDER) + } + + private fun log(message: () -> String) { + if (logger.isDebugEnabled) { + logger.debug(message()) + } + } + + /** + * Obtains the patterns of the paths this publication writes, for + * [checking out][Repository.checkOutOnly] just those paths. + */ + fun checkoutPatterns(): List = + docsCheckoutPatterns(folder, project.version.toString()) + + /** + * Copies the documentation into the repository and commits it. + */ + fun publish() { + val module = project.name + log { "Publishing the `$formatName` documentation of the module `$module`." } + + val documentation = replaceMostRecentDocs() + copyIntoVersionDir(documentation) + + val version = project.version + val commitMessage = + "Publish the `$formatName` documentation of the module" + + " `$module` for the version `$version`." + repository.commitAllChanges(commitMessage) + + log { "Published the `$formatName` documentation of the module `$module`." } + } + + /** + * Replaces the most recent documentation with the one of this version, unless + * the documentation of a newer version is already published. + * + * The runs of `publish-api-docs.yml` are serialized, but not necessarily in the + * order of their commits, and a release of an older line may come after a newer + * one. Either way, the most recent documentation stays that of the newest version. + */ + private fun replaceMostRecentDocs(): ConfigurableFileCollection { + val generatedDocs = project.files(docsSourceFolder) + + val version = project.version.toString() + val newer = publishedVersions().newestAbove(version) + if (newer != null) { + logger.lifecycle( + "The most recent `$formatName` documentation of `${project.name}` stays" + + " that of `$newer`, which is newer than `$version`." + ) + return generatedDocs + } + + log { "Replacing the most recent `$formatName` documentation in `$mostRecentFolder`." } + copyDocs(generatedDocs, mostRecentFolder) + + return generatedDocs + } + + /** + * Obtains the versions whose documentation is published, as named by the folders + * under `v/`. + * + * The names are read from the tree of the current commit, as the folders of + * the other versions are not checked out. + */ + private fun publishedVersions(): List = + repository.listFolders("$folder/$VERSIONS_FOLDER") + + private fun copyDocs(source: FileCollection, destination: File) { + destination.mkdirs() + project.copy { + from(source) + into(destination) + } + } + + private fun copyIntoVersionDir(generatedDocs: ConfigurableFileCollection) { + val versionedDocDir = File(versionsFolder, "${project.version}") + + log { "Storing the new version of `$formatName` documentation in `${versionedDocDir}`." } + copyDocs(generatedDocs, versionedDocDir) + } +} + +private class JavadocPublication( + project: Project, + docsSourceFolder: Path, + repository: Repository, + logger: Logger +) : DocsPublication(project, docsSourceFolder, repository, logger) { + + override val docsDestinationFolder: String + get() = "javadoc" + override val formatName: String + get() = "javadoc" +} + +private class HtmlPublication( + project: Project, + docsSourceFolder: Path, + repository: Repository, + logger: Logger +) : DocsPublication(project, docsSourceFolder, repository, logger) { + + override val docsDestinationFolder: String + get() = "reference" + override val formatName: String + get() = "html" +} + +/** + * Returns the newest of these versions if it is newer than the given [version], + * or `null` otherwise. + * + * The versions are compared by [VersionComparator], so that `2.10.0` is newer than + * `2.9.0`, and a release is newer than the snapshots leading to it. + */ +internal fun Iterable.newestAbove(version: String): String? = + maxWithOrNull(VersionComparator)?.takeIf { VersionComparator.compare(it, version) > 0 } + +/** + * The name of the folder that holds the documentation of each published version + * in a subfolder named after the version. + */ +private const val VERSIONS_FOLDER = "v" + +/** + * Returns the patterns, in the `.gitignore` syntax, of the paths that a publication + * of the documentation in the given [folder] writes: the most recent documentation, + * and the folder of the given [version] under `v/`, but not the folders of the other + * versions. + * + * The folders under `v/` are excluded one by one, and the folder of the [version] + * is included again after them, as the last matching pattern wins. This is how + * `git sparse-checkout` lays out its patterns in cone mode. + * + * For example, for the `javadoc/core` folder and the version `1.2.0`, the patterns + * select everything in `javadoc/core`, exclude each folder in `javadoc/core/v`, and + * select `javadoc/core/v/1.2.0` again. + * + * @param folder the path of the documentation folder, relative to the repository root. + * @param version the version whose folder is included. + */ +internal fun docsCheckoutPatterns(folder: String, version: String): List = listOf( + "/$folder/*", + "!/$folder/$VERSIONS_FOLDER/*/", + "/$folder/$VERSIONS_FOLDER/$version/", +) diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishAllApiDocs.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishAllApiDocs.kt new file mode 100644 index 0000000000..e610c5861e --- /dev/null +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishAllApiDocs.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.github.pages + +import io.spine.gradle.SpineTaskGroup +import io.spine.gradle.getOrRegister +import io.spine.gradle.github.pages.TaskName.publishAllApiDocs +import io.spine.gradle.github.pages.TaskName.publishApiDocs +import org.gradle.api.Project + +/** + * Registers the [TaskName.publishAllApiDocs] task in the root project, which publishes + * the API documentation of all the published projects of the build. + * + * Usage: + * ``` + * PublishAllApiDocs.registerIn(project.rootProject, projectsToPublish) + * ``` + * + * `spinePublishing { }` makes this call, as it knows which projects are published. + * The task is not a part of the publication to Maven repositories: the + * `publish-api-docs.yml` workflow runs it apart from `publish`. + * + * The task is a lifecycle one: it has no action of its own, and publishes + * the documentation through the [TaskName.publishApiDocs] tasks it depends on, + * which the [PublishApiDocs] plugin registers in each documented project. + */ +internal object PublishAllApiDocs { + + /** + * Registers the [TaskName.publishAllApiDocs] task in the [root] project. + * The task publishes the API documentation of the [published] projects. + * + * The task depends on the [TaskName.publishApiDocs] task of each of + * the [published] projects that has one, that is, applies the [PublishApiDocs] + * plugin. This includes [root] when it is published itself, as in a single-project + * build. A project that applies the plugin, but is not published, such as a module + * of integration tests, is left out. The task of a project with a snapshot version + * publishes nothing. + * + * The task is registered even when none of the projects has the documentation + * to publish, and then does nothing. This lets the `publish-api-docs.yml` + * workflow run it in any repository that publishes: Gradle fails on a task + * that is missing from the build. + * + * The plugin registers the task of a project once that project is evaluated, + * so the tasks to depend on are looked up only when Gradle resolves + * the dependencies of this task. Repeated calls add their projects to the same + * task, as `spinePublishing { }` may run more than once in a build. + */ + fun registerIn(root: Project, published: Set) { + val task = root.tasks.getOrRegister(publishAllApiDocs) { + group = SpineTaskGroup.name + description = "Publishes the API documentation of all the published modules" + } + task.configure { + dependsOn(root.provider { + published + .filter { publishApiDocs in it.tasks.names } + .map { it.tasks.named(publishApiDocs) } + }) + } + } +} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPages.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishApiDocs.kt similarity index 65% rename from buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPages.kt rename to buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishApiDocs.kt index e4960aaf71..f4ca18bc31 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPages.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishApiDocs.kt @@ -15,12 +15,12 @@ package io.spine.gradle.github.pages import dokkaHtmlTask -import dokkaJavadocTask import io.spine.gradle.SpineTaskGroup import io.spine.gradle.fs.LazyTempPath import io.spine.gradle.github.pages.TaskName.copyHtmlDocs import io.spine.gradle.github.pages.TaskName.copyJavadocDocs -import io.spine.gradle.github.pages.TaskName.updateGitHubPages +import io.spine.gradle.github.pages.TaskName.publishApiDocs +import io.spine.gradle.isSnapshot import java.io.File import org.gradle.api.Plugin import org.gradle.api.Project @@ -30,12 +30,18 @@ import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider /** - * Registers the `updateGitHubPages` task that performs the update of - * the GitHub Pages with the documentation generated in Javadoc and HTML format - * for a particular Gradle project. + * Registers the `publishApiDocs` task that publishes the API documentation + * of a particular Gradle project, generated in Javadoc and HTML format. * * The generated documentation is appended to the `spine.io` site - * via GitHub pages by pushing commits to the `gh-pages` branch. + * via GitHub Pages by pushing commits to the `gh-pages` branch. + * The documentation of a snapshot version is not published to the site. Its Javadoc and + * HTML archives are still built by Dokka and published to Maven repositories by `publish`. + * + * The task is not a part of the Maven publication. It is run by the `publish-api-docs.yml` + * workflow, apart from `publish.yml`, through the `publishAllApiDocs` task of the root + * project, which [publishes the documentation of the published modules that apply + * this plugin][PublishAllApiDocs]. * * In order to work, the script needs a `deploy_key_rsa` private RSA key file in the * repository root. It is recommended to encrypt it in the repository and then decrypt @@ -47,18 +53,18 @@ import org.gradle.api.tasks.TaskProvider * the SSH private (encrypted `deploy_key_rsa`) and the public * ("GitHub Pages publisher" on GitHub) keys. * - * Another requirement is an environment variable `REPO_SLUG`, which is set by the CI - * environment, such as `Publish` GitHub Actions workflow. It points to the repository - * for which the update is executed. E.g.: + * Another requirement is an environment variable `REPO_SLUG`, which a CI workflow sets, + * such as the `Publish API docs` GitHub Actions workflow. It points to + * the repository for which the documentation is published. E.g.: * * ``` * REPO_SLUG: SpineEventEngine/base * ``` * - * @see UpdateGitHubPagesExtension for the extension that is used to configure + * @see PublishApiDocsExtension for the extension that is used to configure * this plugin */ -class UpdateGitHubPages : Plugin { +class PublishApiDocs : Plugin { /** * Root folder of the repository, to which this `Project` belongs. @@ -74,13 +80,13 @@ class UpdateGitHubPages : Plugin { /** * Path to the temp folder used to gather the Javadoc format output (generated by Dokka) - * before submitting it to the GitHub Pages update. + * before publishing it. */ internal val javadocOutputFolder = LazyTempPath("javadoc") /** * Path to the temp folder used to gather the HTML documentation - * generated by Dokka before submitting it to the GitHub Pages update. + * generated by Dokka before publishing it. */ internal val htmlOutputFolder = LazyTempPath("html") @@ -94,41 +100,45 @@ class UpdateGitHubPages : Plugin { * when the project version changes to non-snapshot. */ override fun apply(project: Project) { - val extension = UpdateGitHubPagesExtension.createIn(project) + val extension = PublishApiDocsExtension.createIn(project) project.afterEvaluate { - registerTasks(extension) + if (version.toString().isSnapshot()) { + registerNoOpTask() + } else { + registerTasks(extension) + } } } /** - * Registers the `updateGitHubPages` task that performs no actual update, but prints - * the message telling the update is skipped, since the project is in - * its `SNAPSHOT` version. + * Registers the [TaskName.publishApiDocs] task that publishes nothing, but tells + * that the documentation is not published, since the project is in its `SNAPSHOT` + * version. */ - @Suppress("unused") private fun Project.registerNoOpTask() { - tasks.register(updateGitHubPages) { + val module = name + val snapshotVersion = version.toString() + tasks.register(publishApiDocs) { group = SpineTaskGroup.name - description = "Skips the GitHub Pages update for snapshot project versions" + description = "Skips publishing the API documentation of snapshot versions" doLast { - val project = this@registerNoOpTask - println( - "GitHub Pages update will be skipped since this project is a snapshot: " + - "`${project.name}-${project.version}`." + logger.lifecycle( + "The API documentation of `$module` is not published, since" + + " `$snapshotVersion` is a snapshot version." ) } } } - private fun Project.registerTasks(extension: UpdateGitHubPagesExtension) { + private fun Project.registerTasks(extension: PublishApiDocsExtension) { rootFolder = extension.rootFolder() includedInputs = extension.includedInputs() tasks.registerCopyJavadoc() tasks.registerCopyDokka() - val updatePagesTask = tasks.registerUpdateTask() - updatePagesTask.configure { + val publishTask = tasks.registerPublishTask() + publishTask.configure { dependsOn(copyJavadocDocs) dependsOn(copyHtmlDocs) } @@ -147,7 +157,8 @@ class UpdateGitHubPages : Plugin { private fun TaskContainer.composeJavadocInputs(): List { val inputs = mutableListOf() - inputs.add(dokkaJavadocTask()!!) + // Unlike the HTML docs, the Javadoc is required, so a missing task fails the build. + inputs.add(named("dokkaGeneratePublicationJavadoc")) inputs.addAll(includedInputs) return inputs } @@ -165,22 +176,19 @@ class UpdateGitHubPages : Plugin { private fun TaskContainer.composeDokkaInputs(): List { val inputs = mutableListOf() - - dokkaHtmlTask()?.let { - inputs.add(it) - } + inputs.add(dokkaHtmlTask()) inputs.addAll(includedInputs) return inputs } - private fun TaskContainer.registerUpdateTask(): TaskProvider { - return register(updateGitHubPages) { + private fun TaskContainer.registerPublishTask(): TaskProvider { + return register(publishApiDocs) { group = SpineTaskGroup.name - description = "Publishes the generated documentation to the `gh-pages` branch" + description = "Publishes the API documentation to the `gh-pages` branch" doLast { try { - updateGhPages(project) + publishToGitHubPages(project) } finally { cleanup() } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPagesExtension.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishApiDocsExtension.kt similarity index 70% rename from buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPagesExtension.kt rename to buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishApiDocsExtension.kt index 5a7b1a88f0..fbf0d9bc96 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/UpdateGitHubPagesExtension.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/PublishApiDocsExtension.kt @@ -23,44 +23,44 @@ import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.property /** - * Configures the `updateGitHubPages` extension. + * Configures the `publishApiDocs` extension. */ @Suppress("unused") -fun Project.updateGitHubPages( - action: UpdateGitHubPagesExtension.() -> Unit +fun Project.publishApiDocs( + action: PublishApiDocsExtension.() -> Unit ) { - apply() + apply() - val extension = extensions.getByType(UpdateGitHubPagesExtension::class) + val extension = extensions.getByType(PublishApiDocsExtension::class) extension.action() } /** - * The extension for configuring the [UpdateGitHubPages] plugin. + * The extension for configuring the [PublishApiDocs] plugin. * - * @property rootFolder The root folder of the repository to which the updated `Project` belongs. + * @property rootFolder The root folder of the repository to which the documented `Project` belongs. * @property includeInputs The external inputs, which output should be included - * into the GitHub Pages update. The values are interpreted according to + * into the published documentation. The values are interpreted according to * [Copy.from][org.gradle.api.tasks.Copy.from] specification. * This property is optional. */ -class UpdateGitHubPagesExtension private constructor( - var rootFolder: Property, - var includeInputs: SetProperty +class PublishApiDocsExtension private constructor( + val rootFolder: Property, + val includeInputs: SetProperty ) { internal companion object { /** * The name of the extension. */ - const val name = "updateGitHubPages" + const val name = "publishApiDocs" /** * Creates a new extension and adds it to the passed project. */ - fun createIn(project: Project): UpdateGitHubPagesExtension { + fun createIn(project: Project): PublishApiDocsExtension { val factory = project.objects - val result = UpdateGitHubPagesExtension( + val result = PublishApiDocsExtension( rootFolder = factory.property(File::class), includeInputs = factory.setProperty(Any::class.java) ) @@ -76,8 +76,8 @@ class UpdateGitHubPagesExtension private constructor( fun rootFolder(): File = rootFolder.get() /** - * Returns the external inputs, which results should be included into the - * GitHub Pages update. + * Returns the external inputs, whose results should be included into the + * published documentation. */ fun includedInputs(): Set = includeInputs.get() } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/RepositoryExtensions.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/RepositoryExtensions.kt index 0be661d2e6..8a704cb949 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/RepositoryExtensions.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/RepositoryExtensions.kt @@ -21,17 +21,19 @@ import io.spine.gradle.repo.RepoSlug import org.gradle.api.Project /** - * Clones the current project repository with the branch dedicated to publishing - * documentation to GitHub Pages checked out. + * Clones the branch of the current project repository dedicated to publishing + * documentation to GitHub Pages. * * The repository's GitHub SSH URL is derived from the `REPO_SLUG` environment * variable. The [branch][Branch.documentation] dedicated to publishing documentation - * is automatically checked out in this repository, and created if it does not exist - * yet. A freshly created branch is seeded with a `CNAME` file so that GitHub Pages - * serves the documentation under the `spine.io` custom domain. Also, the username - * and the email of the git user are automatically configured. + * is cloned [sparsely][Repository.Factory.cloneSparsely]: its tip holds the documentation + * of every published version of every module, while a publication touches the folders + * of one module only; the caller [checks them out][Repository.checkOutOnly]. The branch + * is created if it does not exist yet. A freshly created branch is seeded with a `CNAME` + * file so that GitHub Pages serves the documentation under the `spine.io` custom domain. + * Also, the username and the email of the Git user are automatically configured. * - * The username is set to `"UpdateGitHubPages Plugin"`, and the email is derived from + * The username is set to `"PublishApiDocs Plugin"`, and the email is derived from * the `FORMAL_GIT_HUB_PAGES_AUTHOR` environment variable. * * @throws org.gradle.api.GradleException if any of the environment variables described above @@ -40,7 +42,7 @@ import org.gradle.api.Project internal fun Repository.Factory.forPublishingDocumentation(project: Project): Repository { val host = RepoSlug.fromVar().gitHost() - val username = "UpdateGitHubPages Plugin" + val username = "PublishApiDocs Plugin" val userEmail = AuthorEmail.fromVar().toString() val user = UserInfo(username, userEmail) @@ -51,5 +53,11 @@ internal fun Repository.Factory.forPublishingDocumentation(project: Project): Re // custom domain. val initialFiles = mapOf("CNAME" to "spine.io\n") - return clone(project, host, user, branch, initialFiles) + return cloneSparsely( + project, + sshUrl = host, + user = user, + branch = branch, + initialFiles = initialFiles + ) } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/TaskName.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/TaskName.kt index cc453f0bf8..2d7d20de25 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/TaskName.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/TaskName.kt @@ -17,19 +17,25 @@ package io.spine.gradle.github.pages object TaskName { /** - * The name of the task that updates the GitHub Pages. + * The name of the task that publishes the API documentation of a project. */ - const val updateGitHubPages = "updateGitHubPages" + const val publishApiDocs = "publishApiDocs" /** - * The name of the helper task to gather the generated Javadoc format - * documentation generated by Dokka before updating GitHub Pages. + * The name of the task of the root project that publishes the API documentation + * of all the published projects of the build. + */ + const val publishAllApiDocs = "publishAllApiDocs" + + /** + * The name of the helper task to gather the Javadoc format documentation + * generated by Dokka before publishing it to GitHub Pages. */ const val copyJavadocDocs = "copyJavadocDocs" /** * The name of the helper task to gather HTML documentation - * generated by Dokka before updating GitHub Pages. + * generated by Dokka before publishing it to GitHub Pages. */ const val copyHtmlDocs = "copyHtmlDocs" } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/Update.kt b/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/Update.kt deleted file mode 100644 index 29896d159f..0000000000 --- a/buildSrc/src/main/kotlin/io/spine/gradle/github/pages/Update.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2026 CodeMatters, Lda. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under - * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -package io.spine.gradle.github.pages - -import io.spine.gradle.git.Repository -import java.io.File -import java.nio.file.Path -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.FileCollection -import org.gradle.api.logging.Logger - -/** - * Performs the update of GitHub pages. - */ -fun Task.updateGhPages(project: Project) { - val plugin = project.plugins.getPlugin(UpdateGitHubPages::class.java) - - SshKey(plugin.rootFolder, logger).register() - - val repository = Repository.forPublishingDocumentation(project) - - val updateJavadocFormat = - UpdateJavadocFormat(project, plugin.javadocOutputFolder, repository, logger) - - val updateHtmlFormat = - UpdateHtmlFormat(project, plugin.htmlOutputFolder, repository, logger) - - repository.use { - updateJavadocFormat.run() - updateHtmlFormat.run() - repository.push() - } -} - -private abstract class UpdateDocumentation( - private val project: Project, - private val docsSourceFolder: Path, - private val repository: Repository, - private val logger: Logger -) { - - /** - * The folder under the repository's root(`/`) for storing documentation. - * - * The value should not contain any leading or trailing file separators. - * - * The absolute path to the project's documentation is made by appending its - * name to the end, making `/docsDestinationFolder/project.name`. - */ - protected abstract val docsDestinationFolder: String - - /** - * The name of the format of the documentation to update. - * - * This name will appear in logs as part of a message. - */ - protected abstract val formatName: String - - private val mostRecentFolder by lazy { - File("${repository.location}/${docsDestinationFolder}/${project.name}") - } - - private fun log(message: () -> String) { - if (logger.isDebugEnabled) { - logger.debug(message()) - } - } - - fun run() { - val module = project.name - log { "Update of the `$formatName` documentation for the module `$module` started." } - - val documentation = replaceMostRecentDocs() - copyIntoVersionDir(documentation) - - val version = project.version - val updateMessage = - "Update `$formatName` documentation for the module" + - " `$module` with the version `$version`." - repository.commitAllChanges(updateMessage) - - log { "Update of the `$formatName` documentation for `$module` successfully finished." } - } - - private fun replaceMostRecentDocs(): ConfigurableFileCollection { - val generatedDocs = project.files(docsSourceFolder) - - log { "Replacing the most recent `$formatName` documentation in `$mostRecentFolder`." } - copyDocs(generatedDocs, mostRecentFolder) - - return generatedDocs - } - - private fun copyDocs(source: FileCollection, destination: File) { - destination.mkdir() - project.copy { - from(source) - into(destination) - } - } - - private fun copyIntoVersionDir(generatedDocs: ConfigurableFileCollection) { - val versionedDocDir = File("$mostRecentFolder/v/${project.version}") - - log { "Storing the new version of `$formatName` documentation in `${versionedDocDir}`." } - copyDocs(generatedDocs, versionedDocDir) - } -} - -private class UpdateJavadocFormat( - project: Project, - docsSourceFolder: Path, - repository: Repository, - logger: Logger -) : UpdateDocumentation(project, docsSourceFolder, repository, logger) { - - override val docsDestinationFolder: String - get() = "javadoc" - override val formatName: String - get() = "javadoc" -} - -private class UpdateHtmlFormat( - project: Project, - docsSourceFolder: Path, - repository: Repository, - logger: Logger -) : UpdateDocumentation(project, docsSourceFolder, repository, logger) { - - override val docsDestinationFolder: String - get() = "reference" - override val formatName: String - get() = "html" -} diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt index 825d3254fc..d346328537 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/ExcludeInternalDoclet.kt @@ -89,7 +89,7 @@ private fun Project.appendCustomJavadocTask(excludeInternalDoclet: Configuration !it.absolutePath.contains("generated") }.asFileTree - classpath = javadocTask.classpath + classpath = javadocTask.get().classpath options { encoding = JavadocConfig.encoding.name diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt index cb6998de2f..35ff1fbc3a 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/javadoc/JavadocConfig.kt @@ -19,19 +19,25 @@ import java.io.File import org.gradle.api.JavaVersion import org.gradle.api.Project import org.gradle.api.tasks.TaskContainer +import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.external.javadoc.StandardJavadocDocletOptions import productionModules /** - * Finds a [Javadoc] Gradle task by the passed name. + * Finds a [Javadoc] Gradle task by the passed name without creating it. + * + * The task must be registered. */ -fun TaskContainer.javadocTask(named: String) = this.getByName(named) as Javadoc +fun TaskContainer.javadocTask(named: String): TaskProvider = + this.named(named, Javadoc::class.java) /** - * Finds a default [Javadoc] Gradle task. + * Finds a default [Javadoc] Gradle task without creating it. + * + * The task must be registered. */ -fun TaskContainer.javadocTask() = this.getByName("javadoc") as Javadoc +fun TaskContainer.javadocTask(): TaskProvider = javadocTask("javadoc") /** * Javadoc processing settings. @@ -57,15 +63,23 @@ object JavadocConfig { val encoding = Encoding("UTF-8") + /** + * Applies the Javadoc settings to the `javadoc` task of the given [project], + * or disables the task if the project is not a production module. + * + * The task must be registered. It is configured when Gradle creates it, + * so calling this function does not create the task. + */ fun applyTo(project: Project) { - val javadocTask = project.tasks.javadocTask() - if (!isProductionModule(project)) { - javadocTask.enabled = false - return + project.tasks.javadocTask().configure { + if (!isProductionModule(project)) { + enabled = false + return@configure + } + discardJavaModulesInLinks(this) + val docletOptions = options as StandardJavadocDocletOptions + configureDoclet(docletOptions) } - discardJavaModulesInLinks(javadocTask) - val docletOptions = javadocTask.options as StandardJavadocDocletOptions - configureDoclet(docletOptions) } private fun isProductionModule(project: Project) = project.run { diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt index fd5dba83fc..0ade0727b7 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/ProtoExts.kt @@ -20,6 +20,7 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.file.FileTreeElement import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.tasks.TaskCollection import org.gradle.api.tasks.bundling.Jar /** @@ -67,15 +68,18 @@ internal fun FileTreeElement.isGoogleProtoSource(): Boolean { } /** - * The reference to the `generateProto` task of a `main` source set. + * The `generateProto` task of a `main` source set, found without creating it. + * + * The returned collection is live: it holds the task once it is registered, + * and stays empty in a project without Protobuf. */ -internal fun Project.generateProto(): Task? = tasks.findByName("generateProto") +internal fun Project.generateProto(): TaskCollection = + tasks.named { it == "generateProto" } /** - * Makes this [Jar] task depend on the [generateProto] task, if it exists in the same project. + * Makes this [Jar] task depend on the [generateProto] task, once that task is registered + * in the same project. */ internal fun Jar.dependOnGenerateProto() { - project.generateProto()?.let { - this.dependsOn(it) - } + this.dependsOn(project.generateProto()) } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationChecksums.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationChecksums.kt index 7d6cf7e998..d083018b97 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationChecksums.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationChecksums.kt @@ -15,6 +15,7 @@ package io.spine.gradle.publish import io.spine.gradle.SpineTaskGroup +import io.spine.gradle.getOrRegister import java.io.File import java.security.MessageDigest import java.util.Locale @@ -98,6 +99,13 @@ internal object PublicationChecksums { * the root project of a multi-module build, which is where `publish.yml` * expects the manifest. A module configuring the extension for itself also * gets an aggregator of its own, describing only that module. + * + * The registration is idempotent. `spinePublishing { }` configures the extension + * anew on every call, and the same project can be reached by two such calls: + * the root extension lists a module in [SpinePublishing.modulesWithCustomPublishing], + * while the module itself opens the extension with `customPublishing = true` — + * which is what `uber-jar-module.gradle.kts` does. Plain registration would fail + * on the second call, during configuration. */ fun registerTasks(host: Project, published: Set) { val collectors = published.map { registerCollectorIn(it) } @@ -177,10 +185,10 @@ internal object PublicationChecksums { // build directory afterwards. // // The list belongs to the project rather than to this call, because the - // task is registered once while `registerTasks` may run several times — - // see `getOrRegister`. Were the action to close over one call's projects, - // a later call adding a module would register its collector and still - // leave it out of the manifest. + // task is registered once while `registerTasks` may run several times, as + // the documentation of `registerTasks` explains. Were the action to close + // over one call's projects, a later call adding a module would register its + // collector and still leave it out of the manifest. val sources = host.attestationSources() sources += published.map { collectorOutput(it) } val target = host.layout.buildDirectory.file(aggregatePath) @@ -256,27 +264,6 @@ internal object PublicationChecksums { } } -/** - * Returns the task named [name] in this container, registering it with [init] - * if it is not there yet. - * - * `spinePublishing { }` configures the extension anew on every call, and the - * same project can be reached by two such calls: the root extension lists a - * module in [SpinePublishing.modulesWithCustomPublishing], while the module - * itself opens the extension with `customPublishing = true` — which is what - * `uber-jar-module.gradle.kts` does. Plain registration fails on the second - * call, during configuration, so registration has to be idempotent. - */ -private fun TaskContainer.getOrRegister( - name: String, - init: Task.() -> Unit -): TaskProvider = - if (names.contains(name)) { - named(name) - } else { - register(name, init) - } - /** * Returns the `clean` task of this container, if it has one. * @@ -345,7 +332,7 @@ private fun Project.publishedArtifacts(): Map { */ private fun Project.pomFileOf(publication: MavenPublication): File? = tasks.withType(GenerateMavenPom::class.java) - .findByName("generatePomFileFor${publication.taskSuffix()}") + .findByName(publication.pomTaskName()) ?.destination ?.takeIf { it.exists() } @@ -390,6 +377,11 @@ private fun MavenPublication.rejectMavenSnapshot() { private fun MavenPublication.taskSuffix(): String = "${name.replaceFirstChar { it.titlecase(Locale.ROOT) }}Publication" +/** + * Returns the name of the task that writes the POM of this publication. + */ +internal fun MavenPublication.pomTaskName(): String = "generatePomFileFor${taskSuffix()}" + /** * Returns the SHA-256 digest of this file as a lowercase hexadecimal string. * diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationSbom.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationSbom.kt index e32c622fdf..64ea35ec76 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationSbom.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/PublicationSbom.kt @@ -154,7 +154,7 @@ internal object PublicationSbom { * Whether a project is a JVM or a multiplatform module is known only once it is * evaluated, so the registration waits until then. It is idempotent, because * `spinePublishing { }` may reach one project twice, as the documentation of - * `getOrRegister` in `PublicationChecksums.kt` explains. + * [PublicationChecksums.registerTasks] explains. */ fun registerTasks(host: Project, published: Set) { published.forEach { project -> diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt index 4af48a5633..31a7a9fcd4 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt @@ -17,6 +17,7 @@ package io.spine.gradle.publish import io.spine.dependency.local.Spine +import io.spine.gradle.github.pages.PublishAllApiDocs import io.spine.gradle.repo.Repository import java.util.Locale import org.gradle.api.Project @@ -332,6 +333,9 @@ open class SpinePublishing(private val project: Project) { // After `PublicationSbom`: its `projectsEvaluated` callback adds the SBOM artifacts // that the one of `PublicationState` collects. PublicationState.registerOn(project.rootProject) + // `publishAllApiDocs` is registered here, as this is where the published modules + // are known. It is not tied to `publish`: neither task depends on the other. + PublishAllApiDocs.registerIn(project.rootProject, projectsToPublish) } /** diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt index f17170a071..030d03470c 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/StandardJavaPublicationHandler.kt @@ -17,9 +17,11 @@ package io.spine.gradle.publish import io.spine.gradle.repo.Repository import org.gradle.api.Project import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.maven.tasks.GenerateMavenPom import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.withType /** * A publication for a typical Java project. @@ -58,6 +60,12 @@ class StandardJavaPublicationHandler private constructor( */ const val PUBLICATION_NAME = "mavenJava" + /** + * The packaging of a POM whose main artifact is a JAR, + * and the extension of that artifact. + */ + private const val JAR = "jar" + override fun create( project: Project, destinations: Set, @@ -108,6 +116,7 @@ class StandardJavaPublicationHandler private constructor( val javaComponent = project.components.findByName("java") javaComponent?.let { from(it) + declareJarPackaging() } /* @@ -118,4 +127,63 @@ class StandardJavaPublicationHandler private constructor( artifact(it) } } + + /** + * Sets the `jar` packaging of the POM, which Gradle would calculate from + * the JAR of the `java` component. + * + * Until the packaging is set, reading it makes Gradle calculate it from the artifacts + * of the publication, which creates the tasks producing them. [PublicationSbom] reads + * the packaging in every build, so that the SBOM it adds does not change it. + * + * Writing the POM fails if the publication turns out to have a main artifact other + * than one JAR, so that a misleading POM is not published. The artifacts are checked + * when the POM is written, so that the check neither creates the tasks producing them + * nor takes the artifacts of the component into the publication before it is final. + * The result is an input of the task, so an up-to-date task does not hide a mismatch. + */ + private fun MavenPublication.declareJarPackaging() { + pom.packaging = JAR + val publication = this + val pomTask = pomTaskName() + project.tasks.withType() + .named { it == pomTask } + .configureEach { + val mismatch = project.provider { publication.jarPackagingMismatch() } + inputs.property("jarPackagingMismatch", mismatch.orElse("")) + doFirst { + mismatch.orNull?.let { throw IllegalStateException(it) } + } + } + } + + /** + * Describes how this publication contradicts the `jar` packaging, or returns `null` + * if it does not. + * + * The publication contradicts it if it keeps the `jar` packaging, but does not have + * exactly one main artifact, a JAR, the SBOM not counted. Such is the case if + * the build gives the `jar` task a classifier, or adds an artifact without one. + */ + private fun MavenPublication.jarPackagingMismatch(): String? { + if (pom.packaging != JAR) { + // The build has set a packaging of its own. + return null + } + val mainArtifacts = artifacts.filter { + it.classifier.isNullOrEmpty() && it.extension != PublicationSbom.sbomExtension + } + return if (mainArtifacts.singleOrNull()?.extension == JAR) { + null + } else { + val found = if (mainArtifacts.isEmpty()) { + "no main artifact" + } else { + "the main artifacts ${mainArtifacts.map { it.file.name }}" + } + "The POM of the `$name` publication of `${project.path}` declares the `jar`" + + " packaging, but the publication has $found. Please keep the `jar`" + + " task without a classifier, or publish the module with custom publishing." + } + } } diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt index 65d8d69a14..c2af416980 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/license/LicenseReporter.kt @@ -20,7 +20,6 @@ import com.github.jk1.license.LicenseReportPlugin import io.spine.dependency.local.Spine import io.spine.gradle.SpineTaskGroup import io.spine.gradle.applyPlugin -import io.spine.gradle.getTask import io.spine.gradle.report.license.Paths.outputFilename import io.spine.gradle.report.license.Paths.relativePath import java.io.File @@ -130,10 +129,13 @@ object LicenseReporter { group = SpineTaskGroup.name description = "Merges per-project license reports into a single repository-wide report" val consolidationTask = this - val assembleTask = project.getTask("assemble") + val assembleTask = project.tasks.named("assemble") val sourceProjects: Iterable = sourceProjects(rootProject) sourceProjects.forEach { - val perProjectTask = it.getTask(projectTaskName) + // Obtained rather than configured lazily: in a single-project build, this + // action belongs to the container that holds the task, and Gradle forbids + // configuring a task of a container from such an action. + val perProjectTask = it.tasks.named(projectTaskName).get() consolidationTask.dependsOn(perProjectTask) perProjectTask.dependsOn(assembleTask) } @@ -142,8 +144,9 @@ object LicenseReporter { } dependsOn(assembleTask) } - project.getTask("build") - .finalizedBy(mergeTask) + project.tasks.named("build").configure { + finalizedBy(mergeTask) + } } /** diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt index 479e4f5f20..234a3af44f 100644 --- a/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt +++ b/buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt @@ -100,11 +100,9 @@ object PomGenerator { writer.writeTo(pomFile) } - val assembleTask = project.tasks.findByName("assemble")!! - dependsOn(assembleTask) + dependsOn(project.tasks.named("assemble")) } - val buildTask = project.tasks.findByName("build")!! - buildTask.finalizedBy(task) + project.tasks.named("build").configure { finalizedBy(task) } } } diff --git a/buildSrc/src/main/kotlin/jvm-module.gradle.kts b/buildSrc/src/main/kotlin/jvm-module.gradle.kts index 830335fbd8..11777117af 100644 --- a/buildSrc/src/main/kotlin/jvm-module.gradle.kts +++ b/buildSrc/src/main/kotlin/jvm-module.gradle.kts @@ -26,7 +26,7 @@ import io.spine.dependency.local.Reflect import io.spine.dependency.test.Jacoco import io.spine.gradle.SpineTaskGroup import io.spine.gradle.checkstyle.CheckStyleConfig -import io.spine.gradle.github.pages.updateGitHubPages +import io.spine.gradle.github.pages.publishApiDocs import io.spine.gradle.javac.configureErrorProne import io.spine.gradle.javac.configureJavac import io.spine.gradle.javadoc.JavadocConfig @@ -58,7 +58,7 @@ project.run { val generatedDir = "$projectDir/generated" setTaskDependencies(generatedDir) - configureGitHubPages() + configureApiDocs() } typealias Module = Project @@ -150,19 +150,21 @@ fun Module.setTaskDependencies(generatedDir: String) { clean.configure { dependsOn(cleanGenerated) } - - project.afterEvaluate { - val publish = tasks.findByName("publish") - publish?.dependsOn("${project.path}:updateGitHubPages") - } } afterEvaluate { configureTaskDependencies() } } -fun Module.configureGitHubPages() { - updateGitHubPages { +/** + * Applies the plugin that registers the `publishApiDocs` task of this module. + * + * The `publish-api-docs.yml` workflow runs the task through `publishAllApiDocs` of + * the root project, which depends on it for the published modules only. It is not + * a part of `publish`. + */ +fun Module.configureApiDocs() { + publishApiDocs { rootFolder.set(rootDir) } } diff --git a/buildSrc/src/main/kotlin/module.gradle.kts b/buildSrc/src/main/kotlin/module.gradle.kts index b0e9c8bc9f..48fc38c9fe 100644 --- a/buildSrc/src/main/kotlin/module.gradle.kts +++ b/buildSrc/src/main/kotlin/module.gradle.kts @@ -47,7 +47,7 @@ import io.spine.dependency.local.Time import io.spine.dependency.local.ToolBase import io.spine.dependency.local.Validation import io.spine.dependency.test.JUnit -import io.spine.gradle.github.pages.updateGitHubPages +import io.spine.gradle.github.pages.publishApiDocs import io.spine.gradle.javac.configureErrorProne import io.spine.gradle.javac.configureJavac import io.spine.gradle.javadoc.JavadocConfig @@ -237,11 +237,7 @@ fun Module.configureProtoc() { * Configures documentation publishing for this subproject. */ fun Module.setupDocPublishing() { - updateGitHubPages { + publishApiDocs { rootFolder.set(rootDir) } - - tasks.named("publish") { - dependsOn("${project.path}:updateGitHubPages") - } } diff --git a/buildSrc/src/test/kotlin/BuildExtensionsSpec.kt b/buildSrc/src/test/kotlin/BuildExtensionsSpec.kt new file mode 100644 index 0000000000..4ac7a55670 --- /dev/null +++ b/buildSrc/src/test/kotlin/BuildExtensionsSpec.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import org.gradle.api.DefaultTask +import org.gradle.api.InvalidUserDataException +import org.gradle.api.Project +import org.gradle.api.tasks.JavaExec +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +@DisplayName("Build extensions should") +internal class BuildExtensionsSpec { + + private val project: Project = ProjectBuilder.builder().build() + + @Nested inner class + `set remote debug options` { + + @Test + fun `on a JavaExec task once it is created`() { + project.tasks.register("launch", JavaExec::class.java) + + project.setRemoteDebug("launch") + + val debug = project.tasks.named("launch", JavaExec::class.java).get().debugOptions + debug.enabled.get() shouldBe true + debug.port.get() shouldBe BuildSettings.REMOTE_DEBUG_PORT + } + + @Test + fun `without creating the task`() { + var created = false + project.tasks.register("launch", JavaExec::class.java) { created = true } + + project.setRemoteDebug("launch") + + created shouldBe false + } + + @Test + fun `failing at once for a missing task`() { + shouldThrow { + project.setRemoteDebug("missing") + } + } + + @Test + fun `failing at once for a task other than JavaExec`() { + var created = false + project.tasks.register("plain", DefaultTask::class.java) { created = true } + + shouldThrow { + project.setRemoteDebug("plain") + } + created shouldBe false + } + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/CliSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/CliSpec.kt new file mode 100644 index 0000000000..69f03ba85b --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/CliSpec.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import java.io.File +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +@DisplayName("`Cli` should") +internal class CliSpec { + + @TempDir + lateinit var workingFolder: File + + @Test + fun `return the output as the command wrote it, line breaks included`() { + // `printf` turns the `\n` sequences of its format into line breaks. + Cli(workingFolder).execute("printf", "one\\ntwo\\n") shouldBe "one\ntwo\n" + } + + @Test + fun `run a command assembled as a list`() { + Cli(workingFolder).execute(listOf("printf", "%s", "one")) shouldBe "one" + } + + @Test + fun `fail with the exit code and the error output of a failed command`() { + val error = shouldThrow { + Cli(workingFolder).execute("sh", "-c", "echo 'went wrong' >&2; exit 3") + } + + error.message shouldContain "exit code 3" + error.message shouldContain "went wrong" + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/docs/DokkaSetupIgTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/docs/DokkaSetupIgTest.kt index 6ec5effe99..150fe348f3 100644 --- a/buildSrc/src/test/kotlin/io/spine/gradle/docs/DokkaSetupIgTest.kt +++ b/buildSrc/src/test/kotlin/io/spine/gradle/docs/DokkaSetupIgTest.kt @@ -52,7 +52,7 @@ private const val PROBE_PATH = ":dokka-sample:dokkaGeneratePublicationProbe" * The build publishes a `javadocJar` that depends on the probe, just as the `javadocJar` * that `spinePublishing` creates depends on `dokkaGeneratePublicationJavadoc`. The build * publishes to a file-based repository and to its own Maven Local repository, so it never - * touches `~/.m2`. The `updateGitHubPages` task of the build stands in for the one that + * touches `~/.m2`. The `publishApiDocs` task of the build stands in for the one that * copies the Dokka output to GitHub Pages. * * The project with the probe is named `dokka-sample`, like the modules of `dokka-tools`, @@ -105,10 +105,10 @@ internal class DokkaSetupIgTest { } @Test - fun `run a Dokka task when the build updates GitHub Pages and publishes to Maven Local`() { + fun `run a Dokka task when the build publishes the API docs and publishes to Maven Local`() { runStoringThenReusing( ":dokka-sample:publishToMavenLocal", - ":dokka-sample:updateGitHubPages" + ":dokka-sample:publishApiDocs" ) { it.probeOutcome shouldBe SUCCESS } @@ -227,7 +227,7 @@ internal class DokkaSetupIgTest { } } - tasks.register("updateGitHubPages") { + tasks.register("publishApiDocs") { dependsOn(docsProbe) } """.trimIndent() diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/git/RepositorySpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/git/RepositorySpec.kt new file mode 100644 index 0000000000..e84919f555 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/git/RepositorySpec.kt @@ -0,0 +1,313 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.git + +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.file.shouldExist +import io.kotest.matchers.file.shouldNotExist +import io.kotest.matchers.shouldBe +import io.spine.gradle.Cli +import io.spine.gradle.github.pages.docsCheckoutPatterns +import java.io.File +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * Verifies a [sparse clone][Repository.Factory.cloneSparsely] against a local bare + * repository, reached by a `file:` URL the way Git reaches a remote one: with a plain + * path, Git would ignore `--depth` and `--filter`. + * + * The `gh-pages` branch of the repository holds a top-level `CNAME`, the documentation + * of the module `m`, with the folders of its versions `1.0.0` and `1.1.0` and a file + * next to them, and the documentation of the module `other`. The tests check out + * the paths that [docsCheckoutPatterns] selects for `m`. + */ +@DisplayName("A sparse clone of a `Repository` should") +internal class RepositorySpec { + + @TempDir + lateinit var tempDir: File + + private lateinit var remote: File + + /** A full clone of the [remote], standing in for the publications of other modules. */ + private lateinit var seed: File + + private lateinit var project: Project + + private val user = UserInfo("Docs Publisher", "docs@example.com") + + @BeforeEach + fun createRemote() { + remote = tempDir.resolve("remote.git") + git(tempDir, "init", "--bare", "--initial-branch=master", remote.path) + // Allow partial clones, with the blobs fetched on demand, as GitHub does. + git(remote, "config", "uploadpack.allowFilter", "true") + git(remote, "config", "uploadpack.allowAnySHA1InWant", "true") + + seed = tempDir.resolve("seed") + git(tempDir, "clone", remote.path, seed.path) + git(seed, "config", "user.name", "Seed") + git(seed, "config", "user.email", "seed@example.com") + seed.write("README.md", "The sources.") + seed.commit("Add the sources.") + git(seed, "push", "origin", "HEAD:refs/heads/master") + + git(seed, "switch", "--orphan", "gh-pages") + seed.write("CNAME", "spine.io\n") + seed.write("javadoc/m/index.html", "m, 1.1.0") + seed.write("javadoc/m/v/1.0.0/index.html", "m, 1.0.0") + seed.write("javadoc/m/v/1.1.0/index.html", "m, 1.1.0") + seed.write("javadoc/m/v/notes.txt", "Not a version.") + seed.write("javadoc/other/index.html", "other, 1.1.0") + seed.commit("Publish the documentation.") + git(seed, "push", "origin", "gh-pages") + + project = ProjectBuilder.builder() + .withProjectDir(tempDir.resolve("project")) + .build() + } + + @Test + fun `check out only the top-level files`() { + cloneDocs().use { repo -> + val root = repo.location.toFile() + + root.resolve("CNAME").shouldExist() + root.resolve("javadoc").shouldNotExist() + } + } + + @Test + fun `check out only the paths of a publication`() { + cloneDocs().use { repo -> + repo.checkOutOnly(docsCheckoutPatterns("javadoc/m", "2.0.0")) + val root = repo.location.toFile() + + root.resolve("javadoc/m/index.html").shouldExist() + root.resolve("javadoc/m/v/1.0.0").shouldNotExist() + root.resolve("javadoc/other").shouldNotExist() + } + } + + /** + * Publishing a version again, as a re-run of a failed publication does, writes + * into the folder of that version, which is in the branch already. + */ + @Test + fun `check out and update the folder of a version published before`() { + cloneDocs().use { repo -> + repo.checkOutOnly(docsCheckoutPatterns("javadoc/m", "1.1.0")) + val root = repo.location.toFile() + + root.resolve("javadoc/m/v/1.1.0/index.html").shouldExist() + root.resolve("javadoc/m/v/1.0.0").shouldNotExist() + + root.write("javadoc/m/v/1.1.0/index.html", "m, 1.1.0, again") + repo.commitAllChanges("Publish `m` 1.1.0 again.") + + changedPaths(root) shouldContainExactly listOf("M\tjavadoc/m/v/1.1.0/index.html") + } + } + + @Test + fun `list the folders that are not checked out, without the files`() { + cloneDocs().use { repo -> + repo.listFolders("javadoc/m/v") shouldContainExactly listOf("1.0.0", "1.1.0") + repo.listFolders("javadoc/absent").shouldBeEmpty() + } + } + + @Test + fun `commit only the changed paths, keeping those not checked out`() { + cloneDocs().use { repo -> + repo.checkOutOnly(docsCheckoutPatterns("javadoc/m", "2.0.0")) + val root = repo.location.toFile() + root.write("javadoc/m/index.html", "m, 2.0.0") + root.write("javadoc/m/v/2.0.0/index.html", "m, 2.0.0") + + repo.commitAllChanges("Publish `m` 2.0.0.") + + changedPaths(root) shouldContainExactlyInAnyOrder listOf( + "M\tjavadoc/m/index.html", + "A\tjavadoc/m/v/2.0.0/index.html" + ) + } + } + + @Test + fun `push onto a commit that another publication pushed meanwhile`() { + cloneDocs().use { repo -> + repo.checkOutOnly(docsCheckoutPatterns("javadoc/m", "2.0.0")) + repo.location.toFile().write("javadoc/m/v/2.0.0/index.html", "m, 2.0.0") + repo.commitAllChanges("Publish `m` 2.0.0.") + + seed.write("javadoc/other/index.html", "other, 2.0.0") + seed.commit("Publish `other` 2.0.0.") + git(seed, "push", "origin", "gh-pages") + + repo.push() + } + + val log = git(remote, "log", "--format=%s", "gh-pages").lines() + log shouldContain "Publish `m` 2.0.0." + log shouldContain "Publish `other` 2.0.0." + git(remote, "show", "gh-pages:javadoc/m/v/2.0.0/index.html") shouldBe "m, 2.0.0" + git(remote, "show", "gh-pages:javadoc/other/index.html") shouldBe "other, 2.0.0" + git(remote, "show", "gh-pages:javadoc/m/v/1.0.0/index.html") shouldBe "m, 1.0.0" + } + + @Test + fun `create a branch that does not exist yet, and push to it`() { + val cname = "spine.io\n" + + cloneSite(initialFiles = mapOf("CNAME" to cname)).use { repo -> + git(remote, "ls-tree", "--name-only", "site").trim() shouldBe "CNAME" + git(remote, "show", "site:CNAME") shouldBe cname + + repo.checkOutOnly(docsCheckoutPatterns("javadoc/m", "1.0.0")) + repo.location.toFile().write("javadoc/m/index.html", "m, 1.0.0") + repo.commitAllChanges("Publish `m` 1.0.0.") + repo.push() + } + + git(remote, "show", "site:javadoc/m/index.html") shouldBe "m, 1.0.0" + } + + @Test + fun `adopt a branch that another publication created first, and push to it`() { + val preparedCommit = raceForSiteBranch() + + cloneSite(initialFiles = mapOf("CNAME" to "spine.io\n")).use { repo -> + val root = repo.location.toFile() + git(root, "rev-parse", "HEAD").trim() shouldBe preparedCommit + git(root, "rev-parse", "--abbrev-ref", "site@{upstream}").trim() shouldBe + "origin/site" + + raceHook.delete() + repo.checkOutOnly(docsCheckoutPatterns("javadoc/m", "1.0.0")) + root.write("javadoc/m/index.html", "m, 1.0.0") + repo.commitAllChanges("Publish `m` 1.0.0.") + repo.push() + } + + git(remote, "show", "site:javadoc/m/index.html") shouldBe "m, 1.0.0" + } + + /** + * The full [clone][Repository.Factory.clone] shares the adoption of a branch with + * the sparse one. + */ + @Test + fun `adopt a branch that another publication created first, in a full clone`() { + val preparedCommit = raceForSiteBranch() + + Repository.clone( + project, + sshUrl = url, + user = user, + branch = "site", + initialFiles = mapOf("CNAME" to "spine.io\n") + ).use { repo -> + val root = repo.location.toFile() + git(root, "rev-parse", "HEAD").trim() shouldBe preparedCommit + git(root, "rev-parse", "--abbrev-ref", "site@{upstream}").trim() shouldBe + "origin/site" + } + } + + /** + * Makes another publication create the `site` branch after this one has found it + * missing, and push first. + * + * The `pre-receive` hook of the [remote] stands in for that publication: it creates + * the branch from a prepared commit, and rejects the push of this publication. + * + * @return the prepared commit. + */ + private fun raceForSiteBranch(): String { + git(seed, "switch", "--orphan", "prepared") + seed.write("CNAME", "spine.io\n") + seed.commit("Initialize the `site` branch.") + git(seed, "push", "origin", "prepared:refs/heads/prepared-site") + // Git keeps the objects a push brings in a quarantine until the hook accepts + // them, and refuses to update a reference from there, so the hook leaves it. + raceHook.apply { + writeText( + """ + #!/bin/sh + env -u GIT_QUARANTINE_PATH -u GIT_OBJECT_DIRECTORY \ + -u GIT_ALTERNATE_OBJECT_DIRECTORIES \ + git update-ref refs/heads/site refs/heads/prepared-site + exit 1 + """.trimIndent() + ) + setExecutable(true) + } + return git(seed, "rev-parse", "HEAD").trim() + } + + private val raceHook: File + get() = remote.resolve("hooks/pre-receive") + + private val url: String + get() = remote.toPath().toUri().toString() + + /** + * Obtains the paths the last commit in the [root] of a clone changed, each + * with the letter of its change. + */ + private fun changedPaths(root: File): List = + git(root, "show", "--name-status", "--format=", "HEAD") + .lines() + .filter { it.isNotBlank() } + + private fun cloneDocs(): Repository = + Repository.cloneSparsely(project, sshUrl = url, user = user, branch = "gh-pages") + + /** + * Clones the `site` branch, which the [remote] does not have at first. + */ + private fun cloneSite(initialFiles: Map): Repository = + Repository.cloneSparsely( + project, + sshUrl = url, + user = user, + branch = "site", + initialFiles = initialFiles + ) +} + +private fun git(directory: File, vararg arguments: String): String = + Cli(directory).execute("git", *arguments) + +private fun File.write(path: String, content: String) { + resolve(path).apply { + parentFile.mkdirs() + writeText(content) + } +} + +private fun File.commit(message: String) { + git(this, "add", "--all") + git(this, "commit", "--message=$message") +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/github/pages/NewestAboveSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/github/pages/NewestAboveSpec.kt new file mode 100644 index 0000000000..ad5f631703 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/github/pages/NewestAboveSpec.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.github.pages + +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +/** + * Verifies how the publication of the API documentation decides whether the most + * recent documentation of a module may be replaced by that of the published version. + */ +@DisplayName("`newestAbove` should") +internal class NewestAboveSpec { + + @Test + fun `find no newer version when nothing is published`() { + emptyList().newestAbove("2.0.0").shouldBeNull() + } + + @Test + fun `find no newer version for the newest one`() { + listOf("1.9.0", "2.0.0-SNAPSHOT.575", "2.0.0-SNAPSHOT.99") + .newestAbove("2.0.0").shouldBeNull() + } + + @Test + fun `find no newer version for a version published again`() { + listOf("1.9.0", "2.0.0").newestAbove("2.0.0").shouldBeNull() + } + + @Test + fun `find the newest version above an older one`() { + listOf("1.9.0", "2.1.0", "2.0.0").newestAbove("1.9.5") shouldBe "2.1.0" + } + + @Test + fun `compare versions by their meaning rather than as text`() { + listOf("2.9.0").newestAbove("2.10.0").shouldBeNull() + listOf("2.10.0").newestAbove("2.9.0") shouldBe "2.10.0" + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/github/pages/PublishApiDocsIgTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/github/pages/PublishApiDocsIgTest.kt new file mode 100644 index 0000000000..9fc3a345f1 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/github/pages/PublishApiDocsIgTest.kt @@ -0,0 +1,349 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.github.pages + +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.collections.shouldNotContain +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import java.io.File +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * Verifies which tasks the `publishAllApiDocs` task of the root project, registered by + * `spinePublishing { }`, brings into a build: the `publishApiDocs` tasks of the projects. + * + * The builds that would publish documentation run with `--dry-run`, so that nothing + * is pushed to GitHub Pages. The builds of a snapshot version run for real, as they + * publish nothing. + * + * In the multi-module build, `api` and `backend` are published and document + * themselves, `testing` documents itself, but is not published, as a module of + * integration tests, and `bare` is published, but does not document itself. + * + * No build applies Dokka. A task named like the Dokka one stands in for it, as + * the plugin and the `javadocJar` of `spinePublishing` look the task up by name. + */ +@DisplayName("`publishAllApiDocs` task should") +internal class PublishApiDocsIgTest { + + @TempDir + lateinit var projectDir: File + + @Test + fun `publish the docs of each published module that documents itself`() { + writeMultiModuleBuild(documented = setOf("api", "backend", "testing")) + + val planned = dryRun(":$aggregate") + + planned shouldContain ":$aggregate" + planned.docsTasks() shouldContainExactlyInAnyOrder listOf( + ":api:$taskName", + ":backend:$taskName" + ) + // `testing` has the task, so the aggregate left it out only for not being published. + dryRun(":testing:$taskName").docsTasks() shouldBe listOf(":testing:$taskName") + } + + /** + * `lib` opens `spinePublishing { }` on its own, so the second call reaches + * the task of the root project while `lib` is evaluated, with projects of its own. + */ + @Test + fun `publish the docs of a module that configures its own publishing`() { + file("settings.gradle.kts").writeText( + """ + rootProject.name = "own-publishing-sample" + include("api", "lib") + """.trimIndent() + ) + file("build.gradle.kts").writeText( + """ + ${buildscript()} + + import io.spine.gradle.github.pages.publishApiDocs + import io.spine.gradle.publish.spinePublishing + + allprojects { + group = "io.spine.sample" + version = "1.0.0" + } + + subprojects { + apply(plugin = "java-library") + $generatorStub + } + + project("api").publishApiDocs { + rootFolder.set(rootDir) + } + + spinePublishing { + modules = setOf("api") + destinations = emptySet() + } + """.trimIndent() + ) + file("lib/build.gradle.kts").apply { + parentFile.mkdirs() + writeText( + """ + import io.spine.gradle.github.pages.publishApiDocs + import io.spine.gradle.publish.spinePublishing + + publishApiDocs { + rootFolder.set(rootDir) + } + + spinePublishing { + destinations = emptySet() + } + """.trimIndent() + ) + } + file("api").mkdirs() + + dryRun(":$aggregate").docsTasks() shouldContainExactlyInAnyOrder listOf( + ":api:$taskName", + ":lib:$taskName" + ) + } + + @Test + fun `not be a part of the publication to Maven repositories`() { + writeMultiModuleBuild(documented = setOf("api", "backend", "testing")) + + val planned = dryRun("publish") + + planned shouldContain ":api:publish" + planned shouldNotContain ":$aggregate" + planned.docsTasks().shouldBeEmpty() + } + + @Test + fun `do nothing in a build that documents no module`() { + writeMultiModuleBuild(documented = emptySet()) + + val result = runner(":$aggregate").build() + + result.tasks.map { it.path } shouldBe listOf(":$aggregate") + result.task(":$aggregate")?.outcome shouldBe TaskOutcome.UP_TO_DATE + } + + @Test + fun `publish the docs of the project in a single-project build`() { + writeSingleProjectBuild(version = "1.0.0") + + val planned = dryRun(":$aggregate") + + planned.docsTasks() shouldBe listOf(":$taskName") + planned shouldContain ":${TaskName.copyJavadocDocs}" + } + + @Test + fun `publish no docs of a snapshot version`() { + writeMultiModuleBuild(documented = setOf("api", "backend", "testing"), version = snapshot) + + val result = runner(":$aggregate").build() + + val paths = result.tasks.map { it.path } + paths.docsTasks() shouldContainExactlyInAnyOrder listOf( + ":api:$taskName", + ":backend:$taskName" + ) + paths shouldNotContain ":api:${TaskName.copyJavadocDocs}" + result.output shouldContain "`$snapshot` is a snapshot version" + } + + /** + * Only the publication to GitHub Pages skips a snapshot version. The archives of + * its documentation, built by Dokka, are still published to Maven repositories. + */ + @Test + fun `leave the docs archives of a snapshot version to the Maven publication`() { + writeMultiModuleBuild(documented = setOf("api"), version = snapshot) + file("build.gradle.kts").appendText( + "\n" + """ + project("api") { + apply(plugin = "maven-publish") + the().repositories.maven { + name = "remote" + url = uri(rootDir.resolve("remote-repo")) + } + } + """.trimIndent() + ) + + val planned = dryRun(":api:publish") + + planned shouldContain ":api:javadocJar" + planned shouldContain ":api:dokkaGeneratePublicationJavadoc" + planned.docsTasks().shouldBeEmpty() + } + + @Test + fun `publish no docs of a snapshot version in a single-project build`() { + writeSingleProjectBuild(version = snapshot) + + val result = runner(":$aggregate").build() + + result.tasks.map { it.path } shouldContainExactlyInAnyOrder listOf( + ":$taskName", + ":$aggregate" + ) + result.output shouldContain "`$snapshot` is a snapshot version" + } + + private fun writeSingleProjectBuild(version: String) { + file("settings.gradle.kts").writeText("""rootProject.name = "single-sample"""") + file("build.gradle.kts").writeText( + """ + ${buildscript()} + + import io.spine.gradle.github.pages.publishApiDocs + import io.spine.gradle.publish.spinePublishing + + group = "io.spine.sample" + version = "$version" + + apply(plugin = "java-library") + $generatorStub + + publishApiDocs { + rootFolder.set(rootDir) + } + + spinePublishing { + destinations = emptySet() + } + """.trimIndent() + ) + } + + private fun writeMultiModuleBuild(documented: Set, version: String = "1.0.0") { + val modules = listOf("api", "backend", "bare", "testing") + modules.forEach { file(it).mkdirs() } + file("settings.gradle.kts").writeText( + """ + rootProject.name = "docs-sample" + include(${modules.joinToString { "\"$it\"" }}) + """.trimIndent() + ) + file("build.gradle.kts").writeText( + """ + ${buildscript()} + + import io.spine.gradle.github.pages.publishApiDocs + import io.spine.gradle.publish.spinePublishing + + allprojects { + group = "io.spine.sample" + version = "$version" + } + + subprojects { + apply(plugin = "java-library") + $generatorStub + } + + listOf(${documented.joinToString { "\"$it\"" }}).forEach { + project(it).publishApiDocs { + rootFolder.set(rootDir) + } + } + + spinePublishing { + modules = setOf("api", "backend", "bare") + destinations = emptySet() + } + """.trimIndent() + ) + } + + /** + * Runs the build with `--dry-run` and returns the paths of the tasks + * it would run. + * + * TestKit reports no tasks for a dry run, so they are read from the output, + * which lists each of them as ` SKIPPED`. + */ + private fun dryRun(task: String): List = + runner(task, "--dry-run").build().output.lines() + .filter { it.startsWith(":") && it.endsWith(" SKIPPED") } + .map { it.substringBefore(' ') } + + private fun runner(vararg args: String): GradleRunner = + GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(*args, "--stacktrace") + + /** + * Selects the paths of the tasks that publish documentation. + */ + private fun List.docsTasks(): List = + filter { it.substringAfterLast(':') == taskName } + + private fun file(relativePath: String): File = projectDir.resolve(relativePath) + + private fun buildscript(): String = + """ + buildscript { + dependencies { + classpath(files( + ${buildSrcClasspath()} + )) + } + } + """.trimIndent() + + /** + * Renders the classpath with the production classes of `buildSrc` as + * arguments of `files(...)`, for injection into the build script classpath + * of the generated build. + * + * The classpath comes from the `test` task in `buildSrc/build.gradle.kts`. + */ + private fun buildSrcClasspath(): String { + val classpath = requireNotNull(System.getProperty("buildSrc.classpath")) { + "The `buildSrc.classpath` system property is not set." + + " It is supplied by the `test` task in `buildSrc/build.gradle.kts`." + } + return classpath.split(File.pathSeparator).joinToString(",\n") { + " \"${File(it).invariantSeparatorsPath}\"" + } + } + + private companion object { + + const val taskName = TaskName.publishApiDocs + + const val aggregate = TaskName.publishAllApiDocs + + /** A version in the format the Spine SDK publishes from every merge. */ + const val snapshot = "1.0.0-SNAPSHOT.1" + + /** + * A task standing in for the Dokka one whose output the plugin copies, and on + * which the `javadocJar` of `spinePublishing` depends. + */ + const val generatorStub = """tasks.register("dokkaGeneratePublicationJavadoc")""" + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/javadoc/JavadocConfigSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/javadoc/JavadocConfigSpec.kt new file mode 100644 index 0000000000..19b9e97316 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/javadoc/JavadocConfigSpec.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.javadoc + +import io.kotest.matchers.shouldBe +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("`JavadocConfig` should") +internal class JavadocConfigSpec { + + private val project: Project = ProjectBuilder.builder().build().also { + it.pluginManager.apply(JavaPlugin::class.java) + } + + /** + * The project of a build without subprojects is not a production module, + * so its `javadoc` task is disabled. + */ + @Test + fun `configure the javadoc task once Gradle creates it`() { + var created = false + project.tasks.named { it == "javadoc" }.configureEach { created = true } + + JavadocConfig.applyTo(project) + + created shouldBe false + project.tasks.javadocTask().get().enabled shouldBe false + } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/ProtoExtsSpec.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/ProtoExtsSpec.kt new file mode 100644 index 0000000000..3859d28ab3 --- /dev/null +++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/ProtoExtsSpec.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2026 CodeMatters, Lda. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package io.spine.gradle.publish + +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import org.gradle.api.Project +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.tasks.bundling.Jar +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("Proto extensions should") +internal class ProtoExtsSpec { + + private val project: Project = ProjectBuilder.builder().build().also { + it.pluginManager.apply(BasePlugin::class.java) + } + + private val jar: Jar = project.tasks.register("protoJar", Jar::class.java).get() + + @Test + fun `make a JAR depend on generateProto registered later, without creating it`() { + jar.dependOnGenerateProto() + var created = false + project.tasks.register("generateProto") { created = true } + + created shouldBe false + jar.dependencyNames() shouldContainExactly listOf("generateProto") + } + + @Test + fun `add no dependency to a JAR in a project without generateProto`() { + jar.dependOnGenerateProto() + + jar.dependencyNames().shouldBeEmpty() + } + + private fun Jar.dependencyNames(): List = + taskDependencies.getDependencies(this).map { it.name } +} diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/PublicationSbomIgTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/PublicationSbomIgTest.kt index d64306ebb6..60e0ff7445 100644 --- a/buildSrc/src/test/kotlin/io/spine/gradle/publish/PublicationSbomIgTest.kt +++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/PublicationSbomIgTest.kt @@ -30,6 +30,7 @@ import io.kotest.matchers.file.shouldNotExist import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldNotContain import java.io.File import java.util.jar.JarOutputStream @@ -199,6 +200,73 @@ internal class PublicationSbomIgTest { packagingOf("osgi", osgiBundle) shouldBe "bundle" } + /** + * Until the packaging of a POM is set, reading it creates the tasks that produce + * the artifacts of the publication. A standard publication sets its packaging + * as it is created, so a build that does not publish creates none of them. + * + * The init script reports the tasks created before the task graph is ready, which + * excludes those that the fixture creates later, to report the SBOMs. + */ + @Test + fun `create no archive task of a standard publication in a build that does not publish`() { + val initScript = file("report-created-tasks.gradle") + initScript.writeText( + """ + def graphReady = false + gradle.taskGraph.whenReady { graphReady = true } + allprojects { + tasks.configureEach { task -> + if (!graphReady) println("$createdPrefix${'$'}{task.path}") + } + } + """.trimIndent() + ) + val result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments("help", "--init-script", initScript.path, "--offline", "--stacktrace") + .build() + + val created = result.output.lines() + .filter { it.startsWith(createdPrefix) } + .map { it.removePrefix(createdPrefix) } + created shouldContain ":help" + created shouldNotContainAnyOf listOf("api", "impl").flatMap { module -> + listOf("jar", "sourcesJar", "javadocJar", "htmlDocsJar").map { ":$module:$it" } + } + } + + /** + * A standard publication declares the `jar` packaging before its artifacts are known, + * so writing its POM fails if the build gives the `jar` task a classifier, + * rather than publish a POM that names no main artifact. + * + * The case checks `StandardJavaPublicationHandler`, but shares the fixture of this + * class, whose `api` module is a standard publication with an SBOM. + * + * The build of the fixture has written the POM of `api` already. So the case also shows + * that the result of the check is an input of the task: without it, the task would be + * up to date, and skip the check. + */ + @Test + fun `fail to write the POM of a standard publication whose JAR is classified`() { + val script = file("api/build.gradle.kts") + val original = script.readText() + script.appendText("\ntasks.jar { archiveClassifier.set(\"plain\") }\n") + try { + val result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(":api:${pomTaskOf("mavenJava")}", "--offline", "--stacktrace") + .buildAndFail() + + result.output shouldContain + "The POM of the `mavenJava` publication of `:api` declares the `jar`" + + " packaging, but the publication has no main artifact." + } finally { + script.writeText(original) + } + } + @Test fun `describe runtime dependencies only`() { val names = implSbom().packageNames() @@ -908,6 +976,9 @@ internal class PublicationSbomIgTest { /** Marks the lines in which the fixture reports the SBOMs of each publication. */ const val reportPrefix = "SBOM-REPORT " + /** Marks the lines in which an init script reports the tasks created. */ + const val createdPrefix = "CREATED-TASK " + /** The publication of a thin JAR with a hand-written POM. */ const val pluginJar = "pluginJar" diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/report/license/DependencyReportOutputTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/report/license/DependencyReportOutputTest.kt index a6213ab912..96f756d75d 100644 --- a/buildSrc/src/test/kotlin/io/spine/gradle/report/license/DependencyReportOutputTest.kt +++ b/buildSrc/src/test/kotlin/io/spine/gradle/report/license/DependencyReportOutputTest.kt @@ -74,6 +74,29 @@ class DependencyReportOutputTest { reportFile.readText() shouldBe "license report" } + /** + * In a build of one project, the report to merge is that of the root project, whose + * task is in the same task container as the merging task. + */ + @Test + fun `merge the license report of a single-project build`() { + project.pluginManager.apply(BasePlugin::class.java) + LicenseReporter.generateReportIn(project) + val sourceReport = project.layout.buildDirectory.asFile.get() + .resolve(Paths.relativePath) + .resolve(Paths.outputFilename) + sourceReport.parentFile.mkdirs() + sourceReport.writeText("license report") + + LicenseReporter.mergeAllReports(project) + + project.tasks.named("mergeAllLicenseReports").get() + .executeActions() + + val reportFile = projectDir.resolve("docs/dependencies/dependencies.md") + reportFile.readText() shouldBe "license report" + } + private fun subproject(name: String): Project { val subprojectDir = projectDir.resolve(name) subprojectDir.mkdirs() diff --git a/config b/config index ad3dff0890..3a0d13a6bc 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit ad3dff0890c7716cbba3fe2ce17bf4fe5905a318 +Subproject commit 3a0d13a6bc72f1d3b873a6c5f300aa9bf26ffe0a diff --git a/docs/_preview/go.mod b/docs/_preview/go.mod index f7adfdf8ee..5948063bf4 100644 --- a/docs/_preview/go.mod +++ b/docs/_preview/go.mod @@ -1,8 +1,8 @@ module spine.io/validation/docs/preview -go 1.25.6 +go 1.27.3 require ( - github.com/SpineEventEngine/site-commons v0.0.0-20260522171914-2a606d89558f // indirect + github.com/SpineEventEngine/site-commons v0.0.0-20261102155919-1c7c5ef33424 // indirect github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20800 // indirect ) diff --git a/docs/_preview/go.sum b/docs/_preview/go.sum index bcfc772e1c..6ed2b3b14d 100644 --- a/docs/_preview/go.sum +++ b/docs/_preview/go.sum @@ -2,6 +2,8 @@ github.com/SpineEventEngine/site-commons v0.0.0-20260507130158-84db050dfe11 h1:N github.com/SpineEventEngine/site-commons v0.0.0-20260507130158-84db050dfe11/go.mod h1:tkAl4StIREKmz9r5PiJtuDhvwMMkFXKWcaTyxhIikho= github.com/SpineEventEngine/site-commons v0.0.0-20260522171914-2a606d89558f h1:yF43XTDNj+w1S0fLA/XhrWMrhAoDpqPB7gmc4mUF/9Q= github.com/SpineEventEngine/site-commons v0.0.0-20260522171914-2a606d89558f/go.mod h1:tkAl4StIREKmz9r5PiJtuDhvwMMkFXKWcaTyxhIikho= +github.com/SpineEventEngine/site-commons v0.0.0-20261102155919-1c7c5ef33424 h1:tI8D25LWectjJRDxCrKbAiYFDbJi/3NGSNjAO2xJAnA= +github.com/SpineEventEngine/site-commons v0.0.0-20261102155919-1c7c5ef33424/go.mod h1:TfFFABXgPop1uc4eBdfWZF0Es06TGmNHfReiOzkZqrk= github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20800 h1:j5myhhzYwIHTr5ctK96Elfgp5uRROvrlTzYwwe1nF8o= github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20800/go.mod h1:P5GGyhdxi00C5zW7vkRo/IS532gZY/YS2TS395Xaxho= github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000/go.mod h1:mFberT6ZtcchrsDtfvJM7aAH2bDKLdOnruUHl0hlapI= diff --git a/docs/content/docs/validation/developer/build-and-release.md b/docs/content/docs/validation/developer/build-and-release.md index 2c655e3a3c..414dee15ce 100644 --- a/docs/content/docs/validation/developer/build-and-release.md +++ b/docs/content/docs/validation/developer/build-and-release.md @@ -9,12 +9,12 @@ headline: Documentation This page describes how the Gradle multi-project build is wired, what each publishable module produces, and how the resulting artifacts reach downstream consumers. It is the contributor-side counterpart to “[Adding Validation to your -build](../user/01-getting-started/adding-to-build/)” in the User's Guide: +build](../user/01-getting-started/adding-to-build.md)” in the User's Guide: where that page shows how a *consumer* applies the plugin, this page shows how the plugin and its dependencies are *produced*. For day-to-day commands (`./gradlew build`, `./gradlew dokka`, …) see -[`running-builds.md`][running-builds] in the `.agents/` directory; this page +[`running-builds.md`][running-builds] in the shared agent guidelines; this page focuses on the structure rather than the commands. ## The multi-project layout @@ -47,7 +47,7 @@ through Gradle's `extra` properties: end="validationVersion"> ```kotlin -extra.set("validationVersion", "2.0.0-SNAPSHOT.469") +extra.set("validationVersion", "2.0.0-SNAPSHOT.470") ``` The root build script applies this file under `allprojects { … }` and assigns @@ -199,7 +199,6 @@ flowchart LR bundle[":java-bundle
shadow JAR"] runtime[":jvm-runtime
runtime library"] plugin[":gradle-plugin
io.spine.validation"] - portal["Gradle Plugin Portal"] maven["Maven repositories
(GitHub, GCAR)"] consumer["Consumer project"] @@ -207,31 +206,31 @@ flowchart LR java --> bundle bundle --> maven runtime --> maven - plugin --> portal + plugin --> maven plugin -. records coords of .-> bundle plugin -. records coords of .-> runtime - portal --> consumer maven --> consumer ``` For consumers, the important release contract is this: they apply the plugin -from the Gradle Plugin Portal, and that plugin version selects the recorded -bundle and runtime coordinates resolved from Maven repositories. +from Spine's Maven repositories, and that plugin version selects the recorded +bundle and runtime coordinates resolved from the same repositories. ## Publication destinations The publication targets are configured in the `spinePublishing { destinations }` -block above. The library artifacts go to: +block above. The library artifacts and the Gradle plugin go to: - The repository's [GitHub Packages registry][github-packages-validation] (`gitHub("validation")` resolves to `maven.pkg.github.com/SpineEventEngine/validation`). - Spine's Google Cloud Artifact Registry (`cloudArtifactRegistry`). -The Gradle plugin has an additional destination — the [Gradle Plugin -Portal][gradle-plugin-portal] — configured by the `java-gradle-plugin` and -`com.gradle.plugin-publish` plugins applied via the publishing convention. -That is the destination consumers reach when they write -`id("io.spine.validation")` in their `plugins { … }` block. +`java-gradle-plugin` publishes the Gradle plugin together with its plugin +marker artifact, `io.spine.validation:io.spine.validation.gradle.plugin`. +Gradle looks up this marker when a consumer writes `id("io.spine.validation")` +in their `plugins { … }` block. The plugin is not published to the Gradle Plugin +Portal, so consumers add Spine's repositories to `pluginManagement`, as shown in +“[Adding Validation to your build](../user/01-getting-started/adding-to-build.md)”. Publication is automated by the [`publish.yml`][publish-workflow] GitHub Actions workflow, which runs on every push to `master` after PR checks have already @@ -267,7 +266,7 @@ Two kinds of downstream consumer pick up Validation artifacts: `plugins { … }` block. They never reference `:java-bundle` or `:jvm-runtime` directly — the Gradle plugin adds both, and `version.gradle.kts` in *this* repository determines which versions they get. The consumer-side flow is - documented in “[Adding Validation to your build](../user/01-getting-started/adding-to-build/)”. + documented in “[Adding Validation to your build](../user/01-getting-started/adding-to-build.md)”. - **Tooling projects** that bundle the Validation Compiler into their own Spine Compiler distribution. The most prominent example is the @@ -305,6 +304,5 @@ release across every downstream tool that resolves it by name. [publish-workflow]: https://github.com/SpineEventEngine/validation/blob/master/.github/workflows/publish.yml [shadow]: https://gradleup.com/shadow/ [github-packages-validation]: https://github.com/SpineEventEngine/validation/packages -[gradle-plugin-portal]: https://plugins.gradle.org/plugin/io.spine.validation [core-jvm-compiler]: https://github.com/SpineEventEngine/core-jvm-compiler -[running-builds]: https://github.com/SpineEventEngine/validation/blob/master/.agents/running-builds.md +[running-builds]: https://github.com/SpineEventEngine/agents/blob/master/guidelines/running-builds.md diff --git a/docs/content/docs/validation/developer/documentation/embedded-examples.md b/docs/content/docs/validation/developer/documentation/embedded-examples.md index 6488317091..d2a87c0078 100644 --- a/docs/content/docs/validation/developer/documentation/embedded-examples.md +++ b/docs/content/docs/validation/developer/documentation/embedded-examples.md @@ -194,7 +194,7 @@ binaries are kept up to date. [hello-validation]: https://github.com/spine-examples/hello-validation [spine-time]: https://github.com/SpineEventEngine/time [embed-code-yml]: https://github.com/SpineEventEngine/validation/blob/master/docs/_settings/embed-code.yml -[embed-code-go-embedding]: https://github.com/SpineEventEngine/embed-code-go/blob/master/EMBEDDING.md +[embed-code-go-embedding]: https://github.com/SpineEventEngine/embed-code-go/blob/master/showcase/embedding/README.md [runtime-library]: https://github.com/SpineEventEngine/validation/blob/master/docs/content/docs/validation/developer/runtime-library.md [build-and-release]: https://github.com/SpineEventEngine/validation/blob/master/docs/content/docs/validation/developer/build-and-release.md [check-code-embedding]: https://github.com/SpineEventEngine/validation/blob/master/.github/workflows/check-code-embedding.yml diff --git a/docs/content/docs/validation/developer/documentation/procedures.md b/docs/content/docs/validation/developer/documentation/procedures.md index cb7a6f765e..ec33f37dc0 100644 --- a/docs/content/docs/validation/developer/documentation/procedures.md +++ b/docs/content/docs/validation/developer/documentation/procedures.md @@ -199,8 +199,8 @@ For the underlying mechanics, see "[Embedded examples](embedded-examples.md)". 3. Preview locally (see "Previewing the documentation locally") and click through every internal link. -4. Follow the project documentation guidelines in - [`documentation-guidelines.md`][doc-guidelines] — formatting of file +4. Follow the shared documentation guidelines in + [`documentation.md`][doc-guidelines] — formatting of file and directory names as code, reference-style external links, no widows/runts/orphans/rivers. @@ -322,7 +322,7 @@ for any build, test, or release questions. [version-gradle]: https://github.com/SpineEventEngine/validation/blob/master/version.gradle.kts [core-jvm-compiler-dep]: https://github.com/SpineEventEngine/validation/blob/master/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt [embed-code-yml]: https://github.com/SpineEventEngine/validation/blob/master/docs/_settings/embed-code.yml -[doc-guidelines]: https://github.com/SpineEventEngine/validation/blob/master/.agents/documentation-guidelines.md +[doc-guidelines]: https://github.com/SpineEventEngine/agents/blob/master/guidelines/documentation.md [theme-updates]: https://github.com/SpineEventEngine/site-commons#theme-updates [embed-code-go]: https://github.com/SpineEventEngine/embed-code-go [embed-code-releases]: https://github.com/SpineEventEngine/embed-code-go/releases diff --git a/docs/content/docs/validation/developer/testing-strategy.md b/docs/content/docs/validation/developer/testing-strategy.md index 60924e68cc..99e9b2639b 100644 --- a/docs/content/docs/validation/developer/testing-strategy.md +++ b/docs/content/docs/validation/developer/testing-strategy.md @@ -241,7 +241,7 @@ A handful of conventions keep specs across the suites consistent: behaviour. - **Inline comments are welcome in tests.** The general rule against inline comments in production code does not apply to specs — see - `.agents/documentation-guidelines.md`. Use them to explain non-obvious setup + `.agents/guidelines/documentation.md`. Use them to explain non-obvious setup in fixtures or to flag the violation shape a spec is asserting. - **One concern per spec class.** A spec named after an option covers that option; cross-cutting behaviour goes in its own spec. The diff --git a/docs/content/docs/validation/user/01-getting-started/adding-to-build.md b/docs/content/docs/validation/user/01-getting-started/adding-to-build.md index 1ec494cedd..c37f6ce082 100644 --- a/docs/content/docs/validation/user/01-getting-started/adding-to-build.md +++ b/docs/content/docs/validation/user/01-getting-started/adding-to-build.md @@ -90,7 +90,7 @@ Add the Validation plugin to the build. ```kotlin plugins { module - id("io.spine.validation") version "2.0.0-SNAPSHOT.469" + id("io.spine.validation") version "2.0.0-SNAPSHOT.470" } ``` @@ -120,7 +120,7 @@ adding Validation directly. CoreJvm brings in the Validation Gradle plugin for y ```kotlin plugins { module - id("io.spine.core-jvm") version "2.0.0-SNAPSHOT.098" + id("io.spine.core-jvm") version "2.0.0-SNAPSHOT.099" } ``` diff --git a/docs/content/docs/validation/user/04-validators/_index.md b/docs/content/docs/validation/user/04-validators/_index.md index 9663a7e1d1..8c94f9fe57 100644 --- a/docs/content/docs/validation/user/04-validators/_index.md +++ b/docs/content/docs/validation/user/04-validators/_index.md @@ -108,6 +108,6 @@ their violations together. - [Implement a validator](implement-a-validator.md) - [Using `ValidatorRegistry`](validator-registry.md) - [Custom validation](../05-custom-validation/) -- [Developer's guide](../../developer/) +- [Developer's guide](../../developer/overview-and-audience.md) [auto-service]: https://github.com/google/auto/tree/main/service diff --git a/docs/content/docs/validation/user/04-validators/implement-a-validator.md b/docs/content/docs/validation/user/04-validators/implement-a-validator.md index 868f347e11..9c9f79f4f0 100644 --- a/docs/content/docs/validation/user/04-validators/implement-a-validator.md +++ b/docs/content/docs/validation/user/04-validators/implement-a-validator.md @@ -137,7 +137,7 @@ to the nested field in error (for example, to `starts_at.seconds`). - [Using `ValidatorRegistry`](validator-registry.md) - [Custom validation](../05-custom-validation/) -- [Developer's guide](../06-developers-guide/) +- [Developer's guide](../../developer/overview-and-audience.md) [TimestampValidator]: https://github.com/SpineEventEngine/validation/blob/master/jvm-runtime/src/main/kotlin/io/spine/validation/TimestampValidator.kt diff --git a/docs/content/docs/validation/user/05-custom-validation/pass-to-compiler.md b/docs/content/docs/validation/user/05-custom-validation/pass-to-compiler.md index 2d51ab1499..af9d21f680 100644 --- a/docs/content/docs/validation/user/05-custom-validation/pass-to-compiler.md +++ b/docs/content/docs/validation/user/05-custom-validation/pass-to-compiler.md @@ -72,7 +72,7 @@ in the Spine Time repository. ## What's next - [Summary](summary.md) -- [Developer's guide](../06-developers-guide/) +- [Developer's guide](../../developer/overview-and-audience.md) [time-gradle-plugin-kt]: https://github.com/SpineEventEngine/time/blob/master/gradle-plugin/src/main/kotlin/io/spine/tools/time/gradle/TimeGradlePlugin.kt [time-validation-kt]: https://github.com/SpineEventEngine/time/blob/master/gradle-plugin/src/main/kotlin/io/spine/tools/time/gradle/TimeValidation.kt diff --git a/docs/content/docs/validation/user/05-custom-validation/summary.md b/docs/content/docs/validation/user/05-custom-validation/summary.md index 2fbafdb252..010df292be 100644 --- a/docs/content/docs/validation/user/05-custom-validation/summary.md +++ b/docs/content/docs/validation/user/05-custom-validation/summary.md @@ -41,7 +41,7 @@ Spine Compiler. ## What's next -- Explore library internals: [Developer's guide](../../developer/) +- Explore library internals: [Developer's guide](../../developer/overview-and-audience.md) - See the modules that back this pipeline: [Key modules](../../developer/key-modules.md) diff --git a/docs/content/docs/validation/user/05-custom-validation/typical_custom_option.jpg b/docs/content/docs/validation/user/05-custom-validation/typical_custom_option.jpg new file mode 100644 index 0000000000..5880f4e216 Binary files /dev/null and b/docs/content/docs/validation/user/05-custom-validation/typical_custom_option.jpg differ diff --git a/docs/dependencies/dependencies.md b/docs/dependencies/dependencies.md index 6628a2bc41..afceed41c5 100644 --- a/docs/dependencies/dependencies.md +++ b/docs/dependencies/dependencies.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-context:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -710,7 +710,7 @@ * **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.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.12. * **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) @@ -799,7 +799,7 @@ * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protoc. **Version** : 4.36.0. +1. **Group** : com.google.protobuf. **Name** : protoc. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -844,18 +844,10 @@ * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.102.0. * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.sksamuel.aedile. **Name** : aedile-core. **Version** : 3.0.4. * **Project URL:** [https://www.github.com/sksamuel/aedile](https://www.github.com/sksamuel/aedile) * **License:** [The Apache 2.0 License](https://opensource.org/licenses/Apache-2.0) @@ -1083,10 +1075,6 @@ * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -1785,14 +1773,14 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-context-tests:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -3217,14 +3205,14 @@ This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-docs:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-docs:2.0.0-SNAPSHOT.470` ## Runtime ## Compile, tests, and tooling @@ -3234,14 +3222,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:18 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:28 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-gradle-plugin:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -4934,14 +4922,14 @@ This report was generated on **Thu Oct 22 15:18:18 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-java:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -5651,7 +5639,7 @@ This report was generated on **Thu Oct 22 15:18:20 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.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.12. * **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) @@ -5740,7 +5728,7 @@ This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protoc. **Version** : 4.36.0. +1. **Group** : com.google.protobuf. **Name** : protoc. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -5785,18 +5773,10 @@ This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.102.0. * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.sksamuel.aedile. **Name** : aedile-core. **Version** : 3.0.4. * **Project URL:** [https://www.github.com/sksamuel/aedile](https://www.github.com/sksamuel/aedile) * **License:** [The Apache 2.0 License](https://opensource.org/licenses/Apache-2.0) @@ -6024,10 +6004,6 @@ This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -6726,14 +6702,14 @@ This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-java-bundle:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 13.0. @@ -6785,14 +6761,14 @@ This report was generated on **Thu Oct 22 15:18:20 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:18 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:28 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-java-settings:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-java-settings:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -7577,14 +7553,14 @@ This report was generated on **Thu Oct 22 15:18:18 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 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-validation-jvm-runtime:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine:spine-validation-jvm-runtime:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -8389,14 +8365,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:52:38 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-consumer:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -9747,14 +9723,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-consumer-dependency:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -9849,11 +9825,6 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](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.22.2. - * **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) - 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.3. * **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) @@ -9864,60 +9835,30 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-yaml. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-dataformats-text](https://github.com/FasterXML/jackson-dataformats-text) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-yaml. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-dataformats-text](https://github.com/FasterXML/jackson-dataformats-text) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-guava. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-datatypes-collections](https://github.com/FasterXML/jackson-datatypes-collections) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-guava. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-datatypes-collections](https://github.com/FasterXML/jackson-datatypes-collections) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-jdk8. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) - * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-jdk8. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -9936,12 +9877,6 @@ This report was generated on **Thu Oct 22 15:18:19 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](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-parameter-names. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) - * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-parameter-names. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -9974,10 +9909,6 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](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.google.code.gson. **Name** : gson. **Version** : 2.13.2. - * **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.14.0. * **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) @@ -9986,11 +9917,7 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **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** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.11. - * **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) - -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-api. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.12. * **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) @@ -9998,11 +9925,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **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) -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-common-deps. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-common-deps. **Version** : 2.3.12. * **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) -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-gradle-plugin. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-gradle-plugin. **Version** : 2.3.12. * **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) @@ -10043,21 +9970,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.0. - * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - 1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.0. - * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - 1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -10096,18 +10013,10 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.102.0. * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.sksamuel.aedile. **Name** : aedile-core. **Version** : 3.0.4. * **Project URL:** [https://www.github.com/sksamuel/aedile](https://www.github.com/sksamuel/aedile) * **License:** [The Apache 2.0 License](https://opensource.org/licenses/Apache-2.0) @@ -10206,23 +10115,23 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** Indiana University Extreme! Lab Software License * **License:** [Indiana University Extreme! Lab Software License](https://raw.githubusercontent.com/x-stream/mxparser/master/LICENSE.txt) -1. **Group** : io.grpc. **Name** : grpc-api. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-api. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-context. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-context. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-core. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-core. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-inprocess. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-inprocess. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -10230,15 +10139,15 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **Project URL:** [https://github.com/grpc/grpc-kotlin](https://github.com/grpc/grpc-kotlin) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-protobuf. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-protobuf. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-protobuf-lite. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-protobuf-lite. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-stub. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-stub. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -10674,14 +10583,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-extensions:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -11275,11 +11184,6 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](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.22.2. - * **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) - 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.3. * **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) @@ -11290,60 +11194,30 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-yaml. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-dataformats-text](https://github.com/FasterXML/jackson-dataformats-text) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-yaml. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-dataformats-text](https://github.com/FasterXML/jackson-dataformats-text) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-guava. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-datatypes-collections](https://github.com/FasterXML/jackson-datatypes-collections) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-guava. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-datatypes-collections](https://github.com/FasterXML/jackson-datatypes-collections) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-jdk8. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) - * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-jdk8. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -11362,12 +11236,6 @@ This report was generated on **Thu Oct 22 15:18:19 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](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-parameter-names. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) - * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-parameter-names. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -11400,19 +11268,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](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.google.code.gson. **Name** : gson. **Version** : 2.13.2. - * **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.14.0. * **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.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.11. - * **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) - -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-api. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.12. * **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) @@ -11420,11 +11280,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **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) -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-common-deps. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-common-deps. **Version** : 2.3.12. * **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) -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-gradle-plugin. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-gradle-plugin. **Version** : 2.3.12. * **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) @@ -11465,21 +11325,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.0. - * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - 1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.0. - * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - 1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -11518,18 +11368,10 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.102.0. * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.sksamuel.aedile. **Name** : aedile-core. **Version** : 3.0.4. * **Project URL:** [https://www.github.com/sksamuel/aedile](https://www.github.com/sksamuel/aedile) * **License:** [The Apache 2.0 License](https://opensource.org/licenses/Apache-2.0) @@ -11628,42 +11470,22 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** Indiana University Extreme! Lab Software License * **License:** [Indiana University Extreme! Lab Software License](https://raw.githubusercontent.com/x-stream/mxparser/master/LICENSE.txt) -1. **Group** : io.grpc. **Name** : grpc-api. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-api. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-context. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-context. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-core. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-core. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-inprocess. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-inprocess. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -11672,26 +11494,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **Project URL:** [https://github.com/grpc/grpc-kotlin](https://github.com/grpc/grpc-kotlin) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-protobuf. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-protobuf. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-protobuf-lite. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-protobuf-lite. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-stub. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-stub. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -12116,14 +11926,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-runtime:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -12218,11 +12028,6 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](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.22.2. - * **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) - 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.3. * **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) @@ -12233,60 +12038,30 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-yaml. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-dataformats-text](https://github.com/FasterXML/jackson-dataformats-text) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-yaml. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-dataformats-text](https://github.com/FasterXML/jackson-dataformats-text) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-guava. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-datatypes-collections](https://github.com/FasterXML/jackson-datatypes-collections) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-guava. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-datatypes-collections](https://github.com/FasterXML/jackson-datatypes-collections) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-jdk8. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) - * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-jdk8. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -12305,12 +12080,6 @@ This report was generated on **Thu Oct 22 15:18:19 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](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-parameter-names. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) - * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-parameter-names. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -12347,10 +12116,6 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](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.google.code.gson. **Name** : gson. **Version** : 2.13.2. - * **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.14.0. * **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) @@ -12359,11 +12124,7 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **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** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.11. - * **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) - -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-api. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.12. * **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) @@ -12371,11 +12132,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **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) -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-common-deps. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-common-deps. **Version** : 2.3.12. * **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) -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-gradle-plugin. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-gradle-plugin. **Version** : 2.3.12. * **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) @@ -12427,21 +12188,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.0. - * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - 1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.0. - * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - 1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -12492,18 +12243,10 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.102.0. * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.sksamuel.aedile. **Name** : aedile-core. **Version** : 3.0.4. * **Project URL:** [https://www.github.com/sksamuel/aedile](https://www.github.com/sksamuel/aedile) * **License:** [The Apache 2.0 License](https://opensource.org/licenses/Apache-2.0) @@ -12606,23 +12349,23 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** Indiana University Extreme! Lab Software License * **License:** [Indiana University Extreme! Lab Software License](https://raw.githubusercontent.com/x-stream/mxparser/master/LICENSE.txt) -1. **Group** : io.grpc. **Name** : grpc-api. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-api. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-context. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-context. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-core. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-core. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-inprocess. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-inprocess. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -12630,15 +12373,15 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **Project URL:** [https://github.com/grpc/grpc-kotlin](https://github.com/grpc/grpc-kotlin) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-protobuf. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-protobuf. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-protobuf-lite. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-protobuf-lite. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-stub. **Version** : 1.81.0. +1. **Group** : io.grpc. **Name** : grpc-stub. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -13096,14 +12839,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-time:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-time:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -13858,14 +13601,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:29 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-validating:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -13960,11 +13703,6 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](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.22.2. - * **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) - 1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.22.3. * **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) @@ -13981,12 +13719,6 @@ This report was generated on **Thu Oct 22 15:18:19 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.core. **Name** : jackson-core. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-core. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) @@ -13999,48 +13731,24 @@ This report was generated on **Thu Oct 22 15:18:19 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.core. **Name** : jackson-databind. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.core. **Name** : jackson-databind. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson](https://github.com/FasterXML/jackson) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-yaml. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-dataformats-text](https://github.com/FasterXML/jackson-dataformats-text) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.dataformat. **Name** : jackson-dataformat-yaml. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-dataformats-text](https://github.com/FasterXML/jackson-dataformats-text) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-guava. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-datatypes-collections](https://github.com/FasterXML/jackson-datatypes-collections) - * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-guava. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-datatypes-collections](https://github.com/FasterXML/jackson-datatypes-collections) * **License:** [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) * **License:** [The Apache Software License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-jdk8. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) - * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.datatype. **Name** : jackson-datatype-jdk8. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -14071,12 +13779,6 @@ This report was generated on **Thu Oct 22 15:18:19 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](https://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-parameter-names. **Version** : 2.22.2. - * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) - * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - * **License:** [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group** : com.fasterxml.jackson.module. **Name** : jackson-module-parameter-names. **Version** : 2.22.3. * **Project URL:** [https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -14121,10 +13823,6 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [http://www.apache.org/licenses/LICENSE-2.0.txt](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.google.code.gson. **Name** : gson. **Version** : 2.13.2. - * **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.14.0. * **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) @@ -14133,11 +13831,7 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **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** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.11. - * **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) - -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-api. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : com.google.devtools.ksp.gradle.plugin. **Version** : 2.3.12. * **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) @@ -14145,11 +13839,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **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) -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-common-deps. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-common-deps. **Version** : 2.3.12. * **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) -1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-gradle-plugin. **Version** : 2.3.11. +1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-gradle-plugin. **Version** : 2.3.12. * **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) @@ -14218,21 +13912,11 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.0. - * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - 1. **Group** : com.google.protobuf. **Name** : protobuf-java-util. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.0. - * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) - * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - * **License:** [BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) - 1. **Group** : com.google.protobuf. **Name** : protobuf-kotlin. **Version** : 4.36.2. * **Project URL:** [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/) * **License:** [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) @@ -14283,18 +13967,10 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.102.0. * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) -1. **Group** : com.palantir.javaformat. **Name** : palantir-java-format-spi. **Version** : 2.97.0. - * **Project URL:** [https://github.com/palantir/palantir-java-format](https://github.com/palantir/palantir-java-format) - * **License:** [The Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) - 1. **Group** : com.sksamuel.aedile. **Name** : aedile-core. **Version** : 3.0.4. * **Project URL:** [https://www.github.com/sksamuel/aedile](https://www.github.com/sksamuel/aedile) * **License:** [The Apache 2.0 License](https://opensource.org/licenses/Apache-2.0) @@ -14397,42 +14073,22 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **License:** Indiana University Extreme! Lab Software License * **License:** [Indiana University Extreme! Lab Software License](https://raw.githubusercontent.com/x-stream/mxparser/master/LICENSE.txt) -1. **Group** : io.grpc. **Name** : grpc-api. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-api. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-bom. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-context. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-context. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-core. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-core. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-inprocess. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-inprocess. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -14441,26 +14097,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using * **Project URL:** [https://github.com/grpc/grpc-kotlin](https://github.com/grpc/grpc-kotlin) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-protobuf. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-protobuf. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-protobuf-lite. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-protobuf-lite. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) -1. **Group** : io.grpc. **Name** : grpc-stub. **Version** : 1.81.0. - * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) - * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) - 1. **Group** : io.grpc. **Name** : grpc-stub. **Version** : 1.83.1. * **Project URL:** [https://github.com/grpc/grpc-java](https://github.com/grpc/grpc-java) * **License:** [Apache 2.0](https://opensource.org/licenses/Apache-2.0) @@ -14988,14 +14632,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-validator:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3. @@ -16449,14 +16093,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:30 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-validator-dependency:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -16731,14 +16375,14 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:29 WET 2026** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.469` +# Dependencies of `io.spine.tools:validation-vanilla:2.0.0-SNAPSHOT.470` ## Runtime 1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2. @@ -17433,6 +17077,6 @@ This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Thu Oct 22 15:18:19 WEST 2026** using +This report was generated on **Mon Nov 02 16:51:29 WET 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 69168c1a9f..b7bba31fb3 100644 --- a/docs/dependencies/pom.xml +++ b/docs/dependencies/pom.xml @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject. --> io.spine.tools validation -2.0.0-SNAPSHOT.469 +2.0.0-SNAPSHOT.470 2015 @@ -62,19 +62,19 @@ all modules and does not describe the project structure per-subproject. io.spine spine-time - 2.0.0-SNAPSHOT.251 + 2.0.0-SNAPSHOT.253 compile io.spine spine-time-java - 2.0.0-SNAPSHOT.251 + 2.0.0-SNAPSHOT.253 compile io.spine spine-validation-jvm-runtime - 2.0.0-SNAPSHOT.464 + 2.0.0-SNAPSHOT.469 compile @@ -277,17 +277,17 @@ all modules and does not describe the project structure per-subproject. io.spine.tools core-jvm-gradle-plugin - 2.0.0-SNAPSHOT.098 + 2.0.0-SNAPSHOT.099 io.spine.tools core-jvm-plugins - 2.0.0-SNAPSHOT.098 + 2.0.0-SNAPSHOT.099 io.spine.tools dokka-extensions - 2.0.0-SNAPSHOT.11 + 2.0.0-SNAPSHOT.12 io.spine.tools @@ -297,12 +297,12 @@ all modules and does not describe the project structure per-subproject. io.spine.tools time-validation - 2.0.0-SNAPSHOT.251 + 2.0.0-SNAPSHOT.253 io.spine.tools validation-java-bundle - 2.0.0-SNAPSHOT.464 + 2.0.0-SNAPSHOT.469 net.sourceforge.pmd diff --git a/jvm-runtime/build.gradle.kts b/jvm-runtime/build.gradle.kts index 0f9ffbf7a7..31f4b835a6 100644 --- a/jvm-runtime/build.gradle.kts +++ b/jvm-runtime/build.gradle.kts @@ -1,27 +1,15 @@ /* - * Copyright 2024, TeamDev. All rights reserved. + * Copyright 2026 CodeMatters, Lda. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * - * Redistribution and use in source and/or binary forms, with or without - * modification, must retain the above copyright notice and the following - * disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. */ @file:Suppress("RemoveRedundantQualifierName") @@ -31,8 +19,6 @@ import io.spine.dependency.lib.AutoServiceKsp import io.spine.dependency.local.Base import io.spine.dependency.local.Spine import io.spine.dependency.local.TestLib -import io.spine.gradle.publish.SpinePublishing -import io.spine.gradle.publish.StandardJavaPublicationHandler buildscript { standardSpineSdkRepositories() @@ -46,9 +32,6 @@ buildscript { } } } - dependencies { - classpath(io.spine.dependency.local.CoreJvmCompiler.gradlePlugin) - } } plugins { @@ -75,3 +58,13 @@ dependencies { } forceSpineBase() + +// KSP reads the Java sources that the Protobuf tasks write to `generated//java`. +mapOf( + "kspKotlin" to "generateProto", + "kspTestKotlin" to "generateTestProto", +).forEach { (ksp, generateProto) -> + tasks.named { it == ksp }.configureEach { + dependsOn(generateProto) + } +} diff --git a/version.gradle.kts b/version.gradle.kts index 02fd8c67df..9cfbe87cf7 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -15,4 +15,4 @@ /** * The version of the Validation library to publish. */ -extra.set("validationVersion", "2.0.0-SNAPSHOT.469") +extra.set("validationVersion", "2.0.0-SNAPSHOT.470")