publishing to maven central after loading all properties from a secret file/environment

164 Views Asked by At

I am getting various kinds of error when i tried to use the following script for publishing to maven central.

import java.util.Properties

plugins {
    id("org.jetbrains.kotlin.jvm")
    id("maven-publish")
    id("signing")
}

java {
    withSourcesJar()
    withJavadocJar()
    toolchain.languageVersion.set(JavaLanguageVersion.of(8))
}


val POM_ARTIFACT_ID="sizeunit"
val VERSION_NAME="0.0.1"
val VERSION_CODE=1
val propsFile = File("${rootProject.rootDir.path}/a_secrets/publish.properties")
val props =  if(propsFile.exists()) Properties().also { it.load(propsFile.inputStream()) } else null
val GROUP: String = props?.getProperty("GROUP")?:System.getenv("GROUP")?:""
val POM_URL: String = props?.getProperty("POM_URL")?:System.getenv("POM_URL")?:""
val POM_SCM_URL: String = props?.getProperty("POM_SCM_URL")?:System.getenv("POM_SCM_URL")?:""
val POM_SCM_CONNECTION: String = props?.getProperty("POM_SCM_CONNECTION")?:System.getenv("POM_SCM_CONNECTION")?:""
val POM_SCM_DEV_CONNECTION: String = props?.getProperty("POM_SCM_DEV_CONNECTION")?:System.getenv("POM_SCM_DEV_CONNECTION")?:""
val POM_NAME: String = props?.getProperty("POM_NAME")?:System.getenv("POM_NAME")?:""
val POM_INCEPTION_YEAR: String = props?.getProperty("POM_INCEPTION_YEAR")?:System.getenv("POM_INCEPTION_YEAR")?:""
val POM_DESCRIPTION: String = props?.getProperty("POM_DESCRIPTION")?:System.getenv("POM_DESCRIPTION")?:""
val POM_DEVELOPER_ID: String = props?.getProperty("POM_DEVELOPER_ID")?:System.getenv("POM_DEVELOPER_ID")?:""
val POM_DEVELOPER_NAME: String =  props?.getProperty("POM_DEVELOPER_NAME")?:System.getenv("POM_DEVELOPER_NAME")?:""
val POM_DEVELOPER_URL: String =  props?.getProperty("POM_DEVELOPER_URL")?:System.getenv("POM_DEVELOPER_URL")?:""
val POM_DEVELOPER_EMAIL: String =  props?.getProperty("POM_DEVELOPER_EMAIL")?:System.getenv("POM_DEVELOPER_EMAIL")?:""
val POM_LICENSE_NAME: String =  props?.getProperty("POM_LICENSE_NAME")?:System.getenv("POM_LICENSE_NAME")?:""
val POM_LICENSE_URL: String =  props?.getProperty("POM_LICENSE_URL")?:System.getenv("POM_LICENSE_URL")?:""
val POM_LICENSE_DIST: String =  props?.getProperty("POM_LICENSE_DIST")?:System.getenv("POM_LICENSE_DIST")?:""
val mavenCentralPassword: String =  props?.getProperty("mavenCentralPassword")?:System.getenv("mavenCentralPassword")?:""
val mavenCentralUsername: String =  props?.getProperty("mavenCentralUsername")?:System.getenv("mavenCentralUsername")?:""
val mavenUseSnapshotLink: Boolean = (props?.getProperty("mavenUseSnapshotLink") ?: System.getenv("mavenUseSnapshotLink") ?: "").toBoolean()
val signingPassword: String =  props?.getProperty("signing.password")?:System.getenv("signing.password")?:""
val signingSecretKeyRingFile: String =  props?.getProperty("signing.secretKeyRingFile")?:System.getenv("signing.secretKeyRingFile")?:""
val signingKeyId: String =  props?.getProperty("signing.keyId")?:System.getenv("signing.keyId")?:""





//Configure java doc
tasks.javadoc {
    if (JavaVersion.current().isJava9Compatible) {
        (options as? StandardJavadocDocletOptions)?.addBooleanOption("html5", true)
    }
}



afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("mavenJava") {

                groupId = GROUP
                artifactId =POM_ARTIFACT_ID
                version = VERSION_NAME

                from(components["java"])


                pom {

                    name.set(POM_NAME)
                    description.set(POM_DESCRIPTION)
                    url.set(POM_URL)
                    inceptionYear.set(POM_INCEPTION_YEAR)
                    licenses {
                        license {
                            name.set(POM_LICENSE_NAME)
                            url.set(POM_LICENSE_URL)
                            distribution.set(POM_LICENSE_DIST)
                        }
                    }
                    developers {
                        developer {
                            id.set(POM_DEVELOPER_ID)
                            name.set(POM_DEVELOPER_NAME)
                            email.set(POM_DEVELOPER_EMAIL)
                            url.set(POM_DEVELOPER_URL)
                        }
                    }
                    scm {
                        connection.set(POM_SCM_CONNECTION)
                        developerConnection.set(POM_SCM_DEV_CONNECTION)
                        url.set(POM_SCM_URL)
                    }
                }
            }
        }
        repositories {
            maven {
                val repoUrl = if (mavenUseSnapshotLink) "https://s01.oss.sonatype.org/content/repositories/snapshots/" else "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
                name = "mavenCentral"
                setUrl(repoUrl)
                credentials {
                    username = mavenCentralUsername
                    password = mavenCentralPassword
                }
            }
        }
    }
    signing {
        val signingKey = File(signingSecretKeyRingFile).readText(charset = Charsets.US_ASCII)
        //setRequired { gradle.taskGraph.allTasks.any { it is PublishToMavenRepository } }
        useInMemoryPgpKeys(signingKeyId,signingKey,signingPassword)
        sign(publishing.publications["mavenJava"])
    }
}

