bjornvester / xjc-gradle-plugin

A Gradle plugin for running the XJC binding compiler to generate JAXB Java source code from XSD schemas
MIT License
58 stars 9 forks source link

Mixed up binding-files when using groups #32

Open MZimmermann opened 1 year ago

MZimmermann commented 1 year ago

Hey,

I'm having trouble with release 1.8.1, because of inconsistent bindings when there is more than one xjc-task involved (which will be the case, when multiple groups are configured). I attached a minimal example-project:

XjcTest.zip

Each group is supposed to generate a single class (A and B).

A has an xsd:dateTime-attribute, which is supposed to be handled via a custom-converter, which is specified in the bindings-file xsd-templates/a/bindings_a.xjb.

B has an xsd:duration-attribute, which is supposed to be handled via another converter, which is specified in the bindings-file xsd-templates/b/bindings_b.xjb.

This example compiles, because it's using the plugin at version 1.8.0. But if you change it to 1.8.1, and replace the lines

bindingFiles = project.files(xsdDir.file("bindings_a.xjb"))

with

bindingFiles.setFrom(project.files(xsdDir.file("bindings_a.xjb"))),

(the same for the B-task respectively)

it will fail.

If we're attempting to run ./gradlew xjcA xjcB --rerun-tasks --info, we see the problem in the output:

> Task :xjcA
Caching disabled for task ':xjcA' because:
  Build cache is disabled
Task ':xjcA' is not up-to-date because:
  Executed with '--rerun-tasks'.
Watching 22 directories to track changes
Watching 21 directories to track changes
Loading XSD files [/home/mz/git/private/XjcTest/xsd-templates/a/A.xsd]
file or directory '/home/mz/git/private/XjcTest/build/xjc/extracted-bind-files', not found
Loading binding files: [/home/mz/git/private/XjcTest/xsd-templates/b/bindings_b.xjb]

> Task :xjcB
Caching disabled for task ':xjcB' because:
  Build cache is disabled
Task ':xjcB' is not up-to-date because:
  Executed with '--rerun-tasks'.
Watching 16 directories to track changes
Watching 15 directories to track changes
Loading XSD files [/home/mz/git/private/XjcTest/xsd-templates/b/B.xsd]
file or directory '/home/mz/git/private/XjcTest/build/xjc/extracted-bind-files', not found
Loading binding files: [/home/mz/git/private/XjcTest/xsd-templates/b/bindings_b.xjb]

Note, that the task xjcA now loads the binding-file /home/mz/git/private/XjcTest/xsd-templates/b/bindings_b.xjb, which means that the code from Main.java will lead to compile errors. Either a.setMyDateTime(OffsetDateTime.MAX); or b.setMyDuration(Duration.ofSeconds(4)); will produce an error, because one of the task will have worked with mismatched binding-file.

I'm guessing, that replacing the FileCollection with ConfigurableFileCollection and making them final has something to do with the problem, but I'm not well versed in the intricacies of gradle. I would appreciate it, if you could have a look.

ChristianCiach commented 1 year ago

I see the exact same issue. We are currently workarounding the issue by not using groups altogether in favor of creating completely separate xjc-Task instances:

tasks.register("xjc-a", XjcTask::class) { ... }
tasks.register("xjc-b", XjcTask::class) { ... }

I wonder if this makes the groups-mechanism of this plugin completely redundant.

ChristianCiach commented 1 year ago

Sorry, my workaround doesn't work. I accidentally tested against version 1.8.0.

The issue seems to be that the bindingFiles of the singleton XjcExtension are shared between all groups/tasks. See:

https://github.com/bjornvester/xjc-gradle-plugin/blob/19fe642511d80e8e15976f535ab7045d96cd5598/src/main/kotlin/com/github/bjornvester/xjc/XjcTask.kt#L58C9-L58C21

Since the FileCollection is a constant (cannot be replaced like a Property) and there is no API to clear the collection, I don't even see a workaround for this issue.

