[libgdx][android] Migrate to Central Portal publishing with unified version management

This commit is contained in:
Mario Zechner 2025-05-28 18:17:20 +02:00
parent 4bcf00b7d8
commit cc179858f9
9 changed files with 355 additions and 249 deletions

View File

@ -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()
}

View File

@ -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
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

View File

@ -1,14 +1,33 @@
#!/bin/sh
#
# 1. Set up PGP key for signing
# 2. Create ~/.gradle/gradle.properties
# 3. Add
# ossrhUsername=<sonatype-token-user-name>
# ossrhPassword=<sonatype-token>
# 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=<central-portal-username>
# MAVEN_PASSWORD=<central-portal-token>
# signing.gnupg.passphrase=<pgp-key-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
# 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

View File

@ -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<Jar>("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<Sign> {
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<PublishToMavenRepository>())
doLast {
exec {
commandLine("./gradlew", "jreleaserDeploy")
}
}
}
}

View File

@ -1,23 +1,66 @@
buildscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath 'org.jreleaser:jreleaser-gradle-plugin:1.18.0'
}
}
ext {
libgdxVersion = "1.13.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"
sourceSets.main.java.srcDirs = ["src"]
group = 'com.esotericsoftware.spine'
version = project.getProperty('version')
repositories {
mavenLocal()
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
mavenCentral()
}
sourceSets.main.java.srcDirs = ["src"]
tasks.withType(JavaCompile).configureEach {
sourceCompatibility = '16'
targetCompatibility = '16'
options.release.set(16)
}
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.8'
targetCompatibility = '1.8'
options.release.set(8)
}
}
project("spine-libgdx") {

View File

@ -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

View File

@ -1,14 +1,33 @@
#!/bin/sh
#
# 1. Set up PGP key for signing
# 2. Create ~/.gradle/gradle.properties
# 3. Add
# ossrhUsername=<sonatype-token-user-name>
# ossrhPassword=<sonatype-token>
# 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=<central-portal-username>
# MAVEN_PASSWORD=<central-portal-token>
# signing.gnupg.passphrase=<pgp-key-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
# 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

View File

@ -1,89 +1,128 @@
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") {
apply plugin: "java-library"
apply plugin: "maven-publish"
apply plugin: "signing"
apply plugin: "java-library"
apply plugin: "maven-publish"
apply plugin: "signing"
dependencies {
implementation "com.badlogicgames.gdx:gdx:$libgdxVersion"
}
dependencies {
implementation "com.badlogicgames.gdx:gdx:$libgdxVersion"
}
tasks.register("sourceJar", Jar) {
archiveClassifier.set("sources")
from(sourceSets.main.allJava)
}
tasks.register("sourceJar", Jar) {
archiveClassifier.set("sources")
from(sourceSets.main.allJava)
}
tasks.javadoc {
failOnError = false
}
tasks.javadoc {
failOnError = false
options.addStringOption('Xdoclint:none', '-quiet')
}
tasks.register("javadocJar", Jar) {
dependsOn javadoc
archiveClassifier.set("javadoc")
from(javadoc.destinationDir)
}
tasks.register("javadocJar", Jar) {
dependsOn javadoc
archiveClassifier.set("javadoc")
from(javadoc.destinationDir)
}
afterEvaluate {
publishing {
publications {
create("release", MavenPublication) {
from(components.java)
artifact(tasks.getByName("sourceJar"))
artifact(tasks.getByName("javadocJar"))
afterEvaluate {
publishing {
publications {
create("release", MavenPublication) {
from(components.java)
artifact(tasks.getByName("sourceJar"))
artifact(tasks.getByName("javadocJar"))
groupId = "com.esotericsoftware.spine"
artifactId = "spine-libgdx"
version = libraryVersion
groupId = "com.esotericsoftware.spine"
artifactId = "spine-libgdx"
version = project.version
pom {
packaging = "jar"
name.set("spine-libgdx")
description.set("Spine Runtime for libGDX")
url.set("https://github.com/esotericsoftware/spine-runtimes")
licenses {
license {
name.set("Spine Runtimes License")
url.set("https://esotericsoftware.com/spine-runtimes-license")
}
}
developers {
developer {
name.set("Esoteric Software")
email.set("contact@esotericsoftware.com")
}
}
scm {
url.set(pom.url.get())
connection.set("scm:git:${url.get()}.git")
developerConnection.set("scm:git:${url.get()}.git")
}
}
}
}
pom {
packaging = "jar"
name = POM_NAME
if(!POM_DESCRIPTION.isEmpty())
description = POM_DESCRIPTION
url = POM_URL
licenses {
license {
name = POM_LICENCE_NAME
url = POM_LICENCE_URL
distribution = POM_LICENCE_DIST
}
}
developers {
developer {
name.set("Esoteric Software")
email.set("contact@esotericsoftware.com")
}
}
scm {
connection = POM_SCM_CONNECTION
developerConnection = POM_SCM_DEV_CONNECTION
url = POM_SCM_URL
}
}
}
}
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")
repositories {
maven {
name = "SonaType"
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')
}
}

View File

@ -1,126 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>5</version>
</parent>
<groupId>com.esotericsoftware.spine</groupId>
<artifactId>spine-libgdx</artifactId>
<packaging>jar</packaging>
<version>4.2.2-SNAPSHOT</version>
<name>spine-libgdx</name>
<description>Spine Runtime for libGDX</description>
<url>http://github.com/esotericsoftware/spine-runtimes</url>
<issueManagement>
<url>http://github.com/esotericsoftware/spine-runtimes/issues</url>
</issueManagement>
<licenses>
<license>
<name>Spine Runtime License v2.5</name>
<url>https://github.com/EsotericSoftware/spine-runtimes/blob/master/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>Developers</id>
<url>https://github.com/EsotericSoftware/spine-runtimes/graphs/contributors</url>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/EsotericSoftware/spine-runtimes.git</connection>
<developerConnection>scm:git:https://github.com/EsotericSoftware/spine-runtimes.git</developerConnection>
<url>https://github.com/EsotericSoftware/spine-runtimes/</url>
<tag>spine-libgdx-4.1.0</tag>
</scm>
<repositories>
<repository>
<id>nightlies</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<gdx.version>1.10.0</gdx.version>
</properties>
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<phase>generate-resources</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>16</source>
<target>16</target>
<fork>true</fork>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
<failOnError>false</failOnError>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.0.0-M4</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>perform</goal>
</goals>
<configuration>
<pomFile>spine-libgdx/spine-libgdx/pom.xml</pomFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.0.1</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.badlogicgames.gdx</groupId>
<artifactId>gdx</artifactId>
<version>${gdx.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>