Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate Promoting Artifacts in Sonatype #1453

Merged
merged 12 commits into from
Jul 24, 2024
Merged
6 changes: 6 additions & 0 deletions .github/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
template: |
## What's Changed

$CHANGES

**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...$THIS_TAG
33 changes: 33 additions & 0 deletions .github/workflows/ci_github_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Create Release

on:
push:
tags:
- 'BOM_*.*.*'

jobs:
create_release:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Generate Release Notes
id: drafter
uses: release-drafter/release-drafter@v5
with:
config-name: release-drafter.yml
env:
GITHUB_TOKEN: ${{ secrets.ASSIGN_TO_PROJECT_GITHUB_TOKEN }}

- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.ASSIGN_TO_PROJECT_GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: ${{ steps.drafter.outputs.changelog }}
draft: false
prerelease: false
34 changes: 26 additions & 8 deletions .github/workflows/ci_release_articacts.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
name: Release Artifacts - Sonatype

#on:
# push:
# branches:
# - master

on:
workflow_dispatch:
push:
branches:
- master

jobs:
release:
Expand Down Expand Up @@ -55,7 +52,18 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew

- name: Run releaseAllSDKs task
- name: Run Release all SDKs locally task
env:
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
run: |
./gradlew releaseAllSDKs -Ptype=local

- name: Run Publish all SDKs to Sonatype task
env:
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
Expand All @@ -65,4 +73,14 @@ jobs:
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
run: |
./gradlew releaseAllSDKs -Ptype=sonatype
./gradlew closeSonatypeStagingRepository

- name: Run Close and Release staging repositories task
env:
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
run: |
./gradlew closeAndReleaseMultipleRepositories
191 changes: 190 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@

import com.android.build.gradle.BaseExtension
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.sonarqube.gradle.SonarExtension
import java.util.Base64
import javax.xml.parsers.DocumentBuilderFactory

plugins {
alias(libs.plugins.nexusPublish)
Expand Down Expand Up @@ -99,6 +107,8 @@ task<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}



nexusPublishing {
repositories {
// project.version = "-SNAPSHOT"
Expand All @@ -110,4 +120,183 @@ nexusPublishing {
snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
}
}
}
}

val nexusUsername: String get() = System.getenv("OSSRH_USERNAME")
val nexusPassword: String get() = System.getenv("OSSRH_PASSWORD")
val nexusUrl = "https://s01.oss.sonatype.org/service/local/staging"

tasks.register("closeAndReleaseMultipleRepositories") {
group = "release"
description = "Release all Sonatype staging repositories"

doLast {
val repos = fetchRepositoryIds()
if (repos.isEmpty()) {
println("No open repositories found")
return@doLast
}
closeRepositories(repos)
waitForAllRepositoriesToClose(repos)
releaseRepositories(repos)
waitForArtifactsToBeAvailable()
//todo task for pushing a tag
}
}

fun fetchRepositoryIds(): List<String> {
val client = HttpClients.createDefault()
val httpGet = HttpGet("$nexusUrl/profile_repositories").apply {
setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("$nexusUsername:$nexusPassword".toByteArray()))
}

val response = client.execute(httpGet)
val responseBody = EntityUtils.toString(response.entity)
if (response.statusLine.statusCode != 200) {
throw RuntimeException("Failed: HTTP error code : ${response.statusLine.statusCode} $responseBody")
}

return parseRepositoryIds(responseBody)
}

fun parseRepositoryIds(xmlResponse: String): List<String> {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val inputStream = xmlResponse.byteInputStream()
val doc = builder.parse(inputStream)
val nodeList = doc.getElementsByTagName("stagingProfileRepository")

val repositoryIds = mutableListOf<String>()
for (i in 0 until nodeList.length) {
val node = nodeList.item(i)
val element = node as? org.w3c.dom.Element
val repoId = element?.getElementsByTagName("repositoryId")?.item(0)?.textContent
val type = element?.getElementsByTagName("type")?.item(0)?.textContent
if (repoId != null && type == "open") {
repositoryIds.add(repoId)
}
}
return repositoryIds
}