I can see the following tasks in my gradle task list but none seems to work . enter image description here

i keep getting this error :

Execution failed for task ':sizeunit:signMavenJavaPublication'.
> Cannot perform signing task ':sizeunit:signMavenJavaPublication' because it has no configured signatory

this goes away for publishToMavenLocal task when i uncomment the setRequired { gradle.taskGraph.allTasks.any { it is PublishToMavenRepository } line, but for main task of uploading to release repository, i.e publishMavenJavaPublicationToMavenCentralRepository , it still does not work.

can someone please help? also will you consider this method of accessing all the secret data in main block in module's build.gradle.kts file as correct? or is there a better way?

PS : I checked the stack overflow answers of exact match as a google search, but those didn't worked

PS(2): the architecture of the module is a that of a typical multi module android app, but this module is supposed to be a pure java library without any dependency, so i thought that root's build.gradle or setting/gradle.kts would not be requiring any changes, but if that's the case, kindly let me know. repo link

PS(3) : would be great if i could make a generic script for both java only libs and android libs , but for now, i can work with this script getting fixed too

2

There are 2 best solutions below

2
peacetrue On

This is the publishing script I currently use, located at ~/.gradle/init.d/publish.gradle. It can be used to release the Java module (not tested Android):

def logName = "init.d/publish.gradle"

gradle.afterProject { project ->
    if (Boolean.TRUE.equals(project.findProperty("isPublishDisabled"))) return

    println("$logName: execute script for project '${project.name}'")

    project.apply plugin: "java"
    project.apply plugin: "maven-publish"
    project.java {
        withJavadocJar()
        withSourcesJar()
    }

    def rootProjectName = project.rootProject.name

    project.publishing {
        publications {
            mavenJava(MavenPublication) {
                from project.components.java
                pom {
                    name = project.name
                    description = project.description

                    url = "https://peacetrue.github.io/$rootProjectName/index.html"
                    licenses {
                        license {
                            name = "The Apache License, Version 2.0"
                            url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
                        }
                    }

                    developers {
                        developer {
                            id = "peace"
                            name = "peace"
                            email = "[email protected]"
                        }
                    }

                    scm {
                        connection = "scm:git:git://github.com/peacetrue/${rootProjectName}.git"
                        developerConnection = "scm:git:ssh://github.com/peacetrue/${rootProjectName}.git"
                        url = "https://github.com/peacetrue/${rootProjectName}.git"
                    }
                }
            }
        }

        repositories {
            maven {
                def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
                def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
                url = project.version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
                credentials {
                    username = "${System.env.ossrhUsername}"
                    password = "${System.env.ossrhPassword}"
                }
            }
        }
    }

    project.apply plugin: "signing"
    project.ext.setProperty("signing.keyId", "${System.env.signingKeyId}")
    project.ext.setProperty("signing.password", "${System.env.signingPassword}")
    project.ext.setProperty("signing.secretKeyRingFile", "${System.env.signingSecretKeyRingFile}")
    project.signing {
        sign project.publishing.publications.mavenJava
    }

    project.javadoc {
        if (JavaVersion.current().isJava9Compatible()) {
            options.addBooleanOption('html5', true)
        }
    }
}

You need to adjust the relevant address to your own, and add the following system environment variables. The ZSH configuration I use is as follows:

export ossrhUsername=
export ossrhPassword=
export signingKeyId=
export signingPassword=
export signingSecretKeyRingFile=

Hope it is of help to you.

2
Vampire On

The repo link you posted does not contain Kotlin DSL build scripts, but Groovy DSL build scripts.

Generally, when publishing to Maven Central, you should consider also using the https://github.com/gradle-nexus/publish-plugin plugin unless the publishing can become a bit flaky due to the staging repository configuration on OSSRH servers, but that has nothing to do with your actual problem.

Regarding storing the secrets, this can be done like that, but you will still have the credentials as plain text in a file or environment variable. You might consider using instead something like https://github.com/etiennestuder/gradle-credentials-plugin which is also promoted at https://docs.gradle.org/current/userguide/authoring_maintainable_build_scripts.html#sec:avoiding_passwords_in_plain_text.

Regarding the code you showed here, you should remove the afterEvaluate. afterEvaluate is evil and very seldomly actually necessary. It is often just for symptom treatment, hiding the actual error or delaying it to later, harder to reproduce, harder to investigate, and harder to fix point in time. The main effect of using afterEvaluate is introducing ordering problems, timing problems, and race conditions. It should be avoided wherever possible and from a cursory look should not be necessary here.

Regarding the requirement to have one configuration for multiple projects, the solution you should strive for are convention plugins in buildSrc or an included build for example implemented as precompiled script plugins. This can then be applied where you need its effect just like any other plugin.

Regarding your actual error "because it has no configured signatory", this means that something is not correct with your signing { ... } configuration. It is most probably caused by you reading a keyring file's content as string and setting that as signing key. That parameter should be set to an ascii-armored OpenPGP key instead and is meant for when you do not have a keyring file but for example supply the ascii-armored key as secret in GitHub actions or similar. If you have actually have a keyring file, better do not use useInMemoryPgpKeys but instead configure the keyring file. All the details should be found on https://docs.gradle.org/current/userguide/signing_plugin.html.