refer to Gradle task which is defined later in build - gradle

Our build has some generic logic to create tasks from configuration data. There are also several Gradle files on whose execution order I do not want to depend.
Now I need to add a task dependency to a task without knowing if that task will be defined before my part of the build script runs (at configuration time) or after my part.
[Edit: as #canillas writes I can say myTask.dependsOn "otherTask" and "lazily" depend on yet undefined "otherTask", but I need it the other way around.]
Since I can't write tasks["otherTask"].dependsOn myNewTask before "otherTask" is defined, I made the following helper function:
void safelyDoWithTask(String taskName, Closure func) {
def task = tasks.findByName(taskName)
if (task != null) {
func(task)
} else {
project.tasks.whenTaskAdded { task_ ->
if (taskName.equals(task_.getName())) {
func(task_)
}
}
}
}
safelyDoWithTask('otherTask', { it.dependsOn myNewTask })
Now I wonder if there is more canonical way to achieve this?

Consider the following:
// section 1
def dynamicDependencies = [:]
dynamicDependencies['otherTask'] = 'myNewTask'
// section 2
afterEvaluate { project ->
// taskA dependsOn taskB
dynamicDependencies.each { taskA, taskB ->
def task = project.tasks.findByName(taskA)
if (task) { task.dependsOn "${taskB}" }
}
}
// section 3
task myNewTask() {
doLast {
println 'myNewTask !'
}
}
task otherTask() {
doLast {
println 'otherTask !'
}
}
The gist is:
section 1 defines our dependency info in a custom map
section 2 uses the afterEvaluate hook to process the map
because the above is decoupled from the task definitions, we can simply put them in section 3 (or wherever)

Related

Executing task on its own is working but not when executed from another one

I have three Gradle tasks if I execute them one by one on its own then its working. But when I execute them from another task then its not working. Here is how my task looks like
import com.github.gradle.node.npm.task.NpmTask
plugins {
id("com.github.node-gradle.node") version "3.4.0"
}
// Executing this task on its own is working
tasks.register<NpmTask>("buildFrontEnd") {
workingDir.set(file("${projectDir}/frontend"))
args.set(listOf("run", "build"))
}
// Executing this task on its own is working
tasks.register<Delete>("cleanFrontEnd") {
delete(
fileTree("${projectDir}/backend/main/resources/static/js"),
)
}
// Executing this task on its own is working
tasks.register<Copy>("copyFrontEnd") {
into("$projectDir")
copy {
from("${projectDir}/frontend/dist/css")
into("${projectDir}/backend/main/resources/static/css")
}
copy {
from("${projectDir}/frontend/dist/js")
into("${projectDir}/backend/main/resources/static/js")
}
}
// This tasks is not executing "copyFrontEnd"
tasks.register("frontEndBuild") {
dependsOn("buildFrontEnd", "cleanFrontEnd", "copyFrontEnd")
tasks.findByName("copyFrontEnd")?.mustRunAfter("buildFrontEnd", "cleanFrontEnd")
// Tried this too but it is not working
// tasks.findByName("cleanFrontEnd")?.mustRunAfter("buildFrontEnd")
// tasks.findByName("copyFrontEnd")?.mustRunAfter("cleanFrontEnd")
}
This is the output
> Task :cleanFrontEnd
> Task :copyFrontEnd NO-SOURCE
> Task :frontEndBuild
BUILD SUCCESSFUL in 20s
2 actionable tasks: 2 executed
For :copyFrontEnd it is saying NO-SOURCE but if that is the case then why its working when executing on its own? Is there anyway to fix this.
When you register a task, the bit inside the { } block configures the task. This block will always run whenever the task is required.
tasks.register<Copy>("copyFrontEnd") {
into("$projectDir")
// these copy actions will ALWAYS run whenever Gradle configures this task
copy {
from("${projectDir}/frontend/dist/css")
into("${projectDir}/backend/main/resources/static/css")
}
copy {
from("${projectDir}/frontend/dist/js")
into("${projectDir}/backend/main/resources/static/js")
}
}
Because you've used register when creating the task, Gradle won't execute the task configuration block won't unless the task is required. But when it is, those copy actions will run immediately.
To add task actions, which will actually do the work of the task, add them using doLast { } or doFirst { }.
tasks.register<Copy>("copyFrontEnd") {
into("$projectDir")
doLast {
copy {
from("${projectDir}/frontend/dist/css")
into("${projectDir}/backend/main/resources/static/css")
}
copy {
from("${projectDir}/frontend/dist/js")
into("${projectDir}/backend/main/resources/static/js")
}
}
}
Now those copy actions will only run when the task runs.
This still won't work however...
NO-SOURCE
Task did not need to execute its actions.
Task has inputs and outputs, but no sources
https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:task_outcomes
When you register a Copy task, you need to specify what the source is.
tasks.register<Copy>("copyFrontEnd") {
into("$projectDir")
// from(...) // no from, no source - Gradle thinks "this task doesn't need to run"
}
In your case, you can specify the source as "${projectDir}/frontend/dist/css" and get rid of the two separate copy actions.
tasks.register<Copy>("copyFrontEnd") {
from("${projectDir}/frontend/dist/")
into("${projectDir}/backend/main/resources/static/css")
}
You might want to fiddle around with includes to only copy the css/ and js/ directories - https://docs.gradle.org/current/userguide/working_with_files.html#sec:copying_directories_example
Typesafe references
This last bit is unrelated, but I thought I'd include it as it can help make your buildscript clearer.
While you can reference task by their names, it's nicer to reference them by a variable. When you register a task, it returns a handle to that task.
You can use that handle to specify task dependencies.
val buildFrontEndTask: TaskProvider<NpmTask> = tasks.register<NpmTask>("buildFrontEnd") {
// ...
}
val cleanFrontEndTask: TaskProvider<Delete> = tasks.register<Delete>("cleanFrontEnd") {
// ...
}
val copyFrontEndTask: TaskProvider<Copy> = tasks.register<Copy>("copyFrontEnd") {
// ...
}
tasks.register("frontEndBuild") {
// here you can set the task dependencies based on the task providers
dependsOn(buildFrontEndTask, cleanFrontEndTask, copyFrontEndTask)
}

Gradle : use a task to disable another task

I have three gradle tasks (this is kotlin code inside a plugin):
project.task<Task>("checkNeeded") {
doLast {
if (someTest()) {
listOf("SomeCopy", "SomeAction")
.map { project.tasks[it] as AbstractTask }
.forEach { it.isEnabled = false }
}
}
}
project.task<Copy>("SomeCopy") {
dependsOn("checkNeeded")
from(wherever)
into(whatever)
}
project.task<Task>("SomeAction") {
dependsOn("checkNeeded")
doLast {
/* something magical */
}
}
So, both tasks SomeCopy and someAction depend on checkNeeded. The role of checkNeeded is to disable these two task if it finds that they are not needed.
However, this crashes with the following exception: Cannot call Task.setEnabled(boolean) on task ':SomeCopy' after task has started execution.
So, how can I have a task guaranteed to run before other tasks that can disable other tasks if they are not needed?
I ended up fixing this issue with onlyIf:
var needed by Delegates.notNull<Boolean>()
project.task<Task>("checkNeeded") {
doLast {
needed = !someTest()
}
}
project.task<Copy>("SomeCopy") {
dependsOn("checkNeeded")
onlyIf { needed }
from(wherever)
into(whatever)
}
project.task<Task>("SomeAction") {
dependsOn("checkNeeded")
onlyIf { needed }
doLast {
/* something magical */
}
}
Note that the checkNeeded task is not really necessary, but I like to have it to know, in the process, when the check is made. I could have done:
val needed by lazy { !someTest() }
project.task<Copy>("SomeCopy") {
onlyIf { needed }
from(wherever)
into(whatever)
}
project.task<Task>("SomeAction") {
onlyIf { needed }
doLast {
/* something magical */
}
}
The dependsOn does set the order. It may make sense to move the if outside the checkNeeded task and place it either in the build script (it will be executed in the configuration phase) or in the afterEvaluate {...} block (it will still be executed in the configuration phase, but later)
Another option is to execute checkNeeded task explicitly before running one of the SomeCopy or SomeAction

Execute gradle task after test

After executing test task in Gradle build I want to always execute additional task which should have access to test result. Something like:
task('afterTest') {
doLast {
if (testSuccessful) {
//
} else {
//
}
}
}
There are two parts in your question: 1) make a custom task executed always after test task is executed, 2) make the test "result" available in this custom task.
1) First part is very simple, you just have to use the dedicated Task.finalizedBy method to create a "finalized by" dependency between test task and your custom task. (see Finalizer tasks)
2) Second part is a bit tricky, as there is no simple way provided by Gradle, as far as I know, to get the "result" (SUCCESS or FAILURE) of test task. But you could use API exposed by the Test Task to store the number of test in error into a variable and test this counter in your custom task : here is a working example:
ext{
// counter of test cases in error
nbTestOnError = 0
}
test {
// use "afterTest" hook to increment nbTestOnError counter
afterTest { desc , result ->
if (result.getResultType().equals(TestResult.ResultType.FAILURE)){
nbTestOnError++
}
}
}
task('afterTest') {
doLast {
// implement your logic depending on counter value
if (nbTestOnError > 0) {
// do something if test failed
} else{
// do something when all tests passed
}
}
}
// create "finalized by" dependency
test.finalizedBy afterTest
EDIT : based on important remark in comment from #lance-java : in order to support up-to-date check , you could configure your custom task to be "skipped" if test task is not executed. A simple way would be to use Task upToDateWhen feature (see here) :
task('afterTest') {
// consider this task "UP TO DATE" if `test` task did not execute
outputs.upToDateWhen {
!test.didWork
}
doLast {
//...
}
}
As I said in the other thread, it's best to use the file system to pass values from one task to another. This way the value will be available even if the test task was up to date and skipped. Eg:
test {
outputs.file "$buildDir/test.properties"
ext.errorCount = 0
afterTest { desc , result ->
if (result.resultType.name() == "FAILURE") {
errorCount++
}
}
doLast {
file("$buildDir/test.properties").text = "errorCount=$errorCount"
}
finalizedBy 'afterTest'
ignoreFailures = true
}
task afterTest {
dependsOn 'test'
inputs.file "$buildDir/test.properties"
doLast {
def props = new Properties()
props.load(file("$buildDir/test.properties"))
def errorCount = Integer.parseInt(props['errorCount'])
if (errorCount) {
// doStuff
throw new TaskExecutionException("$errorCount tests failed")
}
}
}
Building on the existing answers, use org.gradle.api.tasks.testing.Test.afterSuite to save the test result to a file:
test {
afterSuite { descriptor, result ->
if (descriptor.parent == null) {
file("$buildDir/test.result").text = result.resultType
}
}
}
afterTest.onlyIf {
file("$buildDir/test.result").text == 'SUCCESS'
}
where the afterTest task is executed only when the test suite succeeds.

