Seeing NoSuchMethodError on deprecated methods of Gradle 4 API


(Pierre) #1

Hello,

I have a small plugin adding SourceSets in some occasion. In Gradle 3.x the groovy code for that plugin was:

def container = project.getConvention().getPlugin(JavaPluginConvention).sourceSets
// omitted for brevity: check that the name is not already taken
def sourceSet = container.create name
// omitted for brevity: set compileClasspath, runtimeClasspath, srcDir and resource dir
// I don't want classes bundled with all classes, so I'll set a classesDir in SourceSetOutput
sourceSet.output.classesDir = "somewhere" as File

So far it worked perfectly when built with Gradle 3.x and ran on project using Gradle 3.x

Now that Gradle 4 is out, with an updated default SourceSetOutput class directory, I wish to update this plugin in a manner that applying it on a project while running Gradle 3.x produces the old behavior (i.e. put classes in separate dir), and applying it on a project while running Gradle 4.x produces the default behavior.

I change the code as follow:

boolean oldGradle = (gradle.gradleVersion <=> '4.0') < 0
if (oldGradle) {
    sourceSet.output.classesDir = "somewhere" as File
}

I run my integration tests using the gradle test kit and they fail with a Gradle 3 runner, but work with Gradle 4 runner.

Error is java.lang.NoSuchMethodError: org.gradle.api.tasks.SourceSetOutput.setClassesDir(Ljava/io/File;)V

I remove the @CompileStatic annotation on my plugin class and everything works, both with Gradle 3 and Gradle 4.
My problem is that I do not understand why the @CompileStatic breaks the execution. I looked in Gradle source code and methods are only deprecated, not removed yet.

Can someone shed a bit of light on this please?
I can provide a small test case if need be.

Regards.


(Sterling Greene) #2

org.gradle.api.tasks.SourceSetOutput#setClassesDir(java.io.File) was added in 4.0.

In 3.x and before, there was only org.gradle.api.tasks.SourceSetOutput#setClassesDir(java.lang.Object).

When compiled against 4.0 with @CompileStatic, your code sourceSet.output.classesDir = "somewhere" as File calls SourceSetOutput#setClassesDir(java.io.File). When you remove @CompileStatic, this turns into a dynamic look-up at runtime. For 4.0, you’ll get the File version and in 3.x, you’ll get the Object version.

I would just use: sourceSet.output.classesDir = "somewhere"

This will call the Object version always and the string is coerced into a File by Gradle. If you really want it to be a file, you should use project.file() (that’s what Gradle does under the covers).


(Pierre) #3

Thank you very much for the explanation.
I was mislead due to the @deprecated, I did not dig deep enough: I thought that if it was deprecated, that was surely an old method and I only checked source code on the master branch of the source repository.

I’ll follow your suggestion and call the Object variant so that I can keep the @CompileStatic.

Thanks again


(Sterling Greene) #4

No problem.

It was added to support better IDE completion, but the property itself was deprecated in 4.0, so the new method was introduced as deprecated.