xjc-gradle-plugin
xjc-gradle-plugin copied to clipboard
SourceSets should be configured outside of tasks
As it stands, the XjcPlugin defers adding the java and resource sourceSets until the XjcTask is actually created. This causes some serious problems when trying to use plugins that read and use those sourceSets.
A big example of this is the gradle kotlin plugin, which expects the sourceSets to be stably set when the compile tasks are called. There's no guarantee of this because the XjcTask will configure the sourceSet at some arbitrarily unknown time in the future. As a result, references from Kotlin code to the generated Java code will result in Unresolved reference
errors.
I expected this to work:
tasks.named("compileKotlin") {
dependsOn("xjc")
}
But it seems this still causes the sourceSet to be configured too late.
Along with weird race conditions, it's possible to have concurrent modification exceptions:
sourceSets.main {
java.srcDir(tasks.named<XjcTask>("xjc").map { it.outputJavaDir })
}
Will throw concurrent modification exceptions because the srcDir is set from both the sourceSets block and the deferred task creation.
A good solution here is to just configure the sourceSet in an afterEvaluate block (outside of the task body). Extensions are fully populated in an afterEvaluate block, and this block will always run before any task is executed or added as long as it's in Plugin.apply
.
For now, a simple workaround is just to eagerly create the xjc
task:
val xjc by tasks.getting {}
afterEvaluate
block is deprecated since Gradle 5 and will be an error in Gradle 7
afterEvaluate
block is deprecated since Gradle 5 and will be an error in Gradle 7
Only for already evaluated project. Calling afterEvaluate
while applying plugin should be OK.
On the issue, I believe this is easily solvable with properties and providers and without afterEvaluate
.
Something like this:
val xjcTask = project.tasks.register(XJC_TASK_NAME, XjcTask::class.java) { xjc ->
xjc.xsdDir.convention(project.layout.projectDirectory.dir("src/main/resources"))
}
project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets.named(MAIN_SOURCE_SET_NAME) { sourceSet ->
sourceSet.java.srcDir(xjcTask.map { xjc ->
xjc.outputJavaDir
})
sourceSet.resources.srcDir(xjcTask.map { xjc ->
xjc.outputResourcesDir
})
}
UPD: Note that source set is configured lazily too (named
). I don't know on current Gradle, but in previous versions this API was present, but configuration actually was not lazy.
But if it is lazy now, maybe there is no need to wrap task properties in providers: when source set is resolved that means that it is about to compile, and xjc
task should be resolved anyway. We could just use xjcTask.get().output...Dir
Moreover, IIUC, there is no need to manually add task dependencies when this method is used:
If the provider represents an output of a task, that task is executed if the file collection is used as an input to another task
The following solution can also be used here:
https://github.com/IntershopCommunicationsAG/jaxb-gradle-plugin/issues/35#issuecomment-1826250680
plugins {
id("com.github.bjornvester.xjc") version "1.8.1"
}
xjc {
// ...
}
sourceSets {
main {
kotlin {
srcDir("${project.buildDir}/generated/sources/xjc/java")
}
}
}
tasks.compileKotlin {
dependsOn("xjc")
}