diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a2fe3a2bb..4f8396ea9 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -24,3 +24,23 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3 - run: ./gradlew checkWithCodenarc checkstyleMain checkstyleTest runUnitTests runLiveObjectsUnitTests :uts:runUtsUnitTests + + # Continuously proves the release pre-flight and that every published module + # builds a publishable artifact set, so version/coordinate regressions surface + # on PRs rather than on release day. + release-dry-run: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 + with: + persist-credentials: false + - name: Set up the JDK + uses: actions/setup-java@17f84c3641ba7b8f6deff6309fc4c864478f5d62 # v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Set up Gradle + uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3 + - run: ./gradlew verifyReleaseArtifacts publishToMavenLocal diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c55cc27a9..0266268de 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -46,6 +46,12 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@d9c87d481d55275bb5441eef3fe0e46805f9ef70 # v3 + # Fails before anything is uploaded if the artifact set, group or lockstep + # version drifts (PDR-091b: core, core-android, device and server release + # together on the same version; partial release must be impossible). + - name: Release pre-flight + run: ./gradlew verifyReleaseArtifacts + - name: Publish and release to Maven Central run: ./gradlew publishAndReleaseToMavenCentral env: diff --git a/README.md b/README.md index a16465305..2a0449358 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,23 @@ Find out more: --- +> [!NOTE] +> **This branch carries the in-development 2.0 device/server package split.** The SDK is being +> restructured into new artifacts that declare which side of the network they run on, so that +> traffic classifies correctly on MAU-priced accounts: +> +> | Artifact | For | Entry point | +> |----------|-----|-------------| +> | `io.ably.pubsub:device` (aar) | Devices: Android apps and other end-user runtimes | `PubSubDevice.clientBuilder(...)` | +> | `io.ably.pubsub:server` (jar) | Servers and other trusted backend environments | `PubSubServer.httpClientBuilder(...)` / `PubSubServer.realtimeClientBuilder(...)` | +> | `io.ably.pubsub:core`, `io.ably.pubsub:core-android` | Internal implementation artifacts — do not depend on these directly | — | +> +> Nothing from this branch is published yet. The `io.ably:ably-java` and `io.ably:ably-android` +> 1.x artifacts continue to work and will receive security and critical fixes from a maintenance +> branch for one year after the 2.0 release. The installation instructions below still describe 1.x. + +--- + ## Getting started Everything you need to get started with Ably: diff --git a/build.gradle.kts b/build.gradle.kts index a98b165b6..6921a8130 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,56 @@ subprojects { } } +/* + * Release pre-flight: the split ships core, core-android, device and server in lockstep + * (one version, one run — PDR-091b), so the set of published artifacts and their + * coordinates are asserted here and the release workflow fails before anything is + * uploaded if they drift. If you add or remove a published module, update this list + * deliberately. + */ +val expectedReleaseArtifacts = sortedSetOf( + "io.ably.pubsub:core:jar", + "io.ably.pubsub:core-android:aar", + "io.ably.pubsub:device:aar", + "io.ably.pubsub:server:jar", + "io.ably.pubsub:liveobjects:jar", + "io.ably.pubsub:pubsub-adapter:jar", + "io.ably.pubsub:network-client-core:jar", + "io.ably.pubsub:network-client-default:jar", + "io.ably.pubsub:network-client-okhttp:jar", +) + +tasks.register("verifyReleaseArtifacts") { + description = "Asserts the published artifact set, group and lockstep version before a release." + doLast { + val rootVersion = project.property("VERSION_NAME") as String + val actual = sortedSetOf() + subprojects.filter { it.pluginManager.hasPlugin("com.vanniktech.maven.publish") }.forEach { p -> + val artifactId = p.findProperty("POM_ARTIFACT_ID") + ?: error("${p.path} applies maven-publish but has no POM_ARTIFACT_ID") + val packaging = p.findProperty("POM_PACKAGING") ?: "jar" + // The version each module publishes at comes from its effective VERSION_NAME + // (a module-local gradle.properties can override the root's — exactly the + // lockstep drift this guards against). + val moduleVersion = p.findProperty("VERSION_NAME") + if (moduleVersion != rootVersion) { + error("Lockstep violation: ${p.path} has VERSION_NAME $moduleVersion, expected $rootVersion") + } + val group = p.findProperty("GROUP") + actual.add("$group:$artifactId:$packaging") + } + if (actual != expectedReleaseArtifacts) { + error( + "Published artifact set does not match the expected release set.\n" + + " expected: $expectedReleaseArtifacts\n" + + " actual: $actual\n" + + "If this change is deliberate, update expectedReleaseArtifacts in build.gradle.kts." + ) + } + logger.lifecycle("Release pre-flight OK: ${actual.size} artifacts at $rootVersion: $actual") + } +} + configure(subprojects) { pluginManager.withPlugin("com.vanniktech.maven.publish") { extensions.configure {