RuleSource: how to use a RuleSource plugin defined in a separate build script

I have a RuleSource defined in a build script that I’d like to re-use in other scripts. It has a RuleSource defined like so:

@Managed
interface Item {
   File getOutputDirectory()
   void setOutputDirectory(File outputDirectory)
}

class ItemRules extends RuleSource {
   @Model void items(ModelMap<Item> theItems) {}
   // ...other methods creating tasks and so on
}

apply plugin: ItemRules

I’m trying to use it from another script like so:

apply from: '../common/gradle/items.gradle'

model {
   items {
      Item1 {
         outputDirectory file("../Item1-Output")
      }
      Item2 {
         outputDirectory file("../Item2-Output")
      }
   }
}

…but doing anything with this other script generates this exception:

> Exception thrown while executing model rule: items { ... } @ build.gradle line 14, column 5
   > Attempt to read a write only view of model of type 'java.lang.Object' given to rule 'items { ... } @ build.gradle line 14, column 5'

The ItemRules plugin works as intended when the definition is contained within the target build script. I only get this exception if I move the plugin definition into a separate build script.

What am I doing wrong? How can I keep the ItemRules definition in this separate script?

@Daniel_L: any suggestions?

I don’t know enough about applying another gradle script with class definition. Obviously, from your example, it seems to be doing more thing in the background then a simple #include. A quick workaround would be the put the ItemRules and Item in groovy class file inside buildSrc.

Maybe you could post the stacktrace (command line flag --stacktrace). It sometimes give more information.

Given the example for the task container (69.5.1 in https://docs.gradle.org/current/userguide/new_model.html), I would try to use:

items {
  create("Item1") {
    outputDirectory = file("../Item1-Output")
  }
}

Note the outputDirectory = file(…) as setter in groovy are transformed into assign operation. With my small test, I have a different error for that case then the one you have, so it may be ok.

I will have a look at it in more detail when I get more time.

Thank you for taking the time during a heavy workload. The create method does get me past the exception I cited above, and so now I have some relative path issues in my plugin implementation that I should be able to work out. I’ll take a look at the --stacktrace output and see if it’s helpful - will post if so (--debug wasn’t very helpful).

It seems that --stacktrace output isn’t too informative (at least to me it isn’t), but just in case, here it is:

org.gradle.api.ProjectConfigurationException: A problem occurred configuring root project 'items-test'.
    at org.gradle.execution.TaskNameResolver.selfClosedTasksNode(TaskNameResolver.java:111)
    at org.gradle.execution.TaskNameResolver.hasTask(TaskNameResolver.java:123)
    at org.gradle.execution.TaskNameResolver.access$100(TaskNameResolver.java:38)
    at org.gradle.execution.TaskNameResolver$MultiProjectTaskSelectionResult.collect(TaskNameResolver.java:190)
    at org.gradle.execution.TaskNameResolver$MultiProjectTaskSelectionResult.collectTasks(TaskNameResolver.java:186)
    at org.gradle.execution.TaskNameResolver.selectWithName(TaskNameResolver.java:61)
    at org.gradle.execution.TaskSelector.getSelection(TaskSelector.java:87)
    at org.gradle.execution.TaskSelector.getSelection(TaskSelector.java:75)
    at org.gradle.execution.commandline.CommandLineTaskParser.parseTasks(CommandLineTaskParser.java:42)
    at org.gradle.execution.TaskNameResolvingBuildConfigurationAction.configure(TaskNameResolvingBuildConfigurationAction.java:44)
    at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:48)
    at org.gradle.execution.DefaultBuildConfigurationActionExecuter.access$000(DefaultBuildConfigurationActionExecuter.java:25)
    at org.gradle.execution.DefaultBuildConfigurationActionExecuter$1.proceed(DefaultBuildConfigurationActionExecuter.java:54)
    at org.gradle.execution.DefaultTasksBuildExecutionAction.configure(DefaultTasksBuildExecutionAction.java:44)
    at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:48)
    at org.gradle.execution.DefaultBuildConfigurationActionExecuter.access$000(DefaultBuildConfigurationActionExecuter.java:25)
    at org.gradle.execution.DefaultBuildConfigurationActionExecuter$1.proceed(DefaultBuildConfigurationActionExecuter.java:54)
    at org.gradle.execution.ExcludedTaskFilteringBuildConfigurationAction.configure(ExcludedTaskFilteringBuildConfigurationAction.java:47)
    at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:48)
    at org.gradle.execution.DefaultBuildConfigurationActionExecuter.select(DefaultBuildConfigurationActionExecuter.java:36)
    at org.gradle.initialization.DefaultGradleLauncher$3.run(DefaultGradleLauncher.java:143)
    at org.gradle.internal.Factories$1.create(Factories.java:22)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:52)
    at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:140)
    at org.gradle.initialization.DefaultGradleLauncher.access$200(DefaultGradleLauncher.java:32)
    at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:99)
    at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:93)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:62)
    at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:93)
    at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:82)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:94)
    at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:43)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:28)
    at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:78)
    at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:48)
    at org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter.execute(DaemonUsageSuggestingBuildActionExecuter.java:51)
    at org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter.execute(DaemonUsageSuggestingBuildActionExecuter.java:28)
    at org.gradle.launcher.cli.RunBuildAction.run(RunBuildAction.java:43)
    at org.gradle.internal.Actions$RunnableActionAdapter.execute(Actions.java:170)
    at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:237)
    at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:210)
    at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:35)
    at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:24)
    at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:206)
    at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:169)
    at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:33)
    at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:22)
    at org.gradle.launcher.Main.doAction(Main.java:33)
    at org.gradle.launcher.bootstrap.EntryPoint.run(EntryPoint.java:45)
    at org.gradle.launcher.bootstrap.ProcessBootstrap.runNoExit(ProcessBootstrap.java:54)
    at org.gradle.launcher.bootstrap.ProcessBootstrap.run(ProcessBootstrap.java:35)
    at org.gradle.launcher.GradleMain.main(GradleMain.java:23)
    at org.gradle.wrapper.BootstrapMainStarter.start(BootstrapMainStarter.java:30)
    at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:129)
    at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)