fun closeRepositories(repoIds: List<String>) {
val closeUrl = "$nexusUrl/bulk/close"
val json = """
{
"data": {
"stagedRepositoryIds": ${repoIds.joinToString(prefix = "[\"", separator = "\",\"", postfix = "\"]")}
}
}
""".trimIndent()
executePostRequest(closeUrl, json)
}

fun waitForAllRepositoriesToClose(repoIds: List<String>) {
val client = HttpClients.createDefault()
val statusUrl = "$nexusUrl/repository/"
val closedRepos = mutableSetOf<String>()

while (closedRepos.size < repoIds.size) {
repoIds.forEach { repoId ->
if (!closedRepos.contains(repoId)) {
val httpGet = HttpGet("$statusUrl$repoId").apply {
setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("$nexusUsername:$nexusPassword".toByteArray()))
}
val response = client.execute(httpGet)
println("GET request to $repoId returned status code: ${response.statusLine.statusCode}")
val responseBody = EntityUtils.toString(response.entity)

val state = parseRepositoryState(responseBody, repoId)
if (state == "closed") {
println("Repository $repoId is now in state: $state")
closedRepos.add(repoId)
} else {
println("Waiting for repository $repoId to be closed, current state: $state")
}
}
}
if (closedRepos.size < repoIds.size) {
Thread.sleep(30000) // Wait for 30 seconds before retrying
}
}
}

fun releaseRepositories(repoIds: List<String>) {
val releaseUrl = "$nexusUrl/bulk/promote"
val json = """
{
"data": {
"stagedRepositoryIds": ${repoIds.joinToString(prefix = "[\"", separator = "\",\"", postfix = "\"]")}
}
}
""".trimIndent()
executePostRequest(releaseUrl, json)
}

fun waitForArtifactsToBeAvailable() {
val client = HttpClients.createDefault()
var artifactsAvailable = false

while (!artifactsAvailable) {
artifactsAvailable = repoIdWithVersion.all { (repoId, version) ->
val artifactUrl = "https://repo1.maven.org/maven2/com/walletconnect/$repoId/$version/"
val httpGet = HttpGet(artifactUrl)
val response = client.execute(httpGet)
response.statusLine.statusCode == 200
}

if (!artifactsAvailable) {
println("Artifacts not yet available. Waiting...")
Thread.sleep(30000) // Wait for 30 seconds before retrying
} else {
println("All artifacts are now available.")
}
}
}

fun executePostRequest(url: String, json: String) {
val client = HttpClients.createDefault()
val httpPost = HttpPost(url).apply {
setHeader("Content-type", "application/json")
entity = StringEntity(json)
setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("$nexusUsername:$nexusPassword".toByteArray()))
}

val response = client.execute(httpPost)
val responseBody = EntityUtils.toString(response.entity)
if (response.statusLine.statusCode != 201) {
throw RuntimeException("Failed: HTTP error code : ${response.statusLine.statusCode} $responseBody")
}
}

fun parseRepositoryState(xmlResponse: String, repositoryId: String): String? {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val inputStream = xmlResponse.byteInputStream()
val doc = builder.parse(inputStream)
val nodeList = doc.getElementsByTagName("stagingProfileRepository")

for (i in 0 until nodeList.length) {
val node = nodeList.item(i)
val element = node as? org.w3c.dom.Element
val repoId = element?.getElementsByTagName("repositoryId")?.item(0)?.textContent
if (repoId == repositoryId) {
return element.getElementsByTagName("type").item(0)?.textContent
}
}
return null
}

private val repoIdWithVersion = listOf(
Pair(ANDROID_BOM, BOM_VERSION),
Pair(FOUNDATION, FOUNDATION_VERSION),
Pair(ANDROID_CORE, CORE_VERSION),
Pair(SIGN, SIGN_VERSION),
Pair(AUTH, AUTH_VERSION),
Pair(CHAT, CHAT_VERSION),
Pair(NOTIFY, NOTIFY_VERSION),
Pair(WEB_3_WALLET, WEB_3_WALLET_VERSION),
Pair(WEB_3_MODAL, WEB_3_MODAL_VERSION),
Pair(WC_MODAL, WC_MODAL_VERSION),
Pair(MODAL_CORE, MODAL_CORE_VERSION)
)
13 changes: 13 additions & 0 deletions buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ const val WEB_3_MODAL_VERSION = "1.5.4"
const val WC_MODAL_VERSION = "1.5.4"
const val MODAL_CORE_VERSION = "1.5.4"

