From 1d311d91354c29e4b27c3f5cca7230b10569c98c Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 28 May 2025 18:17:20 +0200 Subject: [PATCH] [libgdx][android] Migrate to Central Portal publishing with unified version management --- .gitignore | 4 + spine-android/build.gradle.kts | 31 +++++ spine-android/gradle.properties | 16 ++- spine-android/publish.sh | 35 ++++-- spine-android/spine-android/build.gradle.kts | 93 +++++++++++--- spine-libgdx/build.gradle | 51 +++++++- spine-libgdx/gradle.properties | 12 ++ spine-libgdx/publish.sh | 35 ++++-- spine-libgdx/publishing.gradle | 81 ++++++++---- spine-libgdx/spine-libgdx/pom.xml | 126 ------------------- 10 files changed, 297 insertions(+), 187 deletions(-) create mode 100644 spine-libgdx/gradle.properties delete mode 100644 spine-libgdx/spine-libgdx/pom.xml diff --git a/.gitignore b/.gitignore index 1140eb43a..9223ff197 100644 --- a/.gitignore +++ b/.gitignore @@ -222,3 +222,7 @@ spine-godot/vc140.pdb spine-godot/example-v4-extension/bin spine-godot/example-v4-extension/MoltenVK.xcframework spine-flutter/example/android/app/.cxx +.claude +CLAUDE.md +port-plan.md +port-todo.md diff --git a/spine-android/build.gradle.kts b/spine-android/build.gradle.kts index 9e4ae6cfd..6c53873c0 100644 --- a/spine-android/build.gradle.kts +++ b/spine-android/build.gradle.kts @@ -1,6 +1,37 @@ +buildscript { + repositories { + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath("org.jreleaser:jreleaser-gradle-plugin:1.18.0") + } +} + + // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.jetbrainsKotlinAndroid) apply false alias(libs.plugins.androidLibrary) apply false +} + +// Read version from spine-libgdx gradle.properties to ensure consistency +fun getSpineVersion(): String { + val spineLibgdxPropsFile = File(project.projectDir, "../spine-libgdx/gradle.properties") + if (!spineLibgdxPropsFile.exists()) { + throw GradleException("spine-libgdx gradle.properties file not found at ${spineLibgdxPropsFile.absolutePath}") + } + val lines = spineLibgdxPropsFile.readLines() + for (line in lines) { + if (line.startsWith("version=")) { + return line.split("=")[1].trim() + } + } + throw GradleException("version property not found in spine-libgdx gradle.properties") +} + +allprojects { + group = "com.esotericsoftware.spine" + version = getSpineVersion() } \ No newline at end of file diff --git a/spine-android/gradle.properties b/spine-android/gradle.properties index 20e2a0152..ef00bec41 100644 --- a/spine-android/gradle.properties +++ b/spine-android/gradle.properties @@ -20,4 +20,18 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true + +# Version configuration for publishing - matches spine-libgdx version +# Note: Actual version is read from spine-libgdx/gradle.properties at build time + +# POM metadata +POM_NAME=spine-android +POM_DESCRIPTION=Spine Runtime for Android +POM_URL=https://github.com/esotericsoftware/spine-runtimes +POM_SCM_URL=https://github.com/esotericsoftware/spine-runtimes +POM_SCM_CONNECTION=scm:git:https://github.com/esotericsoftware/spine-runtimes.git +POM_SCM_DEV_CONNECTION=scm:git:https://github.com/esotericsoftware/spine-runtimes.git +POM_LICENCE_NAME=Spine Runtimes License +POM_LICENCE_URL=http://esotericsoftware.com/spine-runtimes-license +POM_LICENCE_DIST=repo \ No newline at end of file diff --git a/spine-android/publish.sh b/spine-android/publish.sh index d1f97c465..4ad5bb6f2 100755 --- a/spine-android/publish.sh +++ b/spine-android/publish.sh @@ -1,14 +1,33 @@ #!/bin/sh # -# 1. Set up PGP key for signing -# 2. Create ~/.gradle/gradle.properties -# 3. Add -# ossrhUsername= -# ossrhPassword= +# Modern Central Portal Publishing Setup: +# 1. Set up PGP key for signing: gpg --generate-key +# 2. Create Central Portal account at https://central.sonatype.com/ +# 3. Generate user token in Central Portal settings +# 4. Create ~/.gradle/gradle.properties with: +# MAVEN_USERNAME= +# MAVEN_PASSWORD= # signing.gnupg.passphrase= # -# After publishing via this script, log into https://oss.sonatype.org and release it manually after -# checks pass ("Release & Drop"). +# Version Configuration: +# - Edit spine-libgdx/gradle.properties and update the 'version' property. YES, THIS IS THE SINGLE SOURCE OF TRUTH. +# - For releases: version=4.2.9 (no -SNAPSHOT suffix) +# - For snapshots: version=4.2.9-SNAPSHOT (with -SNAPSHOT suffix) +# +# Usage: ./publish.sh +# The script automatically detects snapshot vs release based on version in gradle.properties +# + set -e - ./gradlew publishReleasePublicationToSonaTypeRepository --info \ No newline at end of file + +# Read version from spine-libgdx gradle.properties (single source of truth) +VERSION=$(grep '^version=' ../spine-libgdx/gradle.properties | cut -d'=' -f2) + +if echo "$VERSION" | grep -q "SNAPSHOT"; then + echo "Publishing SNAPSHOT version $VERSION to Central Portal..." + ./gradlew publishReleasePublicationToSonaTypeRepository --info +else + echo "Publishing RELEASE version $VERSION to Central Portal via JReleaser..." + ./gradlew publishRelease -PRELEASE --info +fi \ No newline at end of file diff --git a/spine-android/spine-android/build.gradle.kts b/spine-android/spine-android/build.gradle.kts index abc9a777f..03d34520f 100644 --- a/spine-android/spine-android/build.gradle.kts +++ b/spine-android/spine-android/build.gradle.kts @@ -1,9 +1,31 @@ +import java.util.Properties + plugins { alias(libs.plugins.androidLibrary) `maven-publish` signing } +// Function to read properties from spine-libgdx gradle.properties - single source of truth +fun readSpineLibgdxProperty(propertyName: String): String { + val spineLibgdxPropsFile = File(rootProject.projectDir, "../spine-libgdx/gradle.properties") + if (!spineLibgdxPropsFile.exists()) { + throw GradleException("spine-libgdx gradle.properties file not found at ${spineLibgdxPropsFile.absolutePath}") + } + val lines = spineLibgdxPropsFile.readLines() + for (line in lines) { + if (line.startsWith("$propertyName=")) { + return line.split("=")[1].trim() + } + } + throw GradleException("Property '$propertyName' not found in spine-libgdx gradle.properties") +} + +// JReleaser config for release builds to Central Portal +if (project.hasProperty("RELEASE")) { + apply(plugin = "org.jreleaser") +} + android { namespace = "com.esotericsoftware.spine" compileSdk = 34 @@ -32,15 +54,14 @@ android { dependencies { implementation(libs.androidx.appcompat) - api("com.badlogicgames.gdx:gdx:1.12.2-SNAPSHOT") - api("com.esotericsoftware.spine:spine-libgdx:4.2.7") + api("com.badlogicgames.gdx:gdx:${readSpineLibgdxProperty("libgdx_version")}") + api("com.esotericsoftware.spine:spine-libgdx:${readSpineLibgdxProperty("version")}") testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) } -val libraryVersion = "4.2.10-SNAPSHOT"; tasks.register("sourceJar") { archiveClassifier.set("sources") @@ -56,17 +77,20 @@ afterEvaluate { groupId = "com.esotericsoftware.spine" artifactId = "spine-android" - version = libraryVersion + version = project.version.toString() pom { packaging = "aar" - name.set("spine-android") - description.set("Spine Runtime for Android") - url.set("https://github.com/esotericsoftware/spine-runtimes") + name.set(project.findProperty("POM_NAME") as String? ?: "spine-android") + if (project.hasProperty("POM_DESCRIPTION")) { + description.set(project.findProperty("POM_DESCRIPTION") as String) + } + url.set(project.findProperty("POM_URL") as String? ?: "https://github.com/esotericsoftware/spine-runtimes") licenses { license { - name.set("Spine Runtimes License") - url.set("http://esotericsoftware.com/spine-runtimes-license") + name.set(project.findProperty("POM_LICENCE_NAME") as String? ?: "Spine Runtimes License") + url.set(project.findProperty("POM_LICENCE_URL") as String? ?: "http://esotericsoftware.com/spine-runtimes-license") + distribution.set(project.findProperty("POM_LICENCE_DIST") as String? ?: "repo") } } developers { @@ -76,9 +100,9 @@ afterEvaluate { } } scm { - url.set(pom.url.get()) - connection.set("scm:git:${url.get()}.git") - developerConnection.set("scm:git:${url.get()}.git") + connection.set(project.findProperty("POM_SCM_CONNECTION") as String? ?: "scm:git:https://github.com/esotericsoftware/spine-runtimes.git") + developerConnection.set(project.findProperty("POM_SCM_DEV_CONNECTION") as String? ?: "scm:git:https://github.com/esotericsoftware/spine-runtimes.git") + url.set(project.findProperty("POM_SCM_URL") as String? ?: "https://github.com/esotericsoftware/spine-runtimes") } withXml { @@ -108,15 +132,34 @@ afterEvaluate { repositories { maven { name = "SonaType" - url = uri(if (libraryVersion.endsWith("-SNAPSHOT")) { - "https://oss.sonatype.org/content/repositories/snapshots" + url = uri(if (project.version.toString().endsWith("-SNAPSHOT")) { + if (project.hasProperty("SNAPSHOT_REPOSITORY_URL")) { + project.property("SNAPSHOT_REPOSITORY_URL") as String + } else { + "https://central.sonatype.com/repository/maven-snapshots/" + } } else { - "https://oss.sonatype.org/service/local/staging/deploy/maven2" + // If release build, dump artifacts to local build/staging-deploy folder for consumption by jreleaser + layout.buildDirectory.dir("staging-deploy") }) - credentials { - username = project.findProperty("ossrhUsername") as String? - password = project.findProperty("ossrhPassword") as String? + if (project.version.toString().endsWith("-SNAPSHOT")) { + val username = if (project.hasProperty("MAVEN_USERNAME")) { + project.property("MAVEN_USERNAME") as String + } else { + "" + } + val password = if (project.hasProperty("MAVEN_PASSWORD")) { + project.property("MAVEN_PASSWORD") as String + } else { + "" + } + if (username.isNotEmpty() || password.isNotEmpty()) { + credentials { + this.username = username + this.password = password + } + } } } } @@ -129,6 +172,18 @@ afterEvaluate { } tasks.withType { - onlyIf { !libraryVersion.endsWith("-SNAPSHOT") } + onlyIf { !project.version.toString().endsWith("-SNAPSHOT") } + } +} + +// For release builds, create a task that depends on publishing and finalizes with jreleaser +if (project.hasProperty("RELEASE")) { + tasks.register("publishRelease") { + dependsOn(tasks.withType()) + doLast { + exec { + commandLine("./gradlew", "jreleaserDeploy") + } + } } } diff --git a/spine-libgdx/build.gradle b/spine-libgdx/build.gradle index bb7497880..38734a0b3 100644 --- a/spine-libgdx/build.gradle +++ b/spine-libgdx/build.gradle @@ -1,22 +1,65 @@ +buildscript { + repositories { + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath 'org.jreleaser:jreleaser-gradle-plugin:1.18.0' + } +} + ext { - libgdxVersion = "1.12.2-SNAPSHOT" + libgdxVersion = { -> + def propsFile = new File(project.projectDir, 'gradle.properties') + if (!propsFile.exists()) { + throw new GradleException("gradle.properties file not found at ${propsFile.absolutePath}") + } + def lines = propsFile.readLines() + for (line in lines) { + if (line.startsWith('libgdx_version=')) { + return line.split('=')[1].trim() + } + } + throw new GradleException("libgdx_version property not found in gradle.properties") + }() + + isReleaseBuild = { + return project.hasProperty("RELEASE") + } + + getSnapshotRepositoryUrl = { + return project.hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL + : "https://central.sonatype.com/repository/maven-snapshots/" + } + + getRepositoryUsername = { + return project.hasProperty('MAVEN_USERNAME') ? MAVEN_USERNAME : "" + } + + getRepositoryPassword = { + return project.hasProperty('MAVEN_PASSWORD') ? MAVEN_PASSWORD : "" + } } allprojects { apply plugin: "java" + group = 'com.esotericsoftware.spine' + version = project.getProperty('version') + sourceSets.main.java.srcDirs = ["src"] repositories { mavenLocal() maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + maven { url "https://central.sonatype.com/repository/maven-snapshots/" } mavenCentral() } tasks.withType(JavaCompile).configureEach { - sourceCompatibility = '1.7' - targetCompatibility = '1.7' - options.release.set(7) // Ensures Java 8 bytecode is produced + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + options.release.set(8) } } diff --git a/spine-libgdx/gradle.properties b/spine-libgdx/gradle.properties new file mode 100644 index 000000000..cbd84db2a --- /dev/null +++ b/spine-libgdx/gradle.properties @@ -0,0 +1,12 @@ +group=com.esotericsoftware.spine +version=4.2.10-SNAPSHOT +libgdx_version=1.13.5 +POM_NAME=spine-libgdx +POM_DESCRIPTION=Spine Runtime for libGDX +POM_URL=https://github.com/esotericsoftware/spine-runtimes +POM_SCM_URL=https://github.com/esotericsoftware/spine-runtimes +POM_SCM_CONNECTION=scm:git@github.com:esotericsoftware/spine-runtimes.git +POM_SCM_DEV_CONNECTION=scm:git@github.com:esotericsoftware/spine-runtimes.git +POM_LICENCE_NAME=Spine Runtimes License +POM_LICENCE_URL=https://esotericsoftware.com/spine-runtimes-license +POM_LICENCE_DIST=repo \ No newline at end of file diff --git a/spine-libgdx/publish.sh b/spine-libgdx/publish.sh index 280b33467..945e2c061 100755 --- a/spine-libgdx/publish.sh +++ b/spine-libgdx/publish.sh @@ -1,14 +1,33 @@ #!/bin/sh # -# 1. Set up PGP key for signing -# 2. Create ~/.gradle/gradle.properties -# 3. Add -# ossrhUsername= -# ossrhPassword= +# Modern Central Portal Publishing Setup: +# 1. Set up PGP key for signing: gpg --generate-key +# 2. Create Central Portal account at https://central.sonatype.com/ +# 3. Generate user token in Central Portal settings +# 4. Create ~/.gradle/gradle.properties with: +# MAVEN_USERNAME= +# MAVEN_PASSWORD= # signing.gnupg.passphrase= # -# After publishing via this script, log into https://oss.sonatype.org and release it manually after -# checks pass ("Close -> Release & Drop"). +# Version Configuration: +# - Edit gradle.properties and update the 'version' property +# - For releases: version=4.2.9 (no -SNAPSHOT suffix) +# - For snapshots: version=4.2.9-SNAPSHOT (with -SNAPSHOT suffix) +# +# Usage: ./publish.sh +# The script automatically detects snapshot vs release based on version in gradle.properties +# + set -e -./gradlew publishReleasePublicationToSonaTypeRepository \ No newline at end of file + +# Read version from gradle.properties +VERSION=$(grep '^version=' gradle.properties | cut -d'=' -f2) + +if echo "$VERSION" | grep -q "SNAPSHOT"; then + echo "Publishing SNAPSHOT version $VERSION to Central Portal..." + ./gradlew publishReleasePublicationToSonaTypeRepository +else + echo "Publishing RELEASE version $VERSION to Central Portal via JReleaser..." + ./gradlew publishRelease -PRELEASE +fi \ No newline at end of file diff --git a/spine-libgdx/publishing.gradle b/spine-libgdx/publishing.gradle index d0fc1bb75..78cf531fc 100644 --- a/spine-libgdx/publishing.gradle +++ b/spine-libgdx/publishing.gradle @@ -1,5 +1,25 @@ -ext { - libraryVersion = "4.2.9-SNAPSHOT" + +// JReleaser config for release builds to Central Portal +if (project.hasProperty('RELEASE')) { + apply plugin: 'org.jreleaser' + + jreleaser { + deploy { + maven { + mavenCentral { + sonatype { + active = 'ALWAYS' + username = getRepositoryUsername() + password = getRepositoryPassword() + url = 'https://central.sonatype.com/api/v1/publisher' + stagingRepository("${buildDir}/staging-deploy") + sign = false + verifyPom = false + } + } + } + } + } } project("spine-libgdx") { @@ -18,6 +38,7 @@ project("spine-libgdx") { tasks.javadoc { failOnError = false + options.addStringOption('Xdoclint:none', '-quiet') } tasks.register("javadocJar", Jar) { @@ -36,17 +57,19 @@ project("spine-libgdx") { groupId = "com.esotericsoftware.spine" artifactId = "spine-libgdx" - version = libraryVersion + version = project.version pom { packaging = "jar" - name.set("spine-libgdx") - description.set("Spine Runtime for libGDX") - url.set("https://github.com/esotericsoftware/spine-runtimes") + name = POM_NAME + if(!POM_DESCRIPTION.isEmpty()) + description = POM_DESCRIPTION + url = POM_URL licenses { license { - name.set("Spine Runtimes License") - url.set("https://esotericsoftware.com/spine-runtimes-license") + name = POM_LICENCE_NAME + url = POM_LICENCE_URL + distribution = POM_LICENCE_DIST } } developers { @@ -56,9 +79,9 @@ project("spine-libgdx") { } } scm { - url.set(pom.url.get()) - connection.set("scm:git:${url.get()}.git") - developerConnection.set("scm:git:${url.get()}.git") + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEV_CONNECTION + url = POM_SCM_URL } } } @@ -67,23 +90,39 @@ project("spine-libgdx") { repositories { maven { name = "SonaType" - url = uri(libraryVersion.endsWith("-SNAPSHOT") ? - "https://oss.sonatype.org/content/repositories/snapshots" : - "https://oss.sonatype.org/service/local/staging/deploy/maven2") + url = project.version.endsWith("-SNAPSHOT") + ? getSnapshotRepositoryUrl() + // If release build, dump artifacts to local build/staging-deploy folder for consumption by jreleaser + : layout.buildDirectory.dir('staging-deploy') - credentials { - username = project.findProperty("ossrhUsername") - password = project.findProperty("ossrhPassword") + if (project.version.endsWith("-SNAPSHOT") && (getRepositoryUsername() || getRepositoryPassword())) { + credentials { + username = getRepositoryUsername() + password = getRepositoryPassword() + } } } } } - if (!libraryVersion.endsWith("-SNAPSHOT")) { - signing { - useGpgCmd() - sign(publishing.publications["release"]) + signing { + useGpgCmd() + sign(publishing.publications["release"]) + } + + // Simply using "required" in signing block doesn't work because taskGraph isn't ready yet. + gradle.taskGraph.whenReady { + tasks.withType(Sign) { + onlyIf { !project.version.endsWith("-SNAPSHOT") } } } } } + +// For release builds, create a task that depends on publishing and finalizes with jreleaser +if (project.hasProperty('RELEASE')) { + tasks.register('publishRelease') { + dependsOn tasks.withType(PublishToMavenRepository) + finalizedBy tasks.named('jreleaserDeploy') + } +} diff --git a/spine-libgdx/spine-libgdx/pom.xml b/spine-libgdx/spine-libgdx/pom.xml deleted file mode 100644 index 4df1aecb4..000000000 --- a/spine-libgdx/spine-libgdx/pom.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - 4.0.0 - - org.sonatype.oss - oss-parent - 5 - - - com.esotericsoftware.spine - spine-libgdx - jar - 4.2.2-SNAPSHOT - - - spine-libgdx - Spine Runtime for libGDX - http://github.com/esotericsoftware/spine-runtimes - - http://github.com/esotericsoftware/spine-runtimes/issues - - - - - Spine Runtime License v2.5 - https://github.com/EsotericSoftware/spine-runtimes/blob/master/LICENSE - repo - - - - - - Developers - https://github.com/EsotericSoftware/spine-runtimes/graphs/contributors - - - - - scm:git:https://github.com/EsotericSoftware/spine-runtimes.git - scm:git:https://github.com/EsotericSoftware/spine-runtimes.git - https://github.com/EsotericSoftware/spine-runtimes/ - spine-libgdx-4.1.0 - - - - - nightlies - https://oss.sonatype.org/content/repositories/snapshots/ - - - - - UTF-8 - 1.10.0 - - - - src - test - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - generate-resources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.7 - 1.7 - true - true - - - - org.apache.maven.plugins - maven-javadoc-plugin - - -Xdoclint:none - false - - - - org.apache.maven.plugins - maven-release-plugin - 3.0.0-M4 - - - default - - perform - - - spine-libgdx/spine-libgdx/pom.xml - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.0.1 - - - - - - - com.badlogicgames.gdx - gdx - ${gdx.version} - compile - true - - - \ No newline at end of file