Gradle shouldRunAfter not available for a task - gradle

I created this repo to reproduce exactly what I'm seeing. I have a project that I'm building with Gradle. I would like to configure my Gradle build so that running:
./gradlew build
Has the exact same effect as running:
./gradlew clean build scalafmt shadowJar fizzbuzz
Meaning Gradle calls tasks in the following order:
clean
build (compile & run unit tests)
scalafmt (run a tool that formats my code to a styleguide)
shadowJar (creates a self-contained executable "fat" jar)
fizzbuzz (prints out "Fizzbuzz!" to the console)
According to the Gradle docs on ordering tasks, it seems that I can use shouldRunAfter to specify ordering on all tasks...
If you clone my repo above and then run ./gradlew build you'll get the following output:
./gradlew build
Fizzbuzz!
FAILURE: Build failed with an exception.
* Where:
Build file '/Users/myUser/thelab/idea-scala-hate-each-other/build.gradle' line: 65
* What went wrong:
A problem occurred evaluating root project 'idea-scala-hate-each-other'.
> Could not find method shouldRunAfter() for arguments [task ':build'] on cz.alenkacz.gradle.scalafmt.PluginExtension_Decorated#6b24ddd7.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 10.983 secs
So even though I specify fizzbuzz to run last...its running first! And there are (obviously) errors with the rest of my config. And I'm not sure how to "hook" clean so that even when I run ./gradlew build it first runs clean.
I'd be OK with a solution that requires me to write my own "wrapper task" to achieve the order I want, and then invoke it, say, via ./gradlew buildMyApp, etc. Just not sure how to accomplish what I want.
Update
I made some changes to build.gradle and am now seeing this:
./gradlew fullBuild
:compileJava UP-TO-DATE
:compileScala
:processResources UP-TO-DATE
:classes
:jar
:startScripts
:distTar
:distZip
:assemble
:compileTestJava UP-TO-DATE
:compileTestScala UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build
:clean
:fizzbuzz
Fizzbuzz!
:scalafmt
:shadowJar
:fullBuild
So when I run ./gradlew fullBuild task execution seems to be:
build (which calls compileJava through check)
clean
fizzbuzz
scalafmt
shadowJar
So the ordering is still wrong even given my most recent changes...

As Oliver already stated, you need to put the console output to a doFirst or doLast closure, otherwise it will be executed when the task is defined (during configuration phase).
The exception is caused by the fact that both extension properties and tasks are added to the scope of the Project object, but if both an extension property and a task with the same name (scalafmt in this case) exist, the extension property will be accessed. As the error message tells you, you are trying to access the shouldRunAfter method on an object of the type PluginExtension, where it does not exist. You need to make sure to access the task:
tasks['scalafmt'].shouldRunAfter build
Update
Actually, I thought that you only need the two specific problems to be solved, but already have a solution for your basic Gradle structure.
First of all, both shouldRunAfter and mustRunAfter do not actually cause a task to be executed, they only define the order if both tasks are executed (caused by command line or a task dependency). This is the reason why the tasks clean, scalafmt, shadowJar and even fizzbuzz are not executed if you call gradle build. So, to solve your first problem, you could let the build task, which you call explicitly, depend on them:
build.dependsOn 'clean', 'scalafmt', 'shadowJar', 'fizzbuzz'
But a task dependency will always run before the parent task, so all tasks will be executed before the build task. This should not be a problem, since the build task does nothing more than collecting task dependencies for all required build steps. You would also need to define the order not only between of your task dependencies, e.g. clean and the parent task build, but mainly between the existing task dependencies, e.g. compileJava. Otherwise clean could run after compileJava, which would delete the compiled files.
Another option would be to define a new task, which then depends on all the tasks you want to execute:
task fullBuild {
dependsOn 'clean', 'build', 'scalafmt', 'shadowJar', 'fizzbuzz'
}
This would still require to define the order between your tasks and the existing task dependencies, e.g.
compileJava.mustRunAfter 'clean'
[...]
Please note, that now you would have to call gradle fullBuild from command line. If you really need to only call gradle build via command line and still execute some tasks after the actual build task, you could use a little trick in your settings.gradle file:
startParameter.with {
if (taskNames == ['build']) {
taskNames = ['clean', 'build', 'scalafmt', 'shadowJar', 'fizzbuzz']
}
}
This piece of code checks the task name input you entered via command line and replaces it, if it only contains the build task. This way you would not have to struggle with the task order, since the command line tasks are executed consecutively.
However, this is not a clean solution. A good solution would include the definition of task dependencies for all tasks that really depend on each other and the invocation of multiple tasks via command line (which you want to avoid). Especially the hard connection between the clean and the build task bypasses a lot of useful features from the Gradle platform, e.g. incremental builds.
Regarding your second point in the update, it is wrong that the fizzbuzz task is running first. It is not running at all. The command line output is printed when the task is configured. Please move the println call to a doFirst / doLast closure:
task fizzbuzz {
doFirst {
println "Fizzbuzz!"
}
}

