Execute a task in project.afterEvaluate (I think)

I have a task that generates code from a set of project resources (mainly it’s generating Java from XSD files). I have that task set as a dependency for the JavaCompile task, which makes sense: you can’t compile everything if the generated files are missing.

What I’d really like to do is make it so that this task is also run in the project.afterEvaluate block, which would look something like this:

project.afterEvaluate {
    project.logger.lifecycle "Completed evaluation of project ${project.name}."
    myTask.process()
}

Of course, this doesn’t work and is not a thing.

We really need this to happen so that, as the IDE is processing the code in the regular src/main/java and src/main/groovy folders, there will be generated code and resources in the appropriate folders and those folders will be added to the appropriate source sets so that, when it goes to compile, the generated code is accessible.

But I really can not figure out any way to get this accomplished. I could just directly call the Java code that does the code generation, but then I give up the incremental build logic that’s part of the task code (or will be: I haven’t actually finished that part yet, but it’ll be there!).

Here’s the way I see this working:

  1. Someone gets a copy of a Gradle project that uses our plugin. The project includes a bunch of Java and Groovy code that references classes that don’t yet exist, because those classes are generated from XSD files that are included in the project.

  2. They import that project into their IDE.

  3. The code generation task is invoked. Since there isn’t any code in the folders for generated code at all, all of the code is generated from the XSD files. The generated code folders are added to the compileJava and compileGroovy tasks (right now it’s actually just compileJava, but will include compileGroovy soon enough).

  4. The IDE scans and processes the source code, resolving the references to the generated code properly.

The other thing is that we really need this to be wired up in the plugin itself as much as possible. The people who are writing code using our plugin are by and large not developers, but are researchers, system administrators, etc., who are writing plugins for our server platform. As soon as they include the plugin reference in their build.gradle, we want to wire up the task dependencies, source sets, archive files, etc.

The closest I can get to making this work is something like this:

class XnatDataBuilderPlugin implements Plugin<Project> {
    void apply(Project project) {
        def final Task xnatDataBuilder = project.task('xnatDataBuilder', type: XdatDataBuilderGenerateSourceTask)

        project.afterEvaluate {
            project.logger.lifecycle "Completed evaluation of project ${project.name}."
            def file = new File("/path/to/somewhere/report.txt")
            file.withWriter('UTF-8') { writer ->
                writer.write "I am finished evaluating the project ${project.name} at ${new Date()}."
            }
        }

        // Set the primary compileJava task to depend on generate source.
        (project.tasks.find { def Task task -> task.name == "compileJava" } as JavaCompile).with {
            def JavaCompile compile ->
                compile.dependsOn xnatDataBuilder
                source xnatDataBuilder.java
                compile.doLast {
                    (project.task('xnatPluginInfo', type: XnatPluginInfoTask) as XnatPluginInfoTask).execute()
                }
        }

        // Set the primary jar task to depend on bean jar task. Add "-plugin" to the output jar name.
        (project.tasks.find { def Task task -> task.name == "jar" } as Jar).with { def Jar jar ->
            jar.dependsOn project.task('xnatBeanJar', type: XdatDataBuilderBeanJarTask)
            jar.baseName = "${project.name}-plugin"
        }
    }
}

This works: I can do stuff in project.afterEvaluate, but I can’t do the thing I want to do the most: run the code generation task. And the code generation task works: if I run the build, compileJava will get wired up with my custom task and the code will get generated in time for the compile pass.

Am I going about this all wrong? Or am I just missing something? This seems like such an obvious thing to need to be able to do, I can’t believe it’s so difficult to figure this out! Any help would be greatly appreciated!

One last thing: I have the source sets configured properly within the plugin/task execution, as demonstrated by the fact that you can run the build, the plugin generates the Java code and resources, and everything gets properly built and packaged as artifacts. However, the IDE (IntelliJ in this case) doesn’t know about the generated code source and won’t find the generated Java dependencies. To get that to resolve, I have to add:

sourceSets {
    main {
        java {
            srcDir 'src/main/java'
            srcDir 'build/xnat-generated/src/main/java'
        }
        resources {
            srcDir 'src/main/resources'
            srcDir 'build/xnat-generated/src/main/resources'
        }
    }
}

It’s not terrible, but again I’d prefer to keep the level of boilerplate configuration required in the build files to as small a level as possible.

Did you find a solution to this problem?
We have a similar setup, but as we all are developers, we can promote the execution of a “setup” target.

The only way I could think of was to bind the generation Task, to a IDE Task, but I’m not sure if that would work in case of integrated plugins like Buildship or Intellij.