From 5c116119ddc6240e592e0ffef5297102cfbcfd95 Mon Sep 17 00:00:00 2001 From: CherryPerry Date: Fri, 9 May 2025 10:38:31 +0100 Subject: [PATCH] Update publishing flow to support the new Sonatype scheme --- .github/workflows/build_1.x.yml | 21 +-- .github/workflows/build_2.x.yml | 14 +- .github/workflows/release.yml | 16 +-- RELEASING.md | 50 ++++--- build.gradle.kts | 1 - gradle/libs.versions.toml | 2 + libraries/core/build.gradle.kts | 2 +- libraries/customisations/build.gradle.kts | 5 +- libraries/interop-ribs/build.gradle.kts | 2 +- libraries/interop-rx2/build.gradle.kts | 2 +- libraries/interop-rx3/build.gradle.kts | 2 +- libraries/testing-junit4/build.gradle.kts | 2 +- libraries/testing-junit5/build.gradle.kts | 2 +- .../testing-ui-activity/build.gradle.kts | 2 +- libraries/testing-ui/build.gradle.kts | 2 +- .../testing-unit-common/build.gradle.kts | 2 +- plugins/publish-plugin/build.gradle.kts | 12 +- .../main/kotlin/AndroidAppyxPublishPlugin.kt | 18 --- .../src/main/kotlin/JavaAppyxPublishPlugin.kt | 14 -- .../src/main/kotlin/ProjectPlugin.kt | 126 ------------------ .../src/main/kotlin/PublishPlugin.kt | 66 +++++++++ 21 files changed, 126 insertions(+), 237 deletions(-) delete mode 100644 plugins/publish-plugin/src/main/kotlin/AndroidAppyxPublishPlugin.kt delete mode 100644 plugins/publish-plugin/src/main/kotlin/JavaAppyxPublishPlugin.kt delete mode 100644 plugins/publish-plugin/src/main/kotlin/ProjectPlugin.kt create mode 100644 plugins/publish-plugin/src/main/kotlin/PublishPlugin.kt diff --git a/.github/workflows/build_1.x.yml b/.github/workflows/build_1.x.yml index 2021a9216..fdabdd372 100644 --- a/.github/workflows/build_1.x.yml +++ b/.github/workflows/build_1.x.yml @@ -35,25 +35,14 @@ jobs: mergeDetektSarif :plugins:buildPlugins --continue - - name: Check publication setup - run: > - ./gradlew - publishAppyxReleasePublicationToOSSRHRepository - publishAppyxReleasePublicationToSonatypeSnapshotRepository - --dry-run - --no-parallel - name: Deploy snapshot if: env.MAIN_BRANCH == 'true' && github.repository == 'bumble-tech/appyx' env: - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - run: > - ./gradlew - publishAppyxReleasePublicationToSonatypeSnapshotRepository - -Psnapshot=true - --no-parallel - -Psigning.password=${{ secrets.SIGNING_PASSWORD }} - -Psonatype.username=${{ secrets.SONATYPE_USERNAME }} - -Psonatype.password=${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} + run: ./gradlew publishAllPublicationsToMavenCentralRepository -Psnapshot=true - uses: github/codeql-action/upload-sarif@v3 if: success() || failure() with: diff --git a/.github/workflows/build_2.x.yml b/.github/workflows/build_2.x.yml index 3608052ef..47d23442f 100644 --- a/.github/workflows/build_2.x.yml +++ b/.github/workflows/build_2.x.yml @@ -38,15 +38,11 @@ jobs: - name: Deploy snapshot if: env.MAIN_BRANCH == 'true' && github.repository == 'bumble-tech/appyx' env: - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - run: > - ./gradlew - publishAllPublicationsToSonatypeSnapshotRepository - -Psnapshot=true - --no-parallel - -Psigning.password=${{ secrets.SIGNING_PASSWORD }} - -Psonatype.username=${{ secrets.SONATYPE_USERNAME }} - -Psonatype.password=${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} + run: ./gradlew publishAllPublicationsToMavenCentralRepository -Psnapshot=true - uses: github/codeql-action/upload-sarif@v3 if: success() || failure() with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92ca2935f..ecc65164b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,13 +17,13 @@ jobs: java-version: '17' - uses: gradle/wrapper-validation-action@v2 - uses: gradle/actions/setup-gradle@v3 - env: - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} with: cache-read-only: true - arguments: | - publishAppyxReleasePublicationToOSSRHRepository - --no-parallel - -Psigning.password=${{ secrets.SIGNING_PASSWORD }} - -Psonatype.username=${{ secrets.SONATYPE_USERNAME }} - -Psonatype.password=${{ secrets.SONATYPE_PASSWORD }} + - name: Build & publish + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} + # Plugin requires "--no-configuration-cache" flag + run: ./gradlew publishAllPublicationsToMavenCentralRepository --no-configuration-cache diff --git a/RELEASING.md b/RELEASING.md index baa73eedc..b683f4d6d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,33 +2,31 @@ ## Automatic -1. Create PR with following changes: - 1. Update version in `gradle.properties`. - 2. Create a new section with version name taking all changes from pending section. -2. Create GitHub release with a tag name of the version and fill notes from pending changes section. -3. Library is published to staging repository automatically with GitHub Action. -4. Proceed to 'Closing staging repository'. +1. Create a PR with the following changes: + 1. Update the version in `gradle.properties`. + 2. Create a new section with the version name, including all changes from the pending section. +2. Create a GitHub release with a tag name matching the version and fill in the release notes using the pending changes section. +3. The library will be published to the staging repository automatically via GitHub Actions. +4. [Proceed to closing the staging repository](#closing-staging-repository). -In case of any issues you can launch Release manually from GitHub Actions tab. +If any issues arise, you can manually launch the release from the GitHub Actions tab. ## Manual -Should be used in case if there are issues with automatic publication. - -1. Create PR with following changes: - 1. Update version in `gradle.properties`. - 2. Create a new section with version name taking all changes from pending section. -2. `./gradle publishAppyxReleasePublicationToOSSRHRepository --no-parallel -Psigning.keyId=$KEY_ID -Psigning.password=$PASS -Psigning.secretKeyRingFile=$FILE -Psonatype.username=$NAME -Psonatype.password=$PASS` - 1. `signing` properties are related to signing information. - 2. `sonatype` properties are your username and password from `oss.sonatype.org`. - 3. `--no-parallel` is required to avoid creation of multiple staging repositories. -3. Create GitHub release with a tag name of the version and fill notes from pending changes section. -4. Cancel Release GitHub Action as it will fail now because version is already released. -5. Proceed to 'Closing staging repository'. - -## Closing staging repository - -1. Open `https://s01.oss.sonatype.org` and sign in with the sonatype credentials. -2. Click `Staging Repositories`. -3. Select the repository (assuming publish succeeded) and click the close button. -4. Select the repository again and click release. +This process should be used if there are issues with automatic publication. + +1. Create a PR with the following changes: + 1. Update the version in `gradle.properties`. + 2. Create a new section with the version name, including all changes from the pending section. +2. Refer to [.github/workflows/release.yml](.github/workflows/release.yml) for the manual release process. + - Check the [vanniktech.github.io plugin documentation](https://vanniktech.github.io/gradle-maven-publish-plugin/central/#secrets) to learn how to use Gradle properties instead of environment variables. +3. Create a GitHub release with the version's tag name and fill in the release notes using the pending changes section. +4. Cancel the `Release` GitHub Action, as it will fail because the version has already been released. +5. [Proceed to closing the staging repository](#closing-staging-repository). + +## Closing the Staging Repository + +1. Open [Sonatype Central](https://central.sonatype.com/publishing) and sign in with the Sonatype credentials. +2. Click `Deployments` and then `Publish` once validated. + +For more information, refer to [the official documentation](https://central.sonatype.org/publish/publish-portal-guide/#component-validation). diff --git a/build.gradle.kts b/build.gradle.kts index d51cbefc3..117dc6b38 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import com.android.Version import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe3e111e4..074f0a39f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -76,6 +76,8 @@ junit-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.ref plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } plugin-detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } plugin-android = { module = "com.android.tools.build:gradle", version.ref = "agp" } +plugin-publish = { module = "com.vanniktech.maven.publish:com.vanniktech.maven.publish.gradle.plugin", version = "0.31.0" } +plugin-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "2.0.0" } detekt-compose = "io.nlopez.compose.rules:detekt:0.1.6" toolargetool = "com.gu.android:toolargetool:0.3.0" diff --git a/libraries/core/build.gradle.kts b/libraries/core/build.gradle.kts index d99a92288..16554b340 100644 --- a/libraries/core/build.gradle.kts +++ b/libraries/core/build.gradle.kts @@ -5,7 +5,7 @@ plugins { alias(libs.plugins.compose.compiler) id("kotlin-android") id("kotlin-parcelize") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/libraries/customisations/build.gradle.kts b/libraries/customisations/build.gradle.kts index eb3e9fa71..ec76e0ae2 100644 --- a/libraries/customisations/build.gradle.kts +++ b/libraries/customisations/build.gradle.kts @@ -2,9 +2,8 @@ import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { - id("java-library") - id("kotlin") - id("appyx-publish-java") + id("org.jetbrains.kotlin.jvm") + id("appyx-publish") id("appyx-detekt") } diff --git a/libraries/interop-ribs/build.gradle.kts b/libraries/interop-ribs/build.gradle.kts index a86e2c23f..38cb52eae 100644 --- a/libraries/interop-ribs/build.gradle.kts +++ b/libraries/interop-ribs/build.gradle.kts @@ -5,7 +5,7 @@ plugins { alias(libs.plugins.compose.compiler) id("kotlin-android") id("kotlin-parcelize") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/libraries/interop-rx2/build.gradle.kts b/libraries/interop-rx2/build.gradle.kts index 5ada0d92c..1e58d4827 100644 --- a/libraries/interop-rx2/build.gradle.kts +++ b/libraries/interop-rx2/build.gradle.kts @@ -3,7 +3,7 @@ import org.jetbrains.kotlin.config.JvmTarget plugins { id("com.android.library") id("kotlin-android") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/libraries/interop-rx3/build.gradle.kts b/libraries/interop-rx3/build.gradle.kts index 30707bacd..0f0b9d374 100644 --- a/libraries/interop-rx3/build.gradle.kts +++ b/libraries/interop-rx3/build.gradle.kts @@ -3,7 +3,7 @@ import org.jetbrains.kotlin.config.JvmTarget plugins { id("com.android.library") id("kotlin-android") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/libraries/testing-junit4/build.gradle.kts b/libraries/testing-junit4/build.gradle.kts index 06ccc4fda..aaf9217b7 100644 --- a/libraries/testing-junit4/build.gradle.kts +++ b/libraries/testing-junit4/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.library") alias(libs.plugins.compose.compiler) id("kotlin-android") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/libraries/testing-junit5/build.gradle.kts b/libraries/testing-junit5/build.gradle.kts index 0ad856ea7..e183165cb 100644 --- a/libraries/testing-junit5/build.gradle.kts +++ b/libraries/testing-junit5/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.library") alias(libs.plugins.compose.compiler) id("kotlin-android") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/libraries/testing-ui-activity/build.gradle.kts b/libraries/testing-ui-activity/build.gradle.kts index 844187df9..49c3b601e 100644 --- a/libraries/testing-ui-activity/build.gradle.kts +++ b/libraries/testing-ui-activity/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.library") alias(libs.plugins.compose.compiler) id("kotlin-android") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/libraries/testing-ui/build.gradle.kts b/libraries/testing-ui/build.gradle.kts index 2897f9337..4213e3eb6 100644 --- a/libraries/testing-ui/build.gradle.kts +++ b/libraries/testing-ui/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.library") alias(libs.plugins.compose.compiler) id("kotlin-android") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/libraries/testing-unit-common/build.gradle.kts b/libraries/testing-unit-common/build.gradle.kts index 2c4fae76d..1db02ac6d 100644 --- a/libraries/testing-unit-common/build.gradle.kts +++ b/libraries/testing-unit-common/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("com.android.library") alias(libs.plugins.compose.compiler) id("kotlin-android") - id("appyx-publish-android") + id("appyx-publish") id("appyx-lint") id("appyx-detekt") } diff --git a/plugins/publish-plugin/build.gradle.kts b/plugins/publish-plugin/build.gradle.kts index 473ecef62..53b65e191 100644 --- a/plugins/publish-plugin/build.gradle.kts +++ b/plugins/publish-plugin/build.gradle.kts @@ -9,6 +9,8 @@ plugins { dependencies { implementation(libs.plugin.android) implementation(libs.plugin.kotlin) + implementation(libs.plugin.publish) + implementation(libs.plugin.dokka) } java { @@ -27,13 +29,9 @@ detekt { gradlePlugin { plugins { - create("appyx-publish-android") { - id = "appyx-publish-android" - implementationClass = "AndroidAppyxPublishPlugin" - } - create("appyx-publish-java") { - id = "appyx-publish-java" - implementationClass = "JavaAppyxPublishPlugin" + create("appyx-publish") { + id = "appyx-publish" + implementationClass = "PublishPlugin" } } } diff --git a/plugins/publish-plugin/src/main/kotlin/AndroidAppyxPublishPlugin.kt b/plugins/publish-plugin/src/main/kotlin/AndroidAppyxPublishPlugin.kt deleted file mode 100644 index 108768377..000000000 --- a/plugins/publish-plugin/src/main/kotlin/AndroidAppyxPublishPlugin.kt +++ /dev/null @@ -1,18 +0,0 @@ -import com.android.build.gradle.LibraryExtension -import org.gradle.api.Project -import org.gradle.kotlin.dsl.configure - -internal class AndroidAppyxPublishPlugin : ProjectPlugin() { - override fun configureDocAndSources(project: Project) { - project.configure { - publishing { - singleVariant(getComponentName()) { - withSourcesJar() - withJavadocJar() - } - } - } - } - - override fun getComponentName(): String = "release" -} diff --git a/plugins/publish-plugin/src/main/kotlin/JavaAppyxPublishPlugin.kt b/plugins/publish-plugin/src/main/kotlin/JavaAppyxPublishPlugin.kt deleted file mode 100644 index 3caffffa9..000000000 --- a/plugins/publish-plugin/src/main/kotlin/JavaAppyxPublishPlugin.kt +++ /dev/null @@ -1,14 +0,0 @@ -import org.gradle.api.Project -import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.kotlin.dsl.configure - -internal class JavaAppyxPublishPlugin : ProjectPlugin() { - override fun configureDocAndSources(project: Project) { - project.configure { - withJavadocJar() - withSourcesJar() - } - } - - override fun getComponentName(): String = "java" -} diff --git a/plugins/publish-plugin/src/main/kotlin/ProjectPlugin.kt b/plugins/publish-plugin/src/main/kotlin/ProjectPlugin.kt deleted file mode 100644 index c6ebd1a6d..000000000 --- a/plugins/publish-plugin/src/main/kotlin/ProjectPlugin.kt +++ /dev/null @@ -1,126 +0,0 @@ -import org.gradle.api.GradleException -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.publish.PublicationContainer -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.create -import org.gradle.kotlin.dsl.get -import org.gradle.plugins.signing.SigningExtension - -internal abstract class ProjectPlugin : Plugin { - override fun apply(project: Project) { - project.pluginManager.apply { - apply("maven-publish") - apply("signing") - } - - configureDocAndSources(project) - - project.afterEvaluate { - project.configure { - configurePublishing(project) - } - - project.configure { - configureSigning() - } - - project.tasks.named("publishAppyxReleasePublicationToSonatypeSnapshotRepository") { - val fail = !project.isSnapshotPublication - doFirst { - if (fail) throw GradleException( - "Publishing to snapshot repository with disabled \"snapshot\" flag is permitted" - ) - } - } - } - } - - protected abstract fun configureDocAndSources(project: Project) - - protected abstract fun getComponentName(): String - - private fun PublishingExtension.configurePublishing(project: Project) { - publications { - setupPublications(project) - } - repositories { - fun addMaven(name: String, url: String) { - maven { - this.name = name - this.url = project.uri(url) - credentials { - username = project.findProperty("sonatype.username").toString() - password = project.findProperty("sonatype.password").toString() - } - } - } - addMaven("OSSRH", "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") - addMaven( - "SonatypeSnapshot", - "https://s01.oss.sonatype.org/content/repositories/snapshots/" - ) - } - } - - private fun PublicationContainer.setupPublications(project: Project) { - create("appyxRelease") { - val definedVersion = project.findProperty("library.version")?.toString() - ?: throw GradleException("'library.version' has not been set") - from(project.components[getComponentName()]) - groupId = "com.bumble.appyx" - version = if (project.isSnapshotPublication) { - "v${definedVersion.split('.').first()}-SNAPSHOT" - } else { - definedVersion - } - - pom { - name.set("Appyx") - description.set("Appyx") - url.set("https://github.com/bumble-tech/appyx") - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - developers { - developer { - id.set("bumble") - name.set("Bumble") - email.set("appyx@team.bumble.com") - } - } - scm { - connection.set("scm:git:ssh://github.com/bumble-tech/appyx.git") - developerConnection.set("scm:git:ssh://github.com/bumble-tech/appyx.git") - url.set("https://github.com/bumble-tech/appyx") - } - } - } - } - - private fun SigningExtension.configureSigning() { - sign((project.extensions.getByName("publishing") as PublishingExtension).publications["appyxRelease"]) - isRequired = true - - val inMemoryKey = System.getenv("SIGNING_KEY") - if (inMemoryKey != null) { - useInMemoryPgpKeys( - inMemoryKey, - project.findProperty("signing.password").toString() - ) - } - } - - private companion object { - - val Project.isSnapshotPublication: Boolean - get() = findProperty("snapshot") == "true" - - } - -} diff --git a/plugins/publish-plugin/src/main/kotlin/PublishPlugin.kt b/plugins/publish-plugin/src/main/kotlin/PublishPlugin.kt new file mode 100644 index 000000000..48345a3dc --- /dev/null +++ b/plugins/publish-plugin/src/main/kotlin/PublishPlugin.kt @@ -0,0 +1,66 @@ +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import com.vanniktech.maven.publish.SonatypeHost +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.publish.maven.MavenPom + +class PublishPlugin : Plugin { + override fun apply(project: Project) { + project.pluginManager.apply("com.vanniktech.maven.publish") + if (project.pluginManager.hasPlugin("org.jetbrains.kotlin.jvm")) { + project.pluginManager.apply("org.jetbrains.dokka") + } + project.extensions.configure("mavenPublishing") { + configureBasedOnAppliedPlugins() + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + signAllPublications() + coordinates( + artifactId = project.name, + groupId = "com.bumble.appyx", + version = project.publishVersion, + ) + pom { setup() } + } + } + + private val Project.isSnapshotPublication: Boolean + get() = findProperty("snapshot") == "true" + + private val Project.publishVersion: String + get() { + val definedVersion = project.findProperty("library.version")?.toString() + ?: throw GradleException("'library.version' has not been set") + val version = if (project.isSnapshotPublication) { + "v${definedVersion.split('.').first()}-SNAPSHOT" + } else { + definedVersion + } + return version + } + + private fun MavenPom.setup() { + name.set("Appyx") + description.set("Appyx") + url.set("https://github.com/bumble-tech/appyx") + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + developers { + developer { + id.set("bumble") + name.set("Bumble") + email.set("appyx@team.bumble.com") + } + } + scm { + connection.set("scm:git:ssh://github.com/bumble-tech/appyx.git") + developerConnection.set("scm:git:ssh://github.com/bumble-tech/appyx.git") + url.set("https://github.com/bumble-tech/appyx") + } + } + +}