One of the possible causes of that error could be because Gradle couldn't compile the task of the method shouldRunAfter or mustRunAfter.
Also it could happen when there is another entity of other type (not a task) with the same name of the task of the method shouldRunAfter or mustRunAfter. In this case you could use the syntax tasks['task1_name'].shouldRunAfter tasks['task2_name'] or tasks['task1_name'].mustRunAfter tasks['task2_name'] to make sure Gradle is referencing a task type entity.

Related

MyTask getting unwantedly executed when building

I want to print a link to our team's documentation if the developer types:
gradle help
using this task:
task help {
println "Full Documentation"
println "https://confluence.org.com/Help"
}
which it does.
However, I do not want it to execute when I run:
gradle build
Is the help task supposed to run whenever build is run? If not, why does this task get executed? As you can tell, my understanding of gradle is limited.
You are effectively overriding the built-in help task that Gradle provides. As a result, other tasks or plugins in your project may be referencing that task already or other parts in the overall Gradle build rely or use help in some fashion.
To summarize, do not override Gradle built-in tasks otherwise it will lead to unexpected/unintended consequences.
To fix, rename your task to something other than help.

Why is there a difference between ./gradlew clean build and ./gradlew clean :build

I have the following situation:
I have a project with several subprojects. Today I tried to build the projects with gradle via command line.
The build was successful when I executed ./gradlew clean :build, but not with ./gradlew clean build. It causes different errors, depending on which subprojects are activated. Why is that? Shouldn't it be the same?
Both commands are executed directly after each other, without changes in the code, and from the same directory (the base-directory, where settings.gradle is located.
The gradle-refresh of Intellij works, the build is successful (but fails on our build server, if that is relevant).
According to the documentation https://docs.gradle.org/current/userguide/command_line_interface.html#executing_tasks_in_multi_project_builds I assumed that it would do the same, since no subproject is specified, and the build task is executed for all submodules. There is no folder called build in the root project, so this should not cause confusion. Am I interpreting it wrong?
I searched online, however, I could not find the result, since : is is not recognized by most search engines, and colon leads to not relevant results like What is the colon operator in Gradle?.
The gradle version is 4.10.2
If you need more information, please let me know.
There is a difference between ./gradlew clean :build and ./gradlew clean build, that's why you have a different behavior: in the first case you are using qualified task name, in the other case you are using simple task name. These documentations here and here explain these two approaches for executing tasks:
Using simple task name ( ./gradlew test) :
The first approach is similar to the single-project use case, but Gradle works slightly differently in the case of a multi-project build. The command gradle test will execute the test task in any subprojects, relative to the current working directory, that have that task. So if you run the command from the root project directory, you’ll run test in api, shared, services:shared and services:webservice. If you run the command from the services project directory, you’ll only execute the task in services:shared and services:webservice.
=> so executing ./gradlew build in the root project directory will trigger execution of build task for root project and all subprojects
Using qualified task name ( ./gradlew :build)
For more control over what gets executed, use qualified names (the second approach mentioned). These are paths just like directory paths, but use ‘:’ instead of ‘/’ or ‘\’. If the path begins with a ‘:’, then the path is resolved relative to the root project. In other words, the leading ‘:’ represents the root project itself. All other colons are path separators.
=> executing ./gradlew :build , you will execute "only" the build task for rootProject
As I said in comment, you migth have some issues in one or more of your subproject, but you will not see these errors if you execute only Root project build ( ./gradlew :build )

How to have Gradle task depend on another task but not re-execute if the other executes?

I would like to have one Gradle task, taskA, to trigger the execution of another task, taskB, if both aren't up-to-date but the execution of taskB shouldn't trigger an execution of taskA if only taskB isn't up-to-date. How can this be done?
IOW, there are two parts of the task dependency involved here, the task hierarchy and the up-to-date checks. I would like to bed able to set up the task hierarchy but not have it imply the up-to-date check for taskA.
Context: taskA must execute if its inputs change or if taskB executes on a dev machine (ie not CI). taskB must execute if it's not up-to-date. This is because the output of taskB is an executable that's run by taskA. The reason taskA shouldn't run if only the executable is updated is because the updated executable can produce different output than the previous executable. That new output can cause issues with other parts of the build. Since the purpose of the output is to help ensure users are following best practices, the new output of the updated executable can be ignored in CI builds.
More concretely, taskA calls a protolock binary which is output by taskB. taskA outputs a proto.lock file which engineers should commit. Auto-committing that file can lead to surprises for engineers and to subversion of the purpose of these checks.
Use onlyIf:
taskB.onlyIf {
trueIffNotCiOrInputsAreNewerThanOutput()
}

Calling Gradle commands and tasks with parameters from Gradle Task

I have a current setup in my build.gradle that I'm trying to understand. I need a number of tasks to go off in a very specific order and execute all with one task call. The setup I want looks like this:
1.) Run liquibase changeset into predefined database
2.) Run a number of tests against database
3.) Rollback all changes made with the previous changeset
I want the database in a 'clean' state every time I test it. It should have only the changes I expect and nothing else. The liquibase is set up with the Gradle plugin for it and the changeset is applied/updated. However, I don't want to call the command manually. This will be something that needs to run in continuous integration, so I need to script it so I simply have our CI call one task and it then runs each task, in order, until the end. I'm unsure of how to call the Gradle command-line task from inside of itself (ie inside the build.gradle file) and then also pass parameters to it (since I'll need to call some type of rollback command task to get the database to be what it was before calling the update).
Right now, all I'm doing is calling the command line tasks like this:
$ gradle update
$ gradle test
$ gradle rollbackToDate -PliquibaseCommandValue=2016-05-25
Again, I can't call them by the command line alone. I need a custom task inside Gradle so that I could just call something like:
$ gradle runDatabaseTests
...And I would have it do everything I expect.
There is no gradle way to invoke/call a task from another task directly. What you can do instead is to use dependsOn or finalizedBy to setup task dependencies which will force the prereq tasks to run first.
If you declare a task:
task runDatabaseTests(dependsOn: [update, test, rollbackToDate]) << {
println "I depend on update, test and rollbackToDate"
}
when you call
gradle runDatabaseTests -PliquibaseCommandValue=2016-05-25
it will force update, test and rollbackToDate first. You can control the order in which they're run, if you care about that, by using mustRunAfter and/or shouldRunAfter

In gradle, how can I run a task against all projects recursively from command line?

I'm looking for a syntax along the lines of ./gradlew :all:myTask that I can quickly execute from the command line. I must have missed it in the documentation somewhere.
I know I can modify the build to include a new task that runs a task against all sub-projects, however I'd prefer to not mess with modifying the build for simple one-off scenarios.
If I understand your goal correctly, running ./gradlew myTask should do what you want by default
:myTask
:foo:myTask
:foo:bar:myTask
:baz:myTask

Resources