IllegalStateException: Cannot add Mutate rule

We recently upgraded from gradle 2.3 to 2.4, and by doing so, an error popped up in our custom gradle-plugins project. I’ve distilled our code into a standalone buildscript sample for purposes of this discussion. The error (below) was produced using

------------------------------------------------------------
Gradle 2.4
------------------------------------------------------------

Build time:   2015-05-05 08:09:24 UTC
Build number: none
Revision:     5c9c3bc20ca1c281ac7972643f1e2d190f2c943c

Groovy:       2.3.10
Ant:          Apache Ant(TM) version 1.9.4 compiled on April 29 2014
JVM:          1.7.0_67 (Oracle Corporation 24.65-b04)
OS:           Windows 7 6.1 amd64

The Exception

$ gradle clean build --stacktrace

FAILURE: Build failed with an exception.

* Where:
Build file 'C:\pluginTest\build.gradle' line: 20

* What went wrong:
A problem occurred evaluating root project 'pluginTest'.
> Failed to apply plugin [class 'org.gradle.api.plugins.JavaPlugin']
   > Cannot add Mutate rule 'org.gradle.language.base.plugins.LanguageBasePlugin$Rules#attachBinariesToAssembleLifecycle(org.gradle.api.Task, org.gradle.platform.base.BinaryContainer)' for model element 'tasks.assemble' when element is in state GraphClosed.

* Try:
Run with --info or --debug option to get more log output.

* Exception is:
org.gradle.api.GradleScriptException: A problem occurred evaluating root project 'pluginTest'.
        at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:76)
        at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl$1.run(DefaultScriptPluginFactory.java:148)
        at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.apply(DefaultScriptPluginFactory.java:156)
        at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:39)
        at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:26)
        at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:34)
        at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:55)
        at org.gradle.api.internal.project.AbstractProject.evaluate(AbstractProject.java:487)
        at org.gradle.api.internal.project.AbstractProject.evaluate(AbstractProject.java:85)
        at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:42)
        at org.gradle.configuration.DefaultBuildConfigurer.configure(DefaultBuildConfigurer.java:35)
        at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:129)
        at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:106)
        at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:86)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:90)
        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:41)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:28)
        at org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter.execute(DaemonUsageSuggestingBuildActionExecuter.java:50)
        at org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter.execute(DaemonUsageSuggestingBuildActionExecuter.java:27)
        at org.gradle.launcher.cli.RunBuildAction.run(RunBuildAction.java:40)
        at org.gradle.internal.Actions$RunnableActionAdapter.execute(Actions.java:169)
        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)
Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin [class 'org.gradle.api.plugins.JavaPlugin']
        at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:160)
        at org.gradle.api.internal.plugins.DefaultPluginManager.addImperativePlugin(DefaultPluginManager.java:67)
        at org.gradle.api.internal.plugins.DefaultPluginManager.addImperativePlugin(DefaultPluginManager.java:73)
        at org.gradle.api.internal.plugins.DefaultPluginContainer.apply(DefaultPluginContainer.java:60)
        at org.gradle.api.plugins.PluginContainer$apply.call(Unknown Source)
        at OurJavaPlugin.apply(C:\pluginTest\build.gradle:20)
        at OurJavaPlugin.apply(C:\pluginTest\build.gradle)
        at org.gradle.api.internal.plugins.ImperativeOnlyPluginApplicator.applyImperative(ImperativeOnlyPluginApplicator.java:35)
        at org.gradle.api.internal.plugins.RuleBasedPluginApplicator.applyImperative(RuleBasedPluginApplicator.java:43)
        at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:144)
        at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:116)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.applyType(DefaultObjectConfigurationAction.java:123)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.applyPlugin(DefaultObjectConfigurationAction.java:107)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.access$100(DefaultObjectConfigurationAction.java:36)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction$2.run(DefaultObjectConfigurationAction.java:71)
        at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.execute(DefaultObjectConfigurationAction.java:136)
        at org.gradle.api.internal.project.AbstractPluginAware.apply(AbstractPluginAware.java:46)
        at org.gradle.api.plugins.PluginAware$apply.call(Unknown Source)
        at org.gradle.api.internal.project.ProjectScript.apply(ProjectScript.groovy:34)
        at org.gradle.api.Script$apply$0.callCurrent(Unknown Source)
        at build_1oznifb20uaz1pooqzrfw0yt1.run(C:\pluginTest\build.gradle:1)
        at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:74)
        ... 35 more
