'Gradle removes directory defined as task output (stale directories)

I have an unzipData gradle task defined as following:

task unzipFile(type: Copy) {
  dependsOn mkdirTrash
  dependsOn downloadFile

  from zipTree(file("$trashDir/file.zip"))
  into trashDir
}

The task unzips archive content right into the directory with archive itself (at the trash directory, which is a root of all task's artifacts and which could be deleted only when all preparation tasks are finished).

Things used to work until I updated gradle wrapper to version 4.2.1.

Task execution started to produce the error:

FAILURE: Build failed with an exception.

* What went wrong:
Cannot expand ZIP 'trash/file.zip' as it does not exist.

In debug output I see that gradle deletes the whole trash directory

> Task :unzipFile
11:56:23.145 [DEBUG] [org.gradle.internal.progress.DefaultBuildOperationExecutor] Build operation 'Task :unzipFile' started
11:56:23.145 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Starting to execute task ':unzipFile'
11:56:23.145 [INFO] [org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter] Putting task artifact state for task ':unzipFile' into context took 0.0 secs.
11:56:23.204 [DEBUG] [org.gradle.internal.progress.DefaultBuildOperationExecutor] Build operation 'Clean stale outputs' started
11:56:23.204 [INFO] [org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter] Deleting stale output file: /.../trash

As far as I can see it's gradle's new feature https://docs.gradle.org/4.2-rc-1/release-notes.html#safer-handling-of-stale-output-files

The docs says that only directories registered as targets for the clean task and source set outputs are stale. I suppose, the Copy task is one of source set and it's output is bound to be deleted.

I wonder what are advantages of this feature? Is there any possibility to forbid cleanup of particular directories? Any not dirty workarounds?



Solution 1:[1]

I had the same issue, followed this approach and it worked for me, in your case it would be something like this:

sourceSets {
    main {
        output.dir("$trashDir", builtBy: 'unzipFile')
    }
}

task unzipFile(type: Copy) {
    dependsOn mkdirTrash
    dependsOn downloadFile
    from zipTree(file("$trashDir/file.zip"))
    into trashDir
    outputs.dir sourceSets.main.output.resourcesDir
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ale Sequeira