Reputation: 1132
I am struggling here to publish a Kotlin artifact to Maven Central. How do I go about doing this. Here is my build.gradle.kts. It seems as if I have to run the publish command twice. Once for the release and the other for the snapshot repository. Is this correct or am I doing something wrong?
Also modules... I was able to see something publish to Nexus however it was of type module not type jar. Any ideas why? I defined packaging below as jar, the build directory has a jar and when publishing a jar is uploaded. Im not sure what could be causing this.
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.jetbrains.kotlin.jvm") version "1.6.10"
id("org.jetbrains.dokka") version "1.6.10"
id("maven-publish")
id("signing")
id("jacoco")
application
}
repositories {
mavenCentral()
}
group = "foo.bar"
version = "0.001"
val dokkaOutputDir = "$buildDir/dokka"
application{
mainClass.set("example.MainKt")
}
tasks.dokkaHtml {
outputDirectory.set(file(dokkaOutputDir))
}
val deleteDokkaOutputDir by tasks.register<Delete>("deleteDokkaOutputDirectory") {
delete(dokkaOutputDir)
}
val javadocJar = tasks.register<Jar>("javadocJar") {
dependsOn(deleteDokkaOutputDir, tasks.dokkaHtml)
archiveClassifier.set("javadoc")
from(dokkaOutputDir)
}
tasks.test {
useJUnitPlatform()
testLogging.showStandardStreams = true
}
tasks.test {
extensions.configure(JacocoTaskExtension::class) {
destinationFile = file("$buildDir/jacoco/jacoco.exec")
}
finalizedBy("jacocoTestReport")
}
jacoco {
toolVersion = "0.8.7"
}
tasks.jacocoTestReport {
reports {
xml.isEnabled = false
csv.isEnabled = false
html.isEnabled = true
html.destination = file("$buildDir/reports/coverage")
}
}
val coverage by tasks.registering {
group = ""
description = "Runs the unit tests with coverage"
dependsOn(":test", ":jacocoTestReport")
tasks["jacocoTestReport"].mustRunAfter(tasks["test"])
tasks["jacocoTestCoverageVerification"].mustRunAfter(tasks["jacocoTestReport"])
}
dependencies {
implementation("com.h2database:h2:2.1.210")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10")
implementation("org.jacoco:org.jacoco.core:0.8.7")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.2")
}
publishing {
repositories {
maven {
name = "Snapshot"
val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshotRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
setUrl { snapshotRepoUrl }
credentials {
username = ""
password = ""
}
}
}
publications {
create<MavenPublication>("Maven") {
groupId = "foo.bar"
artifactId = "foo"
version = "0.001-SNAPSHOT"
from(components["kotlin"])
}
withType<MavenPublication> {
pom {
packaging = "jar"
name.set("foo")
description.set("Project Foo")
url.set("fooyee.com")
licenses {
license {
name.set("MIT license")
url.set("https://opensource.org/licenses/MIT")
}
}
issueManagement {
system.set("Github")
url.set("https://github.com/mcroteau/foo/issues")
}
scm {
connection.set("scm:git:git://github.com/mcroteau/foo.git")
developerConnection.set("scm:git:[email protected]:mcroteau/foo.git")
url.set("https://github.com/mcroteau/foo")
}
developers {
developer {
name.set("Mike Croteau")
email.set("[email protected]")
}
}
}
}
}
}
signing {
useInMemoryPgpKeys(
"",
""
)
sign(publishing.publications)
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
Thank you for any help!
Upvotes: 6
Views: 8843
Reputation: 7110
tl;dr:
repositories {
maven {
// change URLs to point to your repos, e.g. http://my.org/repo
val releasesRepoUrl = uri(layout.buildDirectory.dir("repos/releases"))
val snapshotsRepoUrl = uri(layout.buildDirectory.dir("repos/snapshots"))
url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
}
}
from(components["kotlin"])
to from(components["java"])
It seems as if I have to run the publish command twice. Once for the release and the other for the snapshot repository
Normally only artifacts with a version that ends in -SNAPSHOT
would be published to the snapshot-repo, and only non-snapshot versions would be published to the release-repo, so it makes sense you'd have to run the command twice.
You can configure Gradle so it will publish to the release or snapshot repo depending on the tag. There's a pretty good example on the Gradle website here: https://docs.gradle.org/current/userguide/publishing_maven.html#publishing_maven:complete_example
Here's what it would look like, adapted to your situation
// select the repo based on the version
publishing {
repositories {
name = "sonatypeRepository"
maven {
val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
}
}
}
However your username and password will probably be different for each repo. Gradle has a nice way of picking up credentials from environment variables / $GRADLE_HOME/gradle.properties
based on the repository name https://docs.gradle.org/current/samples/sample_publishing_credentials.html
Here's what that would look like for you:
// select the repo based on the version, and get the credentials from Gradle properties
publishing {
repositories {
if (version.toString().endsWith("SNAPSHOT")) {
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") {
name = "sonatypeReleaseRepository"
credentials(PasswordCredentials::class)
}
} else {
maven("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") {
name = "sonatypeSnapshotRepository"
credentials(PasswordCredentials::class)
}
}
}
}
And finally set the credentials either as environment variables
ORG_GRADLE_PROJECT_sonatypeSnapshotRepositoryUsername=my-sonaytype-SNAPSHOT-username
ORG_GRADLE_PROJECT_sonatypeSnapshotRepositoryPassowrd=Tr0ub4dor
Or in $GRADLE_HOME/gradle.properties
sonatypeReleaseRepositoryUsername=my-sonaytype-RELEASE-username
sonatypeReleaseRepositoryPassowrd=CorrectHorseBatteryStaple
To release,
build.gradle.kts
to a -SNAPSHOT
version./gradlew publish
-SNAPSHOT
version...This can also be improved by writing some more logic in Gradle (or see the end of this answer for info on JitPack and the 'Gradle Git Versioning Plugin'.
I was able to see something publish to Nexus however it was of type module not type jar.
I'm not certain, but I suspect this is due to this config:
create<MavenPublication>("Maven") {
groupId = "foo.bar"
artifactId = "foo"
version = "0.001-SNAPSHOT"
from(components["kotlin"])
}
I think setting the version to be different from the project version could be prone to errors. I'd leave it out, as it will default to the Gradle project version. The same goes for the group and artifact IDs.
Writing from(components["kotlin"])
is tempting, but, to cut a long answer short, it should be from(components["java"])
(because it's producing a JAR).
So I think this is closer to a working solution:
publishing {
publications {
create<MavenPublication>("Maven") {
from(components["java"])
}
}
}
The Dokka tasks you've created don't look correct to me (though they're probably not causing you problems with releasing).
I suspect the defaults will work fine, so you can remove all of the Dokka tasks & config, and you can request a Javadoc JAR as follows:
java {
withJavadocJar()
// withSourcesJar() // if you want sources?
}
These should automatically be included in any publication you make.
Here's a couple of tools that I like using, to make releasing easier.
These won't necessarily help directly with releasing to Maven, but they can be a nice alternative, or support. I'm including them in case anyone else has a problem publishing, and would like a workaround.
The release procedure can involve editing build.gradle.kts
(or some other file), and personally I'm not a fan of this. I'd recommend looking at the Gradle Git Versioning Plugin. Once this is set up...
plugins {
id("me.qoomon.git-versioning")
}
version = "0.0.0-SNAPSHOT"
gitVersioning.apply {
refs {
branch(".+") { version = "\${ref}-SNAPSHOT" }
tag("v(?<version>.*)") { version = "\${ref.version}" }
}
Now the version number is entirely controlled by git tags. Releasing becomes
v1.2.3
./gradlew publish
If you have an open source project, something that's easier to set up than Sonatype and Maven central is JitPack. This project will automatically fetch your code and release a new artifact. Consumers only have to add an additional Maven repository as a dependency provider:
// build.gradle.kts
repositories {
mavenCentral()
maven("https://jitpack.io")
}
Upvotes: 7