Caused by: java.lang.IllegalStateException: Cannot add Mutate rule 'org.gradle.language.base.plugins.LanguageBasePlugin$Rules#attachBinariesToAssembleLifecycle(org.gradle.api.Task, org.gradle.platform.base.BinaryContainer)' for model element 'tasks.assemble' when element is in state GraphClosed.
        at org.gradle.model.internal.registry.DefaultModelRegistry$1.execute(DefaultModelRegistry.java:177)
        at org.gradle.model.internal.registry.DefaultModelRegistry$1.execute(DefaultModelRegistry.java:174)
        at org.gradle.model.internal.registry.PathBinderCreationListener.onCreate(PathBinderCreationListener.java:49)
        at org.gradle.model.internal.registry.ModelGraph.maybeNotify(ModelGraph.java:160)
        at org.gradle.model.internal.registry.ModelGraph.doAddListener(ModelGraph.java:102)
        at org.gradle.model.internal.registry.ModelGraph.addListener(ModelGraph.java:92)
        at org.gradle.model.internal.registry.DefaultModelRegistry.registerListener(DefaultModelRegistry.java:259)
        at org.gradle.model.internal.registry.DefaultModelRegistry.bindMutatorSubject(DefaultModelRegistry.java:190)
        at org.gradle.model.internal.registry.DefaultModelRegistry.flushPendingMutatorBinders(DefaultModelRegistry.java:169)
        at org.gradle.model.internal.registry.DefaultModelRegistry.fireMutations(DefaultModelRegistry.java:485)
        at org.gradle.model.internal.registry.DefaultModelRegistry.transition(DefaultModelRegistry.java:409)
        at org.gradle.model.internal.registry.DefaultModelRegistry.close(DefaultModelRegistry.java:374)
        at org.gradle.model.internal.registry.DefaultModelRegistry.get(DefaultModelRegistry.java:369)
        at org.gradle.model.internal.registry.DefaultModelRegistry.require(DefaultModelRegistry.java:351)
        at org.gradle.model.internal.registry.DefaultModelRegistry.realize(DefaultModelRegistry.java:219)
        at org.gradle.api.internal.tasks.DefaultTaskContainer.realizeTask(DefaultTaskContainer.java:232)
        at org.gradle.api.internal.tasks.DefaultTaskContainer.maybeMaterializePlaceholder(DefaultTaskContainer.java:226)
        at org.gradle.api.internal.tasks.DefaultTaskContainer.findByName(DefaultTaskContainer.java:218)
        at org.gradle.api.internal.tasks.DefaultTaskContainer.findByName(DefaultTaskContainer.java:42)
        at org.gradle.api.internal.DefaultNamedDomainObjectCollection.getByName(DefaultNamedDomainObjectCollection.java:208)
        at org.gradle.api.internal.tasks.DefaultTaskCollection.getByName(DefaultTaskCollection.java:31)
        at org.gradle.api.plugins.JavaPlugin.configureTest(JavaPlugin.java:143)
        at org.gradle.api.plugins.JavaPlugin.apply(JavaPlugin.java:73)
        at org.gradle.api.plugins.JavaPlugin.apply(JavaPlugin.java:47)
        at org.gradle.api.internal.plugins.ImperativeOnlyPluginApplicator.applyImperative(ImperativeOnlyPluginApplicator.java:35)
        at org.gradle.api.internal.plugins.RuleBasedPluginApplicator.applyImperative(RuleBasedPluginApplicator.java:43)
        at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:144)
        ... 56 more


BUILD FAILED

Total time: 2.872 secs

Example Buildscript
Here’s a boiled down buildscript example showing the error with our use of custom plugins. The problem occurs when we add our custom task dependency on the assemble task. We have custom plugins that “augment” the core java & base plugins with our in-house configuration, etc… This script obviously omits a lot of that in-house custom configuration in an attempt to show the problem concisely.

apply plugin: OurJavaPlugin

buildscript {
    repositories { mavenCentral() }
    dependencies { classpath 'org.apache.commons:commons-lang3:3.1' }
}

import org.gradle.api.Plugin;
import org.gradle.api.Project;

import org.apache.commons.lang3.SystemUtils

/**
 * Any Java project that produces production JAR artifacts should apply this plugin.
 */
class OurJavaPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        project.plugins.apply(OurBasePlugin)
        project.plugins.apply(JavaPlugin)
    }
}

/**
 * All custom plugins should apply this basic plugin.  This plugin makes very few assumptions about
 * the type of project being built.
 */
class OurBasePlugin implements Plugin<Project> {
    private static final String BUILD_PROPERTIES_FILE = 'build_output.properties'
    private static final String OUTPUT_PROPERTIES_TASK = 'outputPropertiesFile'
    
    @Override
    public void apply(Project project) {
        project.plugins.apply(BasePlugin.class)
        addOutputPropertiesTask(project)
    }
    
