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+
17plugins {
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+ 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