Providing default values to custom tasks

I am developing a custom task to be used inside of our build. This task has a bunch of properties that can be set. As part of this I want to be able to provide defaults for the task. My question is how do I do this? In the method designated as @TaskAction do I look through the properties the user set in the configuration phase, figure out which ones they didn’t set, and then just use my own? Or is there a way I can do some “stuff” after the initial configuration phase but before the execution phase and basically look through the task’s properties and set properties with defaults before any execution starts?

It depends on the nature of the defaults. The simplest case is to just use default instance variable values.

class MyTask extends DefaultTask {
  String setting = 'default value'
}

What’s the nature of your default values?

I’m creating a task which does code generation from an XSD/DTD file using the XJC compiler. As part of my task I have a bunch of different configurable properties, one of which is a “scope” property. This property says whether this processing should happen for the compile scope or the testCompile scope (the thought being that the sources being generated may be needed to compile the rest of the application, or they may just be sources needed when compiling test cases). In the case of the “compile” scope, I also need to add the output directory to the java source set and then make the compileJava task dependent on the XJC task. If the scope is “testCompile”, then I need to add the output directory to the test source set and make the compileTestJava dependent on the XJC task.

Here’s an example - the consuming build.gradle that uses this task could look like this:

task generateSiteplanSchema(type: com.somcompany.build.types.Xjc, description: 'Generate the Siteplan sources from the schema') {
 javaPackageName = 'com.somecompany.content.generated.jaxb.fatwire.siteplan'
 schemaFile = "content-system-siteplan.xsd"
}
  task generateSnippetsSchema(type: com.somecompany.build.types.Xjc, description: 'Generate the Snippets sources from the schema') {
 javaPackageName = 'com.somecompany.content.generated.jaxb.fatwire.snippet'
 schemaFile = "content-system-snippets.xsd"
}
  task generateContentCarouselSchema(type: com.somecompany.build.types.Xjc, description: 'Generate the Content carousel sources from the schema') {
 scope = com.somecompany.build.types.Xjc.Scope.TEST_COMPILE
 javaPackageName = 'com.somecompany.content.generated.jaxb.ucm.contentcarousel'
 schemaFile = 'content-carousel.xsd'

It was my impression (& please correct me if this isn’t correct) that sourceSet manipulation & adding dependencies to tasks had to happen in the configuration phase - by the time the execution phase starts the entire task dependency graph is already built and therefore can’t be manipulated (i.e. making compileJava depend on the instance of my task). So basically I need a way in the task (or in my custom plugin) to be able to inspect the instance of the Xjc task(s), then based on the configuration the user provided, do some other logic (add dependencies to other tasks, modify sourceSets, etc).

Code generation is a little awkward in Gradle right now (due to some shortcomings of the SourceSet impl) but this should be ok.

I’d change the approach you are using slightly. I’d retarget the idea of your task to be about decorating source sets.

So the user would do:

task generateSiteplanSchema(type: com.somcompany.build.types.Xjc, description: 'Generate the Siteplan sources from the schema') {
    sourceSet = sourceSets.main
    javaPackageName = 'com.somecompany.content.generated.jaxb.fatwire.siteplan'
    schemaFile = "content-system-siteplan.xsd"
}

Inside your task, you can configure the source set and access it’s associated task (e.g. compile). This would be more flexible.

It was my impression (& please correct me if this isn’t correct) that sourceSet manipulation & adding dependencies to tasks had to happen in the configuration phase

Yes, so you’ll have to use a ‘project.afterEvaluate’ hook here to finalise the configuration.

Is there any way for a custom task to get called afterEvaluate without adding a line in the build.gradle for the project?

I.e., in my custom task I would like to write something like:

class Custom extends DefaultTask {
    def afterProjectEvaluated(Project p) {
    //initialize task based on project structure, task graph, etc
  }
}

of more generally: are there any ‘task lifecycle hooks’ that the task itself can use without external help from build script calls?

UPDATE: Answering my own post here. Turns out implementing an empty constructor actually works. You can call super() first and then use project.afterProjectsEvaluated. I erroneously assumed we were using groovy named param constructors and that implementing the default constructor would not work in this case. I stand corrected.