Copy method seems to ignore duplicatesStrategy

task createFile {
  outputs.files 'foo'
      doLast {
    new File('foo').text = 'foo'
  }
}
  task copyFiles(dependsOn:createFiles) {
  inputs.files 'foo'
  outputs.dir 'target'
      doLast {
    copy {
      duplicatesStrategy = 'fail'
      from 'foo'
      into 'target'
    }
        copy {
      duplicatesStrategy = 'fail'
      from 'foo'
      into 'target'
    }
  }
}
  task clean {
  delete 'target', 'foo', 'bar'
}
  defaultTasks 'clean', 'copyFiles'

Isn’t this supposed to cause a DuplicateFileCopyingException?

Oh, this is with Gradle 2.2.

There are really two reasons why this isn’t failing. First, you are creating two separate copy actions, so technically, within each one, there are no duplicates. The second copy action simply overwrites the file. So to get the behavior you expect, you’d have to consolidate that into a single ‘copy {…}’ block. Second, even if that were the case, the example you have would still execute because you’d simply be adding the same file twice to the ‘CopySpec’. This wouldn’t be seen as “duplicate” files. An example of this failing would be if two separate source files with the same name were copied into the same target directory. Like so:

task createFiles << {

file(‘one’).mkdirs()

file(‘two’).mkdirs()

file(‘one/foo.txt’).text = ‘foo’

file(‘two/foo.txt’).text = ‘foo’

}

task copyFiles(dependsOn:createFiles) {

doLast {

copy {

duplicatesStrategy = ‘fail’

from ‘one’

from ‘two’

into ‘target’

}

}

}

task clean {

delete ‘target’, ‘one’, ‘two’

}

defaultTasks ‘clean’, ‘copyFiles’

Thanks a lot for the explanation, that wasn’t clear to me from the docs. I also think there should be a way to configure a CopySpec to not overwrite existing files. I thought that ‘duplicatesStrategy’ did that.

If there are multiple files that are going to the same destination then yes, you can prevent that by configuring the duplicatesStrategy. However, if the same file happens to exist in the output directory then Gradle will overwrite it to ensure the output reflects the latest changes.

I had another look at this. I’m still a bit confused about what is considered duplicates and what is not. For example

copy {
  from [f, f]
  duplicatesStrategy = DuplicatesStrategy.FAIL
  into target
}

reports a duplicate path whereas

copy {
  from f
  from f
  duplicatesStrategy = DuplicatesStrategy.FAIL
  into target
}

does not.

It behaves still differenty if I do something like

def copyOps = [...]
copy {
  duplicatesStrategy = DuplicatesStrategy.FAIL
  copyOps.each { op->
    from op.source, {
      rename {
        op.target
      }
    }
  }
}

Then it reports a duplicate path if the same source/target combination occurs in the copyOps list multiple times.