Skip to content

Commit 4b5d47c

Browse files
RD-1205: Update publishing steps
1 parent 99b6c68 commit 4b5d47c

File tree

2 files changed

+229
-11
lines changed

2 files changed

+229
-11
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
3+
4+
## [1.0.0](https://github.com/maptiler/maptiler-sdk-kotlin/releases/tag/1.0.0)
5+
Released on 2025-10-16.
6+
### Added
7+
- Initial public release

MapTilerSDK/build.gradle.kts

Lines changed: 224 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import java.net.URI
2+
import java.net.http.HttpClient
3+
import java.net.http.HttpRequest
4+
import java.net.http.HttpResponse
5+
import java.util.Base64
6+
17
plugins {
28
id("com.android.library")
39
id("org.jetbrains.kotlin.android")
@@ -148,6 +154,9 @@ publishing {
148154
developer {
149155
id.set("maptiler")
150156
name.set("MapTiler")
157+
email.set("[email protected]")
158+
organization.set("MapTiler")
159+
organizationUrl.set("https://maptiler.com")
151160
url.set("https://www.maptiler.com")
152161
}
153162
}
@@ -158,20 +167,224 @@ publishing {
158167
url.set("https://github.com/maptiler/maptiler-sdk-kotlin")
159168
}
160169
}
170+
171+
// Suppress POM metadata warnings for test fixtures variants that
172+
// cannot be represented in Maven POM but are fine in Gradle module metadata.
173+
suppressPomMetadataWarningsFor("releaseTestFixturesVariantReleaseApiPublication")
174+
suppressPomMetadataWarningsFor("releaseTestFixturesVariantReleaseRuntimePublication")
175+
}
176+
}
177+
repositories {
178+
val isSnapshot = version.toString().endsWith("SNAPSHOT")
179+
maven {
180+
name = if (isSnapshot) "central-snapshots" else "ossrh-staging-api"
181+
url =
182+
uri(
183+
if (isSnapshot) {
184+
// Maven Central snapshots repository via Central Portal
185+
"https://central.sonatype.com/repository/maven-snapshots/"
186+
} else {
187+
// Staging API compatibility endpoint for releases
188+
"https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
189+
},
190+
)
191+
authentication {
192+
create<org.gradle.authentication.http.BasicAuthentication>("basic")
193+
}
194+
credentials(PasswordCredentials::class) {
195+
username =
196+
(
197+
(findProperty("sonatypeMaptilerTokenUsername") as String?)
198+
?.takeIf { it.isNotBlank() }
199+
)
200+
?: System.getenv("SONATYPE_MAPTILER_TOKEN_USERNAME")
201+
password =
202+
(
203+
(findProperty("sonatypeMaptilerTokenPassword") as String?)
204+
?.takeIf { it.isNotBlank() }
205+
)
206+
?: System.getenv("SONATYPE_MAPTILER_TOKEN_PASSWORD")
207+
}
161208
}
162209
}
163210
}
164211

