Dynamically created task to unpack a zip file dependency

I’m trying to write a plugin to dynamically create a task which explodes a zip file dependency into a folder with the same name as the zip file. I’m also keen for the task to ‘know’ if the target folder is already up-to-date, and consequently do nothing.

In order for the up-to-date check to work the task will need to know about the zip file, and will also need to know where that zip file will be unpacked. However this is unknown at the point the task is created.

I’ve been struggling for days with writing a plugin to achieve this.

class PackedDependency {
    final String name
    String unpack_path
    public PackedDependency(String dep_name) {
        name = dep_name
        unpack_path = dep_name
    }
}
  class UnpackDependencyTask extends DefaultTask {
    private PackedDependency dependency
          //@InputFile
    File zippedDependency
          //@OutputFile
    File infoFile
          void setDependency(PackedDependency dep) {
        dependency = dep
    }
                  @TaskAction
    void run() {
        infoFile = new File(project.projectDir.path + "/../" + dependency.unpack_path + "/version_info.txt")
        def dep_name = dependency.unpack_path
        project.configurations.compile.each { File f ->
             if (f.name.endsWith(".zip")) {
                if (f.name.contains(dep_name)) {
                    println "Unpacking '$dep_name'."
                    project.copy {
                        from project.zipTree(f)
                        into project.projectDir.path + "/../" + dep_name
                    }
                    infoFile.write("Unpacked from: " + f.name)
                }
            }
        }
    }
}
  class PackagingPlugin implements Plugin<Project> {
        void apply(Project project) {
        def packedDependency = project.container(PackedDependency)
        packedDependency.all { p ->
             def unpack_task = project.task("unpack" + p.name, type: UnpackDependencyTask)
            unpack_task.dependsOn project.configurations.compile
            unpack_task.setDependency(p)
        }
        project.extensions.packedDependency = packedDependency
    }
}
  project(':framework') {
    configurations {
        compile
    }
      apply plugin: PackagingPlugin
      repositories {
        ivy {
            url "..."
        }
    }
      dependencies {
        compile "group:external-lib:1.0.+:Release@zip"
    }
      packedDependency {
         ExternalLib { unpack_path = "external-lib" }
     }
}

I suspect that I need to populate the zippedDependency and infoFile properties before the task is run, but I’ve no idea how to achieve that. Can anyone please help?

What exactly isn’t working? The up-to-date check? Anything else?

Functionally the dynamically created task unpacks the zip the way I want it. Yes, it’s just the up-to-date check that’s not working. This isn’t surprising given that the relevant lines are commented out, but that’s where I’m stuck.

It’s the classic challenge when implementing plugins. The plugin needs to defer the configuration of anything (e.g. tasks) that it feeds with information from the Gradle object model, because that information might not yet be available or could still change until the end of the configuration phase. There are several techniques for doing so. The most basic technique is to wrap the relevant code with ‘project.gradle.projectsEvaluated { … }’, but there are more sophisticated techniques. I encourage you to search the forum for terms like “plugin task”, “convention mapping”, etc.

Regarding the code in general, I’d probably change the distribution of work between the plugin and task. The plugin should make all the tactical decisions, and should configure the task with everything it needs to know. The task shouldn’t reach out into the Gradle object model (e.g. with ‘project.configurations.compile’), because that makes it inflexible. Instead, the plugin should configure it with such information. For example, the task could just accept an ‘Iterable’ containing all zip files to be unpacked. The plugin could then configure it with something like ‘project.configurations.compile.matching { include “**/*.zip” }’. Potentially, the plugin could just use plain ‘Copy’ tasks to get the copying done.

Great answer. I’ve got it working now. Thank you very much. I’m also grateful for your other suggestions about distribution of work between plugin and task - all good points.