Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# Changelog
All notable changes to this project will be documented in this file.

## [1.0.0](https://github.com/maptiler/maptiler-sdk-kotlin/releases/tag/1.0.0)
Released on 2025-10-16.
### Added
- Initial public release
235 changes: 224 additions & 11 deletions MapTilerSDK/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.Base64

plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
Expand Down Expand Up @@ -148,6 +154,9 @@ publishing {
developer {
id.set("maptiler")
name.set("MapTiler")
email.set("[email protected]")
organization.set("MapTiler")
organizationUrl.set("https://maptiler.com")
url.set("https://www.maptiler.com")
}
}
Expand All @@ -158,20 +167,224 @@ publishing {
url.set("https://github.com/maptiler/maptiler-sdk-kotlin")
}
}

// Suppress POM metadata warnings for test fixtures variants that
// cannot be represented in Maven POM but are fine in Gradle module metadata.
suppressPomMetadataWarningsFor("releaseTestFixturesVariantReleaseApiPublication")
suppressPomMetadataWarningsFor("releaseTestFixturesVariantReleaseRuntimePublication")
}
}
repositories {
val isSnapshot = version.toString().endsWith("SNAPSHOT")
maven {
name = if (isSnapshot) "central-snapshots" else "ossrh-staging-api"
url =
uri(
if (isSnapshot) {
// Maven Central snapshots repository via Central Portal
"https://central.sonatype.com/repository/maven-snapshots/"
} else {
// Staging API compatibility endpoint for releases
"https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
},
)
authentication {
create<org.gradle.authentication.http.BasicAuthentication>("basic")
}
credentials(PasswordCredentials::class) {
username =
(
(findProperty("sonatypeMaptilerTokenUsername") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_TOKEN_USERNAME")
password =
(
(findProperty("sonatypeMaptilerTokenPassword") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_TOKEN_PASSWORD")
}
}
}
}

// Only sign if signing keys are provided via Gradle properties or environment
val shouldSign =
(
project.findProperty("signing.keyId") != null ||
project.findProperty("signingKeyId") != null ||
System.getenv("SIGNING_KEY") != null
)

if (shouldSign) {
signing {
sign(publishing.publications)
// Sign all publications using in-memory PGP keys (required by Maven Central)
signing {
val keyId =
(
(findProperty("sonatypeMaptilerSigningKeyId") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_SIGNING_KEY_ID")
val rawKey =
(
(findProperty("sonatypeMaptilerSigningKey") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_SIGNING_KEY")
val password =
(
(findProperty("sonatypeMaptilerSigningPassword") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_SIGNING_PASSWORD")

// Only initialize/log for publish/sign tasks
val signingRequired =
gradle.startParameter.taskNames.any {
it.contains("publish", ignoreCase = true) || it.contains("sign", ignoreCase = true)
}

if (signingRequired) {
val useKeyNoId =
(
(findProperty("sonatypeMaptilerUseKeyNoId") as String?)
?.equals("true", ignoreCase = true) == true
) || (System.getenv("SONATYPE_MAPTILER_USE_KEY_NO_ID")?.equals("true", ignoreCase = true) == true)

if (!rawKey.isNullOrBlank() && !password.isNullOrBlank()) {
if (useKeyNoId) {
useInMemoryPgpKeys(rawKey, password)
} else if (!keyId.isNullOrBlank()) {
useInMemoryPgpKeys(keyId, rawKey, password)
} else {
logger.lifecycle(
"Signing not configured: SONATYPE_MAPTILER_SIGNING_KEY_ID missing;",
)
}

// Use for local testing or when using different key formats.
// useGpgCmd()

sign(publishing.publications)
} else {
// Defer failure to tasks that require signing; helps local tasks (e.g., publishToMavenLocal)
logger.lifecycle(
"Signing not configured: provide SONATYPE_MAPTILER_SIGNING_KEY_ID/KEY/PASSWORD or matching -P properties.",
)
}
}
}

// Helper task to upload a completed "Maven-like" deployment to the Central Publisher Portal
// so it becomes visible/manageable in https://central.sonatype.com/publishing
tasks.register("centralManualUpload") {
group = "publishing"
description = "POST to Central OSSRH Staging API manual upload endpoint for this namespace"
doLast {
val isSnapshot = version.toString().endsWith("SNAPSHOT")
if (isSnapshot) {
println("Snapshot version detected; manual upload is not required.")
return@doLast
}

val username =
(
(findProperty("sonatypeMaptilerTokenUsername") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_TOKEN_USERNAME")
val password =
(
(findProperty("sonatypeMaptilerTokenPassword") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_TOKEN_PASSWORD")
val namespace =
(
(findProperty("sonatypeMaptilerNamespace") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_NAMESPACE")
?: "com.maptiler"
val repositoryKey = (
(findProperty("sonatypeMaptilerRepositoryKey") as String?)
?.takeIf { it.isNotBlank() }
)

val publishingType =
(
(findProperty("sonatypeMaptilerPublishingType") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_PUBLISHING_TYPE")
?: "user_managed"

val allowedPublishingTypes = setOf("user_managed", "automatic", "portal_api")
require(publishingType in allowedPublishingTypes) {
"Invalid sonatypeMaptilerPublishingType: '$publishingType'. Allowed: $allowedPublishingTypes"
}

require(!username.isNullOrBlank()) { "SONATYPE_MAPTILER_TOKEN_USERNAME (or -PsonatypeMaptilerTokenUsername) is required" }
require(!password.isNullOrBlank()) { "SONATYPE_MAPTILER_TOKEN_PASSWORD (or -PsonatypeMaptilerTokenPassword) is required" }

val bearer = Base64.getEncoder().encodeToString("$username:$password".toByteArray())
val base =
if (repositoryKey != null) {
"https://ossrh-staging-api.central.sonatype.com/manual/upload/repository/$repositoryKey"
} else {
"https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/$namespace"
}
val url = if (publishingType == "user_managed") base else "$base?publishing_type=$publishingType"

val client = HttpClient.newHttpClient()
println("Manual upload target: $url (namespace=$namespace, repoKey=${repositoryKey ?: "<default>"}, type=$publishingType)")
val request =
HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer $bearer")
.POST(HttpRequest.BodyPublishers.noBody())
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
println("Central manual upload response: ${response.statusCode()}\n${response.body()}")
if (response.statusCode() >= 300) {
throw GradleException("Manual upload failed: ${response.statusCode()} ${response.body()}")
}
}
}

// Drop a specific staging repository by key (e.g., from error message or search API)
tasks.register("centralDropRepository") {
group = "publishing"
description = "DELETE a specific OSSRH Staging API repository by key"
doLast {
val username =
(
(findProperty("sonatypeMaptilerTokenUsername") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_TOKEN_USERNAME")
val password =
(
(findProperty("sonatypeMaptilerTokenPassword") as String?)
?.takeIf { it.isNotBlank() }
)
?: System.getenv("SONATYPE_MAPTILER_TOKEN_PASSWORD")
val repositoryKey = (
(findProperty("sonatypeMaptilerRepositoryKey") as String?)
?.takeIf { it.isNotBlank() }
)

require(!username.isNullOrBlank()) { "SONATYPE_MAPTILER_TOKEN_USERNAME (or -PsonatypeMaptilerTokenUsername) is required" }
require(!password.isNullOrBlank()) { "SONATYPE_MAPTILER_TOKEN_PASSWORD (or -PsonatypeMaptilerTokenPassword) is required" }
require(repositoryKey != null) { "-PsonatypeMaptilerRepositoryKey=<repository key> is required" }

val bearer = Base64.getEncoder().encodeToString("$username:$password".toByteArray())
val url = "https://ossrh-staging-api.central.sonatype.com/manual/drop/repository/$repositoryKey"

val client = HttpClient.newHttpClient()
println("Drop repository target: $url")
val request =
HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer $bearer")
.DELETE()
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
println("Central drop response: ${response.statusCode()}\n${response.body()}")
if (response.statusCode() >= 300) {
throw GradleException("Drop failed: ${response.statusCode()} ${response.body()}")
}
}
}