Cannot apply spring-boot-gradle plugin in own plugin with gradle 3.0

I have created a minimal plugin which is reproducing my error:

If you build this with gradle version 2.14.1 it will pass but building this with gradle 3.0 will fail with the following stacktrace:

org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin [class 'io.spring.gradle.dependencymanagement.DependencyManagementPlugin']
	at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:155)
	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.springframework.boot.gradle.dependencymanagement.DependencyManagementPluginFeatures.apply(DependencyManagementPluginFeatures.java:44)
	at org.springframework.boot.gradle.plugin.SpringBootPlugin.apply(SpringBootPlugin.java:49)
	at org.springframework.boot.gradle.plugin.SpringBootPlugin.apply(SpringBootPlugin.java:39)
	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:139)
	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:44)
	at org.gradle.api.plugins.PluginAware$apply.call(Unknown Source)
	at foo.bar.FooPlugin.apply(FooPlugin.groovy:14)
	at foo.bar.FooPlugin.apply(FooPlugin.groovy)
	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:139)
	at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:112)
	at foo.bar.FooPluginTest.setup(FooPluginTest.java:19)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:377)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
	at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.gradle.api.GradleException: Could not generate a proxy class for class io.spring.gradle.dependencymanagement.report.DependencyManagementReportTask.
	at org.gradle.api.internal.AbstractClassGenerator.generateUnderLock(AbstractClassGenerator.java:201)
	at org.gradle.api.internal.AbstractClassGenerator.generate(AbstractClassGenerator.java:64)
	at org.gradle.api.internal.project.taskfactory.TaskFactory.create(TaskFactory.java:115)
	at org.gradle.api.internal.project.taskfactory.TaskFactory.createTask(TaskFactory.java:77)
	at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory.createTask(AnnotationProcessingTaskFactory.java:46)
	at org.gradle.api.internal.project.taskfactory.DependencyAutoWireTaskFactory.createTask(DependencyAutoWireTaskFactory.java:39)
	at org.gradle.api.internal.tasks.DefaultTaskContainer.create(DefaultTaskContainer.java:62)
	at org.gradle.api.internal.tasks.DefaultTaskContainer.create(DefaultTaskContainer.java:97)
	at org.gradle.api.internal.tasks.DefaultTaskContainer.create(DefaultTaskContainer.java:127)
	at org.gradle.api.internal.tasks.DefaultTaskContainer_Decorated.create(Unknown Source)
	at org.gradle.api.internal.tasks.DefaultTaskContainer_Decorated$create.call(Unknown Source)
	at io.spring.gradle.dependencymanagement.DependencyManagementPlugin.apply(DependencyManagementPlugin.groovy:61)
	at io.spring.gradle.dependencymanagement.DependencyManagementPlugin.apply(DependencyManagementPlugin.groovy)
	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:139)
	... 67 more
Caused by: java.lang.NoClassDefFoundError: org/gradle/api/internal/DynamicObject
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
	at java.lang.Class.getDeclaredMethods(Class.java:1975)
	at org.gradle.internal.reflect.ClassInspector.inspectClass(ClassInspector.java:67)
	at org.gradle.internal.reflect.ClassInspector.visitGraph(ClassInspector.java:51)
	at org.gradle.internal.reflect.ClassInspector.inspect(ClassInspector.java:31)
	at org.gradle.api.internal.AbstractClassGenerator.inspectType(AbstractClassGenerator.java:260)
	at org.gradle.api.internal.AbstractClassGenerator.inspectType(AbstractClassGenerator.java:216)
	at org.gradle.api.internal.AbstractClassGenerator.generateUnderLock(AbstractClassGenerator.java:95)
	... 82 more
Caused by: java.lang.ClassNotFoundException: org.gradle.api.internal.DynamicObject
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 91 more

I open this here because it was kind of ruled out that this is a bug in the spring-boot-gradle-plugin itself in https://github.com/spring-gradle-plugins/dependency-management-plugin/issues/114

This is very similar to the problem discussed here and is due to org.gradle.api.internal.DynamicObject being relocated in Gradle 3.0.

Sadly, I think this is another example of the problems caused by Gradle’s internals leaking into its public API. DependencyManagementReportTask extends DefaultTask which in turn extends org.gradle.api.internal.AbstractTask. In other words, every Task implementation that follows the recommended approach of subclassing DefaultTask is inadvertently depending on Gradle’s internal API.

The suggestion in the thread that I linked to above is that plugin developers should produce a Gradle 3.0-specific version of their plugin. I think that’s a very poor solution to the problem as it forces every plugin developer to make changes and to then to either drop support for Gradle 2.x or to maintain multiple versions of their plugin.

Hi Andy,

Thanks for bringing this topic to our attention again. I had a look at the sample project provided by @seakayone. The issue the user is seeing is only exposed if you execute a test that leverages ProjectBuilder which in turn applies the plugin or uses any of its classes (compiled with Gradle 2.x). Applying the plugin to a regular build.gradle file executed with Gradle 3.x works as expected.

The reason for this behavior is grounded in how we load classes with ProjectBuilder. For the specific use case of testing plugins I’d recommend writing the test with TestKit instead which drives the build via the Tooling API.
We’d would like to continue the discussion on your concerns about Gradle internals leaking in your recent post.

@seakayone: We are eager to help you implement your test case with TestKit. I sent you a pull request that demonstrate the use of TestKit for your use case.

Your feedback is important to us and we understand your frustration. We are actively looking into options to make the same functionality available for ProjectBuilder as well. Please track the JIRA issue GRADLE-3558 for more information.

Cheers,

Ben

1 Like

@bmuschko your help is very much appreciate, I have taken a look at your pull request and this way I am able to test and apply the spring-boot-plugin.

This also explains why I haven’t (yet?) experienced any troubles with using the spring-boot plugin with Gradle 3.0 even though it is not supported anymore.

Gradle 3.2 will contain a fix for this. See comment here: