xjc-gradle-plugin icon indicating copy to clipboard operation
xjc-gradle-plugin copied to clipboard

SourceSets should be configured outside of tasks

Open sgarfinkel opened this issue 4 years ago • 6 comments

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 {}

sgarfinkel avatar Feb 04 '20 17:02 sgarfinkel

afterEvaluate block is deprecated since Gradle 5 and will be an error in Gradle 7

eirnym avatar Mar 30 '20 10:03 eirnym

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.

grv87 avatar Jan 31 '21 15:01 grv87

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

grv87 avatar Jan 31 '21 15:01 grv87

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

grv87 avatar Jan 31 '21 15:01 grv87

The following solution can also be used here:

https://github.com/IntershopCommunicationsAG/jaxb-gradle-plugin/issues/35#issuecomment-1826250680

mschorsch avatar Nov 25 '23 08:11 mschorsch

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")
}

mschorsch avatar Nov 25 '23 08:11 mschorsch