What's the best choice between tasks or methods to organize your Gradle build logic? - methods

I'm currently migrating some old huge Maven 1 script to Gradle.
As a consequence, I need to adapt the old Maven 1 / Ant & its goals logic to Gradle.
After having read the Gradle User Guide, and some articles on Gradle tasks and methods, I am quite confused about the way to write my script.
In the official Gradle User Guide, §6.1, it is explained that a Gradle task:
represents some atomic piece of work which a build performs
In §6.11, it is also explained that we can use methods to organize our build logic.
So, my question is: what's the correct way to use each of them?
I am creating a build script so, in my opinion:
tasks should only be what the user is allowed to see, and so, to call through the command line.
By example gradle doSomeInternalTechnicalWork is not correct for me, as the user should even not know that doSomeInternalTechnicalWork exists.
Always in my opinion, it should NOT be a task.
method should be used to organize the build logic, and should NOT be visible by the user
With the former logic, I encounter problems when my methods need to call Gradle tasks (like the JAR creation of the Java plugin).
I know that I should not call task from task (and so the same for task from method), but, have a look to this example:
task independentTask << {
// initialization stuff
println "doing a lot of initialization"
// using methods to organize build logic, good or not?
doComplexThingsThatTheUserShouldNeverDoHimself()
}
task dependentTask(dependsOn: 'independentTask') << {
println "now that 'independentTask' is done, I can continue to do complex things..."
}
void doComplexThingsThatTheUserShouldNeverDoHimself() {
println "doing really complex things"
// I really need to create my JAR here and not somewhere else
// And I know it's not a good thing to directly call the Action.execute
jar.execute()
println "doing other really complex things"
}
In this case, what would be a correct build logic?
Should doComplexThingsThatTheUserShouldNeverDoHimself be converted in 1 or more tasks, so as to be able to dependsOn the JAR task?
But that would mean to have really a lot of tasks, callable by the user, when, indeed, that should not be the case.

After having search quite a lot, I concluded that, when you need to call a task from another task, you have little choice but to rely on tasks relationships (dependsOn, mustRunAfter, finalizedBy).
Which means that methods can't be used to organize the build logic in the same way that they are used to structure a program in Java, Groovy & Co.
As a consequence, you can't prevent the user from seeing (and using) some internal tasks, that should normally only be used as dependency by some other ones.
A "Gradle correct" version of the former build script would hence be:
task dependentTask(dependsOn: 'doComplexThingsThatTheUserShouldNeverDoHimselfPart2') << {
println "now that 'independentTask' is done, I can continue to do complex things..."
}
task doComplexThingsThatTheUserShouldNeverDoHimselfPart2(dependsOn: ['doComplexThingsThatTheUserShouldNeverDoHimselfPart1', 'jar']) << {
println "doing other really complex things"
}
task doComplexThingsThatTheUserShouldNeverDoHimselfPart1(dependsOn: 'independentTask') << {
println "doing really complex things"
}
task independentTask << {
// initialization stuff
println "doing a lot of initialization"
}
Or, with tasks relationships declared separately:
task dependentTask << {
println "now that 'independentTask' is done, I can continue to do complex things..."
}
task independentTask << {
// initialization stuff
println "doing a lot of initialization"
}
task doComplexThingsThatTheUserShouldNeverDoHimselfPart1 << {
println "doing really complex things"
}
task doComplexThingsThatTheUserShouldNeverDoHimselfPart2 << {
println "doing other really complex things"
}
// we declare all tasks relationships separately
dependenTask.dependsOn doComplexThingsThatTheUserShouldNeverDoHimselfPart2
doComplexThingsThatTheUserShouldNeverDoHimselfPart2 dependsOn doComplexThingsThatTheUserShouldNeverDoHimselfPart1, jar
doComplexThingsThatTheUserShouldNeverDoHimselfPart1 dependsOn independentTask
Personally, I prefer the last one, the relationship block being more readable.

Related

Confused about Gradle delete task

