How to create a FileCollection instance in a @Mutate rule method

I’m creating a JavaExec task inside of a @Mutate rule method, and then trying to set the classpath property on it, which requires a FileCollection instance. How do I create a FileCollection instance to give it?

You can get to the Project from the Task and transitively the files() method (i.e. Task.getProject().files()).

getProject() is a static method on the Task class?

The rule method is like so:

@Mutate createTasks(ModelMap<Task> tasks, ModelMap<Item> items) {
   Task.getProject().files("path/to/file.jar")
}

The code above gets me this exception:

> Exception thrown while executing model rule: ItemRules#createTasks
   > No signature of method: static org.gradle.api.Task.getProject() is applicable for argument types: () values: []
Possible solutions: getProperties()

Ah - this works:

@Mutate createTasks(ModelMap<Task> tasks, ModelMap<Item> items) {
        tasks.create("testTask", JavaExec)
        tasks.named("testTask",  { task ->
          task.classpath = task.getProject().files("path/to/file.jar")
        }

@mark_vieira: thanks!

The snippet above can be simplified by the way.

tasks.create("testTask", JavaExec) {
  classpath = project.files("path/to/file.jar")
}

Yes - I’ve been troubleshooting a problem and trying to create these tasks differently. I can’t tell what’s causing my problem.

Inside my rule definition, I iterate over elements of my model and make repeated calls to tasks.create(taskName, JavaExec), and give different values to the args parameter based on the model elements that have been configured.

But, it seems that only the last call to tasks.create() seems to have any effect. After completing that iteration over configured items, there is only one task returned when I attempt to iterate over tasks.withType(JavaExec)

How are you attempting to do this? It’s important to know that calling tasks.create() doesn’t actually create the task. The task isn’t created until it’s required (input to another rule, part of a build invocation, etc). Does the task show up when running gradle tasks?

Yes all tasks show up by name, but no matter which task name I use on the command line, they all use the same data - the last data used in the call to tasks.create()

I’m typing up a snippet so you can see the rule in context

Can you share the code you are using to create/configure these tasks?

Ok - looks something like this:

@Managed
interface Item {
   void setName(String name)
   String getName()
   
   void setOutputDirectory(File directory)
   File getOutputDirectory()
}

class ItemRules extends RuleSource {
   @Model void items(ModelMap<Item> items) {}

   @Mutate void createTasks(ModelMap<Task> tasks, ModelMap<Item> items) {
      for ( Item item: items.values() ) {
        String taskName = 'exec' + item.name
        tasks.create( taskName, JavaExec ) {
            classpath files("path/to/file.jar")
            main "com.myJar.mainClass"
            workingDir "."
            args( 'myCommand', '-outputDir', item.outuptDirectory )
        }
      }
   }
}

apply plugin: ItemRules

model {
  items {
    item1(Item) {
      name = "Item1"
      outputDirectory = file("../item1-output")
    }
    item2(Item) {
      name = "Item2"
      outputDirectory = file("../item2-output")
    }
  }
}

Nothing stands out to me. So you’re saying the tasks have the correct name but the arguments are wrong?

Yes, the task names are all there, but whether I issue execItem1 or execItem2, the JavaExec task runs using the args from whatever the last model item in the list was.

Also, if I add this line to the rule, after the for loop:

    tasks.withType(JavaExec) { task ->
      println "Task [$task.name] has these args:"
      println task.args
    }

…it only lists one task

Have you confirmed the ModelMap<Item> is being configured properly? Does running gradle model show the correct value for ‘outputDirectory’ on each of them?

Yes, the model output is correct - I see the right directory values on the right model elements.

Is this enough information to see if you can reproduce the problem?

Ok, I was able to reproduce this. Seems to have to do with variable scoping when using an enhanced for loop in conjunction with a closure. In essence, item here is just a reference that is being reassigned, which is why the last one wins. Try iterating over the collection another way.

items.values().each { item ->

}

Hot sh*t that was it! Thank you so much @mark_vieira!

I lost most of today trying to figure this out, so glad to get back on track.

Last question - I want to create a single mothership task that, when run, runs all of the tasks associated with each individual item. Is there a straightfoward way to do that?