Gradle Plugin: Task C should run if Task A or Task B: - gradle

I am creating Custom Plugin tasks and got into a situation where I need some help.
I create 3 tasks. Task A, B, C in gradle.
Task C should get executed only if A or B got success.
Note that A and B are 2 separate tasks and are not related.
class A extends DefaultTask { }
class B extends DefaultTask { }
class C extends DefaultTask { }
If I try C.dependsOn(A); C.dependsOn(B);, then I think C is dependent on both A and B (not A or B). Is there any way to specify A or B condition here.

Gradle offers four methods (and related containers) for task dependencies and ordering:
Style: t1.<method>(t2)
dependsOn - Ensures, that t2 is executed if and before t1 is executed.
finalizedBy - Ensures, that t2 is executed if and after t2 is executed.
mustRunAfter - Ensures, that if both t2 and t1 are executed (caused by other triggers), t1 is executed after t2.
shouldRunAfter - Basically the same as mustRunAfter, but may be ignored for special cases (check the docs).
Your requirement is special and won't be solved by a simple method like above. If I understand your question right, you want to ensure that task C gets executed after tasks A and B, but only if it will be executed anyhow (and not trigger it automatically). You can use mustRunAfter for this first part of the requirement. However, you also want to ensure that either task A or task B was executed before. I suggest to use the onlyIf method to skip task execution for task C, if neither A or B were executed before. Example:
task A { }
task B { }
task C {
mustRunAfter A, B
onlyIf { A.state.executed || B.state.executed }
}