165-
// Only sign if signing keys are provided via Gradle properties or environment
166-
val shouldSign =
167-
(
168-
project.findProperty("signing.keyId") != null ||
169-
project.findProperty("signingKeyId") != null ||
170-
System.getenv("SIGNING_KEY") != null
171-
)
172-
173-
if (shouldSign) {
174-
signing {
175-
sign(publishing.publications)
212+
// Sign all publications using in-memory PGP keys (required by Maven Central)
213+
signing {
214+
val keyId =
215+
(
216+
(findProperty("sonatypeMaptilerSigningKeyId") as String?)
217+
?.takeIf { it.isNotBlank() }
218+
)
219+
?: System.getenv("SONATYPE_MAPTILER_SIGNING_KEY_ID")
220+
val rawKey =
221+
(
222+
(findProperty("sonatypeMaptilerSigningKey") as String?)
223+
?.takeIf { it.isNotBlank() }
224+
)
225+
?: System.getenv("SONATYPE_MAPTILER_SIGNING_KEY")
226+
val password =
227+
(
228+
(findProperty("sonatypeMaptilerSigningPassword") as String?)
229+
?.takeIf { it.isNotBlank() }
230+
)
231+
?: System.getenv("SONATYPE_MAPTILER_SIGNING_PASSWORD")
232+
233+
// Only initialize/log for publish/sign tasks
234+
val signingRequired =
235+
gradle.startParameter.taskNames.any {
236+
it.contains("publish", ignoreCase = true) || it.contains("sign", ignoreCase = true)
237+
}
238+
239+
if (signingRequired) {
240+
val useKeyNoId =
241+
(
242+
(findProperty("sonatypeMaptilerUseKeyNoId") as String?)
243+
?.equals("true", ignoreCase = true) == true
244+
) || (System.getenv("SONATYPE_MAPTILER_USE_KEY_NO_ID")?.equals("true", ignoreCase = true) == true)
245+
246+
if (!rawKey.isNullOrBlank() && !password.isNullOrBlank()) {
247+
if (useKeyNoId) {
248+
useInMemoryPgpKeys(rawKey, password)
249+
} else if (!keyId.isNullOrBlank()) {
250+
useInMemoryPgpKeys(keyId, rawKey, password)
251+
} else {
252+
logger.lifecycle(
253+
"Signing not configured: SONATYPE_MAPTILER_SIGNING_KEY_ID missing;",
254+
)
255+
}
256+
257+
// Use for local testing or when using different key formats.
258+
// useGpgCmd()
259+
260+
sign(publishing.publications)
261+
} else {
262+
// Defer failure to tasks that require signing; helps local tasks (e.g., publishToMavenLocal)
263+
logger.lifecycle(
264+
"Signing not configured: provide SONATYPE_MAPTILER_SIGNING_KEY_ID/KEY/PASSWORD or matching -P properties.",
265+
)
266+
}
267+
}
268+
}
269+
270+
// Helper task to upload a completed "Maven-like" deployment to the Central Publisher Portal
271+
// so it becomes visible/manageable in https://central.sonatype.com/publishing
272+
tasks.register("centralManualUpload") {
273+
group = "publishing"
274+
description = "POST to Central OSSRH Staging API manual upload endpoint for this namespace"
275+
doLast {
276+
val isSnapshot = version.toString().endsWith("SNAPSHOT")
277+
if (isSnapshot) {
278+
println("Snapshot version detected; manual upload is not required.")
279+
return@doLast
280+
}
281+
282+
val username =
283+
(
284+
(findProperty("sonatypeMaptilerTokenUsername") as String?)
285+
?.takeIf { it.isNotBlank() }
286+
)
287+
?: System.getenv("SONATYPE_MAPTILER_TOKEN_USERNAME")
288+
val password =
289+
(
290+
(findProperty("sonatypeMaptilerTokenPassword") as String?)
291+
?.takeIf { it.isNotBlank() }
292+
)
293+
?: System.getenv("SONATYPE_MAPTILER_TOKEN_PASSWORD")
294+
val namespace =
295+
(
296+
(findProperty("sonatypeMaptilerNamespace") as String?)
297+
?.takeIf { it.isNotBlank() }
298+
)
299+
?: System.getenv("SONATYPE_MAPTILER_NAMESPACE")
300+
?: "com.maptiler"
301+
val repositoryKey = (
302+
(findProperty("sonatypeMaptilerRepositoryKey") as String?)
303+
?.takeIf { it.isNotBlank() }
304+
)
305+
306+
val publishingType =
307+
(
308+
(findProperty("sonatypeMaptilerPublishingType") as String?)
309+
?.takeIf { it.isNotBlank() }
310+
)
311+
?: System.getenv("SONATYPE_MAPTILER_PUBLISHING_TYPE")
312+
?: "user_managed"
313+
314+
val allowedPublishingTypes = setOf("user_managed", "automatic", "portal_api")
315+
require(publishingType in allowedPublishingTypes) {
316+
"Invalid sonatypeMaptilerPublishingType: '$publishingType'. Allowed: $allowedPublishingTypes"
317+
}
318+
319+
require(!username.isNullOrBlank()) { "SONATYPE_MAPTILER_TOKEN_USERNAME (or -PsonatypeMaptilerTokenUsername) is required" }
320+
require(!password.isNullOrBlank()) { "SONATYPE_MAPTILER_TOKEN_PASSWORD (or -PsonatypeMaptilerTokenPassword) is required" }
321+
322+
val bearer = Base64.getEncoder().encodeToString("$username:$password".toByteArray())
323+
val base =
324+
if (repositoryKey != null) {
325+
"https://ossrh-staging-api.central.sonatype.com/manual/upload/repository/$repositoryKey"
326+
} else {
327+
"https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/$namespace"
328+
}
329+
val url = if (publishingType == "user_managed") base else "$base?publishing_type=$publishingType"
330+
331+
val client = HttpClient.newHttpClient()
332+
println("Manual upload target: $url (namespace=$namespace, repoKey=${repositoryKey ?: "<default>"}, type=$publishingType)")
333+
val request =
334+
HttpRequest.newBuilder()
335+
.uri(URI.create(url))
336+
.header("Authorization", "Bearer $bearer")
337+
.POST(HttpRequest.BodyPublishers.noBody())
338+
.build()
339+
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
340+
println("Central manual upload response: ${response.statusCode()}\n${response.body()}")
341+
if (response.statusCode() >= 300) {
342+
throw GradleException("Manual upload failed: ${response.statusCode()} ${response.body()}")
343+
}
344+
}
345+
}
346+
347+
// Drop a specific staging repository by key (e.g., from error message or search API)
348+
tasks.register("centralDropRepository") {
349+
group = "publishing"
350+
description = "DELETE a specific OSSRH Staging API repository by key"
351+
doLast {
352+
val username =
353+
(
354+
(findProperty("sonatypeMaptilerTokenUsername") as String?)
355+
?.takeIf { it.isNotBlank() }
356+
)
357+
?: System.getenv("SONATYPE_MAPTILER_TOKEN_USERNAME")
358+
val password =
359+
(
360+
(findProperty("sonatypeMaptilerTokenPassword") as String?)
361+
?.takeIf { it.isNotBlank() }
362+
)
363+
?: System.getenv("SONATYPE_MAPTILER_TOKEN_PASSWORD")
364+
val repositoryKey = (
365+
(findProperty("sonatypeMaptilerRepositoryKey") as String?)
366+
?.takeIf { it.isNotBlank() }
367+
)
368+
369+
require(!username.isNullOrBlank()) { "SONATYPE_MAPTILER_TOKEN_USERNAME (or -PsonatypeMaptilerTokenUsername) is required" }
370+
require(!password.isNullOrBlank()) { "SONATYPE_MAPTILER_TOKEN_PASSWORD (or -PsonatypeMaptilerTokenPassword) is required" }
371+
require(repositoryKey != null) { "-PsonatypeMaptilerRepositoryKey=<repository key> is required" }
372+
373+
val bearer = Base64.getEncoder().encodeToString("$username:$password".toByteArray())
374+
val url = "https://ossrh-staging-api.central.sonatype.com/manual/drop/repository/$repositoryKey"
375+
376+
val client = HttpClient.newHttpClient()
377+
println("Drop repository target: $url")
378+
val request =
379+
HttpRequest.newBuilder()
380+
.uri(URI.create(url))
381+
.header("Authorization", "Bearer $bearer")
382+
.DELETE()
383+
.build()
384+
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
385+
println("Central drop response: ${response.statusCode()}\n${response.body()}")
386+
if (response.statusCode() >= 300) {
387+
throw GradleException("Drop failed: ${response.statusCode()} ${response.body()}")
388+
}
176389
}
177390
}

0 commit comments

Comments
 (0)