How to define Gradle tasks - gradle

I tried both versions defining tasks:
Version 1:
tasks {
task helloWorld {
group 'Testing'
description "I don't do very much."
println "I can print in configuration phase, too. I'm a big boy."
doFirst {
println 'Hello'
}
doLast {
println "world"
}
}
}
Version 2:
task helloWorld {
group 'Testing'
description "I don't do very much."
println "I can print in configuration phase, too. I'm a big boy."
doFirst {
println 'Hello'
}
doLast {
println "world"
}
}
Is there any difference? In my opinion version 1 is way cleaner than version 2, especially working with multiple custom tasks and working with common conventions (https://docs.gradle.org/current/samples/sample_convention_plugins.html).
I want to get sure, I don't break anything, because I have never seen version 1 in any source.

Short answer
both versions are quite similar and will behave the same, but they both use the "old" Tasks API, creating tasks eagerly; you should consider switching to the "new" API (since Gradle 4.9) and configure tasks lazily, using TaskContainer.register(...) methods
version 1 might seem cleaner to you but in fact it's way more confusing than version 2, I'll explain why.
Long answer
Old Tasks API vs New Tasks API
In both version you are using the shorthand notation task myTask { /* task configuration closure */ } which is a shortcut for TaskContainer.create() method:
// following is equivalent to:
// project.getTasks().create(...).configure( { /* tasks configuration */} )
tasks.create("myTask ") {
// task configuration closure
}
this notation is specific to Groovy DSL, and will likely be deprecated in future Gradle releases - there will be no replacement for this notation in the new Tasks API (see more information here and here)
you should better use of the Task configuration avoidance feature and declare your custom tasks lazily
Version 1 vs version 2
Version 2 is the way Gradle docs suggested for declaring tasks in older versions, e.g. Helloworld in Gradle 5.6 (in latest versions the documentation references the new API: Helloworld in Gradle 7.5 )
In your version 1: there are two issues
tasks property collision
project.tasks could refer to either Project.getTasks(): TaskContainer property or to the tasks Task, thanks to Dynamic Project properties:
tasks {
// here we don't configure the project.TaskContainer as we could expect, but instead the default task named 'tasks'
// proof:
println " configuring task named: " + it.name
// => configuring task named: tasks
println " type of current object beeing configured: " + it.class.name
// => type of current object beeing configured: org.gradle.api.tasks.diagnostics.TaskReportTask_Decorated
// this will still work, thanks to Groovy closure delegate feature :
// will delegate to parent Project instance => equivalent to project.task("helloWorld3") { /* config */ }
task helloWorld3 {
// tasks configuration
}
}
funny (or not): this would also work, thanks to delegate mechanism:
repositories {
task helloWorldDefinedInRepositories {
// tasks configuration
}
}
dependencies {
task helloWorldDefinedInDependencies {
// tasks configuration
}
}
tasks (TaskContainer) methods
You could group custom tasks declaration under a common "parent" tasks section as follows:
tasks.configure {
// here we configure the Project.taskContainer property.
println " type of current object beeing configured: " + it.class.name
// => org.gradle.api.internal.tasks.DefaultTaskContainer_Decorated
create("eagerTask") {
// config
}
register("lazyTask") {
// config
}
}
This will work, but it's not clear why you think this will help in handling "common convention" , maybe you could give some more details about this expectation.

Related

Error using dependsOn to call task from another gradle file

I am working with a build.gradle file that has multiple ways to specify executions for a task - setup. To call a task from another gradle file - runtests.gradle, I created a task - testTask and added task dependency using dependsOn, but this implementation does not seem to work and giving out an error like :
Could not find property 'testTask' on root project 'GradleFile
My build file looks like this :
build.gradle
task setup(dependsOn: testTask) <<
{
println "In main execution"
}
// new task
task testTask(type: GradleBuild) {
if (getEnvironmentVariable('RUN_TEST').equalsIgnoreCase("true")) {
buildFile = "../Behave/runtests.gradle"
tasks = ['mainTask']
}
else {
println "Exiting runTests Task"
}
}
setup.doFirst {
println "In first execution"
}
setup.doLast {
println "In last execution"
}
D:\>gradle -q GradleFile/build.gradle setup
I am not looking to make much changes to existing tasks, so is there any other workaround I should try?
I have been through many links but could not find anything that suits this scenario. Looking for suggestions please.
Gradle is sensitive to the ordering of tasks in the build script if a task instance is given in the dependsOn. The task setup depends on task (instance) testTask which, at the moment the build script is compiled, doesn't exist yet. The most common options to solve the issue are:
Define task setup below testTask:
task testTask(type: GradleBuild) {
}
task setup(dependsOn: testTask) {
}
Use a relative path to the task, i.e. the task's name, in the dependsOn
task setup(dependsOn: 'testTask') {
}
task testTask(type: GradleBuild) {
}
Please find more details in Javadoc of Task.

How to use a parameter in gradle copy task in the destination folder?

Given the following task in gradle, the dev would start a new module by:
gradle initModule -PmoduleName=derp
task initModule(type: Copy) {
description "Initialize an empty module based on the template. Usage: gradle initModule -P moduleName=derp"
onlyIf { project.hasProperty("moduleName") }
from "$rootDir/modules/template"
into "$rootDir/modules/$moduleName"
}
I am unable to run gradle since moduleName is not defined during evaluation, although I was hoping that "onlyIf" would do so.
To solve this I assign it to a locally defined variable in a guard block:
def modName = ""
if (!project.hasProperty("moduleName")) {
logger.error("Invalid moduleName : It can't be")
} else {
modName = moduleName
}
and finally use the new variable to survive the configuration phase.
Is this the right way to do this? It just doesn't feel right.
Additionally if I was using a rule to make this a tad more elegant:
tasks.addRule("Pattern: initModule_<mod name>") { String taskName ->
if (taskName.startsWith("initModule_")) {
def params = taskName.split('_');
task(taskName) {
doLast {
project.ext.moduleName = params.tail().head()
tasks.initModule.execute()
}
}
}
}
The moduleName is not passed around (even if I change it to finalizedBy).
Any help is appreciated.
As you already figured out the property is not available during the configuration phase.
But can postpone the look-up to the execution phase by using
into "$rootDir/modules/" + project.property("moduleName")
instead of
into "$rootDir/modules/$moduleName"

Grails gradle "a task with that name already exists"

I'm trying to create a test task rule using the example provided in the grails gradle doc but I keep getting "a task with that name already exists" error.
My build script is as follows:
import org.grails.gradle.plugin.tasks.* //Added import here else fails with "Could not find property GrailsTestTask"
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "org.grails:grails-gradle-plugin:2.0.0"
}
}
version "0.1"
group "example"
apply plugin: "grails"
repositories {
grails.central() //creates a maven repo for the Grails Central repository (Core libraries and plugins)
}
grails {
grailsVersion = '2.3.5'
groovyVersion = '2.1.9'
springLoadedVersion '1.1.3'
}
dependencies {
bootstrap "org.grails.plugins:tomcat:7.0.50" // No container is deployed by default, so add this
compile 'org.grails.plugins:resources:1.2' // Just an example of adding a Grails plugin
}
project.tasks.addRule('Pattern: grails-test-app-<phase>') { String taskName ->
println tasks //shows grails-test-app-xxxxx task. Why?
//if (taskName.startsWith('grails-test-app') && taskName != 'grails-test-app') {
// task(taskName, type: GrailsTestTask) {
// String testPhase = (taskName - 'grails-test-app').toLowerCase()
// phases = [testPhase]
// }
//}
}
Running $gradle grails-test-integration
or in fact anything of the form $gradle grails-test-app-xxxxxxxx yields the error "Cannot add task 'gradle grails-test-app-xxxxxxxx as a task with that name already exists".
Can someone please advise how I can resolve this error? Thanks.
If you don't mind overriding the task created by the plugin, you might want to try
task(taskName, type: GrailsTestTask, overwrite: true)
In general, when using task rules that can be called multiple times (for instance if you have multiple tasks depending on a task eventually added by your rules), I use the following test before actually creating the task:
if (tasks.findByPath(taskName) == null) {tasks.create(taskName)}
This will call the task() constructor only if this task name does not exists.

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.

