Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- run: |
./gradlew build
./gradlew -p tests/jvm zipAggregation --configuration-cache
./gradlew -p tests/jvm nmcpZipAggregation --configuration-cache
./gradlew -p tests/jvm build
./gradlew -p tests/kmp publishAggregationToCentralPortal --configuration-cache
./gradlew -p tests/kmp build
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.ggp)
alias(libs.plugins.nmcp)
alias(libs.plugins.compat)
alias(libs.plugins.serialization)
id("maven-publish")
id("signing")
}
Expand Down Expand Up @@ -81,8 +82,11 @@ dependencies {
implementation(libs.json)
implementation(libs.okio)
implementation(libs.okhttp)
implementation(libs.xmlutil)
implementation(libs.gratatouille.runtime)
implementation(libs.okhttp.logging.interceptor)

testImplementation(libs.kotlin.test)
compileOnly(libs.gradle.min)
}

Expand Down
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ okhttp-logging-interceptor = "com.squareup.okhttp3:logging-interceptor:4.12.0"
mockwebserver = "com.squareup.okhttp3:mockwebserver:4.12.0"
gradle-min = "dev.gradleplugins:gradle-api:8.0"
gratatouille-runtime = { module = "com.gradleup.gratatouille:gratatouille-runtime", version.ref = "gratatouille" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test" }
xmlutil = "io.github.pdvrieze.xmlutil:serialization:0.91.1"


[plugins]
kgp = { id = "org.jetbrains.kotlin.jvm", version.ref = "kgp" }
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kgp" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
ggp = { id = "com.gradleup.gratatouille", version.ref = "gratatouille" }
nmcp = { id = "com.gradleup.nmcp", version = "0.1.5" }
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/nmcp/CentralPortalOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ abstract class CentralPortalOptions {
/**
* A name for the publication (optional).
*
* Default: "${project.name}-${project.version}.zip"
* By default, it generates a name from the deployment contents. If the deployment contains several publications, it will
* show the common parts (typically groupId and version).
*/
abstract val publicationName: Property<String>

Expand Down
79 changes: 3 additions & 76 deletions src/main/kotlin/nmcp/NmcpAggregationExtension.kt
Original file line number Diff line number Diff line change
@@ -1,91 +1,18 @@
package nmcp

import gratatouille.GExtension
import javax.inject.Inject
import nmcp.internal.configureAttributes
import nmcp.internal.nmcpConsumerConfigurationName
import nmcp.internal.task.registerPublishReleaseTask
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.tasks.bundling.Zip

@GExtension(pluginId = "com.gradleup.nmcp.aggregation")
abstract class NmcpAggregationExtension(private val project: Project) {
internal val spec = project.objects.newInstance(CentralPortalOptions::class.java)

@get:Inject
abstract val archiveOperations: ArchiveOperations

internal val consumerConfiguration = project.configurations.create(nmcpConsumerConfigurationName) {
it.isCanBeResolved = true
it.isCanBeConsumed = false

it.configureAttributes(project)
}

init {
val operations = archiveOperations
val layout = project.layout
val files = project.files(consumerConfiguration)

val zipTaskProvider = project.tasks.register("zipAggregation", Zip::class.java) {
it.archiveFileName.set("aggregation.zip")
it.destinationDirectory.set(layout.buildDirectory.dir("nmcp/zip"))
it.from(files.elements.map {
it.map {
operations.zipTree(it)
}
})
}

project.registerPublishReleaseTask(
taskName = "publishAggregationToCentralPortal",
inputFile = zipTaskProvider.flatMap { it.archiveFile },
artifactId = project.provider { "${project.name}" },
spec = spec
)
}

interface NmcpAggregationExtension {
/**
* Configures the central portal parameters
*/
fun centralPortal(action: Action<CentralPortalOptions>) {
action.execute(spec)
}

/**
* Applies the `com.gradleup.nmcp` plugin to every project that also applies `maven-publish`.
*
* This function is not compatible with breaking project isolation. To be compatible with project isolation,
* add each subproject to the `nmcpAggregation` configuration dependencies.
*/
fun publishAllProjectsProbablyBreakingProjectIsolation(action: Action<CentralPortalOptions>) {
check(project === project.rootProject) {
"publishAllProjectsProbablyBreakingProjectIsolation() must be called from root project"
}

project.allprojects { aproject ->
aproject.pluginManager.withPlugin("maven-publish") {
aproject.pluginManager.apply("com.gradleup.nmcp")

aproject.extensions.configure(NmcpExtension::class.java) {
action.execute(it.centralPortalOptions)
}
consumerConfiguration.dependencies.add(aproject.dependencies.create(aproject))
}
}
}
fun centralPortal(action: Action<CentralPortalOptions>)

/**
* Applies the `com.gradleup.nmcp` plugin to every project that also applies `maven-publish`.
*
* This function is not compatible with breaking project isolation. To be compatible with project isolation,
* add each subproject to the `nmcpAggregation` configuration dependencies.
*/
fun publishAllProjectsProbablyBreakingProjectIsolation() {
publishAllProjectsProbablyBreakingProjectIsolation { }
}
fun publishAllProjectsProbablyBreakingProjectIsolation()
}


162 changes: 9 additions & 153 deletions src/main/kotlin/nmcp/NmcpExtension.kt
Original file line number Diff line number Diff line change
@@ -1,164 +1,20 @@
package nmcp

import gratatouille.GExtension
import gratatouille.capitalizeFirstLetter
import java.net.URI
import nmcp.internal.configureAttributes
import nmcp.internal.nmcpProducerConfigurationName
import nmcp.internal.task.registerPublishReleaseTask
import nmcp.internal.task.registerPublishSnapshotTask
import nmcp.internal.withRequiredPlugin
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.credentials.PasswordCredentials
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.bundling.Zip

@GExtension(pluginId = "com.gradleup.nmcp")
open class NmcpExtension(private val project: Project) {
internal val centralPortalOptions = project.objects.newInstance(CentralPortalOptions::class.java)
// Lifecycle tasks to publish all the publications in the given project
private val publishAllPublicationsToCentralPortal = project.tasks.register("publishAllPublicationsToCentralPortal")
private val publishAllPublicationsToCentralSnapshots = project.tasks.register("publishAllPublicationsToCentralSnapshots")

init {
project.configurations.create(nmcpProducerConfigurationName) {
it.isCanBeConsumed = true
it.isCanBeResolved = false
// See https://github.com/GradleUp/nmcp/issues/2
it.isVisible = false

it.configureAttributes(project)
}

project.withRequiredPlugin("maven-publish") {
val publishing = project.extensions.getByType(PublishingExtension::class.java)
publishing.publications.configureEach {
registerInternal(it.name)
}
/**
* Not sure how to configure username/password lazily, do it once the build script is evaluated
*/
project.afterEvaluate {
if(centralPortalOptions.username.isPresent) {
publishing.addSnapshotsRepo(centralPortalOptions)
}
}
}
}

interface NmcpExtension {
/**
* This creates a new repository for each publication so that the publications do not overlap each other
* and can use an isolated directory.
* Configures publishing all the publications all at once in a single deployment to the Central Portal.
*
* See https://github.com/GradleUp/nmcp/issues/34#issuecomment-2827704768
* - Adds `nmcpPublishAllPublicationsToCentralPortal`
* - Adds `nmcpPublishAllPublicationsToCentralPortalSnapshots`
*/
private fun registerInternal(publicationName: String) {
val capitalized = publicationName.capitalizeFirstLetter()

val publishing = project.extensions.getByType(PublishingExtension::class.java)
val m2Dir = project.layout.buildDirectory.dir("nmcp/m2$capitalized")
val repoName = "nmcp$capitalized"
publishing.apply {
repositories.apply {
maven {
it.name = repoName
it.url = project.uri(m2Dir)
}
}
}

val publication = publishing.publications.findByName(publicationName)
if (publication == null) {
val candidates = publishing.publications.map { it.name }
error("Nmcp: cannot find publication '$publicationName'. Candidates are: '${candidates.joinToString()}'")
}

val publishToNmcpTaskProvider = project.tasks.named("publish${capitalized}PublicationTo${repoName.capitalizeFirstLetter()}Repository")

publishToNmcpTaskProvider.configure {
// This is mostly an internal task, hide it from `./gradlew --tasks`
it.group = null
it.doFirst {
m2Dir.get().asFile.apply {
deleteRecursively()
mkdirs()
}
}
}

val publishAllToNmcpTaskProvider = project.tasks.named("publishAllPublicationsTo${repoName.capitalizeFirstLetter()}Repository")
publishAllToNmcpTaskProvider.configure {
// This is mostly an internal task, hide it from `./gradlew --tasks`
it.group = null
}



val zipTaskProvider = project.tasks.register("zip${capitalized}Publication", Zip::class.java) {
it.dependsOn(publishToNmcpTaskProvider)
it.from(m2Dir)
it.eachFile {
// Exclude maven-metadata files, or the bundle is not recognized
// See https://slack-chats.kotlinlang.org/t/16407246/anyone-tried-the-https-central-sonatype-org-publish-publish-#c8738fe5-8051-4f64-809f-ca67a645216e
if (it.name.startsWith("maven-metadata")) {
it.exclude()
}
}
it.destinationDirectory.set(project.layout.buildDirectory.dir("nmcp/zip"))
it.archiveFileName.set("publication$capitalized.zip")
}

val artifactId = if (publication is MavenPublication) {
project.provider { publication.artifactId }
} else {
project.provider { "${project.name}"}
}
val publishRelease = project.registerPublishReleaseTask(
taskName = "publish${capitalized}PublicationToCentralPortal",
inputFile = zipTaskProvider.flatMap { it.archiveFile },
artifactId = artifactId,
spec = centralPortalOptions
)
val publishSnapshots = project.tasks.register("publish${capitalized}PublicationToCentralSnapshots") {
if (!centralPortalOptions.username.isPresent) {
it.doFirst {
error("centralPortalOptions.username must be set in each subproject to publish to central snapshots. See https://github.com/GradleUp/nmcp/issues/73 for more information.")
}
} else {
it.dependsOn("publish${capitalized}PublicationTo${nmcpCentralSnapshotsRepoName.capitalizeFirstLetter()}Repository")
}
}

publishAllPublicationsToCentralSnapshots.configure {
it.dependsOn(publishSnapshots)
}
publishAllPublicationsToCentralPortal.configure {
it.dependsOn((publishRelease))
}

project.artifacts.add(nmcpProducerConfigurationName, zipTaskProvider)
}
fun publishAllPublicationsToCentralPortal(action: Action<CentralPortalOptions>)

/**
* Configures the central portal parameters
* Configures publishing a single publication to the Central Portal.
*
* - Adds `nmcpPublish${publicationName.capitalized()}PublicationToCentralPortal`
*/
fun centralPortal(action: Action<CentralPortalOptions>) {
action.execute(centralPortalOptions)
}
}

private val nmcpCentralSnapshotsRepoName = "nmcpCentralSnapshots"
internal fun PublishingExtension.addSnapshotsRepo(centralPortalOptions: CentralPortalOptions) {
repositories {
it.maven {
it.name = nmcpCentralSnapshotsRepoName
it.url = URI("https://central.sonatype.com/repository/maven-snapshots")
it.credentials {
it.username = centralPortalOptions.username.get()
it.password = centralPortalOptions.password.get()
}
}
}
fun publishToCentralPortal(publicationName: String, action: Action<CentralPortalOptions>)
}
48 changes: 48 additions & 0 deletions src/main/kotlin/nmcp/internal/DefaultNmcpAggregationExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nmcp.internal

import nmcp.CentralPortalOptions
import nmcp.NmcpAggregationExtension
import nmcp.internal.task.KindAggregation
import nmcp.internal.task.registerNmcpGuessComponentsTask
import nmcp.internal.task.registerPublishToCentralPortalTasks
import org.gradle.api.Action
import org.gradle.api.Project

abstract class DefaultNmcpAggregationExtension(private val project: Project) : NmcpAggregationExtension {

internal val consumerConfiguration = project.configurations.create(nmcpConsumerConfigurationName) {
it.isCanBeResolved = true
it.isCanBeConsumed = false

it.configureAttributes(project)
}

override fun centralPortal(action: Action<CentralPortalOptions>) {
val centralPortalOptions = project.objects.newInstance(CentralPortalOptions::class.java)
action.execute(centralPortalOptions)

val guessVersion = project.registerNmcpGuessComponentsTask(
inputFiles = consumerConfiguration
)
project.registerPublishToCentralPortalTasks(
deploymentKind = KindAggregation,
inputFiles = consumerConfiguration,
defaultDeploymentName = guessVersion.flatMap { it.outfileFile }.map { it.asFile.readText() },
spec = centralPortalOptions
)
}

override fun publishAllProjectsProbablyBreakingProjectIsolation() {
check(project === project.rootProject) {
"publishAllProjectsProbablyBreakingProjectIsolation() must be called from root project"
}

project.subprojects { aproject ->
aproject.pluginManager.withPlugin("maven-publish") {
aproject.pluginManager.apply("com.gradleup.nmcp")

consumerConfiguration.dependencies.add(aproject.dependencies.create(aproject))
}
}
}
}
Loading