I have been trying to publish the library to MavenCentral(), which is published locally successfully through Maven Central in(.m2/repository/...) and working fine. But when I try to publish the library globally on MavenCentral(), it FAILED.

This is my build.gradle file:

plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    id("signing")
    id("maven-publish")
}

// Configure the signing block
signing {
    val signingKey: String? by project
    val signingPassword: String? by project
    useInMemoryPgpKeys(signingKey, signingPassword)
}


android {
    namespace = "com.gk.image_preview"
    compileSdk = 34

    defaultConfig {
        minSdk = 24

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles("consumer-rules.pro")
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.1"
    }

//    afterEvaluate {
//        android.libraryVariants.forEach { libraryVariant ->
//            publishing.publications.create(libraryVariant.name, MavenPublication::class.java) {
//                from(components.getByName(libraryVariant.name))
//                groupId = "io.github.cypher103360"
//                artifactId = "image-preview"
//                version = "1.0.0"
//            }
//        }
//    }

}

afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("release") {
                from(components["release"])

                groupId = "io.github.cypher103360"
                artifactId = "image-preview"
                version = "1.0.0"

                pom {
                    name = artifactId
                    description = "This is the image preview library."

                    scm {
                        connection = "scm:[email protected]:Cypher103360/MyLibrary.git"
                        url = "https://github.com/Cypher103360/MyLibrary.git"
                    }
                    developers {
                        developer {
                            id = "cypher103360"
                            name = "Gaurav"
                            email = "[email protected]"
                        }
                    }
                }
            }
        }
        repositories {
            maven {
                name = "MyImagePreviewSnapshot"
                url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")

                credentials {
                    username = project.findProperty("ossrhUsername")?.toString()
                    password = project.findProperty("ossrhPassword")?.toString()
                }
            }

            maven {
                name = "imagePreview"
                url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")

                credentials {
                    username = project.findProperty("ossrhUsername")?.toString()
                    password = project.findProperty("ossrhPassword")?.toString()
                }
            }
        }
    }
}


dependencies {
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.10.0")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

    implementation(platform("androidx.compose:compose-bom:2023.08.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
}

And my logs:


> Configure project :image-preview
Kotlin DSL property assignment is an incubating feature.

> Task :image-preview:publishReleasePublicationToImagePreviewRepository FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':image-preview:publishReleasePublicationToImagePreviewRepository'.
> Failed to publish publication 'release' to repository 'imagePreview'
   > Could not PUT 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/cypher103360/image-preview/1.0.1-SNAPSHOT/maven-metadata.xml'. Received status code 400 from server: Bad Request

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 4s
26 actionable tasks: 23 executed, 2 from cache, 1 up-to-date


Can anyone help me to figure out the actual issue, so that I can successfully publish the library?

1

There are 1 best solutions below

7
VonC On BEST ANSWER

Could not PUT 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/cypher103360/image-preview/1.0.1-SNAPSHOT/maven-metadata.xml'. Received status code 400 from server: Bad Request

But... the Sonatype OSSRH (OSS Repository Hosting) Publishing Guide does mention:*

  • deploy development version binaries (snapshots)
  • stage release binaries
  • promote release binaries and sync them to the Central Repository

I would assume you cannot deliver a SNAPSHOT version to staging.


So what modification must I make in the build.gradle file?

You would need a mechanism to decide whether to publish to the snapshot repository or the staging repository based on the version name.
And configure the repository URL dynamically in the publishing section of your build.gradle.

afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("release") {
                // existing publication configuration
            }
        }
        repositories {
            maven {
                String repositoryUrl
                if (version.endsWith("SNAPSHOT")) {
                    // Publish SNAPSHOT versions to the snapshot repository
                    repositoryUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
                } else {
                    // Publish release versions to the staging repository
                    repositoryUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
                }

                url = uri(repositoryUrl)
                println "Publishing to repository URL: $repositoryUrl"

                credentials {
                    username = project.findProperty("ossrhUsername")?.toString()
                    password = project.findProperty("ossrhPassword")?.toString()
                }
            }
        }
    }
}

That would check if the version ends with "SNAPSHOT". If it does, it sets the repository URL to the snapshot repository. Otherwise, it uses the staging repository URL. The credentials block remains the same for both repositories.

Make sure the version variable in your publishing block does reflect the version of your library, and it should be dynamically set based on whether you are doing a snapshot or a release build.
And double-check that ossrhUsername and ossrhPassword are correctly defined in your gradle.properties or wherever you are storing your credentials.

Execute your Gradle task with the --info or --debug flag for more detailed output:

./gradlew publish --info

After implementing the above condition in the code, when I pass the simple version like 1.0.0, it works fine, but when I pass a version like 1.0.0-SNAPSHOT, it throws an error:

