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!"
}
}