As it stands, this seems to be fundamentally broken and unfortunately we cannot upgrade from 1.8.0.

belovaf commented 1 year ago

The cause of this issue is the wrong order of configuration actions. You can easily workaround it:

xjc {
  groups {
    create("group1") {
        includes.add("my-schema.xsd")
        afterEvaluate {
            bindingFiles.setFrom("src/main/resources/xjb/my-binding.xjb")
        }
    }
  }
}

or

xjc {
  groups {
    register("group1") {
        includes.add("my-schema.xsd")
    }
    matching { name == "group1" }.configureEach {
        bindingFiles.setFrom("src/main/resources/xjb/my-binding.xjb")
    }
  }
}
ChristianCiach commented 1 year ago

For others reading this: We've decided that this plugin doesn't carry its weight. We've replaced it by this piece of code:

val xjcDeps: Configuration by configurations.creating {
    extendsFrom(configurations.implementation.get())
}
dependencies {
    xjcDeps("org.glassfish.jaxb:jaxb-xjc:4.0.3")
}

fun addXjcTask(baseName: String, packageName: String): TaskProvider<JavaExec> {
    val outputDir = "src-gen/xjc-${baseName}/java"
    sourceSets["main"].java.srcDir(outputDir)

    val xjcTask = tasks.register("xjc-${baseName}", JavaExec::class) {
        doFirst {
            project.delete(outputDir)
            project.mkdir(outputDir)
        }
        val xsdDir = "xsd-templates/${baseName}"
        val schemaFile = "$xsdDir/schema.xsd"
        val bindingsFile = "$xsdDir/bindings.xjb"
        inputs.files(schemaFile, bindingsFile)
        outputs.dir(outputDir)

        classpath = xjcDeps
        mainClass.set("com.sun.tools.xjc.XJCFacade")
        args("-extension")
        args("-d", outputDir)
        args("-p", packageName)
        args(schemaFile)
        args("-b", bindingsFile)
    }
    tasks.withType<JavaCompile>().configureEach {
        dependsOn(xjcTask)
    }
    return xjcTask
}

This works just as well for us.

flozano commented 9 months ago

Also affected by this, the workaround doesn't work for me :(

pgalbraith commented 4 months ago

Affecting me as well

yyakovlev commented 4 months ago

Spent a day with this problem in my project and fortunately finally encountered this Issue item. Solved by downgrading the plugin to 1.8.0 and configuring bindingFiles by assignment rather than "setFrom()".

alexandre-baron commented 3 months ago

Affecting me as well.

invitor commented 1 month ago

The problem exists in v1.8.2 also.

I think the problem lies in the XjcTask. Each task is initialized with the bindingFiles of the extension and therefore share the same FileCollection instance. See https://github.com/bjornvester/xjc-gradle-plugin/blob/v1.8.2/src/main/kotlin/com/github/bjornvester/xjc/XjcTask.kt#L58

It is val bindingFiles = getXjcExtension().bindingFiles but should be val bindingFiles = objectFactory.fileCollection()

You can work around the problem with reflection:

xjc {
    groups {
         register("A") {
             bindingFilesSetFrom("A.xjb")
         }
         register("B") {
             bindingFilesSetFrom("B.xjb")
         }
    }
}

fun XjcExtensionGroup.bindingFilesSetFrom(vararg paths: Any) {
    val bindingFilesField = XjcTask::class.java.getDeclaredField("bindingFiles")
    bindingFilesField.isAccessible = true

    // see: https://github.com/bjornvester/xjc-gradle-plugin/blob/v1.8.2/src/main/kotlin/com/github/bjornvester/xjc/XjcPlugin.kt#L66
    val taskName = com.github.bjornvester.xjc.XjcPlugin.Companion.XJC_TASK_NAME + name.replaceFirstChar(Char::titlecase)

    bindingFilesField.set( //
        project.tasks.getByName(taskName), //
        project.objects.fileCollection().from(*paths) //
    )
}