CopySpec: support for moving files/directories?


I’m stuck with a seemingly simple problem and I’m wondering if I’m missing something. I have a gradle task that assembles/generates some files/directories within a target directory. A second task should create an archive of just the content of one of the subdirectories created by the first task. However, the actual subdirectory shouldn’t show up in the archive:

The first task looks something like this:

task assembleStuff(type: Copy) {
    into targetDir

… and produces something like that:


|-- file0

|-- subdir1

| |- file1A

| \- file1B

\-- subdir2

  |- file2A

  \- file2B  

I want the Zip to contain just:

|-- file1A

\-- file1B  

I tried using rename:

  task zipSomeStuff(type: Zip) {

from assembleStuff

include 'subdir1/*'

rename 'subdir1/(.*)', '$1'  }  

but the rename methods seem to only work at filename level. There doesn’t seem to be a way to move files across directories while copying, in fact, not even a way to rename directories while copying…

I found this old post: and there doesn’t seem to be an alternative to remapTarget even now… or am I missing something?

Regards, Mike

For something like this, you need to use the [‘eachFile {}’]( hook.

Thanks for putting me into the right direction, Luke.

There is, however, a little problem remaining: while I’m now able to move the files into the right (root) directory, the copySpec still creates the (now empty) “subdir1” folder.

What I have now is:

  task zipSomeStuff(type: Zip) {

from assembleStuff

eachFile { fileCopyDetails ->

 fileCopyDetails.path = fileCopyDetails.path.replaceAll(/(.*\/)subdir1\/(.*)/, '$1$2')


//exclude '**/subdir1/' // Results in no files being copied at all

includeEmptyDirs = false // Doesn't seem to have any effect in my case  }  

While you are developing this, make sure you are cleaning the output dir each time. There are some bugs in the copy task that throw out the incremental build.

Yep, I noticed that, I’m emptying the build folder manually each time I change something in the buildfile, but it still looks like that directory remains…

Oh, I also tried:

  ...  eachFile { fileCopyDetails ->

if (fileCopyDetails.file.isDirectory() && == "subdir1") {


 println "DIR: ${fileCopyDetails.path}"

}  }  

But the hook seems to only be called for files.

I just tried this locally and it worked for me. Any chance you could send me a sample that I can work with? (luke.daley at

Okay, it took me a while but I finally found the difference between my simplified version and my real-life code. Here is a working example that should clarify what I have:

  task assembleStuff() {

ext.destinationDir = "${buildDir}/stuff/"

outputs.dir destinationDir

  doLast {


 file("${destinationDir}/file0").text = "stuff0"

 [1, 2].each { num ->


  ['A','B'].each { let ->

file("${destinationDir}/subdir${num}/file${num}${let}").text = "stuff${num}${let}"



}  }

 task createStuffInIrrelevantDir() {

ext.destinationDir = "${buildDir}/irrelevant/"

outputs.dir destinationDir

  doLast {



 file("${destinationDir}/irrelevantfile").text = "foo"

}  }

 task zipSomeStuff(type: Zip) {

destinationDir = file("${buildDir}")

baseName = 'someStuff'

from { assembleStuff } {

 eachFile { fileCopyDetails ->

  fileCopyDetails.path = fileCopyDetails.path.replaceAll(/(.*\/?)subdir1\/(.*)/, '$1$2')


 include '**/subdir1/**'

 into 'myPath/'

 includeEmptyDirs = false


  from { createStuffInIrrelevantDir } {

 into 'irrelevantPath/'

}  }  

The problem is the “includeEmptyDirs = false”. I found that it only works when put directly in the root copySpec, but not when it’s used in a nested copySpec (like above). When put in a nested CopySpec, the includeEmptyDirs switch isn’t honored and (and this is why it took me so long to find it out) no error / warning about deprecated dynamic properties is issued.

Is this a bug?

My problem (yes I have a pretty complex copy task ;)) is that I don’t want the empty dir in the first nested copySpec, but I do want the emptyDir from the second one (from the createStuffInIrrelevantDir task).

Yeah, it’s a bug. GRADLE-1830 essentially covers this.

Okay, thanks for the feedback! :slight_smile:

Is there a suggested workaround for this? I guess deleting the directories in a doLast block would be an option when using the Copy task, but what about the Zip task?

See the workaround mentioned on the ticket.