Could not PUT 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/io/github/cypher103360/image-preview/1.0.0-SNAPSHOT/maven-metadata.xml'. Received status code 400 from server: Bad Request

The URL should have been https://s01.oss.sonatype.org/content/repositories/snapshots/, not https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/. That is why I have added the println "Publishing to repository URL: $repositoryUrl" debug statement.

Check that you see the right URL when passing a version like 1.0.0-SNAPSHOT.


./gradlew publish -PprojectVersion=1.0.1-SNAPSHOT

> Configure project :image-preview
Kotlin DSL property assignment is an incubating feature.
Publishing to repository URL: 's01.oss.sonatype.org/content/…' for version '1.0.1-SNAPSHOT'

> Task :image-preview:releaseSourcesJar
Encountered duplicate path "main/com/gk/image_preview/ImagePreview.kt" during copy operation configured with DuplicatesStrategy.WARN

BUILD SUCCESSFUL in 16s
26 actionable tasks: 4 executed, 22 up-to-date 

The publishing process is generally working correctly, but there is a warning regarding a duplicate path in the :image-preview:releaseSourcesJar task. That warning indicates an issue in the configuration of the task that creates the sources JAR file for your project.

The warning Encountered duplicate path "main/com/gk/image_preview/ImagePreview.kt" during copy operation configured with DuplicatesStrategy.WARN suggests that the same file path is being included more than once in the sources JAR. That might happen if the task configuration incorrectly includes the same source files multiple times or if there are actual duplicate files in your source directories.

So review the configuration of the sourceSets in your build.gradle. Make sure you are not including the same directory or file more than once. Look at how the releaseSourcesJar task is set up. It should be configured to include the source files from your project without duplication. If you are customizing this task, make sure it is correctly aggregating source files. Verify that there are not duplicate files in your source directories. Sometimes, there can be copies or backups of files that could be inadvertently included.

If you have customized the releaseSourcesJar task, it should resemble something like this:

task releaseSourcesJar(type: Jar) {
     classifier = 'sources'
     from sourceSets.main.allSource
}

That example creates a sources JAR from the main source set without duplication.

If the duplication is intentional and harmless, you can change the duplicate strategy to 'INCLUDE' to suppress the warning. However, this is generally not recommended without understanding why the duplicates exist.

duplicatesStrategy = DuplicatesStrategy.INCLUDE

typeId signature-staging failureMessage Missing Signature: '/io/github/cypher103360/image-preview/1.0.0/image-preview-1.0.0.module.asc' does not exist for 'image-preview-1.0.0.module

You see an error message in the Nexus Repository Manager during the closing of the repository: it indicate that the required signature files for your artifacts are missing. These .asc files are PGP signatures, and their absence means that your artifacts (the .module, .pom, .sources.jar, and .aar files) have not been signed as expected by the repository's requirements.

The signing plugin should be configured to automatically sign the artifacts produced by your build. Make sure the signing block is correctly configured and that it is being applied to all the relevant artifacts (aar, jar, pom, etc.).

An example of a signing configuration in build.gradle might look like this (using useInMemoryPgpKeys):

apply plugin: 'maven-publish'
apply plugin: 'signing'

// other configurations 

publishing {
    publications {
        // Define your publication(s) here
    }
}

signing {
    val signingKey: String? by project
    val signingPassword: String? by project
    useInMemoryPgpKeys(signingKey, signingPassword)
    sign publishing.publications
}

In this example, sign publishing.publications is the key line that explicitly signs all Maven publications defined in the publishing block. I do not see that line in your build.gradle, so I suspect that is the issue.


I have successfully uploaded the library to the repository using: useGpgCmd(), and I have clicked on the release button, it says that the repository will be available publically within 30 minutes. But I want to know how I would be able to use it?

If the repository where you have uploaded your library is not already included in your project's build file, you will need to add it. For example, if you are using Gradle, you can add the repository URL to your build.gradle file in the repositories block:

repositories {
    maven {
        url "https://your-repository-url" // Replace with the actual URL of your repository
    }
    // Other repositories (e.g., mavenCentral)
}

Next, you will need to add your library as a dependency in the module where you want to use it. In your build.gradle file, under the dependencies block, add a line for your library, specifying the group ID, artifact ID, and version:

dependencies {
    implementation "io.github.cypher103360:image-preview:1.0.0" // Replace with your group ID, artifact ID, and version
    // Other dependencies
}

After adding the repository and dependency, sync your project with the updated Gradle files. If you are using an IDE like Android Studio, it usually prompts you to sync after making changes to your Gradle files.

Once the project sync is complete, and the build is successful, you can start using the classes, methods, or resources from your library in your project.