I'm currently learning Gradle, so this is probably a simple question but I can't seem to understand.
I need to create a task in my gradle build that deletes a set of intermediate files. So after a bunch of Google'ing, I tried the following:
task deleteTest (type: Delete) {
doLast {
delete fileTree ('src/main/gen') {
include '**/*'
}
}
}
This has no effect, since when I run the task all of the files in the 'src/main/gen' directory still exist. From reading various websites, it seemed like this was the correct approach, but it just doesn't work.
Just for grins, I tried:
task deleteTest (type: Delete) {
delete fileTree ('src/main/gen') {
include '**/*'
}
}
This seems to work, all of the files get removed from the directory(although it leaves empty sub-directories, which I also don't understand). But from what I read, this is not the correct way to go, since it executes during configuration, not during execution.
Can someone please explain this to me? There's apparently something I'm just not grokking with respect to Gradle in general and this problem in particular.
The short answer:
If you just want to delete the folder src/main/gen and everything inside, use something like this:
task deleteTest(type: Delete) {
delete 'src/main/gen'
}
Your second example is fine, too. It preserves directories because a fileTree is used, which only collects files.
The long answer:
Your first example mixes the two ways to delete files in Gradle. The first one is to use a task of type Delete, the second one is to invoke the method delete of the type Project. But how to they differ and why are they mixed in your example?
Gradle is based on its task system that allows to define and configure tasks which are only run if necessary. Whether a task is required for the build will be determined from task dependencies (dependsOn). This is the reason why Gradle distinguishes between the configuration phase and the execution phase. During configuration phase, the whole build script gets executed except the actual task actions (not visible in the build script) and code wrapped in doFirst / doLast closures. During execution phase, each required task gets run by Gradle. This involved executing the doFirst closures of the task, then the actual task actions and in the end the doLast closures of the task. Now for a Delete task like the one above this means, that the code in the configuration closure delete 'src/main/gen' gets executed during configuration phase, but the actual deletion of the files (the task action) happens later on, during execution phase.
The problem with this approach arises when its required to delete files directly or all the time (e.g. in a plugin or another scenario). It would be too complicated to create a task, setup the dependencies and so on. Here comes the method delete of the type Project to the rescue. It provides the same interface for configuration as the task type Delete, but executes directly. It can be called via the project instance (e.g. project.delete 'src/main/gen') everywhere in your script and runs instantly, but because the project instance is used as scope of the whole script, just using delete is sufficient, too. Well, it is not always sufficient. If the current scope provides a method called delete (with the same signature), this method will be used instead. This is the case inside a task of type Delete and this is the reason why your first script does not work:
Your task of type Delete gets configured in the doLast closure, which runs after the actual deletion should have taken place. If you remove the type: Delete, the method delete will no longer configure the task, but instead delete the files instantly because it is no longer the method delete of the task Delete, but the method delete of the type Project. This works fine, but using a real task should be preferred.
If you remove the type: Delete from your second example, the same thing will happen. Instead of configuring the task, the files will be deleted instantly (now during configuration phase). You do not want this behavior, because the task will be obsolete, since the files will be deleted every time Gradle is invoked. This is what you mentioned as a possible problem.

gradle create custom tasks

I'm a little bit confused about the correct way to create custom tasks on Gradle. On the tutorial for the Creation of custom tasks, they use tasks.register like this:
def check = tasks.register("check")
def verificationTask = tasks.register("verificationTask") {
// Configure verificationTask
}
check.configure {
dependsOn verificationTask
}
Instead here (still official Gradle documentation), they create new tasks that way:
task('hello') {
doLast {
println "hello"
}
}
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}
and
tasks.create('hello') {
doLast {
println "hello"
}
}
tasks.create('copy', Copy) {
from(file('srcDir'))
into(buildDir)
}
Finally, according to the document https://docs.gradle.org/current/userguide/task_configuration_avoidance.html, they suggest to move from the second/third case to the first one. Does it mean that the second/third cases are obsolete? If yes, why is Gradle still making massive usage of the old API inside its documentation?
Which variant should a user use?
The Gradle API has many ways to define tasks. There is no "right" or "wrong" way for application developers so long as you are consistent, but it does matter for Gradle plugin authors.
The Task Configuration Avoidance doc you linked states (emphasis mine):
As of Gradle 5.1, we recommend that the configuration avoidance APIs be used whenever tasks are created by custom plugins.
So if you are a plugin author, use task configuration avoidance wherever possible
For everyone else (application developers), it doesn't particularly matter, to an extent, so long as your as consistent across your entire application.

How to determine if a Gradle task was instantiated (Configuration Avoidance API)

