Gradle Model Rules: How do I create/dependOn tasks in model scope

I apologize for cross posting from StackOverflow, but I didn’t seem to be getting any bites. I will update any answer I get there as well.

I am attempting to use the concepts described in the “Rule based model configuration” docs, and I am having trouble finding a path to do what I need.

I am using the ruleSourcePluginEach sample code to start with, but I need to be able to create a named task and dependOn it somewhere, for each FileItem and/or DirectoryItem.

Basically I need to do something like this:

@Managed interface Item extends Named {}
@Managed interface FileItem extends Item {}
@Managed interface DirectoryItem extends Item {
    ModelMap<Item> getChildren()
}

class PluginRules extends RuleSource {
    @Rules void ApplyRulesForDirectory(DirectoryRules rules, @Each DirectoryItem directory)  {}
}
apply plugin: PluginRules

abstract class DirectoryRules extends RuleSource {
    @Mutate
    void CreateTaskForFiles(ModelMap<Task> tasks, ModelMap<FileItem> children) {
        children.each {
            tasks.create(it.name, Task) {
                // ...
            }
        }
    }
}

model {
    root(DirectoryItem) {
        children {
            dir(DirectoryItem) {
                children {
                    file1(FileItem)
                    file2(FileItem)
                }
            }
            file3(FileItem)
        }
    }
}

However, running this in gradle 3.3 fails with:

The following model rules could not be applied due to unbound inputs and/or subjects:

DirectoryRules#CreateTaskForFiles(ModelMap, ModelMap)
subject:
- ModelMap (parameter 1) []
scope: root
inputs:
- ModelMap (parameter 2) [
]

[*] - indicates that a model item could not be found for the path or type.

Any ideas how to achieve this?

Cheers and thanks for your time!

Here’s my answer copied from StackOverflow:

As far as my limited knowledge of Gradle’s rule-based model can tell, there are 2 problems with your configuration:

  1. Since the DirectoryRules rules are applied with scope (i.e. not applied to the root of the model as a plugin), you need to give it all model elements it needs access to. You haven’t given it the ModelMap<Task> tasks, so that’s the first problem. A potential solution would be:

    class PluginRules extends RuleSource {
        @Rules
        void ApplyRulesForDirectory(DirectoryRules rules,
                ModelMap<Task> tasks, @Each DirectoryItem directory)  {}
    }
    

    However, this reveals the second problem…

  2. It looks like Gradle’s @Each annotation can only be used on the target of a rule (i.e. the thing being modified), whereas you’re trying to use it on one of the rule’s inputs (the target is the ModelMap<Task> tasks). I don’t know why this restriction is applied to @Each; presumably it’s to do with when/how matching model objects are detected.

Without knowing exactly what you’re trying to achieve, it’s hard to suggest an ideal or idiomatic solution. However, naively, this seems to work:

@Managed interface Item extends Named {}
@Managed interface FileItem extends Item {}
@Managed interface DirectoryItem extends Item {
    ModelMap<Item> getChildren()
}

class PluginRules extends RuleSource {
    @Mutate
    void CreateTasksForFiles(ModelMap<Task> tasks, @Path("root") DirectoryItem directory) {
        directory.children.withType(FileItem).each {
            tasks.create(it.name, Task) {
                // ...
            }
        }
        directory.children.withType(DirectoryItem).each {
            ApplyRulesForDirectory(tasks, it)
        }
    }
}
apply plugin: PluginRules

model {
    root(DirectoryItem) {
        children {
            dir(DirectoryItem) {
                children {
                    file1(FileItem)
                    file2(FileItem)
                }
            }
            file3(FileItem)
        }
    }
}

I don’t know if there’s a more idiomatic way of doing this. Perhaps one of the core devs can help?

Good work around. I would also like to know if there is a “proper” solution however. Maybe a feature request for later version?

There hasn’t been much movement on this area of Gradle lately. I’ve asked about it here, but I haven’t had a reply yet.