Recursive Method call - Method not found

The Gradle build script I am working on has the following method defined:

def unzip = { zipFilePath, destDir ->
    def zipFile = new java.util.zip.ZipFile(new File(zipFilePath))
    //finds all zip entries which are not a directory
    zipFile.entries().each {
        if (it.directory) {
            new File(destDir + '/' + it.name).mkdirs()
        }
        else {
            def extractedFile = new File(destDir + '/' + it.name)
            extractedFile.withOutputStream { os ->
                os << zipFile.getInputStream(it)
                //note os automatically closed after the closure ends
            }
        }
    }
}

This unzips an archive and creates all files and directories in the archive. The method is called like this:

if(it.endsWith('.zip')) {
    //extract to prep directory
    System.out.println 'Unzip: Src: ' + it + ' DestDir: ' + tmpDir
    unzip it, tmpDir
}

This works fine and extracts all files successfully, however nested archives are not extracted. I do not care about archives in sub directories, only archives in the root of the initial zip.

What I want to achieve:

Files.zip 
         |- MoreFiles.zip   <------ Unzip required
         |- myFile.txt
         |- subDir/ 
                   |- EvenMoreFiles.zip <------ Unzip not required 
                   |- myOtherFile.txt

I have tried to achieve this by modifying the contents of else in the unzip method like so:

else {
    def extractedFile = new File(destDir + '/' + it.name)
    extractedFile.withOutputStream { os ->
        os << zipFile.getInputStream(it)
        //note os automatically closed after the closure ends
    }
    if (extractedFile.name.endsWith('.zip')) {
        //Nested zip found, extract in current directory
        unzip extractedFile.path, destDir
    }
}

but gradle produced the following error:

Could not find method unzip() for arguments [build/tmp/MoreFiles.zip, build/tmp] on root project

It seems to me like I am providing incorrect arguments. I have added instanceof test on variables it, tmpDir, extractedFile.path and destDir, and all of the variables are of type String.

Any help will be appreciated.

StackTrace:

org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':ftpFilesForExe'.
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
    	at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
    	at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:64)
    	at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
    	at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:42)
    	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
    	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
    	at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
    	at org.gradle.api.internal.AbstractTask.executeWithoutThrowingTaskFailure(AbstractTask.java:310)
    	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.executeTask(AbstractTaskPlanExecutor.java:79)
    	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:63)
    	at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:51)
    	at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:23)
    	at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:88)
    	at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
    	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
    	at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
    	at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:68)
    	at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
    	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
    	at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:55)
    	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:149)
    	at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:106)
    	at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:86)
    	at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:90)
    	at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
    	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:41)
    	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:28)
    	at org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter.execute(DaemonUsageSuggestingBuildActionExecuter.java:50)
    	at org.gradle.launcher.exec.DaemonUsageSuggestingBuildActionExecuter.execute(DaemonUsageSuggestingBuildActionExecuter.java:27)
    	at org.gradle.launcher.cli.RunBuildAction.run(RunBuildAction.java:40)
    	at org.gradle.internal.Actions$RunnableActionAdapter.execute(Actions.java:169)
    	at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:237)
    	at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:210)
    	at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:35)
    	at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:24)
    	at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:206)
    	at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:169)
    	at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:33)
    	at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:22)
    	at org.gradle.launcher.Main.doAction(Main.java:33)
    	at org.gradle.launcher.bootstrap.EntryPoint.run(EntryPoint.java:45)
    	at org.gradle.launcher.bootstrap.ProcessBootstrap.runNoExit(ProcessBootstrap.java:54)
    	at org.gradle.launcher.bootstrap.ProcessBootstrap.run(ProcessBootstrap.java:35)
    	at org.gradle.launcher.GradleMain.main(GradleMain.java:23)
    	at org.gradle.wrapper.BootstrapMainStarter.start(BootstrapMainStarter.java:30)
    	at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:127)
    	at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:56)
    Caused by: org.gradle.api.internal.MissingMethodException: Could not find method unzipWrapper() for arguments [build/tmp/MoreFiles.zip, build/tmp] on root project 'my-project'.
    	at org.gradle.api.internal.AbstractDynamicObject.methodMissingException(AbstractDynamicObject.java:68)
    	at org.gradle.api.internal.AbstractDynamicObject.invokeMethod(AbstractDynamicObject.java:56)
    	at org.gradle.api.internal.CompositeDynamicObject.invokeMethod(CompositeDynamicObject.java:175)
    	at org.gradle.groovy.scripts.BasicScript.methodMissing(BasicScript.java:79)
    	at build_cd9okkyy49053q1e0qelycrc1$_run_closure4_closure28.doCall(/path/to/my/build.gradle:137)
    	at build_cd9okkyy49053q1e0qelycrc1$_run_closure4.doCall(/path/to/my/build.gradle:119)
    	at build_cd9okkyy49053q1e0qelycrc1$_run_closure12_closure36_closure37.doCall(/path/to/my/build.gradle:250)
    	at build_cd9okkyy49053q1e0qelycrc1$_run_closure12_closure36.doCall(/path/to/my/build.gradle:234)
    	at org.gradle.api.internal.AbstractTask$ClosureTaskAction.execute(AbstractTask.java:558)
    	at org.gradle.api.internal.AbstractTask$ClosureTaskAction.execute(AbstractTask.java:539)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
    	... 49 more

Note: I am using the Gradle 2.4 wrapper
Link to SO question: http://stackoverflow.com/questions/30639085/gradle-recursive-method-call-could-not-find-method

The problem is that you are not actually defining a method. Your defining a script variable which happens to be a closure and using it like a method. Because of the scoping, that variable is not visible from within the closure itself. There’s a couple of ways to address this:

  • Change the closure to an extension property of the script:
    ext.unzip = {...
  • Change the recursive call to the closure to use the call() method : call extractedFile.path, destDir Note that this calls the current closure, so if you are nested down inside an each or something similar, you may have to use owner.call() to invoke the surrounding closure (or reference owner for as many levels deep as you happen to be when you invoke it).
  • Change this to be a method rather than a closure: def unzip(zipFilePath, destDir) {...

There may be other solutions as well that aren’t immediately leaping to mind, but the issue is the visibility of your script variable.