Caused by: org.gradle.model.internal.core.ModelRuleExecutionException: Exception thrown while executing model rule: items { ... } @ build.gradle line 13, column 5
    at org.gradle.model.internal.registry.DefaultModelRegistry.fireAction(DefaultModelRegistry.java:488)
    at org.gradle.model.internal.registry.DefaultModelRegistry.access$1400(DefaultModelRegistry.java:44)
    at org.gradle.model.internal.registry.DefaultModelRegistry$RunModelAction.apply(DefaultModelRegistry.java:1511)
    at org.gradle.model.internal.registry.DefaultModelRegistry.transitionTo(DefaultModelRegistry.java:379)
    at org.gradle.model.internal.registry.DefaultModelRegistry.transition(DefaultModelRegistry.java:457)
    at org.gradle.model.internal.registry.DefaultModelRegistry.atStateOrMaybeLater(DefaultModelRegistry.java:191)
    at org.gradle.model.internal.registry.DefaultModelRegistry.atStateOrLater(DefaultModelRegistry.java:178)
    at org.gradle.execution.TaskNameResolver.selfClose(TaskNameResolver.java:101)
    at org.gradle.execution.TaskNameResolver.selfClosedTasksNode(TaskNameResolver.java:109)
    ... 58 more
Caused by: org.gradle.model.WriteOnlyModelViewException: Attempt to read a write only view of model of type 'java.lang.Object' given to rule 'items { ... } @ build.gradle line 13, column 5'
    at org.gradle.model.internal.core.DefaultModelViewState.assertCanReadChildren(DefaultModelViewState.java:65)
    at org.gradle.model.internal.core.NodeBackedModelMap.get(NodeBackedModelMap.java:307)
    at org.gradle.model.internal.core.ModelMapGroovyView.getProperty(ModelMapGroovyView.java:72)
    at build_bdqyl3ad7dccqdee2w9vy0n8z$_run_closure1$_closure3.doCall(C:\Users\hoobajoob\Documents\Visual Studio 2013\Projects\items-test\build.gradle:14)
    at org.gradle.api.internal.ClosureBackedAction.execute(ClosureBackedAction.java:67)
    at org.gradle.api.internal.ClosureBackedAction.execute(ClosureBackedAction.java:49)
    at org.gradle.model.dsl.internal.transform.ClosureBackedRuleFactory$1$1.execute(ClosureBackedRuleFactory.java:88)
    at org.gradle.model.dsl.internal.transform.ClosureBackedRuleFactory$1$1.execute(ClosureBackedRuleFactory.java:82)
    at org.gradle.model.internal.core.InputUsingModelAction.execute(InputUsingModelAction.java:48)
    at org.gradle.model.internal.core.AbstractModelActionWithView.execute(AbstractModelActionWithView.java:46)
    at org.gradle.model.internal.registry.DefaultModelRegistry$2.run(DefaultModelRegistry.java:483)
    at org.gradle.model.internal.registry.RuleContext.run(RuleContext.java:59)
    at org.gradle.model.internal.registry.DefaultModelRegistry.fireAction(DefaultModelRegistry.java:480)
    ... 66 more

