Extend compileJava with Lazy API configuration

Short Version
Q) How do I extend the compileJava task to make use of the LazyProvider API?

Details
I’m trying to have a task myGenerateJava which generates java code, which compileJava() should depend on. So far, the build works if I run the build twice. If I only run the build once, then compileJava fails because it can’t see all of the code generated from myGenerateJava.

Simply adding compileJava.dependsOn('myGenerateJava') and compileJava.mustRunAfter('myGenerateJava') doesn’t work, I think because the code that compileJava is meant to depend on doesn’t exist yet. I think, I am meant to make use of the lazy_configuration API to fix this problem as the descriptive text maps perfectly onto what I’m trying to do. [1]

However, the example given with a producer and consumer are both custom/bespoke tasks, whereas in my case, the producer is my own codegen, and the consumer is the out of the box compileJava task.

References
[1] https://docs.gradle.org/current/userguide/lazy_configuration.html

Many builds have several tasks connected together, where one task consumes the outputs of another task as an input. To make this work, we would need to configure each task to know where to look for its inputs and place its outputs, make sure that the producing and consuming tasks are configured with the same location, and attach task dependencies between the tasks. This can be cumbersome and brittle if any of these values are configurable by a user or configured by multiple plugins, as task properties need to be configured in the correct order and locations and task dependencies kept in sync as values change. The Property API makes this easier by keeping track of not just the value for a property, which we have seen already, but also the task that produces the value, so that you don’t have to specify it as well.

Hi Harsha.

I think you need to look into source sets for including your generated source code in the main compilation unit. To avoid messing up the up-to-date checks, you will also need to generate your sources in a different folder than the standard “src/main” folder if that is what you are doing.

As an example, you can have a custom task implemented by a Groovy class like this:

class MyJavaGen extends DefaultTask {
    @OutputDirectory
    DirectoryProperty outputDir = project.objects.directoryProperty().convention(project.layout.buildDirectory.dir("generated/sources/my-java"))

    @TaskAction
    void doCodeGeneration() {
        Directory packageDir = outputDir.dir("example/project").get()
        project.mkdir(packageDir)
        packageDir.file("MyGeneratedJavaClass.java").asFile.text = """
package example.project;

public class MyGeneratedJavaClass {
    public boolean someMethod() {
        return true;
    }
}
"""
    }
}

To begin with, you can put the class definition in your build.gradle file, but it is generally recommended to use the buildSrc folder to organize build logic when it starts to get complex.

Then you can register the task and hook it up to the “main” source set like this:

tasks.register("myGenerateJava", MyJavaGen) { myGenerateJavaTask ->
    sourceSets.named(SourceSet.MAIN_SOURCE_SET_NAME) {
        it.java.srcDir(myGenerateJavaTask.outputDir)
    }
}

And lastly, like you have already done, you have to make the compile task depend on your custom task:

tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME) {
    it.dependsOn("myGenerateJava")
}

When you run “build” or “compileJava”, it will also run your task which will generate a Java source class in the “build/generated/sources/my-java” folder. I can’t speak for Eclipse, but if you use IntelliJ, the “my-java” folder will be automatically marked as a source folder just like the “src/main” folder when you (re-)import the project.

The Groovy DSL does not use the “lazy” API, so the above code avoids the DSL and should be all lazy (though I haven’t checked). So this is why it might look a bit different than what you have seen before.

Hope at the very least there is some inspiration here for you.

1 Like