    /**
     * Provide a build output properties file during assembly.
     */
    private void addOutputPropertiesTask(Project project) {
        
        project.task(OUTPUT_PROPERTIES_TASK) {
            group 'OUR'
            description 'Outputs build properties to file'
            doLast {
                project.buildDir.mkdir()
                File buildPropertyFile =  new File(project.buildDir, BUILD_PROPERTIES_FILE)
                //clear file if it already exists.
                buildPropertyFile.withWriter { it << '' }
                String buildVersionPropertyKeyValue = "SOME_PROPERTY_KEY" + "=" + "SOME_PROPERTY_VALUE";

                buildPropertyFile.withWriterAppend {
                    it << buildVersionPropertyKeyValue.concat(SystemUtils.LINE_SEPARATOR)
                }
            }
        }
        // This seems to be the root cause of failure
        project.assemble.dependsOn project.outputPropertiesFile
    }
}

I have also ran this example script against Gradle 2.5, revision 093765bccd3ee722ed5310583e5ed140688a8c2b, and the only noticeable difference on quick glance is the removal of the word Mutate in the error message:

Failed to apply plugin [class 'org.gradle.language.base.plugins.LanguageBasePlugin']
   Cannot add rule org.gradle.language.base.plugins.LanguageBasePlugin$Rules#attachBinariesToAssembleLifecycle(org.gradle.api.Task, org.gradle.platform.base.BinaryContainer)

There are still missing constructs in the new rule based model configuration mechanism.
You might get lucky and set your dependency between task with a string like .project.assemble.dependsOn ‘outputPropertiesFile’

Or you can try creating your task with a @Mutate rule with the Task container as rule subject.
That’s what you will have to do in the end, when the transition to the new model will be complete

@Francois_Guillot Thanks for the reply. However, even though the error message mentions a Mutate rule, we are not directly using the new rules-based configuration model DSL or classes in this case. And this is a build script that worked under Gradle 1.7 and 2.3. So it feels like a regression.

@mark_vieira I showed you this error message on my iPad at the Gradle Summit, and you said it looked familiar. So @shomeo has boiled down the relevant build script, and we’re hoping to get some guidance. Thanks!

You’re not using the new rule based mechanism, but the LanguageBasePlugin does.
It’s true that having an error in this internal plug-in, about something you’re not even using, is wrong. There is most likely a bug here.
However, using the rule based mechanism to create your task (which is one of the well documented things one can do using the current state of the rule based mechanism) might integrate better with internal plugins that already use it, and might be a viable work around the problem.

For the rest, I let the experts intervene.

For now you can do the following as a workaround:

project.tasks.matching { it.name == 'assemble' }.all { dependsOn project.outputPropertiesFile }

@luke_daley, can you weigh in here? Should we open an issue for this?

Here’s a summary of what’s going on:

  1. The assemble task is defined by a rule (and therefore, is created on demand)
  2. Your custom plugin is forcing realization of the assemble task, which means that all known mutators (i.e. rules) are run, then made available to your code (i.e. project.assemble.dependsOn project.outputPropertiesFile)
  3. The “java plugin” then tries to define further mutations on the assemble task

At a general level, the problem is that our bridging between rules and non-rules world is a little undercooked.

Immediately available workarounds:

  1. @mark_vieira’s workaround above
  2. Flip the application order of OurPlugin and JavaPlugin

Unfortunately, we haven’t yet made it nice to use something like extra properties in rules space, so moving your rule (i.e. project.assemble.dependsOn project.outputPropertiesFile) to an actual rule isn’t practical yet.

As for the appropriate long term fix options:

  1. Provide a construct that is functionally similar to @mark_vieira’s workaround, but more convenient
  2. Don’t completely realize rule originating tasks when using them from non-rules land

@mark_vieira let’s open an issue for this, addressing the core problem: rule based tasks cannot be configured outside of a rule before rules mutating the task are applied (feel free to reword).

This problem is being tracked as GRADLE-3321.

Could you elaborate on the reason that makes Mark workaround work?
This workaround or similar ones are given these days, for any problem related to task realization.

Why referencing tasks by name works here?
Since tasks created by rules are created on demand, how does referencing them by their name help? Does it ‘create’ them in the non-rules world?

@mark_vieira, thanks for the workaround, and @luke_daley for the explanation. I was curious if this was an actual issue or if we were doing something unconventional. Thanks again - we’ll keep an eye on GRADLE-3321

We aren’t so much referencing the task by name in the workaround as we are registering a configuration rule that only applies to tasks with that name. Since the task is lazily created, the rule is not evaluated until the task which matches the criteria is added to the task container (which is after all other @Mutate rules have run). We are getting around this problem by not directly referencing the task at all, doing so would cause realization of the task (as Luke mentioned).