//Artifact ids
const val ANDROID_BOM = "android-bom"
const val FOUNDATION = "foundation"
const val ANDROID_CORE = "android-core"
const val SIGN = "sign"
const val AUTH = "auth"
const val CHAT = "chat"
const val NOTIFY = "notify"
const val WEB_3_WALLET = "web3wallet"
const val WEB_3_MODAL = "web3modal"
const val WC_MODAL = "walletconnect-modal"
const val MODAL_CORE = "modal-core"

val jvmVersion = JavaVersion.VERSION_11
const val MIN_SDK: Int = 23
const val TARGET_SDK: Int = 34
Expand Down
2 changes: 1 addition & 1 deletion core/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

project.apply {
extra[KEY_PUBLISH_ARTIFACT_ID] = "android-core"
extra[KEY_PUBLISH_ARTIFACT_ID] = ANDROID_CORE
extra[KEY_PUBLISH_VERSION] = CORE_VERSION
extra[KEY_SDK_NAME] = "Android Core"
}
Expand Down
2 changes: 1 addition & 1 deletion core/bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

project.apply {
extra[KEY_PUBLISH_ARTIFACT_ID] = "android-bom"
extra[KEY_PUBLISH_ARTIFACT_ID] = ANDROID_BOM
extra[KEY_PUBLISH_VERSION] = BOM_VERSION
extra[KEY_SDK_NAME] = "Android BOM"
}
Expand Down
2 changes: 1 addition & 1 deletion core/modal/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

project.apply {
extra[KEY_PUBLISH_ARTIFACT_ID] = "modal-core"
extra[KEY_PUBLISH_ARTIFACT_ID] = MODAL_CORE
extra[KEY_PUBLISH_VERSION] = MODAL_CORE_VERSION
extra[KEY_SDK_NAME] = "Modal Core"
}
Expand Down
2 changes: 1 addition & 1 deletion foundation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

project.apply {
extra[KEY_PUBLISH_ARTIFACT_ID] = "foundation"
extra[KEY_PUBLISH_ARTIFACT_ID] = FOUNDATION
extra[KEY_PUBLISH_VERSION] = FOUNDATION_VERSION
extra[KEY_SDK_NAME] = "Foundation"
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -207,5 +207,5 @@ javaLibrary = { id = "org.gradle.java-library" }

sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" }
sonarqube = { id = "org.sonarqube", version = "4.4.1.3373" }
nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "1.1.0" }
nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" }
paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi" }
2 changes: 1 addition & 1 deletion product/walletconnectmodal/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

project.apply {
extra[KEY_PUBLISH_ARTIFACT_ID] = "walletconnect-modal"
extra[KEY_PUBLISH_ARTIFACT_ID] = WC_MODAL
extra[KEY_PUBLISH_VERSION] = WC_MODAL_VERSION
extra[KEY_SDK_NAME] = "Wallet Connect Modal"
}
Expand Down
2 changes: 1 addition & 1 deletion product/web3modal/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

project.apply {
extra[KEY_PUBLISH_ARTIFACT_ID] = "web3modal"
extra[KEY_PUBLISH_ARTIFACT_ID] = WEB_3_MODAL
extra[KEY_PUBLISH_VERSION] = WEB_3_MODAL_VERSION
extra[KEY_SDK_NAME] = "web3modal"
}
Expand Down
2 changes: 1 addition & 1 deletion product/web3wallet/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
}

project.apply {
extra[KEY_PUBLISH_ARTIFACT_ID] = "web3wallet"
extra[KEY_PUBLISH_ARTIFACT_ID] = WEB_3_WALLET
extra[KEY_PUBLISH_VERSION] = WEB_3_WALLET_VERSION
extra[KEY_SDK_NAME] = "web3wallet"
}
Expand Down
Loading
Loading