Best practice to handle frequently API signature changes

What is the best practice to handle the frequently API signature changes in Gradle? We need to compile our plugin for different Gradle versions again.

Without the recompile or if you use the wrong plugin version the users receive a java.lang.NoSuchMethodError at runtime. Currently we have already 4 different plugin versions for different Gradle versions. Can the plugin portal handle this?

This sounds like you are using internal APIs, because we don’t break the public ones. Could you please elaborate which APIs you are referring to?

I just had a look at the project and there is internal API usage everywhere. Anything containing the word internal is not supported.

Please refactor the plugin towards using only the public APIs. If there is something that you can’t do with the public API, please let us know. We’ll try to find a solution together.

  • First our tasks and extensions implements CopySpec. There is no default implementation. If you extends this interface we need to do it also.
  • Then we use DefaultExecAction because the java.lang.ProcessBuilder has produce problems inside from Gradle.
  • Get a FileResolver which self is internal.
  • Get a CopySpecResolver which self is internal

Theoretical your are right. We should not use internal. But then we need to implements all self like a CopySpec. Then the build scripts can not interact with other tasks harmonically.

If you try to implements a task like “zip” in your own plugin without the usage of the internal API then you will see where are the problems.

Did you check out Project.exec, project.copySpec and project.file? They
should solve what you are currently doing with subclasses.

CopySpec and ExecAction are not meant to be subclassed, that’s why they are
annotated with @HasInternalProtocol.

This APIs can I only call but we want implement it. I need to implement it that the user can write Gradle typical scripts like:

mytask {
    from( "${buildDir}/data") {
       inlicude '*.abc'

Also If I use the CopySpec from project I need internal API to use it. For example iterate.

If you mean that a task should not implements CopySpec why then such many buildin task has implement it like jar, zip, tar, …

Project.exec: We was not able to call it from Java. A closure is simple from Groovy but not from Java.

You don’t need to implement it for that. Here’s a rough sketch how to use CopySpec in a custom task:

task mytask(type: MyTask) {
  outputDir = "$buildDir/foo"
  content {
    from 'dummy'

class MyTask extends DefaultTask {
  CopySpec content
  def outputDir

  MyTask() {
    content = project.copySpec()

  def doStuff() {
    project.copy {
      into outputDir
      with content

  def content(Action<? super CopySpec> config) {

Why iterate? You can execute the copy as shown in the custom task above. Then you can invoke some other tool on the copied directory, like invoking tar.

Legacy :slight_smile: We wouldn’t do it like that again, but now users are used to having the from method directly on the Jar task, so it will be hard to change.

Calling a Closure from Java isn’t that hard. It’s definitely not a blocker. And we are adding Action methods for all Closure methods in recent Gradle versions. Here’s how to call a closure method:

project.exec(new Closure(null) {
  public void doCall(ExecSpec spec) {
    // your code here

Please don’t get me wrong by the way. I’m sure there are things that are only possible with internal Apis at the moment. We should talk about those use cases and then publish the necessary minimal Api.

But what I saw so far in the plugin could definitely be done with far less internals. So the first step should be refactoring to use as little internals as possible. The second step would be to look at the remaining internals together to see what needs to be public.

The “content” variable look not like typical Gradle code. The calling of a copy() method will produce a very large performance decrement. It is very bad practice to copy hundreds of megabytes to receive the directory structure and then delete it.

But we have lost touch the original question. It there any good option to handle the version of plugins depends on the Gradle version?

I don’t know what you mean. The distribution plugin does the same and this is how all copying/bundling tasks should look like.

As I said, you shouldn’t need to. Only use public APIs and tell us about the specific situations that don’t work, so we can fix them. How is Gradle supposed to get better if we don’t know the things that don’t work for you?

I’ve forwarded the bundling use case to the team. I’m sure we can make something happen there.