Copy(Jar) custom plugin to call "into ..., { from ... }" from configurations

Hi,

I’m trying a custom plugin with a task extending Jar, which is to import a part of resource files into a subdirectory. The directory would be specified from configuration.

// build.gradle configuration for the task I'd like to make
configurations {
    target
}

myJar {
    targetResourceInto 'subdir'
}
// Example plugin code to show the point
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.bundling.Jar

class MyJar extends Jar {
    MyJar() {
        // targetResourceInto is not configured yet...
        with project.jar
    }

    @TaskAction @Override void copy() {
        into targetResourceInto, {
            from {
                project.configurations.target
            }
        }
    }

    String targetResourceInto
}

But, it fails from Gradle 4 with:

> You cannot add child specs at execution time. Consider configuring this task during configuration time or using a separate task to do the configuration.

I understand the message that I need to set before @TaskAction void copy(). But, the constructor is too early to read the configuration targetResourceInto

Could anyone advice how this problem can be solved?

An example code set is at: https://github.com/dmikurube/discuss.gradle.org-24083

$ ./gradlew myJar

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':myJar'.
> You cannot add child specs at execution time. Consider configuring this task during configuration time or using a separate task to do the configuration.

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

* Get more help at https://help.gradle.org

BUILD FAILED in 2s
1 actionable task: 1 executed



$ ./gradlew localJar

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed



$ unzip -l build/libs/test-target.jar 
Archive:  build/libs/test-target.jar
  Length     Date   Time    Name
 --------    ----   ----    ----
        0  09-15-17 00:25   META-INF/
       25  09-15-17 00:25   META-INF/MANIFEST.MF
        0  09-15-17 00:25   localjardir/
   314932  07-27-17 12:12   localjardir/junit-4.12.jar
    45024  10-26-16 15:57   localjardir/hamcrest-core-1.3.jar
      507  09-15-17 00:25   Main.class
 --------                   -------
   360488                   6 files

jfyi, I’ve checked that into(value).from(value) (without closures) does not work as intended. (It moves “all” (including Main.class) into the subdirectory, not just a part.)

Jfyi2, hooking targetResourceInto (like setTargetResourceInto(...)) actually does not work for my specific case because the behavior needs to switch whether targetResourceInto is configured or not. (Not programmed in the example, though.)

Another way to approach this is to have your plugin add a task extension to the Jar class which will change your DSL to:

configurations {
  target
}

jar {
  targetResources {
    into 'subdir'
  }
}

Your physical code will be something like

class JarExtension {
  JarExtension( Jar task ) {
    this.task = task
  }

  void into(final String subdir) {
    task.into (subdir), {
      from task.project.configurations.getByName('target')
    } 
  }

  private Jar task
}

and in the plugin apply code

void apply(Project project) {
  projects.tasks.withType(Jar) { task ->
    task.extensions.create( 'targetResource', JarExtension, task )
  }
  project.tasks.whenTaskAdded { task ->
    if(task instanceof Jar) {
       // ... as above 
    }
  }
}

This approach can sometimes provide a cleaner DSL

Thanks, but the purposes of this were

  1. to provide a totally independent task for this type of JAR without contaminating the original “jar” task
  2. to provide a simple syntax to build.gradle writers who are building such special JAR files.

I’ve finally solved this by myself with moving everything into project.afterEvaluate in the constructor like:

class MyJar extends Jar {
    MyJar() {
        super()
        // targetResourceInto is not configured yet...
        project.afterEvaluate {
            into targetResourceInto, {
                from {
                    project.configurations.target
                }
            }
            with project.jar
        }
    }

    @TaskAction @Override void copy() {
        super.copy()
    }

    String targetResourceInto
}

Thanks.