Dynamically created task with type Exec doesn't run command line

Hi,

I’m using gradle in my Android project and trying to execute some actions based on the devices currently connected to a machine (running OS X). In order to do that, I’m doing the following:

task pushFixturesToDevice << {
    devices().each {
        task "pushFixturesToDeviceWithId$device.id"(type: Exec) {
            commandLine "adb -s $device.id push fixture.txt sdcard/".split(' ')
        }
        tasks["pushFixturesToDeviceWithId$device.id"].mustRunAfter pushFixturesToDevice
    }
}
  tasks.whenTaskAdded {
    tasks.each { task ->
        if (task.name.contains('Test')) {
            task.dependsOn pushFixturesToDevice
        }
    }
}

Where “device()” returns a list of the currently connected devices, “fixtures” is a file in the machine and “adb -s device-id push local/path remote/path” is a command the copies a file from the local machine to the device with id equals to device-id. The task is run but the adb command isn’t actually run. It works fine though if I create a (non-dynamic) task of the same type.

I’m a bit lost here, could anyone please explain me why this doesn’t work?

Thanks very much!

The problem here is the lifecycle of the build. Tasks can only be created during the configuration phase. The main objective of the configuration phase is to create the task execution graph. Tasks execute during the execution phase, so it’s not possible to create a task in an action of another task. All tasks must be created during the configuration phase. As a first step, you can move the devices.each{} block outside of the task action for pushFixturesToDevice.

Hi Gary,

Thanks very much for your input. I finally managed to achieve what I need doing the following:

def deviceTasks = []
  devices().each { device ->
 task "pushFixturesToDeviceWithId$device.id"(type: Exec) {
     commandLine "adb -s $device.id push fixture.txt sdcard/".split(' ')
  }
    deviceTasks.add tasks["pushFixturesToDeviceWithId$device.id"]
}
  tasks.whenTaskAdded { task ->
  if (task.name.startsWith('Test')) deviceTasks.each { deviceTask -> task.dependsOn deviceTask }
}

Would you do it that way or is there a better way to do it?

One thing that confused me a lot while testing my first solution: even if the tasks I created dynamically weren’t executed, I could see the logging statements I added being printed out. How can it be?

Thanks again for your help.

Do you rely on calling the tasks from the command-line?

Otherwise you might instead get away with calling ‘project.exec’ inside your ‘pushFixturesToDevice’ task.

Yes I am. But how could I call it otherwise?

As an example if you have devices ‘FOO’ and ‘BAR’, do you want to do

./gradlew pushFixturesToDeviceWithIdFOO pushFixturesToDeviceWithIdBAR

or just

./gradlew pushFixturesToDevices

If it is the former then my suggestion is of no use. If it is the second then it might be of use.

The usage of ‘project.exec’ vs ‘Exec’ is in general for dependent on your use case. ‘Exec’ provides more flexibility and is the goto when the purpose of the external process is a task outcome. ‘project.exec’ can be used when the primary outcome is not execution of an external process, just a minor intermediate step.