Use of closure to defer execution for "include" of Copy task

plugins

(Mukarram Baig) #1

Continuing the discussion from Right way to copy contents from dependency archives?:

Hello folks,

I am a gradle newbie. From the linked topic, I see how the “from” configuration of the Copy task is deferred until the execution time. I wanted to do the same thing with the “include” configuration of the Copy task, i.e. I want to defer the evaluation of the include patterns for the Copy task until execution time (since the patterns are being created by a preceding dependent task). This logic is being written in a custom groovy plugin and I will try to explain it below:

CustomExtension.groovy:

class CustomExtension {
   String patternsFile // The file is a text file with a line for each pattern
}

CustomPlugin.groovy

class CustomPlugin implements Plugin<Project> {

    void apply(Project project) {

        def customSourceSet = ... // Created here
        customExtension = project.extensions.create("customExtension", CustomExtension, project)
        project.task("generateMatchingPatterns") { 
            // Populate customExtension.patternsFile here
        }
        // Creating the custom copying task
        project.task("copyMatchingClassses",type: Copy, dependsOn: "generateMatchingPatterns") {
            include {
                def listingFile = project.file(customExtension.patternsFile )
                def includedFiles = [] // Empty list
                listingFile.eachLine { includedFiles << it } // Add patterns per line of the listing file
                includedFiles // Return the final populated list -- this might be the source of the problem
            }
            from {
                customSourceSet.compileClasspath.collect { project.zipTree(it)}
            }
            into customSourceSet.output.classesDir
        }
        // Rest of the plugin.apply logic
    }
}

The copy task doesn’t seem to be honoring the include patterns being supplied in this manner (and rather copying everything over). It is highly likely that I am messing up some groovy syntax to give the list of include patterns. Would appreciate any help here. Thanks!


(Sterling Greene) #2

The docs don’t make it very clear, but include(Closure includeSpec) expects you to provide a Closure that takes a FileTreeElement and returns a boolean (if the file should be included or not). Compare this to include(Spec<FileTreeElement> spec) which is doing the same thing, but with only Java types.

Since you’re returning a list, Groovy coerces that into a boolean. An empty collection is “false” and a collection with at least one element is “true”. That’s why you’re seeing every file included.

A couple of suggestions…

  1. Go ahead and make this a custom task type. The @TaskAction can use project.copy {}. You can annotate your inputs.
  2. Since you’re using a custom task, you can just read the input file directly and turn it into a list of include patterns (and avoid the Closure/Spec).
  3. Consider using a TextResource instead of a plain File to read your list of includes from. You’ll get auto-wiring of dependencies with it.

It also looks a little suspicious that you’re copying class files into a compile task’s output directory, which might break incremental build.


(Mukarram Baig) #3

Thanks a lot @sterling for the help! I will also incorporate your suggestions of annotating the inputs and using the TextResource.

It also looks a little suspicious that you’re copying class files into a compile task’s output directory, which might break incremental build.

I am writing the plugin that will generate sources and compile it. The reason why I am copying additional files to the compile task’s output directory is to include these classes in the jar that is created. Can you please elaborate on why it might break incremental build (and also suggest idiomatic alternatives that can be used).

Here is what I ended up with for CustomPlugin.groovy

class CustomPlugin implements Plugin<Project> {

    void apply(Project project) {

        def customSourceSet = ... // Created here
        customExtension = project.extensions.create("customExtension", CustomExtension, project)
        project.task("generateMatchingPatterns") { 
            // Populate customExtension.patternsFile here
        }
        // Creating the custom task
        project.task("copyMatchingClassses", dependsOn: "generateMatchingPatterns") {
            doFirst {
                def listingFile = project.file(customExtension.patternsFile )
                def includedFiles = [] // Empty list
                listingFile.eachLine { includedFiles << it } // Add patterns per line of the listing file
                project.copy {
                    include includedFiles
                    from customSourceSet.compileClasspath.collect { project.zipTree(it)}
                    into customSourceSet.output.classesDir
                }
            }
        }
        // Rest of the plugin.apply logic
    }
}