CopySpec: support for moving files/directories?

(Mike Meessen) #1


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

(Luke Daley) #2

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

(Mike Meessen) #3

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  }  

(Luke Daley) #4

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.

(Mike Meessen) #5

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…

(Mike Meessen) #6

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.

(Luke Daley) #7

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

(Mike Meessen) #8

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).

(Luke Daley) #9

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

(Mike Meessen) #10

Okay, thanks for the feedback! :slight_smile:

(David Pärsson) #11

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?

(Luke Daley) #12

See the workaround mentioned on the ticket.