I am trying to improve the performance of our Gradle builds and discovered the Gradle Task Configuration Avoidance API (https://docs.gradle.org/current/userguide/task_configuration_avoidance.html). It allows to postpone the creation and configuration of a task, unless it is really needed. This might save a lot of startup time and as we call Gradle multiple times during a build, this could amount to considerable time savings.
We developed some plugins for internal use and I put an effort into changing how I define the tasks to avoid creation when not needed. I want to test if my changes are successful and the task instantiation is delayed.
Simple example of how to create a task without instantiating it:
class MyTask extends DefaultTask {
}
TaskProvider customTask = tasks.register("customAction", MyTask) {
println "task configured!" // configuration time output
doLast {
println "action 1!" // execution time output
}
}
// configuration like this avoids task instantiation
tasks.named("customAction") {
doLast {
println "action 2!"
}
}
tasks.withType(MyTask).configureEach {
doLast {
println "action 3!"
}
}
Executing gradle help does not print the "task configured!" message, while gradle customAction does.
To make sure I do not accidentally trigger task instantiation, I would like to write tests for our plugins. But I could not find a way to determine if a task is instantiated or not.
I know about Build Scans (https://guides.gradle.org/creating-build-scans/), but our corporate guidelines are strict and clearance is pending, so I can not use it for now. Also, I do not see a way to use it in tests.
Is there a way to
get a list of created/instantiated tasks from the Gradle project?
or is there any property on Task or TaskProvider showing whether the task has been created/instantiated?
or can buildscans be used offline somehow?
It would be cool, if the solution could be used in the plugin's test code, but manual evaluation would also be valuable.
configureEach called only when task is created. you can write something like this to get list of all configured tasks
def tasks = []
project.allprojects { Project sp ->
sp.tasks.configureEach { Task t ->
tasks,add(t.path)
}
}
project.gradle.buildFinished {
println tasks
}
This is not exactly what OP asked for, but this will give you some stats on how many of each type of task were configured:
gradlew :help -Dorg.gradle.internal.tasks.stats

Gradle custom task action order

I'm looking at a simple example of a custom task in a Gradle build file from Mastering Gradle by Mainak Mitra (page 70). The build script is:
println "Working on custom task in build script"
class SampleTask extends DefaultTask {
String systemName = "DefaultMachineName"
String systemGroup = "DefaultSystemGroup"
#TaskAction
def action1() {
println "System Name is "+systemName+" and group is "+systemGroup
}
#TaskAction
def action2() {
println 'Adding multiple actions for refactoring'
}
}
task hello(type: SampleTask)
hello {
systemName='MyDevelopmentMachine'
systemGroup='Development'
}
hello.doFirst {println "Executing first statement "}
hello.doLast {println "Executing last statement "}
If I run the build script with gradle -q :hello, the output is:
Executing first statement
System Name is MyDevelopmentMachine and group is Development
Adding multiple actions for refactoring
Executing last statement
As expected with the doFirst method excecuting first, the two custom actions executing in the order in which they were defined, then the doLast action executing. If I comment out the lines adding the doFirst and doLast actions, the output is:
Adding multiple actions for refactoring
System Name is MyDevelopmentMachine and group is Development
The custom actions are now executing in the reverse order in which they are defined. I'm not sure why.
I think it's simply a case that the ordering is not deterministic, and you get different results depending on how you further configure the task.
Why do you want 2 separate #TaskAction methods as opposed to a single one that calls methods in a deterministic sequence, though ? I can't think of a particular advantage of doing it that way (I realize it's from a sample given in a book).
Most other samples I find only have a single method
#TaskAction
void execute() {...}
which I think makes more sense and be more predictable.
Patrice M. is correct, the way those methods will be executed is undeterministic.
In detail
#TaskAction annotated methods are being processed by AnnotationProcessingTaskFactory.
But first Task action methods are fetched with DefaultTaskClassInfoStore and results stored in TaskClassInfo.
You can see that Class.getDeclaredMethods() is being used to fetch all methods to check if they contain #TaskAction annotation
And here the definition of public Method[] getDeclaredMethods() throws SecurityException
Description contains following note:
The elements in the returned array are not sorted and are not in any
particular order.
Link to Gradle discussion forum with the topic about #TaskAction
It doesn't guarantee the order.
For your information, just added one more link which was an issue I raised few years ago, it should be giving warnings or replaced with some other better solutions by Gradle.
https://github.com/gradle/gradle/issues/8118

Referencing the outputs of a task in another project in Gradle

Consider the following setup
rootProject
|--projectA
|--projectB
There is a task taskB in projectB and I would like the reference the outputs of that task in a copy task taskA in projectA. For example, taskA might look something like:
task taskA(type: Copy) {
dependsOn ':projectB:taskB'
from ':projectB:taskB.outputs'
into 'someFolder'
}
Of course the above example doesn't actually work. While it's okay to reference the task as :projectB:taskB as a dependency, :projectB:taskB.outputs doesn't seem to mean anything to Gradle. I've tried reading through the Gradle docs but didn't find anything that referenced what I'm trying to do.
The accepted answer has been to only and recommended way to solve this problem. However building up this kind of project dependencies and reaching from one project into another is discouraged by the Gradle team now. Instead of this, projects should only interact with each others using publication variants. So the idiomatic (but sadly at the moment more verbose) way would be:
On the producing side (projectB) define a configuration that is not resolvable but consumable by other projects and creates a new variant (called taskB-variant)
configurations.create("taskElements") {
isCanBeResolved = false
isCanBeConsumed = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class, "taskB-variant"))
}
outgoing.artifact(taskB.outputs)
}
On the consuming side (projectA) define a configuration that is resolvable but not consumable for the same variant and define a dependency to projectB
val taskOutputs by configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
attributes {
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class, "taskB-variant"))
}
}
dependencies {
taskOutputs(project(":projectB"))
}
tasks.register<Copy>("taskA") {
from taskOutputs
into 'someFolder'
}
This way you decouple how the outputs are produced and the publication variant (called "taskB-variant") becomes the interface between projectA and projectB. So whenever you change the way the output is created you only need to refactor projectB but not projectA as long as you make sure the outputs end up in the taskElements configuration.
At the moment this is still pretty verbose but hopefully Gradle will get more powerful APIs to describe this kind of project relationships in the future.
projectA build.gradle should be:
evaluationDependsOn(':projectB')
task taskA(type:Copy, dependsOn:':projectB:taskB'){
from tasks.getByPath(':projectB:taskB').outputs
into 'someFolder'
}

Resources