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
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@ package com.github.vlsi.jandex
import com.github.vlsi.jandex.JandexProcessResources.Companion.getTaskName
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
Expand Down Expand Up @@ -69,13 +65,19 @@ open class JandexPlugin : Plugin<Project> {
})
}

val resourceDir = sourceSet.output.resourcesDir!!
// Build the index into a dedicated directory and register it as an extra output of
// the sourceSet. Gradle then makes every consumer of sourceSet.output depend on
// processJandexIndex automatically, so no per-task wiring is needed. It also keeps
// Gradle 9 from reporting an implicit dependency for consumers the plugin does not
// know about (e.g. the JMH bytecode generator or checkstyle on non-main sourceSets).
val jandexResourcesDir =
project.layout.buildDirectory.dir("jandexResources/$sourceSetName")
val processJandexIndex = tasks.register(
getTaskName(sourceSet),
JandexProcessResources::class
) {
description = "Copies Jandex index for $sourceSetName to the resources"
destinationDir = resourceDir
description = "Copies Jandex index for $sourceSetName to the sourceSet output"
destinationDir = jandexResourcesDir.get().asFile
jandexBuildAction.set(task.flatMap { it.jandexBuildAction })
onlyIf {
jandexBuildAction.get() != JandexBuildAction.NONE
Expand All @@ -89,41 +91,7 @@ open class JandexPlugin : Plugin<Project> {
})
}
}
if (name == SourceSet.MAIN_SOURCE_SET_NAME) {
// Assume all sourceSets depend on main one, so we make ALL tasks depend on
// processJandexIndex from the main sourceSet
val compileJavaTaskName = sourceSet.compileJavaTaskName
tasks.withType<JavaCompile>()
.matching { it.name != compileJavaTaskName }
.configureEach {
dependsOn(processJandexIndex)
}
tasks.withType<Jar>().configureEach {
dependsOn(processJandexIndex)
}
tasks.withType<Javadoc>().configureEach {
dependsOn(processJandexIndex)
}
tasks.matching {
it.name.startsWith("forbiddenApis") ||
it.name.startsWith("compile") && it.name.endsWith("Kotlin") && it.name != "compileKotlin"
}
.configureEach {
dependsOn(processJandexIndex)
}
} else {
// Non-main sourceSets depend on their processJandexIndex as well
val jarTaskName = sourceSet.jarTaskName
val sourcesJarTaskName = sourceSet.sourcesJarTaskName
tasks.withType<Jar>().matching { it.name == jarTaskName || it.name == sourcesJarTaskName }.configureEach {
dependsOn(processJandexIndex)
}
sourceSet.javadocTaskName.let { taskName ->
tasks.withType<Javadoc>().matching { it.name == taskName }.configureEach {
dependsOn(processJandexIndex)
}
}
}
sourceSet.output.dir(mapOf("builtBy" to processJandexIndex), jandexResourcesDir)
}
sourceSets.whenObjectRemoved {
tasks.named(getTaskName(JANDEX_TASK_NAME, null)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ package com.github.vlsi.jandex

import com.github.vlsi.gradle.BaseGradleTest
import org.gradle.testkit.runner.TaskOutcome
import org.gradle.util.GradleVersion
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.parallel.Execution
import org.junit.jupiter.api.parallel.ExecutionMode
import org.junit.jupiter.params.ParameterizedTest
Expand Down Expand Up @@ -89,4 +93,105 @@ class JandexPluginTest : BaseGradleTest() {
}
}
}

/**
* processJandexIndex contributes to the sourceSet output, so every task that consumes that
* output must depend on it. For non-main sourceSets the plugin used to wire only the jar and
* javadoc tasks, which left other consumers (the JMH bytecode generator, checkstyle,
* forbidden-apis) without the dependency. Gradle 9 rejects such an implicit dependency, failing
* the build. The fix registers the index as an extra output directory of the sourceSet so the
* dependency is wired automatically for every consumer, in every [JandexBuildAction] mode.
*
* See https://github.com/pgjdbc/pgjdbc/pull/4010 for the original report.
*/
@ParameterizedTest(name = "{0}")
@MethodSource("jandexBuildActions")
fun jandexDoesNotBreakNonMainSourceSetConsumers(
label: String,
jandexConfig: String,
indexIncludedInJar: Boolean
) {
// The implicit-dependency check is only a warning before Gradle 9, so pin a Gradle 9 here
val testCase = TestCase(GradleVersion.version("9.0.0"), ConfigurationCache.ON)
createSettings(testCase)
projectDir.resolve("src/extra/java/acme").toFile().mkdirs()
projectDir.resolve("src/extra/java/acme/Extra.java").write(
"""
package acme;
public class Extra {
public int inc(int a) {
return a + 1;
}
}
""".trimIndent()
)
projectDir.resolve("build.gradle").write(
"""
plugins {
id 'java-library'
id 'com.github.vlsi.jandex'
}

repositories {
mavenCentral()
}

$jandexConfig

sourceSets {
extra
}

// The jandex plugin makes extraJar depend on processExtraJandexIndex, so the index
// task is scheduled and contributes to the extra sourceSet output.
tasks.register('extraJar', Jar) {
archiveClassifier = 'extra'
from sourceSets.extra.output
}

// A task that reads the sourceSet output, the way the JMH bytecode generator or
// checkstyle do. It must not trip Gradle's implicit-dependency validation against
// processExtraJandexIndex, which contributes to that same output.
tasks.register('useExtraOutput') {
def extraOutput = sourceSets.extra.output
inputs.files(extraOutput).withPropertyName('extraOutput')
outputs.dir(layout.buildDirectory.dir('useExtraOutput')).withPropertyName('outputDir')
doLast {
extraOutput.files.findAll { it.exists() }
}
}
""".trimIndent()
)
val result = prepare(testCase, "extraJar", "useExtraOutput", "-i").build()
if (isCI) {
println(result.output)
}
assertNotNull(result.task(":processExtraJandexIndex")) {
"[$label] processExtraJandexIndex should be wired into the graph via the sourceSet output"
}
assertEquals(TaskOutcome.SUCCESS, result.task(":useExtraOutput")?.outcome) {
"[$label] a consumer of the extra sourceSet output must build without an implicit-dependency failure"
}
val extraJar = projectDir.resolve("build/libs/sample-extra.jar").toFile()
assertTrue(extraJar.exists()) { "[$label] extraJar should be built at $extraJar" }
java.util.zip.ZipFile(extraJar).use { zip ->
val indexEntry = zip.getEntry("META-INF/jandex.idx")
if (indexIncludedInJar) {
assertNotNull(indexEntry) { "[$label] the Jandex index should be packaged into the jar" }
} else {
assertNull(indexEntry) { "[$label] the Jandex index must not be packaged into the jar" }
}
}
}

companion object {
@JvmStatic
fun jandexBuildActions(): List<Arguments> = listOf(
// label, build.gradle snippet that selects the JandexBuildAction, index expected in jar
arguments("default => BUILD_AND_INCLUDE", "", true),
arguments("BUILD", "jandex { includeIndexInJar(false) }", false),
arguments("VERIFY_ONLY", "jandex { skipIndexFileGeneration() }", false),
arguments("NONE", "jandex { skipDefaultProcessing() }", false),
)
}
}
Loading