Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions .github/workflows/increment-guard.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -76,15 +80,15 @@ 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.
#
# Skipped for fork PRs: `GITHUB_TOKEN` is read-only for them, so the status cannot be
# posted (and a fork head SHA is not in this repo). The Spine agent workflow pushes PR
# branches to the same repository; fork contributions are handled by a maintainer, who
# owns the version bump.
- name: Report the Version Guard status
- name: Report the Version bump status
if: always() && github.event.pull_request.head.repo.fork == false
shell: bash
env:
Expand All @@ -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"
100 changes: 100 additions & 0 deletions .github/workflows/publish-api-docs.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ on:
jobs:
publish:
name: Publish to Maven repositories
uses: SpineEventEngine/config/.github/workflows/publishing.yml@604420cd7eab82b382c9a5c1d812be454552c27c
uses: SpineEventEngine/config/.github/workflows/publishing.yml@363fc1da3184d001361fa9c327929eb5e05fa673
permissions:
contents: read
packages: write
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/revalidate-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down Expand Up @@ -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 }}
Expand Down
24 changes: 12 additions & 12 deletions buildSrc/src/main/kotlin/BuildExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -168,17 +167,17 @@ 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
* to prevent its direct usage from outside.
*/
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 })
}
}

Expand Down Expand Up @@ -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)
}

/**
Expand Down
25 changes: 16 additions & 9 deletions buildSrc/src/main/kotlin/DokkaExts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Task> =
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<Task> =
named { it == "dokkaGeneratePublicationJavadoc" }

/**
* Locates or creates the `htmlDocsJar` task in this [Project].
Expand All @@ -205,9 +214,7 @@ fun Project.htmlDocsJar(): TaskProvider<Jar> = tasks.getOrCreate("htmlDocsJar")
archiveClassifier.set("html-docs")
from(files(dokkaHtmlOutput()))

tasks.dokkaHtmlTask()?.let { dokkaTask ->
this@getOrCreate.dependsOn(dokkaTask)
}
this@getOrCreate.dependsOn(tasks.dokkaHtmlTask())
}

/**
Expand Down Expand Up @@ -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`,
Expand Down Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions buildSrc/src/main/kotlin/dokka-setup.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ afterEvaluate {
DocumentationSettings.SourceLink.url(project)
)
}
val kspKotlin = tasks.findByName("kspKotlin")
kspKotlin?.let {
tasks.withType<DokkaBaseTask>().configureEach {
dependsOn(kspKotlin)
}
tasks.withType<DokkaBaseTask>().configureEach {
dependsOn(tasks.named { it == "kspKotlin" })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,31 @@ object OpenTelemetryKotlin : Dependency() {
*/
val compat = "$group:compat:$version"

/**
* The export machinery of the Kotlin SDK, including the `batchLogRecordProcessor`
* builder.
*
* Use it together with [implementation] and an exporter artifact, such as
* [exportersOtlp]. Like [core] and [implementation], it is for applications
* and tests.
*/
val exportersCore = "$group:exporters-core:$version"

/**
* The OTLP exporters of the Kotlin SDK, such as `otlpHttpLogRecordExporter`,
* which send data over HTTP using Ktor.
*
* Use it together with [exportersCore].
*/
val exportersOtlp = "$group:exporters-otlp:$version"

override val modules: List<String> = listOf(
"$group:api",
"$group:noop",
"$group:core",
"$group:implementation",
"$group:compat",
"$group:exporters-core",
"$group:exporters-otlp",
)
}
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.225"
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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading