Generated Sources with Configurable Output Directory

I working on a plugin that adds a task which generates sources. The output directory for the generator should be configurable in an extension so that it can be within the build directory by default or outside the build directory when working with IDEA (as suggested here).

I figured out that I can use the convention mapping to wire the extension property value to the task and I used afterEvaluate to add the directory to the main java source set so that it gets compiled.

myTask.conventionMapping.map('outputDir') {
    myExtension.outputDir
}
project.afterEvaluate {
    javaConvention.sourceSets.main.java.srcDir(myTask.outputDir)
}

Is that the recommend way to set things up? I had to use ConventionTask for my task to get the convention mapping feature, but it’s internal API. And using afterEvaluate seems to be more like a workaround.

If that extension property is only used to configure a single task then I would likely simplify it by simply setting the property directly on the task. If you have a custom extension whose properties are used to configure multiple tasks then yes, using convention mapping might be your best bet. The other option would be to set the properties using ‘Project.afterEvaluate()’.

You can certainly use the internal API, just be aware that it is likely it may change at any time.

So, currently there is no way to avoid internal API or ‘Project.afterEvaluate()’.

Another caveat with ‘afterEvaluate’ is that the ‘org.gradle.testfixtures.ProjectBuilder’ does not trigger ‘afterEvaluate’. So in my tests I have to use more internal API which seems odd since ‘afterEvaluate’ is not internal API:

Project project = ProjectBuilder.builder().build()
project.with {
    ...
}
(project as ProjectInternal).projectEvaluationBroadcaster.afterEvaluate(project, project.state)

Can you just defer the evaluation of myTask.outputDir (or the extension outputDir) when adding it to the source set? srcDir evaluates things like Project.file

apply plugin: 'java'
  class SomeExt {
  String outputDir = "default/dir"
}
  extensions.create('someExt', SomeExt)
  // defer with Closure
sourceSets.main.java.srcDir { someExt.outputDir }
  someExt.outputDir = "somewhere/else"
  task print() << {
   println sourceSets.main.java.srcDirs
}

For unit testing plugins that leverage ‘afterEvaluate { }’ you’ll want to simply call ‘Project.evaluate()’, which admittedly is also on the ‘ProjectInternal’ interface but a bit cleaner than your solution.

Great! Thanks for the suggestions.

Currently I add a dependency from the compileJava task to my source generating task, because it’s most likely the first task to use the source set as input.

project.tasks[javaConvention.sourceSets.main.compileJavaTaskName].dependsOn(myTask)

But in theory a user could define another task that uses the source set as input and runs before compileJava. Can I somehow express a dependency from the source set to my task so that my task is executed before another tasks uses the source set as input? The opposite of ‘SourceSet.compiledBy’ so to speak.