You can add multiple dependencies.
task B << {
println 'Hello from B'
}
task C << {
println 'Hello from C'
}
task D(dependsOn: ['B', 'C'] << {
println 'Hello from D'
}
The output is:
> gradle -q D
Hello from B
Hello from C
Hello from D

I think the answer to the PM question in gradle 7.x+ would be something like:
A.finalizedBy('C')
B.finalizedBy('C')
tasks.register('C'){
onlyIf{
(gradle.taskGraph.hasTask('A') &&
tasks.A.getState().getExecuted() &&
tasks.A.getState().getFailure() == null)
||
(gradle.taskGraph.hasTask('B') &&
tasks.B.getState().getExecuted() &&
tasks.B.getState().getFailure() == null)
}
}
With this condition you can't execute C by itself as in 'gradle C'.
Another option could be a Task listener:
A.finalizedBy('C')
B.finalizedBy('C')
gradle.taskGraph.afterTask { Task task, TaskState taskState ->
if (['A','B'].contains(task.getName())){
if(taskState.getFailure() == null){
project.tasks.getByName('C').setEnabled(true)
}else{
project.tasks.getByName('C').setEnabled(false)
}
}
}
With this option it is possible to just execute C with 'gradle C'.
Yes another option could be to declare inputs to C and then in a task listener change C's input as necessary to influence its upToDate check.
Then A and B are executed directly or by some other trigger. if both A and B are in the tasks graph then C will probably be executed twice. if A and B are executed in parallel the result IMO is unpredictable.

Related

Gradle configure task based on subproject property

I'm trying to configure a Zip task based on one of the property inside sub-projects, but the property is not yet accessible at the time of configuring the task. For instance, I want to exclude all my projects that has toexclude = true from my zip file. So, the build.gradle of the sub-projects that I want to exclude starts with this:
ext.toexclude = true;
...
And my main build.gradle has this task:
task zipContent (type: Zip){
def excludedProjects = allprojects.findAll{Project p -> p.toexclude == true}.collect{it.name}
println excludedProjects
destinationDir = "/some/path"
baseName = "myFile.zip"
exclude excludedProjects
from "/some/other/path"
}
The problem is that excludedProjects is always empty. Indeed, when I am executing the task, I can see []. I believe this is due to the fact that the property that I set in the subproject's build.gradle is not available at the moment the task is configured. As a proof, if I replace the first line of the task by this:
def excludedProjects = allprojects.collect{it.name}
The task prints out all of my project's name, and the zip contains nothing (which means the problem is in the p.toexclude == true).
Also, if I try this:
task zipContent (type: Zip){
def excludedProjects = []
doFirst{
excludedProjects = allprojects.findAll{Project p -> p.toexclude == true}.collect{it.name}
println "IN DOFIRST"
println excludedProjects
}
println "IN TASK CONFIG"
println excludedProjects
destinationDir = "/some/path"
baseName = "myFile.zip"
exclude excludedProjects
from "/some/other/path"
}
The task prints out IN TASK CONFIG followed by an empty array, then IN DOFIRST with the array containing only the subprojects that I set ext.toexclude == true.
So, is there a way to get the properties of the sub-projects at configuration time?
Well, the crucial question is: At which point of the build is all necessary information available?
Since we want to know each project in the build, where the extra property toexclude is set to true and it is possible (and by design) that the property is set via the build script, we need each build script to be evaluated.
Now, we have two options:
By default, subprojects are evaluated after the parent (root) project. To ensure the evaluation of each project, we need to wait for the point of the build, where all projects are evaluated. Gradle provides a listener for that point:
gradle.addListener(new BuildAdapter() {
#Override
void projectsEvaluated(Gradle gradle) {
tasks.getByPath('zipContent').with {
exclude allprojects.findAll { it.toexclude }.collect{ it.name }
}
}
})
Gradle provides the method evaluationDependsOnChildren(), to turn the evaluation order around. It may be possible to use your original approach by calling this method before querying the excluded projects. Since this method only applies on child projects, you may try to call evaluationDependsOn(String) for each project in the build to also apply for 'sibling' projects. Since this solution breaks Gradle default behavior, it may have undesired side effects.
Just define excludedProjects outside the task
def excludedProjects = allprojects.findAll{Project p -> p.toexclude == true}.collect{it.name}
task zipContent (type: Zip){
destinationDir = file("/some/path")
baseName = "myFile.zip"
exclude excludedProjects
from "/some/other/path"
}
You can call evaluationDependsOnChildren() in the root project so that child projects are evaluated before the root
Eg
evaluationDependsOnChildren()
task zipContent (type: Zip) { ... }
Another option is to use an afterEvaluate { ... } closure to delay evaluation
Eg:
afterEvaluate {
task zipContent (type: Zip) { ... }
}

What is the meaning of << in gradle task definition

What is the difference between these two tasks. Only task with << in its definition is shown in the output of ./gradlew tasks.
task greet(type: GreetingToFileTask) {
destination = { project.greetingFile }
}
task sayGreeting(dependsOn: greet) << {
println file(greetingFile).text
}
The lines above are from gradle documentation Here
The << is a shortcut to the toLast configuration item of a task definition. I.e. the following two declarations are equivalent:
task hello << {
println 'Hello world!'
}
and:
task hello {
doLast {
println 'Hello world!'
}
}
(example taken from Gradle documentation here).
Now, in the first code snippet you just define a task and configuring its destination property. However, the task will only be executed if needed.
In the second code snippet, however, you are actually defining an action that will always be executed during the configuration phase, regardless of the tasks targeted for execution (cite from here):
A task has both configuration and actions. When using the <<, you are
simply using a shortcut to define an action. Code defined in the
configuration section of your task will get executed during the
configuration phase of the build regardless of what task was targeted.

Code block in gradle.processResources conditional on whether another task was requested

We have an optional gradle task docker that depends on task war, which if executed, needs a war file generated with an extra file in it. This extra file can be added to the resources within the processResources task (or potentially directly in the war task). However, the corresponding code block must not run if task docker has not been requested and will not be run.
We need a correct condition in the following block checking if task docker is in the pipeline:
processResources {
if (/* CONDITION HERE: task docker is requested */) {
from ("${projectDir}/docker") {
include "app.properties"
}
}
}
task docker(type: Dockerfile) {
dependsOn build
...
Clarification: processResources is a standard dependency of the war task and the latter is a standard dependency of the build task. processResources is always executed on build, with or without the docker task to collect resources for assembling the war and may not be fully disabled in this case. One could move the code in question to a separate task dependent on docker and working on the output directory of processResources, yet before war is run, however, such a construct will result in much less clarity for such a simple thing.
You can simply add additional dependency to your docker task, to make it relying not only on build task, but also on processResources. In that case, your processResources task will be called only if docker should be executed.
Another solution is to use TaskExecutionGraph. This let you initialize some variable, which could tell you, whether or not some task will be executed. But you have to understand, that graph is prepared only after all the configuration is done and you can rely on it only during the execution phase. Here is a short example, how it could be used:
//some flag, whether or not some task will be executed
def variable = false
//task with some logic executed during the execution phase
task test1 << {
if (variable) {
println 'task2 will be executed'
} else {
println 'task2 will not be executed'
}
}
//according to whether or not this task will run or not,
//will differs test1 task behavior
task test2(dependsOn: test1) {
}
//add some logic according to task execution graph
gradle.taskGraph.whenReady {
taskGraph ->
//check the flag and execute only if it's true
if (taskGraph.hasTask(test2)) {
variable = true
println 'task test2 will be executed'
}
}
Furthermore, you can try to configure your custom task to make it disabled by setting is enabled property to false, if docker task is not in the execution graph. In that case you don't have to provide some flags and logic in execution phase. Like:
task test1 {
//some logic for execution
doLast {
println "execute some logic"
}
}
task test2(dependsOn: test1) {
}
gradle.taskGraph.whenReady {
taskGraph ->
if (!taskGraph.hasTask(test2)) {
//Disable test1 task if test2 will not run
test1.enabled = false
}
}
But it'll be impossible to run this custom task separately without some additional configuration.

How can you set a Gradle property to different values in the configuration step?

This Android example build file contains the snippet
buildTypes {
release {
minifyEnabled true
}
But since Gradle always executes all configure statements in the build script, won't this always set minifyEnabled to true even for a debug build?
I wish Gradle would let me set a variable to true in one task and false in another and configure things differently, but the only way I've been able to do it is by waiting for taskGraph.whenReady. This is how it normally works:
def myBool = false
task runs {
myBool = true
}
task doesNotRun {
myBool = false
}
task whoWins(dependsOn: runs) {
doLast { println "myBool is ${myBool}" }
}
gradle whoWins
:runs
:whoWins
myBool is false
The configuration step is to help Gradle build the task execution graph (what will actually be run), which is why all the configuration code is executed. What you are describing sounds like a circular task dependency, where a task depends on a variable being set by a task that in turn depends on the first task.
Task A -> depends on Task B -> depends on variable from Task A
What you are probably looking for is the ability to configure a task based on the execution graph. See the Gradle user guide, section 6.13 Configure by DAG. Using this allows you to break your circular dependency.
Task A -> depends on Task B -> depends on gradle.taskGraph.whenReady
or alternatively you can create a new task, Task C, that handles setting the variable based on the task graph.
def myBool = false
task runs {}
task doesNotRun {}
task whoWins(dependsOn: runs) {
doLast { println "myBool is ${myBool}" }
}
task taskC << {
if (gradle.taskGraph.hasTask(runs)) {
myBool = true
} else if (gradle.taskGraph.hasTask(doesNotRun)) {
myBool = false
}
}
runs.dependsOn(taskC)
doesNotRun.dependsOn(taskC)
Results:
$ gradle whois
:taskC
:runs
:whoWins
myBool is true

how to always run gradle task B after task A

Anyone knows how to get B to always run after A even if I only use 'gradle A' on the command line? My first thought was that I could use mustRunAfter, but that seems to require me to specify B on the command line.
task A << {
println 'A'
}
task B << {
println 'B'
}
The (only) way to do so (not counting hacks) is A finalizedBy B. Note that this will run B even if A failed.
You need to use dependsOn feature in Gradle task.
For ex: If you run "gradle ohohWorldTask", it'll always calls myHelloTask to echo "hello" first.
// Say hello
task myHelloTask() << {
println "hello"
}
// Say world
//task ohohWorldTask( dependsOn: [ myHelloTask ] ) << {
//or - using the above you can specify multiple tasks comma separated within [ aTask, bTask, cTask ]
task ohohWorldTask( dependsOn: myHelloTask ) << {
println "-- World!"
}

Resources