Slackbot
12/26/2023, 6:25 PMAdam
12/26/2023, 8:41 PMgradle.properties in the root project directory and adding
group=my.project.group
version=1.2.3-XYZ
There are other ways of setting these values, but that's the most basic.
The artifact ID will be set automatically using the name of the subproject. I mention it because it's best not to override this - it can cause problems with resolving dependencies between subprojects.
The description can also be set per subproject, and Gradle will automatically use it in the POM
// ./some-subproject/build.gradle.kts
description = "I'm some-subproject and I do <stuff>"
That covers the basics...Adam
12/26/2023, 8:53 PMbuildSrc/build.gradle.kts
// ./buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
2. create buildSrc/settings.gradle.kts
// ./buildSrc/settings.gradle.kts
rootProject.name = "buildSrc"
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
@Suppress("UnstableApiUsage")
dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
3. create a 'Maven publishing' convention plugin - the ID my-maven-publishing-convention is based on the filename (and the Java package, if one is defined, which in this example it's not but I'm mentioning it because it is significant if defined)
// ./buildSrc/src/main/kotlin/my-maven-publishing-convention.gradle.kts
plugins {
`maven-publish`
}
publishing {
publications.withType<MavenPublication>().configureEach {
pom {
url.convention("...")
scm {
connection.convention("...")
developerConnection.convention("...")
url.convention("...")
}
licenses {
license {
name.convention("...")
}
}
developers {
developer {
id.set("...")
name.set("...")
email.set("...")
}
}
}
}
}
4. now in your subprojects that you want to publish you can apply this convention, which will automatically configure the defaults for the POM
// ./some-subproject/build.gradle.kts
plugins {
`my-maven-publishing-convention`
}
It would also probably be worth creating a my-java-library convention (or whatever other language you're developing with) so you can re-use any Java conventions along with the Maven publishing conventions. For example, setting withSourcesJar() and withJavadocJar(), creating a MavenPublication, and setting up signing.
(You might see Gradle projects using allprojects {} and subprojects {} to share configuration, instead of convention plugins. This makes Gradle act more like Maven, where child projects 'inherit' from parents, but even though it seems easier to understand it always makes things more complicated, so avoid it!)Adam
12/26/2023, 8:54 PM3. the modules get published togetherDo you mean running one task, so you only have to run one task and everything gets published together? Or do you mean shading the subprojects together so they end up as a single JAR?
Mike Wacker
12/26/2023, 9:43 PMorg.immutables:value, org.immutables:value-annotations, etc., get released at the same time and with the same version number.
I noticed that Immutables (which uses Maven) puts most of the publishing information in the root POM. It seems like if the information was put in the conventions plugin (which was my first thought on how to do it), the result would be one standalone POM file for each module with (mostly) the same publishing information--though that info would be defined once in the conventions plugin. Though I don't know what the implications of having standalone POMs would be w.r.t. staging and publishing a release on Sonatype, vs. what Immutables does with its root POM.Adam
12/26/2023, 10:44 PM: or subproject path (e.g. :some-subproject:task then Gradle will run all matching tasks in the subprojects, so you'd only have to run ./gradlew publishToMavenLocal (or an equivalent) https://docs.gradle.org/8.5/userguide/command_line_interface.html#executing_tasks_in_multi_project_builds - would that suite you? :)Adam
12/26/2023, 10:52 PMI noticed that Immutables (which uses Maven) puts most of the publishing information in the root POMAh okay, I've looked further and I can see that each Immutables subproject has a
<parent> reference e.g. https://repo1.maven.org/maven2/org/immutables/data/2.10.0/data-2.10.0.pom - are you asking if it's possible/necessary to set up the same 'paren't hierarchy in the POMs that Gradle creates? I've published a few libraries to Maven Central using Gradle and I've never noticed it before, so I don't think it really matters. The 'effective POM' will be the same.Mike Wacker
12/26/2023, 11:57 PM./gradlew publish works as intended. I probably would want to key off of a Github release to stage a release in OSSRH (see the GitHub workflow in https://docs.github.com/en/actions/publishing-packages/publishing-java-packages-with-gradle#publishing-packages-to-the-maven-central-repository). The one open question is how the POM structure matters after artifacts are staged in OSSRH: what are the implication of pushing a hierarchical POM structure (like what Immutables has) to OSSRH, vs. pushing a bunch of standalone POMs to OSSRH.Vampire
12/27/2023, 10:49 AMMike Wacker
01/04/2024, 8:34 PM./gradlew publish will create a single staging repository in OSSRH with all the modules (as opposed to a separate staging repository for each module). That's the one remaining question I had, in part because these modules have dependencies between them.
Here are the contents of `io.github.mikewacker.drift.publish-conventions.gradle.kts`:
plugins {
java
`maven-publish`
signing
}
java {
withSourcesJar()
withJavadocJar()
}
tasks.javadoc {
(options as StandardJavadocDocletOptions).addBooleanOption("Werror", true)
}
group = "io.github.mikewacker.drift"
version = "0.2.0-SNAPSHOT"
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
pom {
url = "<https://github.com/mikewacker/drift>"
licenses {
license {
name = "MIT License"
url = "<https://opensource.org/license/mit/>"
}
}
developers {
developer {
id = "mikewacker"
name = "Mike Wacker"
email = "<mailto:11431865+mikewacker@users.noreply.github.com|11431865+mikewacker@users.noreply.github.com>"
url = "<https://github.com/mikewacker>"
}
}
scm {
connection = "scm:git:<git://github.com/mikewacker/drift.git>"
developerConnection = "scm:git:<ssh://github.com>:mikewacker/drift.git"
url = "<https://github.com/mikewacker/drift/tree/main>"
}
}
}
}
repositories {
maven {
name = "Ossrh"
url = uri("<https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/>")
credentials {
val ossrhUsername: String? by project
val ossrhToken: String? by project
username = ossrhUsername
password = ossrhToken
}
}
}
}
signing {
val signingKeyId: String? by project
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
sign(publishing.publications["mavenJava"])
}
And then each published module would also have a snippet like this:
publishing {
publications.named<MavenPublication>("mavenJava") {
pom {
name = "Drift"
description = "Lightweight, all-in-one library to rapidly prototype a JSON API."
}
}
}
A few notes:
• I also have a GitHub workflow that runs ./gradlew publish when I create a release in GitHub.
• I'm not sure what the difference is between `.set()`/`=and.convention()`, but using = works fine for me. (But, I don't have any values that I need to "override"; everything is either set once in the conventions plugin or set once in the module build file.)
• Having a separate convention plugins in buildSrc for Java conventions and publishing conventions is beneficial. E.g., I have an example module; I still want to apply my Java conventions there, but I don't want to publish it or enable the strict JavaDoc rules that I used for published modules [(options as StandardJavadocDocletOptions).addBooleanOption("Werror", true)].Vampire
01/05/2024, 4:10 AM"foo"
=> "foo"
`.set()`/`=` to "bar"
=> "bar"
`.set()`/`=` to null
=> unset
.convention() to "foo"
=> "foo"
.set()/= to "bar"
=> "bar"
.set()/= to null
=> "foo"