Gradle aggregation task

In my gradle scripts, I've built a task that runs a java process. This process depends on a target property. The task is defined by:
task('bulk', type: JavaExec, dependsOn: 'classes', description : 'Bulk data import on a target (defined by -Ptarget=[event|member|...]]') {
//available imports
ext{
event = relativePath('src/main/scripts/events.csv')
member = relativePath('src/main/scripts/member.csv')
membership = relativePath('src/main/scripts/membership.csv')
}
//check the target is set
doFirst {
if(!project.hasProperty('target')){
println "\nUsage:"
println "\tgradle bulk -Ptarget=[event|member|...]"
println "where target is : "
bulk.ext.each{ println it }
throw new GradleException('Target argument required')
} else {
println "\nBulk import of $target\n"
}
}
main = 'org.yajug.users.bulkimport.BulkImport'
classpath = sourceSets.main.runtimeClasspath
if(project.hasProperty('target')){
bulk{
args target
args bulk.ext[target]
debug false
}
}
}
And to run it:
gradle bulk -Ptarget=event
It's working fine, but know I have to run this process for different targets:
gradle bulk -Ptarget=event
gradle bulk -Ptarget=member
gradle bulk -Ptarget=membership
...
How can I group all these calls into an other single task with the gradle's dependency model ? (I know the list of targets)
SOLUTION
task bulk;
['event','member','membership'].each {target ->
task("bulk${target}", type: JavaExec, dependsOn: 'classes', description : "Bulk data import of ${target}s") {
//available imports
ext{
event = relativePath('src/main/scripts/events.csv')
member = relativePath('src/main/scripts/member.csv')
membership = relativePath('src/main/scripts/membership.csv')
}
//check the target is set
doFirst {
println "\nBulk import of $target\n"
}
main = 'org.yajug.users.bulkimport.BulkImport'
classpath = sourceSets.main.runtimeClasspath
args target
args ext[target]
debug false
}
bulk.dependsOn("bulk${target}")
}
How can I group all these calls into an other single task with the gradle's dependency model ?
You can't, because a task (instance) will be executed at most once per build. Instead, the way to go is to declare multiple task instances. You could do this by putting the task declaration in a loop, putting it in a method and calling it multiple times, or by writing a task class and instantiating it multiple times. Then you'll add one other task that depends on all bulk tasks. Assuming the execution order between bulk tasks is irrelevant, that's it.
By adding a helper method or two, you can create a nice little API around this, to improve readability and allow reuse in other places.
Another way to tackle this is with a task rule. You can read more on task rules in the Gradle User Guide.
What about creating a -Ptarget=all, and deal with that specific case in the build.gradle file. This could be done as:
['event','member','membership'].each { t ->
task("bulk${t}", ...) {
onlyIf project.hasProperty("target") && (project.getProperty("target").equals(t) || project.getProperty("target").equals("all"))
args target
args bulk.ext[target]
debug false
}
}

Resources