I tried your code and it seems to all be find. The problem could be in the code you left out. Here is what I have:

items.gradle:

@Managed
interface Item {
   File getOutputDirectory()
   void setOutputDirectory(File outputDirectory)
}

class ItemRules extends RuleSource {
   @Model void items(ModelMap<Item> theItems) {}
   @Mutate
    void buildTasks(TaskContainer tasks, ModelMap<Item> items) {
        items.eachWithIndex { b, i ->
            tasks.create("bob${i}") {
                doLast {
                    println b
                }
            }
        }
    }
}

apply plugin: ItemRules

build.gradle:

apply from: 'items.gradle'

model {
   items {
      create('Item1') {
         outputDirectory = file("../Item1-Output")
      }
      create('Item2') {
         outputDirectory = file("../Item2-Output")
      }
   }
}

I get the following:

$ gradle bob1 bob0
:bob1
Item ‘items.Item2’
:bob2
Item ‘items.Item1’

I used gradle 2.9 on Windows. I hope you can find the problem.

@Daniel_L: sorry if I wasn’t clear, but I am also able to get it working, but only if I use this form:

items {
   create("Item1") {
      // set outputDirectory
   }
}

It’s not clear to me why I’m getting failures when I use either of these forms:

items {
   Item1 {
      // set outputDirectory
   }
}

…or

items {
   Item1(Item) {
      // set outputDirectory
   }
}

Of course, I’d prefer not having to use create at all, or even to have to specify the type of Item1 and Item2.

I’m using version 2.10

I get you and you are right. I get a feeling this may be possible in the future. However, for readability by someone who has little knowledge of Gradle, it make it obvious you are creating an object with create(“Item1”).

According to the documentation, the right way for now is to use create(“Item1”) {…}.

I hope it answer your questions. Don’t hesitate to ask more.

A “creation rule” is defined as item1(Item) { }. This syntax should work and it shouldn’t be necessary to call ModelMap.create().

Ok - do you think it’s a defect?

I’m not able to reproduce the error you’re getting. Can you share a small reproducible example?

Sure - I’ll post a gist or something and update when ready

Here’s a link to a repository containing a reproducer:

https://github.com/chefhoobajoob/gradle-rulesource-reproducer

The problem here is that when you do Item1(Item) { } in your build script, it has no idea what an Item is. Classes defined in build scripts are visible only from that script. Unfortunately the error you are receiving is a bit of a red herring and not indicating this. If you were to add something like Item foo to your script you’ll see a more useful error.

So the basic problem is that model types need to be on the classpath of every script that intends to use them. You can either package this into a standalone plugin or put it in the ‘buildSrc’ project.

As for the bug in the error reporting, I’ve raised GRADLE-3376 to track this.

ok - why doesn’t this form work?

model {
   items {
      Item1 {
         outputDirectory file('../item1-output')
      }
   }
}

That’s a “mutation rule”, meaning it configures an existing model element. This will only work if ‘Item1’ already exists.

ok - thanks for clarifying