Creating a task that runs before all other tasks in gradle

I need to create an initialize task that will run before all other task when I execute it.
task A {
println "Task A"
}
task initializer {
println "initialized"
}
If I execute gradle -q A, the output will be:
>initialized
>Task A
Now if i'll add:
task B {
println "Task B"
}
Execute gradle -q B, and I get:
>initialized
>Task B
So it doesn't matter which task I execute, it always get "initialized" first.
You can make every Task who's name is NOT 'initializer' depend on the 'initializer' task. Eg:
task initializer {
doLast { println "initializer" }
}
task task1() {
doLast { println "task1" }
}
// make every other task depend on 'initializer'
// matching() and all() are "live" so any tasks declared after this line will also depend on 'initializer'
tasks.matching { it.name != 'initializer' }.all { Task task ->
task.dependsOn initializer
}
task task2() {
doLast { println "task2" }
}
Or you could add a BuildListener (or use one of the convenience methods eg: Gradle.buildStarted(...))
Seems like you aim execution phase, and you want a task precursing each task or just run as a first task in the execution phase?
If you want a task to always execute in every project before each other task after its being evaluated you can add a closure to he main build.gradle:
allprojects {
afterEvaluate {
for(def task in it.tasks)
if(task != rootProject.tasks.YourTask)
task.dependsOn rootProject.tasks.YourTask
}
}
or
tasks.matching {it != YourTask}.all {it.dependsOn YourTask}
You can also use the Task Execution Graph to define the lifecycle. There are few ways of achieving your goal, depending on your needs and a project structure.
The previously suggested solution with dependsOn works fine, but I don't like about it that it changes and clutters the task dependencies. The first solution coming to my mind is using Gradle Initialization Scripts. They are really cool. But, the usage is a bit tedious: currently there is no way to have a default project-local Gradle init script. You have to either explicitly specify the script(s) on command line, or place them in USER_HOME/GRADLE_HOME.
So another solution (already briefliy mentioned by #lance-java) which can be used to run some initialization, like a init task/script, is "build listeners". Depending on how early/late the initialization code should run, I use one of these two:
gradle.afterProject {
println '=== initialized in afterProject'
}
or
gradle.taskGraph.whenReady {
println '=== initialized in taskGraph.whenReady'
}
Here the docs of the Gradle interface and of BuildListener.
Note that some of the events occur very early, and you probably can't use them because of that, like e.g. beforeProject and buildStarted (explanations here and there).

Gradle - publish artifacts

I want to publish artifacts to ivy repository but it doesn't work. I read this article and after read created this sample build:
task ivyPublishTest << {
def buildDir = new File("build")
buildDir.mkdirs()
def fileToPublish = new File("build/file.abc")
fileToPublish.write("asdasdasd")
}
artifacts {
archives(ivyPublishTest.fileToPublish) {
name 'gradle-test-artifact'
builtBy ivyPublishTest
}
}
uploadArchives {
repositories {
ivy {
url "http://my.ivy.repo/ivyrep/shared"
}
}
}
Of course the problem is that it doesn't work. I get this error Could not find property 'fileToPublish' on task ':ivyPublishTest'
In Groovy, def creates a local variable, which is lexically scoped. Therefore, fileToPublish is not visible outside the task action. Additionally, configuration has to be done in the configuration phase (i.e. the declaration and assignment of fileToPublish in your task action would come too late). Here is a correct solution:
task ivyPublishTest {
// configuration (always evaluated)
def buildDir = new File("build")
ext.fileToPublish = new File("build/file.abc")
doLast {
// execution (only evaluated if and when the task executes)
buildDir.mkdirs()
fileToPublish.write("asdasdasd")
}
}
artifacts {
// configuration (always evaluated)
archives(ivyPublishTest.fileToPublish) {
name 'gradle-test-artifact'
builtBy ivyPublishTest
}
}
ext.fileToPublish = ... declares an extra property, a new property attached to an existing object that's visible everywhere the object (task in this case) is visible. You can read more about extra properties here in the Gradle User Guide.

Resources